Repository: stylelint/vscode-stylelint
Pull request #580 (2024-08-26 14:21:13)

test: migrate `jest-runner-vscode` to `@vscode/test-cli`
<!-- Each pull request must be associated with an open issue unless it's a documentation fix. If a corresponding issue does not exist, please create one so we can discuss the change first. -->

<!-- Please answer the following. We close pull requests that don't. -->

> Which issue, if any, is this issue related to?

Related to #504

> Is there anything in the PR that needs further explanation?

[`jest-runner-vscode`](https://github.com/adalinesimonian/jest-runner-vscode) has been inactive for a long time, while [`@vscode/test-cli`](https://github.com/microsoft/vscode-test-cli) is officially maintained by the VS Code team.
See also https://code.visualstudio.com/api/working-with-extensions/testing-extension

Additionally, [Mocha.js's BDD style](https://mochajs.org/#bdd), which is the basis of `@vscode/test-cli`, is similar to Jest, so I believe the learning cost is low.



Patch:
diff --git a/.eslintrc.js b/.eslintrc.js
index dabdaa8..bc16133 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -79,6 +79,12 @@ const config = {
 				'jest/unbound-method': 'error',
 			},
 		},
+		{
+			files: ['test/e2e/__tests__/**/*'],
+			rules: {
+				'jest/expect-expect': 'off',
+			},
+		},
 	],
 };
 
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index 659c5c4..2072b97 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -10,14 +10,12 @@ updates:
       - 'pr: dependencies'
     groups:
       jest:
-        patterns: ['jest', 'jest-runner-vscode', 'ts-jest', '@types/jest']
+        patterns: ['jest', 'ts-jest', '@types/jest']
       eslint:
         patterns: ['eslint', 'eslint-config-*', '@typescript-eslint/*']
       vscode:
         patterns: ['vscode-*', '@vscode/*']
     ignore:
-      - dependency-name: p-wait-for
-        update-types: [version-update:semver-major]
       - dependency-name: '@types/vscode'
         update-types: [version-update:semver-minor]
   - package-ecosystem: 'github-actions'
diff --git a/cspell.config.js b/cspell.config.js
index b8d06b1..1ccad52 100644
--- a/cspell.config.js
+++ b/cspell.config.js
@@ -7,6 +7,7 @@ const config = {
 	files: ['**/*.js', '**/*.ts', '**/*.json', '**/*.md', '**/*.yml', '**/*.css', '**/*.scss'],
 	ignorePaths: ['**/node_modules', '**/.yarn', '**/coverage', 'dist'],
 	words: [
+		'Autofix',
 		'browserslist',
 		'caniuse',
 		'color',
@@ -36,13 +37,6 @@ const config = {
 		'Xerus',
 		'Supertypes',
 	],
-	overrides: [
-		{
-			filename: 'package.json',
-			// cspell:disable-next-line
-			words: ['Autofix'],
-		},
-	],
 };
 
 module.exports = config;
diff --git a/jest.config.js b/jest.config.js
index 72238e4..abb2478 100644
--- a/jest.config.js
+++ b/jest.config.js
@@ -2,11 +2,7 @@
 
 /** @type {import('@jest/types').Config.InitialOptions} */
 const config = {
-	projects: [
-		'<rootDir>/test/unit/jest.config.js',
-		'<rootDir>/test/integration/jest.config.js',
-		'<rootDir>/test/e2e/jest.config.js',
-	],
+	projects: ['<rootDir>/test/unit/jest.config.js', '<rootDir>/test/integration/jest.config.js'],
 	maxWorkers: 2,
 };
 
diff --git a/package-lock.json b/package-lock.json
index 5988159..49e18d1 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -35,7 +35,8 @@
         "@typescript-eslint/eslint-plugin": "^7.5.0",
         "@typescript-eslint/parser": "^7.5.0",
         "@typescript/lib-dom": "npm:@types/web@^0.0.47",
-        "@vscode/test-electron": "^1.6.2",
+        "@vscode/test-cli": "^0.0.10",
+        "@vscode/test-electron": "^2.4.1",
         "cspell": "^8.6.1",
         "esbuild": "^0.20.2",
         "eslint": "^8.57.0",
@@ -43,9 +44,7 @@
         "eslint-plugin-jest": "^27.9.0",
         "fast-glob": "^3.2.7",
         "jest": "^27.4.3",
-        "jest-runner-vscode": "^2.1.0",
         "npm-run-all": "^4.1.5",
-        "p-wait-for": "^3.1.0",
         "postcss": "^8.4.38",
         "postcss-sass": "^0.5.0",
         "postcss-scss": "^4.0.9",
@@ -2736,6 +2735,13 @@
       "integrity": "sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==",
       "dev": true
     },
+    "node_modules/@types/mocha": {
+      "version": "10.0.7",
+      "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.7.tgz",
+      "integrity": "sha512-GN8yJ1mNTcFcah/wKEFIJckJx9iJLoMSzWcfRRuxz/Jk+U6KQNnml+etbtxFK8lPjzOw3zp4Ha/kjSst9fsHYw==",
+      "dev": true,
+      "license": "MIT"
+    },
     "node_modules/@types/node": {
       "version": "16.11.6",
       "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.6.tgz",
@@ -2748,12 +2754,6 @@
       "integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==",
       "dev": true
     },
-    "node_modules/@types/parse-json": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz",
-      "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==",
-      "dev": true
-    },
     "node_modules/@types/path-is-inside": {
       "version": "1.0.3",
       "resolved": "https://registry.npmjs.org/@types/path-is-inside/-/path-is-inside-1.0.3.tgz",
@@ -3333,19 +3333,206 @@
       "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==",
       "dev": true
     },
+    "node_modules/@vscode/test-cli": {
+      "version": "0.0.10",
+      "resolved": "https://registry.npmjs.org/@vscode/test-cli/-/test-cli-0.0.10.tgz",
+      "integrity": "sha512-B0mMH4ia+MOOtwNiLi79XhA+MLmUItIC8FckEuKrVAVriIuSWjt7vv4+bF8qVFiNFe4QRfzPaIZk39FZGWEwHA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@types/mocha": "^10.0.2",
+        "c8": "^9.1.0",
+        "chokidar": "^3.5.3",
+        "enhanced-resolve": "^5.15.0",
+        "glob": "^10.3.10",
+        "minimatch": "^9.0.3",
+        "mocha": "^10.2.0",
+        "supports-color": "^9.4.0",
+        "yargs": "^17.7.2"
+      },
+      "bin": {
+        "vscode-test": "out/bin.mjs"
+      },
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@vscode/test-cli/node_modules/brace-expansion": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
+      "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "balanced-match": "^1.0.0"
+      }
+    },
+    "node_modules/@vscode/test-cli/node_modules/cliui": {
+      "version": "8.0.1",
+      "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
+      "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
+      "dev": true,
+      "license": "ISC",
+      "dependencies": {
+        "string-width": "^4.2.0",
+        "strip-ansi": "^6.0.1",
+        "wrap-ansi": "^7.0.0"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@vscode/test-cli/node_modules/glob": {
+      "version": "10.4.5",
+      "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
+      "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
+      "dev": true,
+      "license": "ISC",
+      "dependencies": {
+        "foreground-child": "^3.1.0",
+        "jackspeak": "^3.1.2",
+        "minimatch": "^9.0.4",
+        "minipass": "^7.1.2",
+        "package-json-from-dist": "^1.0.0",
+        "path-scurry": "^1.11.1"
+      },
+      "bin": {
+        "glob": "dist/esm/bin.mjs"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/isaacs"
+      }
+    },
+    "node_modules/@vscode/test-cli/node_modules/jackspeak": {
+      "version": "3.4.3",
+      "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
+      "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
+      "dev": true,
+      "license": "BlueOak-1.0.0",
+      "dependencies": {
+        "@isaacs/cliui": "^8.0.2"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/isaacs"
+      },
+      "optionalDependencies": {
+        "@pkgjs/parseargs": "^0.11.0"
+      }
+    },
+    "node_modules/@vscode/test-cli/node_modules/minimatch": {
+      "version": "9.0.5",
+      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
+      "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
+      "dev": true,
+      "license": "ISC",
+      "dependencies": {
+        "brace-expansion": "^2.0.1"
+      },
+      "engines": {
+        "node": ">=16 || 14 >=14.17"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/isaacs"
+      }
+    },
+    "node_modules/@vscode/test-cli/node_modules/supports-color": {
+      "version": "9.4.0",
+      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-9.4.0.tgz",
+      "integrity": "sha512-VL+lNrEoIXww1coLPOmiEmK/0sGigko5COxI09KzHc2VJXJsQ37UaQ+8quuxjDeA7+KnLGTWRyOXSLLR2Wb4jw==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/supports-color?sponsor=1"
+      }
+    },
+    "node_modules/@vscode/test-cli/node_modules/yargs": {
+      "version": "17.7.2",
+      "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
+      "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "cliui": "^8.0.1",
+        "escalade": "^3.1.1",
+        "get-caller-file": "^2.0.5",
+        "require-directory": "^2.1.1",
+        "string-width": "^4.2.3",
+        "y18n": "^5.0.5",
+        "yargs-parser": "^21.1.1"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@vscode/test-cli/node_modules/yargs-parser": {
+      "version": "21.1.1",
+      "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
+      "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
+      "dev": true,
+      "license": "ISC",
+      "engines": {
+        "node": ">=12"
+      }
+    },
     "node_modules/@vscode/test-electron": {
-      "version": "1.6.2",
-      "resolved": "https://registry.npmjs.org/@vscode/test-electron/-/test-electron-1.6.2.tgz",
-      "integrity": "sha512-W01ajJEMx6223Y7J5yaajGjVs1QfW3YGkkOJHVKfAMEqNB1ZHN9wCcViehv5ZwVSSJnjhu6lYEYgwBdHtCxqhQ==",
+      "version": "2.4.1",
+      "resolved": "https://registry.npmjs.org/@vscode/test-electron/-/test-electron-2.4.1.tgz",
+      "integrity": "sha512-Gc6EdaLANdktQ1t+zozoBVRynfIsMKMc94Svu1QreOBC8y76x4tvaK32TljrLi1LI2+PK58sDVbL7ALdqf3VRQ==",
       "dev": true,
+      "license": "MIT",
       "dependencies": {
-        "http-proxy-agent": "^4.0.1",
-        "https-proxy-agent": "^5.0.0",
-        "rimraf": "^3.0.2",
-        "unzipper": "^0.10.11"
+        "http-proxy-agent": "^7.0.2",
+        "https-proxy-agent": "^7.0.5",
+        "jszip": "^3.10.1",
+        "ora": "^7.0.1",
+        "semver": "^7.6.2"
+      },
+      "engines": {
+        "node": ">=16"
+      }
+    },
+    "node_modules/@vscode/test-electron/node_modules/agent-base": {
+      "version": "7.1.1",
+      "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz",
+      "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "debug": "^4.3.4"
+      },
+      "engines": {
+        "node": ">= 14"
+      }
+    },
+    "node_modules/@vscode/test-electron/node_modules/http-proxy-agent": {
+      "version": "7.0.2",
+      "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz",
+      "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "agent-base": "^7.1.0",
+        "debug": "^4.3.4"
       },
       "engines": {
-        "node": ">=8.9.3"
+        "node": ">= 14"
+      }
+    },
+    "node_modules/@vscode/test-electron/node_modules/https-proxy-agent": {
+      "version": "7.0.5",
+      "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz",
+      "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "agent-base": "^7.0.2",
+        "debug": "4"
+      },
+      "engines": {
+        "node": ">= 14"
       }
     },
     "node_modules/abab": {
@@ -3434,6 +3621,16 @@
         "url": "https://github.com/sponsors/epoberezkin"
       }
     },
+    "node_modules/ansi-colors": {
+      "version": "4.1.3",
+      "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz",
+      "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=6"
+      }
+    },
     "node_modules/ansi-escapes": {
       "version": "4.3.2",
       "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz",
@@ -3740,34 +3937,67 @@
       "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
       "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
     },
-    "node_modules/big-integer": {
-      "version": "1.6.50",
-      "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.50.tgz",
-      "integrity": "sha512-+O2uoQWFRo8ysZNo/rjtri2jIwjr3XfeAgRjAUADRqGG+ZITvyn8J1kvXLTaKVr3hhGXk+f23tKfdzmklVM9vQ==",
+    "node_modules/base64-js": {
+      "version": "1.5.1",
+      "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
+      "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
       "dev": true,
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/feross"
+        },
+        {
+          "type": "patreon",
+          "url": "https://www.patreon.com/feross"
+        },
+        {
+          "type": "consulting",
+          "url": "https://feross.org/support"
+        }
+      ],
+      "license": "MIT"
+    },
+    "node_modules/binary-extensions": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
+      "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
+      "dev": true,
+      "license": "MIT",
       "engines": {
-        "node": ">=0.6"
+        "node": ">=8"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
       }
     },
-    "node_modules/binary": {
-      "version": "0.3.0",
-      "resolved": "https://registry.npmjs.org/binary/-/binary-0.3.0.tgz",
-      "integrity": "sha1-n2BVO8XOjDOG87VTz/R0Yq3sqnk=",
+    "node_modules/bl": {
+      "version": "5.1.0",
+      "resolved": "https://registry.npmjs.org/bl/-/bl-5.1.0.tgz",
+      "integrity": "sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==",
       "dev": true,
+      "license": "MIT",
       "dependencies": {
-        "buffers": "~0.1.1",
-        "chainsaw": "~0.1.0"
+        "buffer": "^6.0.3",
+        "inherits": "^2.0.4",
+        "readable-stream": "^3.4.0"
+      }
+    },
+    "node_modules/bl/node_modules/readable-stream": {
+      "version": "3.6.2",
+      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
+      "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "inherits": "^2.0.3",
+        "string_decoder": "^1.1.1",
+        "util-deprecate": "^1.0.1"
       },
       "engines": {
-        "node": "*"
+        "node": ">= 6"
       }
     },
-    "node_modules/bluebird": {
-      "version": "3.4.7",
-      "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.4.7.tgz",
-      "integrity": "sha1-9y12C+Cbf3bQjtj66Ysomo0F+rM=",
-      "dev": true
-    },
     "node_modules/brace-expansion": {
       "version": "1.1.11",
       "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@@ -3796,6 +4026,13 @@
       "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==",
       "dev": true
     },
+    "node_modules/browser-stdout": {
+      "version": "1.3.1",
+      "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz",
+      "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==",
+      "dev": true,
+      "license": "ISC"
+    },
     "node_modules/browserslist": {
       "version": "4.23.0",
       "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz",
@@ -3849,30 +4086,37 @@
         "node-int64": "^0.4.0"
       }
     },
+    "node_modules/buffer": {
+      "version": "6.0.3",
+      "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
+      "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
+      "dev": true,
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/feross"
+        },
+        {
+          "type": "patreon",
+          "url": "https://www.patreon.com/feross"
+        },
+        {
+          "type": "consulting",
+          "url": "https://feross.org/support"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "base64-js": "^1.3.1",
+        "ieee754": "^1.2.1"
+      }
+    },
     "node_modules/buffer-from": {
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
       "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
       "dev": true
     },
-    "node_modules/buffer-indexof-polyfill": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/buffer-indexof-polyfill/-/buffer-indexof-polyfill-1.0.2.tgz",
-      "integrity": "sha512-I7wzHwA3t1/lwXQh+A5PbNvJxgfo5r3xulgpYDB5zckTu/Z9oUK9biouBKQUjEqzaz3HnAT6TYoovmE+GqSf7A==",
-      "dev": true,
-      "engines": {
-        "node": ">=0.10"
-      }
-    },
-    "node_modules/buffers": {
-      "version": "0.1.1",
-      "resolved": "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz",
-      "integrity": "sha1-skV5w77U1tOWru5tmorn9Ugqt7s=",
-      "dev": true,
-      "engines": {
-        "node": ">=0.2.0"
-      }
-    },
     "node_modules/builtin-modules": {
       "version": "3.3.0",
       "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz",
@@ -3894,6 +4138,98 @@
         "semver": "^7.0.0"
       }
     },
+    "node_modules/c8": {
+      "version": "9.1.0",
+      "resolved": "https://registry.npmjs.org/c8/-/c8-9.1.0.tgz",
+      "integrity": "sha512-mBWcT5iqNir1zIkzSPyI3NCR9EZCVI3WUD+AVO17MVWTSFNyUueXE82qTeampNtTr+ilN/5Ua3j24LgbCKjDVg==",
+      "dev": true,
+      "license": "ISC",
+      "dependencies": {
+        "@bcoe/v8-coverage": "^0.2.3",
+        "@istanbuljs/schema": "^0.1.3",
+        "find-up": "^5.0.0",
+        "foreground-child": "^3.1.1",
+        "istanbul-lib-coverage": "^3.2.0",
+        "istanbul-lib-report": "^3.0.1",
+        "istanbul-reports": "^3.1.6",
+        "test-exclude": "^6.0.0",
+        "v8-to-istanbul": "^9.0.0",
+        "yargs": "^17.7.2",
+        "yargs-parser": "^21.1.1"
+      },
+      "bin": {
+        "c8": "bin/c8.js"
+      },
+      "engines": {
+        "node": ">=14.14.0"
+      }
+    },
+    "node_modules/c8/node_modules/cliui": {
+      "version": "8.0.1",
+      "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
+      "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
+      "dev": true,
+      "license": "ISC",
+      "dependencies": {
+        "string-width": "^4.2.0",
+        "strip-ansi": "^6.0.1",
+        "wrap-ansi": "^7.0.0"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/c8/node_modules/convert-source-map": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+      "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/c8/node_modules/v8-to-istanbul": {
+      "version": "9.3.0",
+      "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz",
+      "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==",
+      "dev": true,
+      "license": "ISC",
+      "dependencies": {
+        "@jridgewell/trace-mapping": "^0.3.12",
+        "@types/istanbul-lib-coverage": "^2.0.1",
+        "convert-source-map": "^2.0.0"
+      },
+      "engines": {
+        "node": ">=10.12.0"
+      }
+    },
+    "node_modules/c8/node_modules/yargs": {
+      "version": "17.7.2",
+      "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
+      "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "cliui": "^8.0.1",
+        "escalade": "^3.1.1",
+        "get-caller-file": "^2.0.5",
+        "require-directory": "^2.1.1",
+        "string-width": "^4.2.3",
+        "y18n": "^5.0.5",
+        "yargs-parser": "^21.1.1"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/c8/node_modules/yargs-parser": {
+      "version": "21.1.1",
+      "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
+      "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
+      "dev": true,
+      "license": "ISC",
+      "engines": {
+        "node": ">=12"
+      }
+    },
     "node_modules/call-bind": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
@@ -3996,18 +4332,6 @@
         }
       ]
     },
-    "node_modules/chainsaw": {
-      "version": "0.1.0",
-      "resolved": "https://registry.npmjs.org/chainsaw/-/chainsaw-0.1.0.tgz",
-      "integrity": "sha1-XqtQsor+WAdNDVgpE4iCi15fvJg=",
-      "dev": true,
-      "dependencies": {
-        "traverse": ">=0.3.0 <0.4"
-      },
-      "engines": {
-        "node": "*"
-      }
-    },
     "node_modules/chalk": {
       "version": "2.4.2",
       "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
@@ -4058,6 +4382,44 @@
         "node": ">=10"
       }
     },
+    "node_modules/chokidar": {
+      "version": "3.6.0",
+      "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
+      "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "anymatch": "~3.1.2",
+        "braces": "~3.0.2",
+        "glob-parent": "~5.1.2",
+        "is-binary-path": "~2.1.0",
+        "is-glob": "~4.0.1",
+        "normalize-path": "~3.0.0",
+        "readdirp": "~3.6.0"
+      },
+      "engines": {
+        "node": ">= 8.10.0"
+      },
+      "funding": {
+        "url": "https://paulmillr.com/funding/"
+      },
+      "optionalDependencies": {
+        "fsevents": "~2.3.2"
+      }
+    },
+    "node_modules/chokidar/node_modules/glob-parent": {
+      "version": "5.1.2",
+      "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+      "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+      "dev": true,
+      "license": "ISC",
+      "dependencies": {
+        "is-glob": "^4.0.1"
+      },
+      "engines": {
+        "node": ">= 6"
+      }
+    },
     "node_modules/ci-info": {
       "version": "3.2.0",
       "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.2.0.tgz",
@@ -4086,6 +4448,35 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
+    "node_modules/cli-cursor": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz",
+      "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "restore-cursor": "^4.0.0"
+      },
+      "engines": {
+        "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/cli-spinners": {
+      "version": "2.9.2",
+      "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz",
+      "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=6"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
     "node_modules/cliui": {
       "version": "7.0.4",
       "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
@@ -4245,22 +4636,6 @@
       "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==",
       "dev": true
     },
-    "node_modules/cosmiconfig": {
-      "version": "7.1.0",
-      "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz",
-      "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==",
-      "dev": true,
-      "dependencies": {
-        "@types/parse-json": "^4.0.0",
-        "import-fresh": "^3.2.1",
-        "parse-json": "^5.0.0",
-        "path-type": "^4.0.0",
-        "yaml": "^1.10.0"
-      },
-      "engines": {
-        "node": ">=10"
-      }
-    },
     "node_modules/create-require": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
@@ -4690,10 +5065,11 @@
       }
     },
     "node_modules/debug": {
-      "version": "4.3.4",
-      "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
-      "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+      "version": "4.3.6",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz",
+      "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==",
       "dev": true,
+      "license": "MIT",
       "dependencies": {
         "ms": "2.1.2"
       },
@@ -4896,30 +5272,12 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
-    "node_modules/duplexer2": {
-      "version": "0.1.4",
-      "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz",
-      "integrity": "sha1-ixLauHjA1p4+eJEFFmKjL8a93ME=",
-      "dev": true,
-      "dependencies": {
-        "readable-stream": "^2.0.2"
-      }
-    },
     "node_modules/eastasianwidth": {
       "version": "0.2.0",
       "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
       "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
       "dev": true
     },
-    "node_modules/easy-stack": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/easy-stack/-/easy-stack-1.0.1.tgz",
-      "integrity": "sha512-wK2sCs4feiiJeFXn3zvY0p41mdU5VUgbgs1rNsc/y5ngFUijdWd+iIN8eoyuZHKB8xN6BL4PdWmzqFmxNg6V2w==",
-      "dev": true,
-      "engines": {
-        "node": ">=6.0.0"
-      }
-    },
     "node_modules/electron-to-chromium": {
       "version": "1.4.715",
       "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.715.tgz",
@@ -4949,6 +5307,20 @@
       "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz",
       "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ=="
     },
+    "node_modules/enhanced-resolve": {
+      "version": "5.17.1",
+      "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz",
+      "integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "graceful-fs": "^4.2.4",
+        "tapable": "^2.2.0"
+      },
+      "engines": {
+        "node": ">=10.13.0"
+      }
+    },
     "node_modules/error-ex": {
       "version": "1.3.2",
       "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
@@ -5655,15 +6027,6 @@
         "node": ">=0.10.0"
       }
     },
-    "node_modules/event-pubsub": {
-      "version": "4.3.0",
-      "resolved": "https://registry.npmjs.org/event-pubsub/-/event-pubsub-4.3.0.tgz",
-      "integrity": "sha512-z7IyloorXvKbFx9Bpie2+vMJKKx1fH1EN5yiTfp8CiLOTptSYy1g8H4yDpGlEdshL1PBiFtBHepF2cNsqeEeFQ==",
-      "dev": true,
-      "engines": {
-        "node": ">=4.0.0"
-      }
-    },
     "node_modules/execa": {
       "version": "5.1.1",
       "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz",
@@ -5869,6 +6232,16 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
+    "node_modules/flat": {
+      "version": "5.0.2",
+      "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz",
+      "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==",
+      "dev": true,
+      "license": "BSD-3-Clause",
+      "bin": {
+        "flat": "cli.js"
+      }
+    },
     "node_modules/flat-cache": {
       "version": "3.0.4",
       "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz",
@@ -5955,33 +6328,6 @@
         "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
       }
     },
-    "node_modules/fstream": {
-      "version": "1.0.12",
-      "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz",
-      "integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==",
-      "dev": true,
-      "dependencies": {
-        "graceful-fs": "^4.1.2",
-        "inherits": "~2.0.0",
-        "mkdirp": ">=0.5 0",
-        "rimraf": "2"
-      },
-      "engines": {
-        "node": ">=0.6"
-      }
-    },
-    "node_modules/fstream/node_modules/rimraf": {
-      "version": "2.7.1",
-      "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
-      "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
-      "dev": true,
-      "dependencies": {
-        "glob": "^7.1.3"
-      },
-      "bin": {
-        "rimraf": "bin.js"
-      }
-    },
     "node_modules/function-bind": {
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
@@ -6345,6 +6691,16 @@
         "node": ">= 0.4"
       }
     },
+    "node_modules/he": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
+      "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
+      "dev": true,
+      "license": "MIT",
+      "bin": {
+        "he": "bin/he"
+      }
+    },
     "node_modules/hosted-git-info": {
       "version": "2.8.9",
       "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz",
@@ -6429,6 +6785,27 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/ieee754": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
+      "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
+      "dev": true,
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/feross"
+        },
+        {
+          "type": "patreon",
+          "url": "https://www.patreon.com/feross"
+        },
+        {
+          "type": "consulting",
+          "url": "https://feross.org/support"
+        }
+      ],
+      "license": "BSD-3-Clause"
+    },
     "node_modules/ignore": {
       "version": "5.3.1",
       "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz",
@@ -6438,6 +6815,13 @@
         "node": ">= 4"
       }
     },
+    "node_modules/immediate": {
+      "version": "3.0.6",
+      "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
+      "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==",
+      "dev": true,
+      "license": "MIT"
+    },
     "node_modules/import-fresh": {
       "version": "3.3.0",
       "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
@@ -6584,6 +6968,19 @@
         "url": "https://github.com/sponsors/ljharb"
       }
     },
+    "node_modules/is-binary-path": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+      "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "binary-extensions": "^2.0.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
     "node_modules/is-boolean-object": {
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz",
@@ -6693,6 +7090,19 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/is-interactive": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz",
+      "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
     "node_modules/is-negative-zero": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz",
@@ -6846,6 +7256,19 @@
       "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=",
       "dev": true
     },
+    "node_modules/is-unicode-supported": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz",
+      "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
     "node_modules/is-weakref": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.1.tgz",
@@ -6861,8 +7284,9 @@
     "node_modules/isarray": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
-      "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
-      "dev": true
+      "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
+      "dev": true,
+      "license": "MIT"
     },
     "node_modules/isexe": {
       "version": "2.0.0",
@@ -6904,17 +7328,18 @@
       }
     },
     "node_modules/istanbul-lib-report": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz",
-      "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==",
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz",
+      "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==",
       "dev": true,
+      "license": "BSD-3-Clause",
       "dependencies": {
         "istanbul-lib-coverage": "^3.0.0",
-        "make-dir": "^3.0.0",
+        "make-dir": "^4.0.0",
         "supports-color": "^7.1.0"
       },
       "engines": {
-        "node": ">=8"
+        "node": ">=10"
       }
     },
     "node_modules/istanbul-lib-report/node_modules/has-flag": {
@@ -6962,10 +7387,11 @@
       }
     },
     "node_modules/istanbul-reports": {
-      "version": "3.1.0",
-      "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.0.tgz",
-      "integrity": "sha512-rgeP8yMlXeH4mfd9K/sQXZv1lvcS7xo379zntcotPDdMwkcGYwMxGHGZYo0/+YW5B/nor2YGKz2BH5ume405ow==",
+      "version": "3.1.7",
+      "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz",
+      "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==",
       "dev": true,
+      "license": "BSD-3-Clause",
       "dependencies": {
         "html-escaper": "^2.0.0",
         "istanbul-lib-report": "^3.0.0"
@@ -8063,27 +8489,6 @@
         "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0"
       }
     },
-    "node_modules/jest-runner-vscode": {
-      "version": "2.1.0",
-      "resolved": "https://registry.npmjs.org/jest-runner-vscode/-/jest-runner-vscode-2.1.0.tgz",
-      "integrity": "sha512-69frE9umIx2AAu5QG79XB2B3XofXq452LsEkfEdtWOrXmC4AzjQGWKns2E/JkYb1LsLsf1upKjWlYs3Gs/HsKQ==",
-      "dev": true,
-      "dependencies": {
-        "@jest/core": "^27.2.1",
-        "@vscode/test-electron": "^1.6.2",
-        "cosmiconfig": "^7.0.1",
-        "jest-cli": "^27.2.1",
-        "jest-environment-node": "^27.2.0",
-        "node-ipc": "^9.0.0"
-      },
-      "engines": {
-        "node": ">=14.16.0",
-        "vscode": ">=1.56.0"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/adalinesimonian"
-      }
-    },
     "node_modules/jest-runner/node_modules/ansi-styles": {
       "version": "4.3.0",
       "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
@@ -8691,27 +9096,6 @@
         "url": "https://github.com/chalk/supports-color?sponsor=1"
       }
     },
-    "node_modules/js-message": {
-      "version": "1.0.7",
-      "resolved": "https://registry.npmjs.org/js-message/-/js-message-1.0.7.tgz",
-      "integrity": "sha512-efJLHhLjIyKRewNS9EGZ4UpI8NguuL6fKkhRxVuMmrGV2xN/0APGdQYwLFky5w9naebSZ0OwAGp0G6/2Cg90rA==",
-      "dev": true,
-      "engines": {
-        "node": ">=0.6.0"
-      }
-    },
-    "node_modules/js-queue": {
-      "version": "2.0.2",
-      "resolved": "https://registry.npmjs.org/js-queue/-/js-queue-2.0.2.tgz",
-      "integrity": "sha512-pbKLsbCfi7kriM3s1J4DDCo7jQkI58zPLHi0heXPzPlj0hjUsm+FesPUbE0DSbIVIK503A36aUBoCN7eMFedkA==",
-      "dev": true,
-      "dependencies": {
-        "easy-stack": "^1.0.1"
-      },
-      "engines": {
-        "node": ">=1.0.0"
-      }
-    },
     "node_modules/js-tokens": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@@ -8839,6 +9223,19 @@
         "node": ">=6"
       }
     },
+    "node_modules/jszip": {
+      "version": "3.10.1",
+      "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz",
+      "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==",
+      "dev": true,
+      "license": "(MIT OR GPL-3.0-or-later)",
+      "dependencies": {
+        "lie": "~3.3.0",
+        "pako": "~1.0.2",
+        "readable-stream": "~2.3.6",
+        "setimmediate": "^1.0.5"
+      }
+    },
     "node_modules/keyv": {
       "version": "4.5.4",
       "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
@@ -8899,18 +9296,22 @@
         "node": ">= 0.8.0"
       }
     },
+    "node_modules/lie": {
+      "version": "3.3.0",
+      "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz",
+      "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "immediate": "~3.0.5"
+      }
+    },
     "node_modules/lines-and-columns": {
       "version": "1.1.6",
       "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz",
       "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=",
       "dev": true
     },
-    "node_modules/listenercount": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/listenercount/-/listenercount-1.0.1.tgz",
-      "integrity": "sha1-hMinKrWcRyUyFIDJdeZQg0LnCTc=",
-      "dev": true
-    },
     "node_modules/load-json-file": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz",
@@ -8987,6 +9388,99 @@
       "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==",
       "dev": true
     },
+    "node_modules/log-symbols": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz",
+      "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "chalk": "^4.1.0",
+        "is-unicode-supported": "^0.1.0"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/log-symbols/node_modules/ansi-styles": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+      "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "color-convert": "^2.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+      }
+    },
+    "node_modules/log-symbols/node_modules/chalk": {
+      "version": "4.1.2",
+      "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+      "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "ansi-styles": "^4.1.0",
+        "supports-color": "^7.1.0"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/chalk?sponsor=1"
+      }
+    },
+    "node_modules/log-symbols/node_modules/color-convert": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+      "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "color-name": "~1.1.4"
+      },
+      "engines": {
+        "node": ">=7.0.0"
+      }
+    },
+    "node_modules/log-symbols/node_modules/color-name": {
+      "version": "1.1.4",
+      "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+      "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/log-symbols/node_modules/has-flag": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+      "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/log-symbols/node_modules/supports-color": {
+      "version": "7.2.0",
+      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+      "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "has-flag": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
     "node_modules/logform": {
       "version": "2.6.0",
       "resolved": "https://registry.npmjs.org/logform/-/logform-2.6.0.tgz",
@@ -9007,6 +9501,7 @@
       "version": "6.0.0",
       "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
       "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+      "dev": true,
       "dependencies": {
         "yallist": "^4.0.0"
       },
@@ -9015,29 +9510,21 @@
       }
     },
     "node_modules/make-dir": {
-      "version": "3.1.0",
-      "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
-      "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==",
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz",
+      "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==",
       "dev": true,
+      "license": "MIT",
       "dependencies": {
-        "semver": "^6.0.0"
+        "semver": "^7.5.3"
       },
       "engines": {
-        "node": ">=8"
+        "node": ">=10"
       },
       "funding": {
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
-    "node_modules/make-dir/node_modules/semver": {
-      "version": "6.3.0",
-      "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
-      "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
-      "dev": true,
-      "bin": {
-        "semver": "bin/semver.js"
-      }
-    },
     "node_modules/make-error": {
       "version": "1.3.6",
       "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
@@ -9260,28 +9747,153 @@
       "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==",
       "dev": true,
       "engines": {
-        "node": ">=0.10.0"
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/minipass": {
+      "version": "7.1.2",
+      "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
+      "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
+      "dev": true,
+      "license": "ISC",
+      "engines": {
+        "node": ">=16 || 14 >=14.17"
+      }
+    },
+    "node_modules/mocha": {
+      "version": "10.7.3",
+      "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.7.3.tgz",
+      "integrity": "sha512-uQWxAu44wwiACGqjbPYmjo7Lg8sFrS3dQe7PP2FQI+woptP4vZXSMcfMyFL/e1yFEeEpV4RtyTpZROOKmxis+A==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "ansi-colors": "^4.1.3",
+        "browser-stdout": "^1.3.1",
+        "chokidar": "^3.5.3",
+        "debug": "^4.3.5",
+        "diff": "^5.2.0",
+        "escape-string-regexp": "^4.0.0",
+        "find-up": "^5.0.0",
+        "glob": "^8.1.0",
+        "he": "^1.2.0",
+        "js-yaml": "^4.1.0",
+        "log-symbols": "^4.1.0",
+        "minimatch": "^5.1.6",
+        "ms": "^2.1.3",
+        "serialize-javascript": "^6.0.2",
+        "strip-json-comments": "^3.1.1",
+        "supports-color": "^8.1.1",
+        "workerpool": "^6.5.1",
+        "yargs": "^16.2.0",
+        "yargs-parser": "^20.2.9",
+        "yargs-unparser": "^2.0.0"
+      },
+      "bin": {
+        "_mocha": "bin/_mocha",
+        "mocha": "bin/mocha.js"
+      },
+      "engines": {
+        "node": ">= 14.0.0"
+      }
+    },
+    "node_modules/mocha/node_modules/brace-expansion": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
+      "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "balanced-match": "^1.0.0"
+      }
+    },
+    "node_modules/mocha/node_modules/diff": {
+      "version": "5.2.0",
+      "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz",
+      "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==",
+      "dev": true,
+      "license": "BSD-3-Clause",
+      "engines": {
+        "node": ">=0.3.1"
+      }
+    },
+    "node_modules/mocha/node_modules/escape-string-regexp": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+      "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/mocha/node_modules/glob": {
+      "version": "8.1.0",
+      "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz",
+      "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==",
+      "deprecated": "Glob versions prior to v9 are no longer supported",
+      "dev": true,
+      "license": "ISC",
+      "dependencies": {
+        "fs.realpath": "^1.0.0",
+        "inflight": "^1.0.4",
+        "inherits": "2",
+        "minimatch": "^5.0.1",
+        "once": "^1.3.0"
+      },
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/isaacs"
+      }
+    },
+    "node_modules/mocha/node_modules/has-flag": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+      "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=8"
       }
     },
-    "node_modules/minipass": {
-      "version": "7.0.4",
-      "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz",
-      "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==",
+    "node_modules/mocha/node_modules/minimatch": {
+      "version": "5.1.6",
+      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
+      "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
       "dev": true,
+      "license": "ISC",
+      "dependencies": {
+        "brace-expansion": "^2.0.1"
+      },
       "engines": {
-        "node": ">=16 || 14 >=14.17"
+        "node": ">=10"
       }
     },
-    "node_modules/mkdirp": {
-      "version": "0.5.5",
-      "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
-      "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
+    "node_modules/mocha/node_modules/ms": {
+      "version": "2.1.3",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+      "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/mocha/node_modules/supports-color": {
+      "version": "8.1.1",
+      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
+      "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
       "dev": true,
+      "license": "MIT",
       "dependencies": {
-        "minimist": "^1.2.5"
+        "has-flag": "^4.0.0"
       },
-      "bin": {
-        "mkdirp": "bin/cmd.js"
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/supports-color?sponsor=1"
       }
     },
     "node_modules/ms": {
@@ -9325,20 +9937,6 @@
       "integrity": "sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=",
       "dev": true
     },
-    "node_modules/node-ipc": {
-      "version": "9.2.1",
-      "resolved": "https://registry.npmjs.org/node-ipc/-/node-ipc-9.2.1.tgz",
-      "integrity": "sha512-mJzaM6O3xHf9VT8BULvJSbdVbmHUKRNOH7zDDkCrA1/T+CVjq2WVIDfLt0azZRXpgArJtl3rtmEozrbXPZ9GaQ==",
-      "dev": true,
-      "dependencies": {
-        "event-pubsub": "4.3.0",
-        "js-message": "1.0.7",
-        "js-queue": "2.0.2"
-      },
-      "engines": {
-        "node": ">=8.0.0"
-      }
-    },
     "node_modules/node-modules-regexp": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz",
@@ -9579,13 +10177,125 @@
         "node": ">= 0.8.0"
       }
     },
-    "node_modules/p-finally": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz",
-      "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=",
+    "node_modules/ora": {
+      "version": "7.0.1",
+      "resolved": "https://registry.npmjs.org/ora/-/ora-7.0.1.tgz",
+      "integrity": "sha512-0TUxTiFJWv+JnjWm4o9yvuskpEJLXTcng8MJuKd+SzAzp2o+OP3HWqNhB4OdJRt1Vsd9/mR0oyaEYlOnL7XIRw==",
       "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "chalk": "^5.3.0",
+        "cli-cursor": "^4.0.0",
+        "cli-spinners": "^2.9.0",
+        "is-interactive": "^2.0.0",
+        "is-unicode-supported": "^1.3.0",
+        "log-symbols": "^5.1.0",
+        "stdin-discarder": "^0.1.0",
+        "string-width": "^6.1.0",
+        "strip-ansi": "^7.1.0"
+      },
       "engines": {
-        "node": ">=4"
+        "node": ">=16"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/ora/node_modules/ansi-regex": {
+      "version": "6.0.1",
+      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
+      "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+      }
+    },
+    "node_modules/ora/node_modules/chalk": {
+      "version": "5.3.0",
+      "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz",
+      "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": "^12.17.0 || ^14.13 || >=16.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/chalk?sponsor=1"
+      }
+    },
+    "node_modules/ora/node_modules/emoji-regex": {
+      "version": "10.4.0",
+      "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz",
+      "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/ora/node_modules/is-unicode-supported": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz",
+      "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/ora/node_modules/log-symbols": {
+      "version": "5.1.0",
+      "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-5.1.0.tgz",
+      "integrity": "sha512-l0x2DvrW294C9uDCoQe1VSU4gf529FkSZ6leBl4TiqZH/e+0R7hSfHQBNut2mNygDgHwvYHfFLn6Oxb3VWj2rA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "chalk": "^5.0.0",
+        "is-unicode-supported": "^1.1.0"
+      },
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/ora/node_modules/string-width": {
+      "version": "6.1.0",
+      "resolved": "https://registry.npmjs.org/string-width/-/string-width-6.1.0.tgz",
+      "integrity": "sha512-k01swCJAgQmuADB0YIc+7TuatfNvTBVOoaUWJjTB9R4VJzR5vNWzf5t42ESVZFPS8xTySF7CAdV4t/aaIm3UnQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "eastasianwidth": "^0.2.0",
+        "emoji-regex": "^10.2.1",
+        "strip-ansi": "^7.0.1"
+      },
+      "engines": {
+        "node": ">=16"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/ora/node_modules/strip-ansi": {
+      "version": "7.1.0",
+      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
+      "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "ansi-regex": "^6.0.1"
+      },
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/strip-ansi?sponsor=1"
       }
     },
     "node_modules/p-limit": {
@@ -9618,18 +10328,6 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
-    "node_modules/p-timeout": {
-      "version": "3.2.0",
-      "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz",
-      "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==",
-      "dev": true,
-      "dependencies": {
-        "p-finally": "^1.0.0"
-      },
-      "engines": {
-        "node": ">=8"
-      }
-    },
     "node_modules/p-try": {
       "version": "2.2.0",
       "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
@@ -9639,20 +10337,19 @@
         "node": ">=6"
       }
     },
-    "node_modules/p-wait-for": {
-      "version": "3.2.0",
-      "resolved": "https://registry.npmjs.org/p-wait-for/-/p-wait-for-3.2.0.tgz",
-      "integrity": "sha512-wpgERjNkLrBiFmkMEjuZJEWKKDrNfHCKA1OhyN1wg1FrLkULbviEy6py1AyJUgZ72YWFbZ38FIpnqvVqAlDUwA==",
+    "node_modules/package-json-from-dist": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz",
+      "integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==",
       "dev": true,
-      "dependencies": {
-        "p-timeout": "^3.0.0"
-      },
-      "engines": {
-        "node": ">=8"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/sindresorhus"
-      }
+      "license": "BlueOak-1.0.0"
+    },
+    "node_modules/pako": {
+      "version": "1.0.11",
+      "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
+      "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",
+      "dev": true,
+      "license": "(MIT AND Zlib)"
     },
     "node_modules/parent-module": {
       "version": "2.0.0",
@@ -9729,16 +10426,17 @@
       "dev": true
     },
     "node_modules/path-scurry": {
-      "version": "1.10.1",
-      "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz",
-      "integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==",
+      "version": "1.11.1",
+      "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
+      "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
       "dev": true,
+      "license": "BlueOak-1.0.0",
       "dependencies": {
-        "lru-cache": "^9.1.1 || ^10.0.0",
+        "lru-cache": "^10.2.0",
         "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
       },
       "engines": {
-        "node": ">=16 || 14 >=14.17"
+        "node": ">=16 || 14 >=14.18"
       },
       "funding": {
         "url": "https://github.com/sponsors/isaacs"
@@ -10107,7 +10805,8 @@
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
       "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
-      "dev": true
+      "dev": true,
+      "license": "MIT"
     },
     "node_modules/prompts": {
       "version": "2.4.2",
@@ -10175,6 +10874,16 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
+    "node_modules/randombytes": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
+      "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "safe-buffer": "^5.1.0"
+      }
+    },
     "node_modules/react-is": {
       "version": "17.0.2",
       "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
@@ -10282,10 +10991,11 @@
       }
     },
     "node_modules/readable-stream": {
-      "version": "2.3.7",
-      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
-      "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
+      "version": "2.3.8",
+      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
+      "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
       "dev": true,
+      "license": "MIT",
       "dependencies": {
         "core-util-is": "~1.0.0",
         "inherits": "~2.0.3",
@@ -10296,6 +11006,19 @@
         "util-deprecate": "~1.0.1"
       }
     },
+    "node_modules/readdirp": {
+      "version": "3.6.0",
+      "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
+      "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "picomatch": "^2.2.1"
+      },
+      "engines": {
+        "node": ">=8.10.0"
+      }
+    },
     "node_modules/redent": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/redent/-/redent-4.0.0.tgz",
@@ -10426,6 +11149,23 @@
         "node": ">=10"
       }
     },
+    "node_modules/restore-cursor": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz",
+      "integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "onetime": "^5.1.0",
+        "signal-exit": "^3.0.2"
+      },
+      "engines": {
+        "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
     "node_modules/reusify": {
       "version": "1.0.4",
       "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
@@ -10542,12 +11282,10 @@
       }
     },
     "node_modules/semver": {
-      "version": "7.6.0",
-      "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",
-      "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==",
-      "dependencies": {
-        "lru-cache": "^6.0.0"
-      },
+      "version": "7.6.3",
+      "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
+      "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
+      "license": "ISC",
       "bin": {
         "semver": "bin/semver.js"
       },
@@ -10580,11 +11318,22 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
+    "node_modules/serialize-javascript": {
+      "version": "6.0.2",
+      "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz",
+      "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==",
+      "dev": true,
+      "license": "BSD-3-Clause",
+      "dependencies": {
+        "randombytes": "^2.1.0"
+      }
+    },
     "node_modules/setimmediate": {
       "version": "1.0.5",
       "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
-      "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=",
-      "dev": true
+      "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==",
+      "dev": true,
+      "license": "MIT"
     },
     "node_modules/shebang-command": {
       "version": "2.0.0",
@@ -10888,6 +11637,22 @@
         "node": ">=8"
       }
     },
+    "node_modules/stdin-discarder": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.1.0.tgz",
+      "integrity": "sha512-xhV7w8S+bUwlPTb4bAOUQhv8/cSS5offJuX8GQGq32ONF0ZtDWKfkdomM3HMRA+LhX6um/FZ0COqlwsjD53LeQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "bl": "^5.0.0"
+      },
+      "engines": {
+        "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
     "node_modules/string_decoder": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
@@ -11442,6 +12207,16 @@
       "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
       "dev": true
     },
+    "node_modules/tapable": {
+      "version": "2.2.1",
+      "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz",
+      "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=6"
+      }
+    },
     "node_modules/terminal-link": {
       "version": "2.1.1",
       "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz",
@@ -11552,15 +12327,6 @@
         "node": ">=8"
       }
     },
-    "node_modules/traverse": {
-      "version": "0.3.9",
-      "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.3.9.tgz",
-      "integrity": "sha1-cXuPIgzAu3tE5AUUwisui7xw2Lk=",
-      "dev": true,
-      "engines": {
-        "node": "*"
-      }
-    },
     "node_modules/trim-newlines": {
       "version": "4.1.1",
       "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-4.1.1.tgz",
@@ -11800,24 +12566,6 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
-    "node_modules/unzipper": {
-      "version": "0.10.11",
-      "resolved": "https://registry.npmjs.org/unzipper/-/unzipper-0.10.11.tgz",
-      "integrity": "sha512-+BrAq2oFqWod5IESRjL3S8baohbevGcVA+teAIOYWM3pDVdseogqbzhhvvmiyQrUNKFUnDMtELW3X8ykbyDCJw==",
-      "dev": true,
-      "dependencies": {
-        "big-integer": "^1.6.17",
-        "binary": "~0.3.0",
-        "bluebird": "~3.4.1",
-        "buffer-indexof-polyfill": "~1.0.0",
-        "duplexer2": "~0.1.4",
-        "fstream": "^1.0.12",
-        "graceful-fs": "^4.2.2",
-        "listenercount": "~1.0.1",
-        "readable-stream": "~2.3.6",
-        "setimmediate": "~1.0.4"
-      }
-    },
     "node_modules/update-browserslist-db": {
       "version": "1.0.13",
       "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz",
@@ -12154,6 +12902,13 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/workerpool": {
+      "version": "6.5.1",
+      "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz",
+      "integrity": "sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==",
+      "dev": true,
+      "license": "Apache-2.0"
+    },
     "node_modules/wrap-ansi": {
       "version": "7.0.0",
       "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
@@ -12330,16 +13085,8 @@
     "node_modules/yallist": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
-      "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
-    },
-    "node_modules/yaml": {
-      "version": "1.10.2",
-      "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
-      "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
-      "dev": true,
-      "engines": {
-        "node": ">= 6"
-      }
+      "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
+      "dev": true
     },
     "node_modules/yargs": {
       "version": "16.2.0",
@@ -12359,13 +13106,65 @@
         "node": ">=10"
       }
     },
-    "node_modules/yargs-parser": {
-      "version": "20.2.9",
-      "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz",
-      "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==",
+    "node_modules/yargs-parser": {
+      "version": "20.2.9",
+      "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz",
+      "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==",
+      "dev": true,
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/yargs-unparser": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz",
+      "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "camelcase": "^6.0.0",
+        "decamelize": "^4.0.0",
+        "flat": "^5.0.2",
+        "is-plain-obj": "^2.1.0"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/yargs-unparser/node_modules/camelcase": {
+      "version": "6.3.0",
+      "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz",
+      "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/yargs-unparser/node_modules/decamelize": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz",
+      "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/yargs-unparser/node_modules/is-plain-obj": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz",
+      "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==",
       "dev": true,
+      "license": "MIT",
       "engines": {
-        "node": ">=10"
+        "node": ">=8"
       }
     },
     "node_modules/yn": {
@@ -14357,6 +15156,12 @@
       "integrity": "sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==",
       "dev": true
     },
+    "@types/mocha": {
+      "version": "10.0.7",
+      "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.7.tgz",
+      "integrity": "sha512-GN8yJ1mNTcFcah/wKEFIJckJx9iJLoMSzWcfRRuxz/Jk+U6KQNnml+etbtxFK8lPjzOw3zp4Ha/kjSst9fsHYw==",
+      "dev": true
+    },
     "@types/node": {
       "version": "16.11.6",
       "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.6.tgz",
@@ -14369,12 +15174,6 @@
       "integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==",
       "dev": true
     },
-    "@types/parse-json": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz",
-      "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==",
-      "dev": true
-    },
     "@types/path-is-inside": {
       "version": "1.0.3",
       "resolved": "https://registry.npmjs.org/@types/path-is-inside/-/path-is-inside-1.0.3.tgz",
@@ -14741,16 +15540,147 @@
       "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==",
       "dev": true
     },
+    "@vscode/test-cli": {
+      "version": "0.0.10",
+      "resolved": "https://registry.npmjs.org/@vscode/test-cli/-/test-cli-0.0.10.tgz",
+      "integrity": "sha512-B0mMH4ia+MOOtwNiLi79XhA+MLmUItIC8FckEuKrVAVriIuSWjt7vv4+bF8qVFiNFe4QRfzPaIZk39FZGWEwHA==",
+      "dev": true,
+      "requires": {
+        "@types/mocha": "^10.0.2",
+        "c8": "^9.1.0",
+        "chokidar": "^3.5.3",
+        "enhanced-resolve": "^5.15.0",
+        "glob": "^10.3.10",
+        "minimatch": "^9.0.3",
+        "mocha": "^10.2.0",
+        "supports-color": "^9.4.0",
+        "yargs": "^17.7.2"
+      },
+      "dependencies": {
+        "brace-expansion": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
+          "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+          "dev": true,
+          "requires": {
+            "balanced-match": "^1.0.0"
+          }
+        },
+        "cliui": {
+          "version": "8.0.1",
+          "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
+          "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
+          "dev": true,
+          "requires": {
+            "string-width": "^4.2.0",
+            "strip-ansi": "^6.0.1",
+            "wrap-ansi": "^7.0.0"
+          }
+        },
+        "glob": {
+          "version": "10.4.5",
+          "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
+          "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
+          "dev": true,
+          "requires": {
+            "foreground-child": "^3.1.0",
+            "jackspeak": "^3.1.2",
+            "minimatch": "^9.0.4",
+            "minipass": "^7.1.2",
+            "package-json-from-dist": "^1.0.0",
+            "path-scurry": "^1.11.1"
+          }
+        },
+        "jackspeak": {
+          "version": "3.4.3",
+          "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
+          "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
+          "dev": true,
+          "requires": {
+            "@isaacs/cliui": "^8.0.2",
+            "@pkgjs/parseargs": "^0.11.0"
+          }
+        },
+        "minimatch": {
+          "version": "9.0.5",
+          "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
+          "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
+          "dev": true,
+          "requires": {
+            "brace-expansion": "^2.0.1"
+          }
+        },
+        "supports-color": {
+          "version": "9.4.0",
+          "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-9.4.0.tgz",
+          "integrity": "sha512-VL+lNrEoIXww1coLPOmiEmK/0sGigko5COxI09KzHc2VJXJsQ37UaQ+8quuxjDeA7+KnLGTWRyOXSLLR2Wb4jw==",
+          "dev": true
+        },
+        "yargs": {
+          "version": "17.7.2",
+          "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
+          "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
+          "dev": true,
+          "requires": {
+            "cliui": "^8.0.1",
+            "escalade": "^3.1.1",
+            "get-caller-file": "^2.0.5",
+            "require-directory": "^2.1.1",
+            "string-width": "^4.2.3",
+            "y18n": "^5.0.5",
+            "yargs-parser": "^21.1.1"
+          }
+        },
+        "yargs-parser": {
+          "version": "21.1.1",
+          "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
+          "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
+          "dev": true
+        }
+      }
+    },
     "@vscode/test-electron": {
-      "version": "1.6.2",
-      "resolved": "https://registry.npmjs.org/@vscode/test-electron/-/test-electron-1.6.2.tgz",
-      "integrity": "sha512-W01ajJEMx6223Y7J5yaajGjVs1QfW3YGkkOJHVKfAMEqNB1ZHN9wCcViehv5ZwVSSJnjhu6lYEYgwBdHtCxqhQ==",
+      "version": "2.4.1",
+      "resolved": "https://registry.npmjs.org/@vscode/test-electron/-/test-electron-2.4.1.tgz",
+      "integrity": "sha512-Gc6EdaLANdktQ1t+zozoBVRynfIsMKMc94Svu1QreOBC8y76x4tvaK32TljrLi1LI2+PK58sDVbL7ALdqf3VRQ==",
       "dev": true,
       "requires": {
-        "http-proxy-agent": "^4.0.1",
-        "https-proxy-agent": "^5.0.0",
-        "rimraf": "^3.0.2",
-        "unzipper": "^0.10.11"
+        "http-proxy-agent": "^7.0.2",
+        "https-proxy-agent": "^7.0.5",
+        "jszip": "^3.10.1",
+        "ora": "^7.0.1",
+        "semver": "^7.6.2"
+      },
+      "dependencies": {
+        "agent-base": {
+          "version": "7.1.1",
+          "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz",
+          "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==",
+          "dev": true,
+          "requires": {
+            "debug": "^4.3.4"
+          }
+        },
+        "http-proxy-agent": {
+          "version": "7.0.2",
+          "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz",
+          "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==",
+          "dev": true,
+          "requires": {
+            "agent-base": "^7.1.0",
+            "debug": "^4.3.4"
+          }
+        },
+        "https-proxy-agent": {
+          "version": "7.0.5",
+          "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz",
+          "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==",
+          "dev": true,
+          "requires": {
+            "agent-base": "^7.0.2",
+            "debug": "4"
+          }
+        }
       }
     },
     "abab": {
@@ -14817,6 +15747,12 @@
         "uri-js": "^4.2.2"
       }
     },
+    "ansi-colors": {
+      "version": "4.1.3",
+      "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz",
+      "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==",
+      "dev": true
+    },
     "ansi-escapes": {
       "version": "4.3.2",
       "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz",
@@ -15052,28 +15988,42 @@
       "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
       "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
     },
-    "big-integer": {
-      "version": "1.6.50",
-      "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.50.tgz",
-      "integrity": "sha512-+O2uoQWFRo8ysZNo/rjtri2jIwjr3XfeAgRjAUADRqGG+ZITvyn8J1kvXLTaKVr3hhGXk+f23tKfdzmklVM9vQ==",
+    "base64-js": {
+      "version": "1.5.1",
+      "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
+      "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
       "dev": true
     },
-    "binary": {
-      "version": "0.3.0",
-      "resolved": "https://registry.npmjs.org/binary/-/binary-0.3.0.tgz",
-      "integrity": "sha1-n2BVO8XOjDOG87VTz/R0Yq3sqnk=",
+    "binary-extensions": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
+      "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
+      "dev": true
+    },
+    "bl": {
+      "version": "5.1.0",
+      "resolved": "https://registry.npmjs.org/bl/-/bl-5.1.0.tgz",
+      "integrity": "sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==",
       "dev": true,
       "requires": {
-        "buffers": "~0.1.1",
-        "chainsaw": "~0.1.0"
+        "buffer": "^6.0.3",
+        "inherits": "^2.0.4",
+        "readable-stream": "^3.4.0"
+      },
+      "dependencies": {
+        "readable-stream": {
+          "version": "3.6.2",
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
+          "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
+          "dev": true,
+          "requires": {
+            "inherits": "^2.0.3",
+            "string_decoder": "^1.1.1",
+            "util-deprecate": "^1.0.1"
+          }
+        }
       }
     },
-    "bluebird": {
-      "version": "3.4.7",
-      "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.4.7.tgz",
-      "integrity": "sha1-9y12C+Cbf3bQjtj66Ysomo0F+rM=",
-      "dev": true
-    },
     "brace-expansion": {
       "version": "1.1.11",
       "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@@ -15099,6 +16049,12 @@
       "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==",
       "dev": true
     },
+    "browser-stdout": {
+      "version": "1.3.1",
+      "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz",
+      "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==",
+      "dev": true
+    },
     "browserslist": {
       "version": "4.23.0",
       "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz",
@@ -15129,24 +16085,22 @@
         "node-int64": "^0.4.0"
       }
     },
+    "buffer": {
+      "version": "6.0.3",
+      "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
+      "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
+      "dev": true,
+      "requires": {
+        "base64-js": "^1.3.1",
+        "ieee754": "^1.2.1"
+      }
+    },
     "buffer-from": {
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
       "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
       "dev": true
     },
-    "buffer-indexof-polyfill": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/buffer-indexof-polyfill/-/buffer-indexof-polyfill-1.0.2.tgz",
-      "integrity": "sha512-I7wzHwA3t1/lwXQh+A5PbNvJxgfo5r3xulgpYDB5zckTu/Z9oUK9biouBKQUjEqzaz3HnAT6TYoovmE+GqSf7A==",
-      "dev": true
-    },
-    "buffers": {
-      "version": "0.1.1",
-      "resolved": "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz",
-      "integrity": "sha1-skV5w77U1tOWru5tmorn9Ugqt7s=",
-      "dev": true
-    },
     "builtin-modules": {
       "version": "3.3.0",
       "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz",
@@ -15162,6 +16116,76 @@
         "semver": "^7.0.0"
       }
     },
+    "c8": {
+      "version": "9.1.0",
+      "resolved": "https://registry.npmjs.org/c8/-/c8-9.1.0.tgz",
+      "integrity": "sha512-mBWcT5iqNir1zIkzSPyI3NCR9EZCVI3WUD+AVO17MVWTSFNyUueXE82qTeampNtTr+ilN/5Ua3j24LgbCKjDVg==",
+      "dev": true,
+      "requires": {
+        "@bcoe/v8-coverage": "^0.2.3",
+        "@istanbuljs/schema": "^0.1.3",
+        "find-up": "^5.0.0",
+        "foreground-child": "^3.1.1",
+        "istanbul-lib-coverage": "^3.2.0",
+        "istanbul-lib-report": "^3.0.1",
+        "istanbul-reports": "^3.1.6",
+        "test-exclude": "^6.0.0",
+        "v8-to-istanbul": "^9.0.0",
+        "yargs": "^17.7.2",
+        "yargs-parser": "^21.1.1"
+      },
+      "dependencies": {
+        "cliui": {
+          "version": "8.0.1",
+          "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
+          "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
+          "dev": true,
+          "requires": {
+            "string-width": "^4.2.0",
+            "strip-ansi": "^6.0.1",
+            "wrap-ansi": "^7.0.0"
+          }
+        },
+        "convert-source-map": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+          "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+          "dev": true
+        },
+        "v8-to-istanbul": {
+          "version": "9.3.0",
+          "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz",
+          "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==",
+          "dev": true,
+          "requires": {
+            "@jridgewell/trace-mapping": "^0.3.12",
+            "@types/istanbul-lib-coverage": "^2.0.1",
+            "convert-source-map": "^2.0.0"
+          }
+        },
+        "yargs": {
+          "version": "17.7.2",
+          "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
+          "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
+          "dev": true,
+          "requires": {
+            "cliui": "^8.0.1",
+            "escalade": "^3.1.1",
+            "get-caller-file": "^2.0.5",
+            "require-directory": "^2.1.1",
+            "string-width": "^4.2.3",
+            "y18n": "^5.0.5",
+            "yargs-parser": "^21.1.1"
+          }
+        },
+        "yargs-parser": {
+          "version": "21.1.1",
+          "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
+          "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
+          "dev": true
+        }
+      }
+    },
     "call-bind": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
@@ -15222,15 +16246,6 @@
       "integrity": "sha512-+2S9/2JFhYmYaDpZvo0lKkfvuKIglrx68MwOBqMGHhQsNkLjB5xtc/TGoEPs+MxjSyN/72qer2g97nzR641mOQ==",
       "dev": true
     },
-    "chainsaw": {
-      "version": "0.1.0",
-      "resolved": "https://registry.npmjs.org/chainsaw/-/chainsaw-0.1.0.tgz",
-      "integrity": "sha1-XqtQsor+WAdNDVgpE4iCi15fvJg=",
-      "dev": true,
-      "requires": {
-        "traverse": ">=0.3.0 <0.4"
-      }
-    },
     "chalk": {
       "version": "2.4.2",
       "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
@@ -15265,6 +16280,33 @@
       "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==",
       "dev": true
     },
+    "chokidar": {
+      "version": "3.6.0",
+      "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
+      "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
+      "dev": true,
+      "requires": {
+        "anymatch": "~3.1.2",
+        "braces": "~3.0.2",
+        "fsevents": "~2.3.2",
+        "glob-parent": "~5.1.2",
+        "is-binary-path": "~2.1.0",
+        "is-glob": "~4.0.1",
+        "normalize-path": "~3.0.0",
+        "readdirp": "~3.6.0"
+      },
+      "dependencies": {
+        "glob-parent": {
+          "version": "5.1.2",
+          "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+          "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+          "dev": true,
+          "requires": {
+            "is-glob": "^4.0.1"
+          }
+        }
+      }
+    },
     "ci-info": {
       "version": "3.2.0",
       "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.2.0.tgz",
@@ -15287,6 +16329,21 @@
         "resolve-from": "^5.0.0"
       }
     },
+    "cli-cursor": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz",
+      "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==",
+      "dev": true,
+      "requires": {
+        "restore-cursor": "^4.0.0"
+      }
+    },
+    "cli-spinners": {
+      "version": "2.9.2",
+      "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz",
+      "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==",
+      "dev": true
+    },
     "cliui": {
       "version": "7.0.4",
       "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
@@ -15424,19 +16481,6 @@
       "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==",
       "dev": true
     },
-    "cosmiconfig": {
-      "version": "7.1.0",
-      "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz",
-      "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==",
-      "dev": true,
-      "requires": {
-        "@types/parse-json": "^4.0.0",
-        "import-fresh": "^3.2.1",
-        "parse-json": "^5.0.0",
-        "path-type": "^4.0.0",
-        "yaml": "^1.10.0"
-      }
-    },
     "create-require": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
@@ -15747,9 +16791,9 @@
       }
     },
     "debug": {
-      "version": "4.3.4",
-      "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
-      "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+      "version": "4.3.6",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz",
+      "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==",
       "dev": true,
       "requires": {
         "ms": "2.1.2"
@@ -15892,27 +16936,12 @@
         "is-obj": "^2.0.0"
       }
     },
-    "duplexer2": {
-      "version": "0.1.4",
-      "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz",
-      "integrity": "sha1-ixLauHjA1p4+eJEFFmKjL8a93ME=",
-      "dev": true,
-      "requires": {
-        "readable-stream": "^2.0.2"
-      }
-    },
     "eastasianwidth": {
       "version": "0.2.0",
       "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
       "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
       "dev": true
     },
-    "easy-stack": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/easy-stack/-/easy-stack-1.0.1.tgz",
-      "integrity": "sha512-wK2sCs4feiiJeFXn3zvY0p41mdU5VUgbgs1rNsc/y5ngFUijdWd+iIN8eoyuZHKB8xN6BL4PdWmzqFmxNg6V2w==",
-      "dev": true
-    },
     "electron-to-chromium": {
       "version": "1.4.715",
       "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.715.tgz",
@@ -15936,6 +16965,16 @@
       "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz",
       "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ=="
     },
+    "enhanced-resolve": {
+      "version": "5.17.1",
+      "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz",
+      "integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==",
+      "dev": true,
+      "requires": {
+        "graceful-fs": "^4.2.4",
+        "tapable": "^2.2.0"
+      }
+    },
     "error-ex": {
       "version": "1.3.2",
       "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
@@ -16421,12 +17460,6 @@
       "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
       "dev": true
     },
-    "event-pubsub": {
-      "version": "4.3.0",
-      "resolved": "https://registry.npmjs.org/event-pubsub/-/event-pubsub-4.3.0.tgz",
-      "integrity": "sha512-z7IyloorXvKbFx9Bpie2+vMJKKx1fH1EN5yiTfp8CiLOTptSYy1g8H4yDpGlEdshL1PBiFtBHepF2cNsqeEeFQ==",
-      "dev": true
-    },
     "execa": {
       "version": "5.1.1",
       "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz",
@@ -16588,6 +17621,12 @@
       "integrity": "sha512-q7Us7kcjj2VMePAa02hDAF6d+MzsdsAWEwYyOpwUtlerRBkOEPBCRZrAV4XfcSN8fHAgaD0hP7miwoay6DCprw==",
       "dev": true
     },
+    "flat": {
+      "version": "5.0.2",
+      "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz",
+      "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==",
+      "dev": true
+    },
     "flat-cache": {
       "version": "3.0.4",
       "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz",
@@ -16651,29 +17690,6 @@
       "dev": true,
       "optional": true
     },
-    "fstream": {
-      "version": "1.0.12",
-      "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz",
-      "integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==",
-      "dev": true,
-      "requires": {
-        "graceful-fs": "^4.1.2",
-        "inherits": "~2.0.0",
-        "mkdirp": ">=0.5 0",
-        "rimraf": "2"
-      },
-      "dependencies": {
-        "rimraf": {
-          "version": "2.7.1",
-          "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
-          "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
-          "dev": true,
-          "requires": {
-            "glob": "^7.1.3"
-          }
-        }
-      }
-    },
     "function-bind": {
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
@@ -16927,6 +17943,12 @@
         "function-bind": "^1.1.2"
       }
     },
+    "he": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
+      "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
+      "dev": true
+    },
     "hosted-git-info": {
       "version": "2.8.9",
       "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz",
@@ -16990,12 +18012,24 @@
         "safer-buffer": ">= 2.1.2 < 3"
       }
     },
+    "ieee754": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
+      "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
+      "dev": true
+    },
     "ignore": {
       "version": "5.3.1",
       "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz",
       "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==",
       "dev": true
     },
+    "immediate": {
+      "version": "3.0.6",
+      "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
+      "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==",
+      "dev": true
+    },
     "import-fresh": {
       "version": "3.3.0",
       "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
@@ -17104,6 +18138,15 @@
         "has-bigints": "^1.0.1"
       }
     },
+    "is-binary-path": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+      "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+      "dev": true,
+      "requires": {
+        "binary-extensions": "^2.0.0"
+      }
+    },
     "is-boolean-object": {
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz",
@@ -17174,6 +18217,12 @@
         "is-extglob": "^2.1.1"
       }
     },
+    "is-interactive": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz",
+      "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==",
+      "dev": true
+    },
     "is-negative-zero": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz",
@@ -17270,6 +18319,12 @@
       "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=",
       "dev": true
     },
+    "is-unicode-supported": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz",
+      "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==",
+      "dev": true
+    },
     "is-weakref": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.1.tgz",
@@ -17282,7 +18337,7 @@
     "isarray": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
-      "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
+      "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
       "dev": true
     },
     "isexe": {
@@ -17318,13 +18373,13 @@
       }
     },
     "istanbul-lib-report": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz",
-      "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==",
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz",
+      "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==",
       "dev": true,
       "requires": {
         "istanbul-lib-coverage": "^3.0.0",
-        "make-dir": "^3.0.0",
+        "make-dir": "^4.0.0",
         "supports-color": "^7.1.0"
       },
       "dependencies": {
@@ -17365,9 +18420,9 @@
       }
     },
     "istanbul-reports": {
-      "version": "3.1.0",
-      "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.0.tgz",
-      "integrity": "sha512-rgeP8yMlXeH4mfd9K/sQXZv1lvcS7xo379zntcotPDdMwkcGYwMxGHGZYo0/+YW5B/nor2YGKz2BH5ume405ow==",
+      "version": "3.1.7",
+      "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz",
+      "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==",
       "dev": true,
       "requires": {
         "html-escaper": "^2.0.0",
@@ -18230,20 +19285,6 @@
         }
       }
     },
-    "jest-runner-vscode": {
-      "version": "2.1.0",
-      "resolved": "https://registry.npmjs.org/jest-runner-vscode/-/jest-runner-vscode-2.1.0.tgz",
-      "integrity": "sha512-69frE9umIx2AAu5QG79XB2B3XofXq452LsEkfEdtWOrXmC4AzjQGWKns2E/JkYb1LsLsf1upKjWlYs3Gs/HsKQ==",
-      "dev": true,
-      "requires": {
-        "@jest/core": "^27.2.1",
-        "@vscode/test-electron": "^1.6.2",
-        "cosmiconfig": "^7.0.1",
-        "jest-cli": "^27.2.1",
-        "jest-environment-node": "^27.2.0",
-        "node-ipc": "^9.0.0"
-      }
-    },
     "jest-runtime": {
       "version": "27.4.2",
       "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-27.4.2.tgz",
@@ -18652,21 +19693,6 @@
         }
       }
     },
-    "js-message": {
-      "version": "1.0.7",
-      "resolved": "https://registry.npmjs.org/js-message/-/js-message-1.0.7.tgz",
-      "integrity": "sha512-efJLHhLjIyKRewNS9EGZ4UpI8NguuL6fKkhRxVuMmrGV2xN/0APGdQYwLFky5w9naebSZ0OwAGp0G6/2Cg90rA==",
-      "dev": true
-    },
-    "js-queue": {
-      "version": "2.0.2",
-      "resolved": "https://registry.npmjs.org/js-queue/-/js-queue-2.0.2.tgz",
-      "integrity": "sha512-pbKLsbCfi7kriM3s1J4DDCo7jQkI58zPLHi0heXPzPlj0hjUsm+FesPUbE0DSbIVIK503A36aUBoCN7eMFedkA==",
-      "dev": true,
-      "requires": {
-        "easy-stack": "^1.0.1"
-      }
-    },
     "js-tokens": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@@ -18765,6 +19791,18 @@
       "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
       "dev": true
     },
+    "jszip": {
+      "version": "3.10.1",
+      "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz",
+      "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==",
+      "dev": true,
+      "requires": {
+        "lie": "~3.3.0",
+        "pako": "~1.0.2",
+        "readable-stream": "~2.3.6",
+        "setimmediate": "^1.0.5"
+      }
+    },
     "keyv": {
       "version": "4.5.4",
       "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
@@ -18813,18 +19851,21 @@
         "type-check": "~0.4.0"
       }
     },
+    "lie": {
+      "version": "3.3.0",
+      "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz",
+      "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==",
+      "dev": true,
+      "requires": {
+        "immediate": "~3.0.5"
+      }
+    },
     "lines-and-columns": {
       "version": "1.1.6",
       "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz",
       "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=",
       "dev": true
     },
-    "listenercount": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/listenercount/-/listenercount-1.0.1.tgz",
-      "integrity": "sha1-hMinKrWcRyUyFIDJdeZQg0LnCTc=",
-      "dev": true
-    },
     "load-json-file": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz",
@@ -18888,6 +19929,67 @@
       "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==",
       "dev": true
     },
+    "log-symbols": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz",
+      "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==",
+      "dev": true,
+      "requires": {
+        "chalk": "^4.1.0",
+        "is-unicode-supported": "^0.1.0"
+      },
+      "dependencies": {
+        "ansi-styles": {
+          "version": "4.3.0",
+          "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+          "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+          "dev": true,
+          "requires": {
+            "color-convert": "^2.0.1"
+          }
+        },
+        "chalk": {
+          "version": "4.1.2",
+          "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+          "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+          "dev": true,
+          "requires": {
+            "ansi-styles": "^4.1.0",
+            "supports-color": "^7.1.0"
+          }
+        },
+        "color-convert": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+          "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+          "dev": true,
+          "requires": {
+            "color-name": "~1.1.4"
+          }
+        },
+        "color-name": {
+          "version": "1.1.4",
+          "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+          "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+          "dev": true
+        },
+        "has-flag": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+          "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+          "dev": true
+        },
+        "supports-color": {
+          "version": "7.2.0",
+          "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+          "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+          "dev": true,
+          "requires": {
+            "has-flag": "^4.0.0"
+          }
+        }
+      }
+    },
     "logform": {
       "version": "2.6.0",
       "resolved": "https://registry.npmjs.org/logform/-/logform-2.6.0.tgz",
@@ -18905,25 +20007,18 @@
       "version": "6.0.0",
       "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
       "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+      "dev": true,
       "requires": {
         "yallist": "^4.0.0"
       }
     },
     "make-dir": {
-      "version": "3.1.0",
-      "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
-      "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==",
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz",
+      "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==",
       "dev": true,
       "requires": {
-        "semver": "^6.0.0"
-      },
-      "dependencies": {
-        "semver": {
-          "version": "6.3.0",
-          "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
-          "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
-          "dev": true
-        }
+        "semver": "^7.5.3"
       }
     },
     "make-error": {
@@ -19098,18 +20193,103 @@
       }
     },
     "minipass": {
-      "version": "7.0.4",
-      "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz",
-      "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==",
+      "version": "7.1.2",
+      "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
+      "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
       "dev": true
     },
-    "mkdirp": {
-      "version": "0.5.5",
-      "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
-      "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
+    "mocha": {
+      "version": "10.7.3",
+      "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.7.3.tgz",
+      "integrity": "sha512-uQWxAu44wwiACGqjbPYmjo7Lg8sFrS3dQe7PP2FQI+woptP4vZXSMcfMyFL/e1yFEeEpV4RtyTpZROOKmxis+A==",
       "dev": true,
       "requires": {
-        "minimist": "^1.2.5"
+        "ansi-colors": "^4.1.3",
+        "browser-stdout": "^1.3.1",
+        "chokidar": "^3.5.3",
+        "debug": "^4.3.5",
+        "diff": "^5.2.0",
+        "escape-string-regexp": "^4.0.0",
+        "find-up": "^5.0.0",
+        "glob": "^8.1.0",
+        "he": "^1.2.0",
+        "js-yaml": "^4.1.0",
+        "log-symbols": "^4.1.0",
+        "minimatch": "^5.1.6",
+        "ms": "^2.1.3",
+        "serialize-javascript": "^6.0.2",
+        "strip-json-comments": "^3.1.1",
+        "supports-color": "^8.1.1",
+        "workerpool": "^6.5.1",
+        "yargs": "^16.2.0",
+        "yargs-parser": "^20.2.9",
+        "yargs-unparser": "^2.0.0"
+      },
+      "dependencies": {
+        "brace-expansion": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
+          "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+          "dev": true,
+          "requires": {
+            "balanced-match": "^1.0.0"
+          }
+        },
+        "diff": {
+          "version": "5.2.0",
+          "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz",
+          "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==",
+          "dev": true
+        },
+        "escape-string-regexp": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+          "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+          "dev": true
+        },
+        "glob": {
+          "version": "8.1.0",
+          "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz",
+          "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==",
+          "dev": true,
+          "requires": {
+            "fs.realpath": "^1.0.0",
+            "inflight": "^1.0.4",
+            "inherits": "2",
+            "minimatch": "^5.0.1",
+            "once": "^1.3.0"
+          }
+        },
+        "has-flag": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+          "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+          "dev": true
+        },
+        "minimatch": {
+          "version": "5.1.6",
+          "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
+          "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
+          "dev": true,
+          "requires": {
+            "brace-expansion": "^2.0.1"
+          }
+        },
+        "ms": {
+          "version": "2.1.3",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+          "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+          "dev": true
+        },
+        "supports-color": {
+          "version": "8.1.1",
+          "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
+          "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
+          "dev": true,
+          "requires": {
+            "has-flag": "^4.0.0"
+          }
+        }
       }
     },
     "ms": {
@@ -19141,17 +20321,6 @@
       "integrity": "sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=",
       "dev": true
     },
-    "node-ipc": {
-      "version": "9.2.1",
-      "resolved": "https://registry.npmjs.org/node-ipc/-/node-ipc-9.2.1.tgz",
-      "integrity": "sha512-mJzaM6O3xHf9VT8BULvJSbdVbmHUKRNOH7zDDkCrA1/T+CVjq2WVIDfLt0azZRXpgArJtl3rtmEozrbXPZ9GaQ==",
-      "dev": true,
-      "requires": {
-        "event-pubsub": "4.3.0",
-        "js-message": "1.0.7",
-        "js-queue": "2.0.2"
-      }
-    },
     "node-modules-regexp": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz",
@@ -19337,11 +20506,78 @@
         "type-check": "^0.4.0"
       }
     },
-    "p-finally": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz",
-      "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=",
-      "dev": true
+    "ora": {
+      "version": "7.0.1",
+      "resolved": "https://registry.npmjs.org/ora/-/ora-7.0.1.tgz",
+      "integrity": "sha512-0TUxTiFJWv+JnjWm4o9yvuskpEJLXTcng8MJuKd+SzAzp2o+OP3HWqNhB4OdJRt1Vsd9/mR0oyaEYlOnL7XIRw==",
+      "dev": true,
+      "requires": {
+        "chalk": "^5.3.0",
+        "cli-cursor": "^4.0.0",
+        "cli-spinners": "^2.9.0",
+        "is-interactive": "^2.0.0",
+        "is-unicode-supported": "^1.3.0",
+        "log-symbols": "^5.1.0",
+        "stdin-discarder": "^0.1.0",
+        "string-width": "^6.1.0",
+        "strip-ansi": "^7.1.0"
+      },
+      "dependencies": {
+        "ansi-regex": {
+          "version": "6.0.1",
+          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
+          "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
+          "dev": true
+        },
+        "chalk": {
+          "version": "5.3.0",
+          "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz",
+          "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==",
+          "dev": true
+        },
+        "emoji-regex": {
+          "version": "10.4.0",
+          "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz",
+          "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==",
+          "dev": true
+        },
+        "is-unicode-supported": {
+          "version": "1.3.0",
+          "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz",
+          "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==",
+          "dev": true
+        },
+        "log-symbols": {
+          "version": "5.1.0",
+          "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-5.1.0.tgz",
+          "integrity": "sha512-l0x2DvrW294C9uDCoQe1VSU4gf529FkSZ6leBl4TiqZH/e+0R7hSfHQBNut2mNygDgHwvYHfFLn6Oxb3VWj2rA==",
+          "dev": true,
+          "requires": {
+            "chalk": "^5.0.0",
+            "is-unicode-supported": "^1.1.0"
+          }
+        },
+        "string-width": {
+          "version": "6.1.0",
+          "resolved": "https://registry.npmjs.org/string-width/-/string-width-6.1.0.tgz",
+          "integrity": "sha512-k01swCJAgQmuADB0YIc+7TuatfNvTBVOoaUWJjTB9R4VJzR5vNWzf5t42ESVZFPS8xTySF7CAdV4t/aaIm3UnQ==",
+          "dev": true,
+          "requires": {
+            "eastasianwidth": "^0.2.0",
+            "emoji-regex": "^10.2.1",
+            "strip-ansi": "^7.0.1"
+          }
+        },
+        "strip-ansi": {
+          "version": "7.1.0",
+          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
+          "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
+          "dev": true,
+          "requires": {
+            "ansi-regex": "^6.0.1"
+          }
+        }
+      }
     },
     "p-limit": {
       "version": "3.1.0",
@@ -19361,29 +20597,23 @@
         "p-limit": "^3.0.2"
       }
     },
-    "p-timeout": {
-      "version": "3.2.0",
-      "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz",
-      "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==",
-      "dev": true,
-      "requires": {
-        "p-finally": "^1.0.0"
-      }
-    },
     "p-try": {
       "version": "2.2.0",
       "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
       "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
       "dev": true
     },
-    "p-wait-for": {
-      "version": "3.2.0",
-      "resolved": "https://registry.npmjs.org/p-wait-for/-/p-wait-for-3.2.0.tgz",
-      "integrity": "sha512-wpgERjNkLrBiFmkMEjuZJEWKKDrNfHCKA1OhyN1wg1FrLkULbviEy6py1AyJUgZ72YWFbZ38FIpnqvVqAlDUwA==",
-      "dev": true,
-      "requires": {
-        "p-timeout": "^3.0.0"
-      }
+    "package-json-from-dist": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz",
+      "integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==",
+      "dev": true
+    },
+    "pako": {
+      "version": "1.0.11",
+      "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
+      "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",
+      "dev": true
     },
     "parent-module": {
       "version": "2.0.0",
@@ -19442,12 +20672,12 @@
       "dev": true
     },
     "path-scurry": {
-      "version": "1.10.1",
-      "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz",
-      "integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==",
+      "version": "1.11.1",
+      "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
+      "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
       "dev": true,
       "requires": {
-        "lru-cache": "^9.1.1 || ^10.0.0",
+        "lru-cache": "^10.2.0",
         "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
       },
       "dependencies": {
@@ -19734,6 +20964,15 @@
       "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==",
       "dev": true
     },
+    "randombytes": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
+      "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
+      "dev": true,
+      "requires": {
+        "safe-buffer": "^5.1.0"
+      }
+    },
     "react-is": {
       "version": "17.0.2",
       "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
@@ -19815,9 +21054,9 @@
       }
     },
     "readable-stream": {
-      "version": "2.3.7",
-      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
-      "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
+      "version": "2.3.8",
+      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
+      "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
       "dev": true,
       "requires": {
         "core-util-is": "~1.0.0",
@@ -19829,6 +21068,15 @@
         "util-deprecate": "~1.0.1"
       }
     },
+    "readdirp": {
+      "version": "3.6.0",
+      "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
+      "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+      "dev": true,
+      "requires": {
+        "picomatch": "^2.2.1"
+      }
+    },
     "redent": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/redent/-/redent-4.0.0.tgz",
@@ -19920,6 +21168,16 @@
       "integrity": "sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ==",
       "dev": true
     },
+    "restore-cursor": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz",
+      "integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==",
+      "dev": true,
+      "requires": {
+        "onetime": "^5.1.0",
+        "signal-exit": "^3.0.2"
+      }
+    },
     "reusify": {
       "version": "1.0.4",
       "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
@@ -20005,12 +21263,9 @@
       }
     },
     "semver": {
-      "version": "7.6.0",
-      "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",
-      "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==",
-      "requires": {
-        "lru-cache": "^6.0.0"
-      }
+      "version": "7.6.3",
+      "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
+      "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A=="
     },
     "serialize-error": {
       "version": "8.1.0",
@@ -20027,10 +21282,19 @@
         }
       }
     },
+    "serialize-javascript": {
+      "version": "6.0.2",
+      "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz",
+      "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==",
+      "dev": true,
+      "requires": {
+        "randombytes": "^2.1.0"
+      }
+    },
     "setimmediate": {
       "version": "1.0.5",
       "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
-      "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=",
+      "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==",
       "dev": true
     },
     "shebang-command": {
@@ -20284,6 +21548,15 @@
         }
       }
     },
+    "stdin-discarder": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.1.0.tgz",
+      "integrity": "sha512-xhV7w8S+bUwlPTb4bAOUQhv8/cSS5offJuX8GQGq32ONF0ZtDWKfkdomM3HMRA+LhX6um/FZ0COqlwsjD53LeQ==",
+      "dev": true,
+      "requires": {
+        "bl": "^5.0.0"
+      }
+    },
     "string_decoder": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
@@ -20718,6 +21991,12 @@
         }
       }
     },
+    "tapable": {
+      "version": "2.2.1",
+      "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz",
+      "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==",
+      "dev": true
+    },
     "terminal-link": {
       "version": "2.1.1",
       "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz",
@@ -20806,12 +22085,6 @@
         "punycode": "^2.1.1"
       }
     },
-    "traverse": {
-      "version": "0.3.9",
-      "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.3.9.tgz",
-      "integrity": "sha1-cXuPIgzAu3tE5AUUwisui7xw2Lk=",
-      "dev": true
-    },
     "trim-newlines": {
       "version": "4.1.1",
       "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-4.1.1.tgz",
@@ -20956,24 +22229,6 @@
         "crypto-random-string": "^4.0.0"
       }
     },
-    "unzipper": {
-      "version": "0.10.11",
-      "resolved": "https://registry.npmjs.org/unzipper/-/unzipper-0.10.11.tgz",
-      "integrity": "sha512-+BrAq2oFqWod5IESRjL3S8baohbevGcVA+teAIOYWM3pDVdseogqbzhhvvmiyQrUNKFUnDMtELW3X8ykbyDCJw==",
-      "dev": true,
-      "requires": {
-        "big-integer": "^1.6.17",
-        "binary": "~0.3.0",
-        "bluebird": "~3.4.1",
-        "buffer-indexof-polyfill": "~1.0.0",
-        "duplexer2": "~0.1.4",
-        "fstream": "^1.0.12",
-        "graceful-fs": "^4.2.2",
-        "listenercount": "~1.0.1",
-        "readable-stream": "~2.3.6",
-        "setimmediate": "~1.0.4"
-      }
-    },
     "update-browserslist-db": {
       "version": "1.0.13",
       "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz",
@@ -21247,6 +22502,12 @@
       "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
       "dev": true
     },
+    "workerpool": {
+      "version": "6.5.1",
+      "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz",
+      "integrity": "sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==",
+      "dev": true
+    },
     "wrap-ansi": {
       "version": "7.0.0",
       "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
@@ -21373,12 +22634,7 @@
     "yallist": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
-      "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
-    },
-    "yaml": {
-      "version": "1.10.2",
-      "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
-      "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
+      "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
       "dev": true
     },
     "yargs": {
@@ -21402,6 +22658,38 @@
       "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==",
       "dev": true
     },
+    "yargs-unparser": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz",
+      "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==",
+      "dev": true,
+      "requires": {
+        "camelcase": "^6.0.0",
+        "decamelize": "^4.0.0",
+        "flat": "^5.0.2",
+        "is-plain-obj": "^2.1.0"
+      },
+      "dependencies": {
+        "camelcase": {
+          "version": "6.3.0",
+          "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz",
+          "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==",
+          "dev": true
+        },
+        "decamelize": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz",
+          "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==",
+          "dev": true
+        },
+        "is-plain-obj": {
+          "version": "2.1.0",
+          "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz",
+          "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==",
+          "dev": true
+        }
+      }
+    },
     "yn": {
       "version": "3.1.1",
       "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
diff --git a/package.json b/package.json
index 8ebc931..f734e15 100644
--- a/package.json
+++ b/package.json
@@ -225,7 +225,8 @@
     "@typescript-eslint/eslint-plugin": "^7.5.0",
     "@typescript-eslint/parser": "^7.5.0",
     "@typescript/lib-dom": "npm:@types/web@^0.0.47",
-    "@vscode/test-electron": "^1.6.2",
+    "@vscode/test-cli": "^0.0.10",
+    "@vscode/test-electron": "^2.4.1",
     "cspell": "^8.6.1",
     "esbuild": "^0.20.2",
     "eslint": "^8.57.0",
@@ -233,9 +234,7 @@
     "eslint-plugin-jest": "^27.9.0",
     "fast-glob": "^3.2.7",
     "jest": "^27.4.3",
-    "jest-runner-vscode": "^2.1.0",
     "npm-run-all": "^4.1.5",
-    "p-wait-for": "^3.1.0",
     "postcss": "^8.4.38",
     "postcss-sass": "^0.5.0",
     "postcss-scss": "^4.0.9",
@@ -257,7 +256,6 @@
     "bundle-base": "ts-node --transpile-only -P tsconfig.scripts.json scripts/bundle.ts",
     "bundle-watch": "npm run bundle-base -- --sourcemap --watch",
     "build-bundle": "run-s build bundle",
-    "download-vscode": "ts-node --transpile-only -P tsconfig.scripts.json scripts/download-vscode.ts",
     "format": "prettier . --write",
     "lint": "run-s \"lint:*\"",
     "lint:formatting": "prettier . --check",
@@ -266,7 +264,7 @@
     "lint:types": "tsc -b",
     "lint:unit-tests": "ts-node --transpile-only -P tsconfig.scripts.json scripts/enforce-unit-tests-per-file.ts",
     "test": "npm run build-bundle && jest",
-    "test:e2e": "npm run build-bundle && jest --projects test/e2e",
+    "test:e2e": "npm run build-bundle && vscode-test",
     "test:integration": "jest --projects test/integration",
     "test:unit": "jest --projects test/unit",
     "vscode:prepublish": "npm run build && npm run bundle-base -- --minify"
diff --git a/scripts/download-vscode.ts b/scripts/download-vscode.ts
deleted file mode 100644
index 19a1b58..0000000
--- a/scripts/download-vscode.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-import { downloadAndUnzipVSCode } from '@vscode/test-electron';
-import { version, platform } from '../test/e2e/jest-runner-vscode.config.js';
-
-void downloadAndUnzipVSCode(version, platform);
diff --git a/tsconfig.base.json b/tsconfig.base.json
index 8029c47..a36c185 100644
--- a/tsconfig.base.json
+++ b/tsconfig.base.json
@@ -12,6 +12,7 @@
 		"noImplicitAny": true,
 		"alwaysStrict": true,
 		"esModuleInterop": true,
-		"resolveJsonModule": true
+		"resolveJsonModule": true,
+		"skipLibCheck": true
 	}
 }
diff --git a/tsconfig.scripts.json b/tsconfig.scripts.json
index 0182cd5..143a961 100644
--- a/tsconfig.scripts.json
+++ b/tsconfig.scripts.json
@@ -6,13 +6,6 @@
 		"noEmit": true,
 		"skipLibCheck": true
 	},
-	"include": [
-		"*.js",
-		".*.js",
-		"package.json",
-		"scripts/**/*",
-		"scripts/**/*.*.js",
-		"test/e2e/jest-runner-vscode.config.js"
-	],
+	"include": ["*.js", ".*.js", "package.json", "scripts/**/*", "scripts/**/*.*.js"],
 	"exclude": ["**/coverage"]
 }


Test patch:
diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml
index 45298d4..10d43a6 100644
--- a/.github/workflows/testing.yml
+++ b/.github/workflows/testing.yml
@@ -46,24 +46,19 @@ jobs:
         run: npm i -D stylelint@^14 stylelint-scss@^4.3.0
         if: matrix.stylelint == 14
 
-      - name: Cache VS Code binaries
-        uses: actions/cache@v4
-        id: vscode-cache
-        with:
-          path: .vscode-test
-          key: ${{ runner.os }}-vscode-${{ hashFiles('test/e2e/jest-runner-vscode.config.js') }}
-          restore-keys: |
-            ${{ runner.os }}-vscode-
-
-      - name: Download VS Code
-        run: npm run download-vscode
-
       - name: Run unit tests
         run: npm run test:unit -- --coverage
 
       - name: Run integration tests
         run: npm run test:integration -- --coverage
 
+      - name: Cache VS Code binaries
+        uses: actions/cache@v4
+        id: vscode-cache
+        with:
+          path: .vscode-test
+          key: ${{ runner.os }}-vscode-${{ hashFiles('package.json') }}
+
       - name: Run end-to-end tests (Linux)
         if: runner.os == 'Linux'
         run: xvfb-run -a npm run test:e2e -- --silent
diff --git a/.vscode-test.js b/.vscode-test.js
new file mode 100644
index 0000000..9840782
--- /dev/null
+++ b/.vscode-test.js
@@ -0,0 +1,20 @@
+'use strict';
+
+const { defineConfig } = require('@vscode/test-cli');
+
+const pkg = require('./package.json');
+
+const minimumVscodeVersion = pkg.engines.vscode.match(/^>=(.+)$/)?.[1];
+
+if (!minimumVscodeVersion) throw new Error(`"engines.vscode" is unexpected: ${pkg.engines.vscode}`);
+
+module.exports = defineConfig({
+	files: ['test/e2e/__tests__/**/*.ts'],
+	workspaceFolder: 'test/e2e/workspace/workspace.code-workspace',
+	version: minimumVscodeVersion,
+	mocha: {
+		timeout: 60000,
+		ui: 'bdd',
+		require: ['ts-node/register'],
+	},
+});
diff --git a/test/e2e/__tests__/__snapshots__/code-actions.ts.snap b/test/e2e/__tests__/__snapshots__/code-actions.ts.snap
deleted file mode 100644
index 286cc4c..0000000
--- a/test/e2e/__tests__/__snapshots__/code-actions.ts.snap
+++ /dev/null
@@ -1,146 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`Code actions should disable rules for a specific line with a comment on the previous line 1`] = `
-"a {
-  /* stylelint-disable-next-line indentation */
-  font-size: 1.2em;
-    /* stylelint-disable-next-line comment-no-empty */
-  color: #00;
-}
-"
-`;
-
-exports[`Code actions should disable rules for a specific line with a comment on the same line 1`] = `
-"a {
-  font-size: 1.2em; /* stylelint-disable-line indentation */
-    /* stylelint-disable-next-line comment-no-empty */
-  color: #00;
-}
-"
-`;
-
-exports[`Code actions should disable rules for a specific line with a comment on the same line 2`] = `
-"#!/usr/bin/env node
-/* stylelint-disable color-no-invalid-hex */
-/* eslint-disable node/shebang */
-'use strict';
-
-const css = css\`
-	.foo {
-		color: #00;
-	}
-\`;
-"
-`;
-
-exports[`Code actions should disable rules for an entire file 1`] = `
-"/* stylelint-disable indentation */
-a {
-  font-size: 1.2em;
-    /* stylelint-disable-next-line comment-no-empty */
-  color: #00;
-}
-"
-`;
-
-exports[`Code actions should disable rules for an entire file with a shebang 1`] = `
-"#!/usr/bin/env node
-/* stylelint-disable color-no-invalid-hex */
-/* eslint-disable node/shebang */
-'use strict';
-
-const css = css\`
-	.foo {
-		color: #00;
-	}
-\`;
-"
-`;
-
-exports[`Code actions should provide code actions for problems 1`] = `
-Array [
-  Object {
-    "edit": Array [
-      Array [
-        "<uri>",
-        Array [
-          Object {
-            "newEol": undefined,
-            "newText": "  /* stylelint-disable-next-line indentation */
-",
-            "range": Array [
-              Object {
-                "character": 0,
-                "line": 1,
-              },
-              Object {
-                "character": 0,
-                "line": 1,
-              },
-            ],
-          },
-        ],
-      ],
-    ],
-    "isPreferred": undefined,
-    "kind": _ {
-      "value": "quickfix",
-    },
-    "title": "Disable indentation for this line",
-  },
-  Object {
-    "edit": Array [
-      Array [
-        "<uri>",
-        Array [
-          Object {
-            "newEol": undefined,
-            "newText": "/* stylelint-disable indentation */
-",
-            "range": Array [
-              Object {
-                "character": 0,
-                "line": 0,
-              },
-              Object {
-                "character": 0,
-                "line": 0,
-              },
-            ],
-          },
-        ],
-      ],
-    ],
-    "isPreferred": undefined,
-    "kind": _ {
-      "value": "quickfix",
-    },
-    "title": "Disable indentation for the entire file",
-  },
-  Object {
-    "command": Object {
-      "arguments": Array [
-        Object {
-          "uri": "https://stylelint.io/user-guide/rules/indentation",
-        },
-      ],
-      "command": "stylelint.openRuleDoc",
-      "title": "Open documentation for indentation",
-    },
-    "isPreferred": undefined,
-    "kind": _ {
-      "value": "quickfix",
-    },
-    "title": "Show documentation for indentation",
-  },
-]
-`;
-
-exports[`Code actions should run auto-fix action on save 1`] = `
-"a {
-    font-size: 1.2em;
-    /* stylelint-disable-next-line comment-no-empty */
-    color: #00;
-}
-"
-`;
diff --git a/test/e2e/__tests__/__snapshots__/config-basedir.ts.snap b/test/e2e/__tests__/__snapshots__/config-basedir.ts.snap
deleted file mode 100644
index df058eb..0000000
--- a/test/e2e/__tests__/__snapshots__/config-basedir.ts.snap
+++ /dev/null
@@ -1,44 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`"stylelint.configBasedir" setting should resolve referenced configs using the base directory 1`] = `
-Array [
-  Object {
-    "code": "color-hex-case",
-    "codeDescription": Object {
-      "href": "https://stylelint.io/user-guide/rules/color-hex-case",
-    },
-    "message": "Expected \\"#fff\\" to be \\"#FFF\\" (color-hex-case)",
-    "range": Object {
-      "end": Object {
-        "character": 10,
-        "line": 1,
-      },
-      "start": Object {
-        "character": 9,
-        "line": 1,
-      },
-    },
-    "severity": 1,
-    "source": "Stylelint",
-  },
-  Object {
-    "code": "indentation",
-    "codeDescription": Object {
-      "href": "https://stylelint.io/user-guide/rules/indentation",
-    },
-    "message": "Expected indentation of 8 spaces (indentation)",
-    "range": Object {
-      "end": Object {
-        "character": 14,
-        "line": 1,
-      },
-      "start": Object {
-        "character": 2,
-        "line": 1,
-      },
-    },
-    "severity": 1,
-    "source": "Stylelint",
-  },
-]
-`;
diff --git a/test/e2e/__tests__/__snapshots__/config-file.ts.snap b/test/e2e/__tests__/__snapshots__/config-file.ts.snap
deleted file mode 100644
index 04c1a47..0000000
--- a/test/e2e/__tests__/__snapshots__/config-file.ts.snap
+++ /dev/null
@@ -1,25 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`"stylelint.configFile" setting should resolve the config file using the specified path 1`] = `
-Array [
-  Object {
-    "code": "block-no-empty",
-    "codeDescription": Object {
-      "href": "https://stylelint.io/user-guide/rules/block-no-empty",
-    },
-    "message": "Unexpected empty block (block-no-empty)",
-    "range": Object {
-      "end": Object {
-        "character": 4,
-        "line": 0,
-      },
-      "start": Object {
-        "character": 2,
-        "line": 0,
-      },
-    },
-    "severity": 1,
-    "source": "Stylelint",
-  },
-]
-`;
diff --git a/test/e2e/__tests__/__snapshots__/custom-syntax.ts.snap b/test/e2e/__tests__/__snapshots__/custom-syntax.ts.snap
deleted file mode 100644
index 9ebb254..0000000
--- a/test/e2e/__tests__/__snapshots__/custom-syntax.ts.snap
+++ /dev/null
@@ -1,7 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`"stylelint.customSyntax" setting should auto-fix using the specified custom syntax 1`] = `
-"/* prettier-ignore */
-.foo .bar
-    color: red"
-`;
diff --git a/test/e2e/__tests__/__snapshots__/format.ts.snap b/test/e2e/__tests__/__snapshots__/format.ts.snap
deleted file mode 100644
index 2893b2c..0000000
--- a/test/e2e/__tests__/__snapshots__/format.ts.snap
+++ /dev/null
@@ -1,8 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`Document formatting should format document using formatting options 1`] = `
-"a {
-	color: red;
-}
-"
-`;
diff --git a/test/e2e/__tests__/__snapshots__/ignore-disables.ts.snap b/test/e2e/__tests__/__snapshots__/ignore-disables.ts.snap
deleted file mode 100644
index fa82bc8..0000000
--- a/test/e2e/__tests__/__snapshots__/ignore-disables.ts.snap
+++ /dev/null
@@ -1,44 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`"stylelint.ignoreDisables" setting should ignore disable directives when enabled 1`] = `
-Array [
-  Object {
-    "code": "color-hex-case",
-    "codeDescription": Object {
-      "href": "https://stylelint.io/user-guide/rules/color-hex-case",
-    },
-    "message": "Expected \\"#fff\\" to be \\"#FFF\\" (color-hex-case)",
-    "range": Object {
-      "end": Object {
-        "character": 10,
-        "line": 3,
-      },
-      "start": Object {
-        "character": 9,
-        "line": 3,
-      },
-    },
-    "severity": 1,
-    "source": "Stylelint",
-  },
-  Object {
-    "code": "indentation",
-    "codeDescription": Object {
-      "href": "https://stylelint.io/user-guide/rules/indentation",
-    },
-    "message": "Expected indentation of 4 spaces (indentation)",
-    "range": Object {
-      "end": Object {
-        "character": 14,
-        "line": 3,
-      },
-      "start": Object {
-        "character": 2,
-        "line": 3,
-      },
-    },
-    "severity": 1,
-    "source": "Stylelint",
-  },
-]
-`;
diff --git a/test/e2e/__tests__/__snapshots__/lint.ts.snap b/test/e2e/__tests__/__snapshots__/lint.ts.snap
deleted file mode 100644
index 5755060..0000000
--- a/test/e2e/__tests__/__snapshots__/lint.ts.snap
+++ /dev/null
@@ -1,84 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`Linting should display rule documentation links when one is available 1`] = `
-Array [
-  Object {
-    "code": "plugin/foo-bar",
-    "message": "Bar (plugin/foo-bar)",
-    "range": Object {
-      "end": Object {
-        "character": 6,
-        "line": 0,
-      },
-      "start": Object {
-        "character": 5,
-        "line": 0,
-      },
-    },
-    "severity": 1,
-    "source": "Stylelint",
-  },
-  Object {
-    "code": "color-no-invalid-hex",
-    "codeDescription": Object {
-      "href": "https://stylelint.io/user-guide/rules/color-no-invalid-hex",
-    },
-    "message": "Unexpected invalid hex color \\"#y3\\" (color-no-invalid-hex)",
-    "range": Object {
-      "end": Object {
-        "character": 14,
-        "line": 6,
-      },
-      "start": Object {
-        "character": 11,
-        "line": 6,
-      },
-    },
-    "severity": 1,
-    "source": "Stylelint",
-  },
-  Object {
-    "code": "indentation",
-    "codeDescription": Object {
-      "href": "https://stylelint.io/user-guide/rules/indentation",
-    },
-    "message": "Expected indentation of 4 spaces (indentation)",
-    "range": Object {
-      "end": Object {
-        "character": 13,
-        "line": 2,
-      },
-      "start": Object {
-        "character": 2,
-        "line": 2,
-      },
-    },
-    "severity": 1,
-    "source": "Stylelint",
-  },
-]
-`;
-
-exports[`Linting should lint CSS documents 1`] = `
-Array [
-  Object {
-    "code": "indentation",
-    "codeDescription": Object {
-      "href": "https://stylelint.io/user-guide/rules/indentation",
-    },
-    "message": "Expected indentation of 4 spaces (indentation)",
-    "range": Object {
-      "end": Object {
-        "character": 13,
-        "line": 2,
-      },
-      "start": Object {
-        "character": 2,
-        "line": 2,
-      },
-    },
-    "severity": 1,
-    "source": "Stylelint",
-  },
-]
-`;
diff --git a/test/e2e/__tests__/__snapshots__/report-descriptionless-disables.ts.snap b/test/e2e/__tests__/__snapshots__/report-descriptionless-disables.ts.snap
deleted file mode 100644
index 7f1d464..0000000
--- a/test/e2e/__tests__/__snapshots__/report-descriptionless-disables.ts.snap
+++ /dev/null
@@ -1,22 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`"stylelint.reportDescriptionlessDisables" setting should report invalid-scope disables when enabled 1`] = `
-Array [
-  Object {
-    "code": "--report-descriptionless-disables",
-    "message": "Disable for \\"indentation\\" is missing a description",
-    "range": Object {
-      "end": Object {
-        "character": 48,
-        "line": 2,
-      },
-      "start": Object {
-        "character": 4,
-        "line": 2,
-      },
-    },
-    "severity": 1,
-    "source": "Stylelint",
-  },
-]
-`;
diff --git a/test/e2e/__tests__/__snapshots__/report-invalid-scope-disables.ts.snap b/test/e2e/__tests__/__snapshots__/report-invalid-scope-disables.ts.snap
deleted file mode 100644
index 326ae8e..0000000
--- a/test/e2e/__tests__/__snapshots__/report-invalid-scope-disables.ts.snap
+++ /dev/null
@@ -1,54 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`"stylelint.reportInvalidScopeDisables" setting should report invalid-scope disables when enabled 1`] = `
-Array [
-  Object {
-    "code": "--report-invalid-scope-disables",
-    "message": "Rule \\"foo\\" isn't enabled",
-    "range": Object {
-      "end": Object {
-        "character": 36,
-        "line": 0,
-      },
-      "start": Object {
-        "character": 0,
-        "line": 0,
-      },
-    },
-    "severity": 1,
-    "source": "Stylelint",
-  },
-  Object {
-    "code": "--report-invalid-scope-disables",
-    "message": "Rule \\"foo\\" isn't enabled",
-    "range": Object {
-      "end": Object {
-        "character": 31,
-        "line": 2,
-      },
-      "start": Object {
-        "character": 0,
-        "line": 2,
-      },
-    },
-    "severity": 1,
-    "source": "Stylelint",
-  },
-  Object {
-    "code": "--report-invalid-scope-disables",
-    "message": "Rule \\"foo\\" isn't enabled",
-    "range": Object {
-      "end": Object {
-        "character": 26,
-        "line": 4,
-      },
-      "start": Object {
-        "character": 0,
-        "line": 4,
-      },
-    },
-    "severity": 1,
-    "source": "Stylelint",
-  },
-]
-`;
diff --git a/test/e2e/__tests__/__snapshots__/report-needless-disables.ts.snap b/test/e2e/__tests__/__snapshots__/report-needless-disables.ts.snap
deleted file mode 100644
index 65f56e0..0000000
--- a/test/e2e/__tests__/__snapshots__/report-needless-disables.ts.snap
+++ /dev/null
@@ -1,86 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`"stylelint.reportNeedlessDisables" setting should report needless disables when enabled 1`] = `
-Array [
-  Object {
-    "code": "--report-needless-disables",
-    "message": "Needless disable for \\"indentation\\"",
-    "range": Object {
-      "end": Object {
-        "character": 55,
-        "line": 2,
-      },
-      "start": Object {
-        "character": 2,
-        "line": 2,
-      },
-    },
-    "severity": 1,
-    "source": "Stylelint",
-  },
-  Object {
-    "code": "--report-needless-disables",
-    "message": "Needless disable for \\"indentation\\"",
-    "range": Object {
-      "end": Object {
-        "character": 34,
-        "line": 6,
-      },
-      "start": Object {
-        "character": 0,
-        "line": 6,
-      },
-    },
-    "severity": 1,
-    "source": "Stylelint",
-  },
-  Object {
-    "code": "--report-needless-disables",
-    "message": "Needless disable for \\"indentation\\"",
-    "range": Object {
-      "end": Object {
-        "character": 55,
-        "line": 14,
-      },
-      "start": Object {
-        "character": 16,
-        "line": 14,
-      },
-    },
-    "severity": 1,
-    "source": "Stylelint",
-  },
-  Object {
-    "code": "--report-needless-disables",
-    "message": "Needless disable for \\"indentation\\"",
-    "range": Object {
-      "end": Object {
-        "character": 34,
-        "line": 17,
-      },
-      "start": Object {
-        "character": 0,
-        "line": 17,
-      },
-    },
-    "severity": 1,
-    "source": "Stylelint",
-  },
-  Object {
-    "code": "--report-needless-disables",
-    "message": "Needless disable for \\"unknown\\"",
-    "range": Object {
-      "end": Object {
-        "character": 55,
-        "line": 2,
-      },
-      "start": Object {
-        "character": 2,
-        "line": 2,
-      },
-    },
-    "severity": 1,
-    "source": "Stylelint",
-  },
-]
-`;
diff --git a/test/e2e/__tests__/__snapshots__/stylelint-resolution.ts.snap b/test/e2e/__tests__/__snapshots__/stylelint-resolution.ts.snap
deleted file mode 100644
index e2c1b08..0000000
--- a/test/e2e/__tests__/__snapshots__/stylelint-resolution.ts.snap
+++ /dev/null
@@ -1,85 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`Stylelint resolution should resolve Stylelint using "stylelint.stylelintPath" 1`] = `
-Array [
-  Object {
-    "code": "fake",
-    "message": "Fake result",
-    "range": Object {
-      "end": Object {
-        "character": 1,
-        "line": 0,
-      },
-      "start": Object {
-        "character": 0,
-        "line": 0,
-      },
-    },
-    "severity": 1,
-    "source": "Stylelint",
-  },
-]
-`;
-
-exports[`Stylelint resolution should resolve Stylelint using PnP 1`] = `
-Array [
-  Object {
-    "code": "fake",
-    "message": "Fake result from yarn-pnp",
-    "range": Object {
-      "end": Object {
-        "character": 1,
-        "line": 0,
-      },
-      "start": Object {
-        "character": 0,
-        "line": 0,
-      },
-    },
-    "severity": 1,
-    "source": "Stylelint",
-  },
-]
-`;
-
-exports[`Stylelint resolution should resolve Stylelint using Yarn 2.x PnP 1`] = `
-Array [
-  Object {
-    "code": "fake",
-    "message": "Fake result from yarn-2-pnp",
-    "range": Object {
-      "end": Object {
-        "character": 1,
-        "line": 0,
-      },
-      "start": Object {
-        "character": 0,
-        "line": 0,
-      },
-    },
-    "severity": 1,
-    "source": "Stylelint",
-  },
-]
-`;
-
-exports[`Stylelint resolution should resolve Stylelint using local node_modules 1`] = `
-Array [
-  Object {
-    "code": "fake",
-    "message": "Fake result from resolve-local",
-    "range": Object {
-      "end": Object {
-        "character": 1,
-        "line": 0,
-      },
-      "start": Object {
-        "character": 0,
-        "line": 0,
-      },
-    },
-    "severity": 1,
-    "source": "Stylelint",
-  },
-]
-`;
diff --git a/test/e2e/__tests__/__snapshots__/validate.ts.snap b/test/e2e/__tests__/__snapshots__/validate.ts.snap
deleted file mode 100644
index 560d6bc..0000000
--- a/test/e2e/__tests__/__snapshots__/validate.ts.snap
+++ /dev/null
@@ -1,52 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`"stylelint.validate" setting when set to ["scss"] should lint and auto-fix scss 1`] = `
-Array [
-  Object {
-    "code": "indentation",
-    "codeDescription": Object {
-      "href": "https://stylelint.io/user-guide/rules/indentation",
-    },
-    "message": "Expected indentation of 4 spaces (indentation)",
-    "range": Object {
-      "end": Object {
-        "character": 13,
-        "line": 2,
-      },
-      "start": Object {
-        "character": 2,
-        "line": 2,
-      },
-    },
-    "severity": 1,
-    "source": "Stylelint",
-  },
-]
-`;
-
-exports[`"stylelint.validate" setting when set to ["scss"] should lint and auto-fix scss 2`] = `
-"/* prettier-ignore */
-a {
-    color: red;
-}
-"
-`;
-
-exports[`"stylelint.validate" setting when set to ["scss"] shouldn't lint or fix css 1`] = `
-"/* prettier-ignore */
-a {
-  color: red;
-}
-"
-`;
-
-exports[`"stylelint.validate" setting when set to ["scss"] shouldn't lint or fix markdown 1`] = `
-"# title
-
-\`\`\`css
-a {
-  color: red;
-}
-\`\`\`
-"
-`;
diff --git a/test/e2e/__tests__/code-actions.ts b/test/e2e/__tests__/code-actions.ts
index 6c85174..1ca1136 100644
--- a/test/e2e/__tests__/code-actions.ts
+++ b/test/e2e/__tests__/code-actions.ts
@@ -1,73 +1,55 @@
-/* eslint-disable jest/no-standalone-expect */
-import fs from 'fs/promises';
-import path from 'path';
-import process from 'process';
+import * as assert from 'node:assert/strict';
+import { EOL } from 'node:os';
+
+import { workspace, Selection, Position } from 'vscode';
 
-import pWaitFor from 'p-wait-for';
 import {
-	commands,
-	extensions,
-	workspace,
-	Selection,
-	Range,
-	Position,
-	CodeAction,
-	TextEditor,
-} from 'vscode';
-import { ApiEvent, PublicApi } from '../../../src/extension/index';
-
-const getCodeActions = async (editor: TextEditor): Promise<CodeAction[]> =>
-	(await commands.executeCommand(
-		'vscode.executeCodeActionProvider',
-		editor.document.uri,
-		new Range(editor.selection.start, editor.selection.end),
-	)) ?? [];
-
-const serializeCodeActions = (actions: CodeAction[]) =>
-	actions.map((action) => ({
-		...action,
-		...(action.edit ? { edit: action.edit?.entries()?.map(([, edits]) => ['<uri>', edits]) } : {}),
-	}));
-
-const cssPath = path.resolve(workspaceDir, 'code-actions/test.css');
-const jsPath = path.resolve(workspaceDir, 'code-actions/test.js');
-const settingsPath = path.resolve(workspaceDir, 'code-actions/.vscode/settings.json');
-
-// TODO: Investigate why editing tests intermittently fail on CI
-const localIt = process.env.CI ? it.skip : it;
+	assertCommand,
+	assertTextEdits,
+	waitForDiagnostics,
+	openDocument,
+	closeAllEditors,
+	restoreFile,
+	setupSettings,
+	getCodeActions,
+} from '../helpers';
+
+const cssPath = 'code-actions/test.css';
+const jsPath = 'code-actions/test.js';
 
 describe('Code actions', () => {
-	beforeAll(async () => {
-		const api = (await extensions.getExtension('stylelint.vscode-stylelint')?.exports) as PublicApi;
-
-		await pWaitFor(() => api.codeActionReady, { timeout: 5000 });
-	});
-
-	let savedFiles: Map<string, string>;
-
-	beforeEach(async () => {
-		savedFiles = new Map([
-			[cssPath, await fs.readFile(cssPath, 'utf8')],
-			[settingsPath, await fs.readFile(settingsPath, 'utf8')],
-		]);
-	});
-
 	afterEach(async () => {
-		for (const [filePath, content] of savedFiles.entries()) {
-			await fs.writeFile(filePath, content);
-		}
+		await closeAllEditors();
 	});
 
+	restoreFile(cssPath);
+	restoreFile(jsPath);
+
 	it('should provide code actions for problems', async () => {
 		const editor = await openDocument(cssPath);
 
 		await waitForDiagnostics(editor);
 
-		editor.selection = new Selection(new Position(1, 2), new Position(1, 2));
-
-		const actions = await getCodeActions(editor);
+		const actions = await getCodeActions(editor, new Selection(1, 2, 1, 2));
 
-		expect(serializeCodeActions(actions)).toMatchSnapshot();
+		assert.equal(actions.length, 3);
+		assertTextEdits(actions[0].edit?.get(editor.document.uri), [
+			{
+				newText: `  /* stylelint-disable-next-line indentation */${EOL}`,
+				range: [1, 0, 1, 0],
+			},
+		]);
+		assertTextEdits(actions[1].edit?.get(editor.document.uri), [
+			{
+				newText: `/* stylelint-disable indentation */${EOL}`,
+				range: [0, 0, 0, 0],
+			},
+		]);
+		assertCommand(actions[2].command, {
+			title: 'Open documentation for indentation',
+			command: 'stylelint.openRuleDoc',
+			arguments: [{ uri: 'https://stylelint.io/user-guide/rules/indentation' }],
+		});
 	});
 
 	it('should not provide disable code actions for disable reports', async () => {
@@ -75,20 +57,16 @@ describe('Code actions', () => {
 
 		await waitForDiagnostics(editor);
 
-		editor.selection = new Selection(new Position(2, 4), new Position(2, 4));
+		const actions = await getCodeActions(editor, new Selection(2, 4, 2, 4));
 
-		const actions = await getCodeActions(editor);
-
-		expect(actions).toHaveLength(0);
+		assert.equal(actions.length, 0);
 	});
 
-	test('should run auto-fix action on save', async () => {
+	it('should run auto-fix action on save', async () => {
 		const editor = await openDocument(cssPath);
 
 		await waitForDiagnostics(editor);
 
-		await getCodeActions(editor);
-
 		// API won't save unless we dirty the document, unlike saving via the UI
 		await editor.edit((editBuilder) => {
 			editBuilder.insert(new Position(3, 0), ' ');
@@ -96,99 +74,123 @@ describe('Code actions', () => {
 
 		await editor.document.save();
 
-		expect(editor.document.getText()).toMatchSnapshot();
+		assert.equal(
+			editor.document.getText(),
+			`a {
+    font-size: 1.2em;
+    /* stylelint-disable-next-line comment-no-empty */
+    color: #00;
+}
+`,
+		);
 	});
 
-	localIt('should disable rules for an entire file', async () => {
+	it('should disable rules for an entire file', async () => {
 		const editor = await openDocument(cssPath);
 
 		await waitForDiagnostics(editor);
 
-		editor.selection = new Selection(new Position(1, 2), new Position(1, 2));
-
-		const actions = await getCodeActions(editor);
+		const actions = await getCodeActions(editor, new Selection(1, 2, 1, 2));
 
 		const fileAction = actions.find((action) =>
 			action.title.match(/^Disable .+ for the entire file$/),
 		);
 
-		expect(fileAction?.edit).toBeDefined();
+		assert.ok(fileAction?.edit);
 
-		await workspace.applyEdit(fileAction!.edit!);
+		await workspace.applyEdit(fileAction.edit);
 
-		expect(editor.document.getText()).toMatchSnapshot();
+		assert.equal(
+			editor.document.getText(),
+			`/* stylelint-disable indentation */
+a {
+  font-size: 1.2em;
+    /* stylelint-disable-next-line comment-no-empty */
+  color: #00;
+}
+`,
+		);
 	});
 
-	localIt('should disable rules for an entire file with a shebang', async () => {
+	it('should disable rules for an entire file with a shebang', async () => {
 		const editor = await openDocument(jsPath);
 
 		await waitForDiagnostics(editor);
 
-		editor.selection = new Selection(new Position(6, 9), new Position(6, 9));
-
-		const actions = await getCodeActions(editor);
+		const actions = await getCodeActions(editor, new Selection(6, 9, 6, 9));
 
 		const fileAction = actions.find((action) =>
 			action.title.match(/^Disable .+ for the entire file$/),
 		);
 
-		expect(fileAction?.edit).toBeDefined();
-
-		await workspace.applyEdit(fileAction!.edit!);
+		assert.ok(fileAction?.edit);
 
-		expect(editor.document.getText()).toMatchSnapshot();
-	});
-
-	localIt(
-		'should disable rules for a specific line with a comment on the previous line',
-		async () => {
-			const editor = await openDocument(cssPath);
+		await workspace.applyEdit(fileAction.edit);
 
-			editor.selection = new Selection(new Position(1, 2), new Position(1, 2));
+		assert.equal(
+			editor.document.getText(),
+			`#!/usr/bin/env node
+/* stylelint-disable color-no-invalid-hex */
+/* eslint-disable node/shebang */
+'use strict';
 
-			await waitForDiagnostics(editor);
+const css = css\`
+	.foo {
+		color: #00;
+	}
+\`;
+`,
+		);
+	});
 
-			const actions = await getCodeActions(editor);
-			const lineAction = actions.find((action) => action.title.match(/^Disable .+ for this line$/));
+	it('should disable rules for a specific line with a comment on the previous line', async () => {
+		const editor = await openDocument(cssPath);
 
-			expect(lineAction?.edit).toBeDefined();
+		await waitForDiagnostics(editor);
 
-			await workspace.applyEdit(lineAction!.edit!);
+		const actions = await getCodeActions(editor, new Selection(1, 2, 1, 2));
+		const lineAction = actions.find((action) => action.title.match(/^Disable .+ for this line$/));
 
-			expect(editor.document.getText()).toMatchSnapshot();
-		},
-	);
+		assert.ok(lineAction?.edit);
 
-	localIt('should disable rules for a specific line with a comment on the same line', async () => {
-		const settingsEditor = await openDocument(settingsPath);
+		await workspace.applyEdit(lineAction.edit);
 
-		await settingsEditor.edit((edit) =>
-			edit.insert(
-				new Position(10, 2),
-				',\n\t"stylelint.codeAction.disableRuleComment": { "location": "sameLine" }',
-			),
+		assert.equal(
+			editor.document.getText(),
+			`a {
+  /* stylelint-disable-next-line indentation */
+  font-size: 1.2em;
+    /* stylelint-disable-next-line comment-no-empty */
+  color: #00;
+}
+`,
 		);
+	});
 
-		const resetPromise = waitForApiEvent(ApiEvent.DidResetConfiguration);
-
-		await settingsEditor.document.save();
-		await resetPromise;
-		//await openDocument(cssPath);
-		//await commands.executeCommand('workbench.action.closeActiveEditor');
+	context('when "stylelint.codeAction.disableRuleComment" is set to "sameLine"', () => {
+		setupSettings({ 'stylelint.codeAction.disableRuleComment': { location: 'sameLine' } });
 
-		const editor = await openDocument(cssPath);
-
-		await waitForDiagnostics(editor);
+		it('should disable rules for a specific line with a comment on the same line', async () => {
+			const editor = await openDocument(cssPath);
 
-		editor.selection = new Selection(new Position(1, 2), new Position(1, 2));
+			await waitForDiagnostics(editor);
 
-		const actions = await getCodeActions(editor);
-		const lineAction = actions.find((action) => action.title.match(/^Disable .+ for this line$/));
+			const actions = await getCodeActions(editor, new Selection(1, 2, 1, 2));
+			const lineAction = actions.find((action) => action.title.match(/^Disable .+ for this line$/));
 
-		expect(lineAction?.edit).toBeDefined();
+			assert.ok(lineAction?.edit);
 
-		await workspace.applyEdit(lineAction!.edit!);
+			await workspace.applyEdit(lineAction.edit);
 
-		expect(editor.document.getText()).toMatchSnapshot();
+			assert.equal(
+				editor.document.getText(),
+				`a {
+  font-size: 1.2em; /* stylelint-disable-line indentation */
+    /* stylelint-disable-next-line comment-no-empty */
+  color: #00;
+}
+`,
+			);
+		});
 	});
 });
diff --git a/test/e2e/__tests__/config-basedir.ts b/test/e2e/__tests__/config-basedir.ts
index 5c5278b..2974c64 100644
--- a/test/e2e/__tests__/config-basedir.ts
+++ b/test/e2e/__tests__/config-basedir.ts
@@ -1,13 +1,29 @@
-import path from 'path';
-import { normalizeDiagnostic } from '../utils';
+import { assertDiagnostics, waitForDiagnostics, openDocument, closeAllEditors } from '../helpers';
 
 describe('"stylelint.configBasedir" setting', () => {
+	afterEach(async () => {
+		await closeAllEditors();
+	});
+
 	it('should resolve referenced configs using the base directory', async () => {
-		const { document } = await openDocument(
-			path.resolve(workspaceDir, 'config/config-basedir.css'),
-		);
+		const document = await openDocument('config/config-basedir.css');
 		const diagnostics = await waitForDiagnostics(document);
 
-		expect(diagnostics.map(normalizeDiagnostic)).toMatchSnapshot();
+		assertDiagnostics(diagnostics, [
+			{
+				code: 'color-hex-case',
+				codeDescription: 'https://stylelint.io/user-guide/rules/color-hex-case',
+				message: 'Expected "#fff" to be "#FFF" (color-hex-case)',
+				range: [1, 9, 1, 10],
+				severity: 'error',
+			},
+			{
+				code: 'indentation',
+				codeDescription: 'https://stylelint.io/user-guide/rules/indentation',
+				message: 'Expected indentation of 8 spaces (indentation)',
+				range: [1, 2, 1, 14],
+				severity: 'error',
+			},
+		]);
 	});
 });
diff --git a/test/e2e/__tests__/config-file.ts b/test/e2e/__tests__/config-file.ts
index 33bfeb5..df24571 100644
--- a/test/e2e/__tests__/config-file.ts
+++ b/test/e2e/__tests__/config-file.ts
@@ -1,11 +1,22 @@
-import path from 'path';
-import { normalizeDiagnostic } from '../utils';
+import { openDocument, waitForDiagnostics, assertDiagnostics, closeAllEditors } from '../helpers';
 
 describe('"stylelint.configFile" setting', () => {
+	afterEach(async () => {
+		await closeAllEditors();
+	});
+
 	it('should resolve the config file using the specified path', async () => {
-		const { document } = await openDocument(path.resolve(workspaceDir, 'config/config-file.css'));
+		const document = await openDocument('config/config-file.css');
 		const diagnostics = await waitForDiagnostics(document);
 
-		expect(diagnostics.map(normalizeDiagnostic)).toMatchSnapshot();
+		assertDiagnostics(diagnostics, [
+			{
+				code: 'block-no-empty',
+				codeDescription: 'https://stylelint.io/user-guide/rules/block-no-empty',
+				message: 'Unexpected empty block (block-no-empty)',
+				range: [0, 2, 0, 4],
+				severity: 'error',
+			},
+		]);
 	});
 });
diff --git a/test/e2e/__tests__/custom-syntax.ts b/test/e2e/__tests__/custom-syntax.ts
index 4c07ed0..6403b3c 100644
--- a/test/e2e/__tests__/custom-syntax.ts
+++ b/test/e2e/__tests__/custom-syntax.ts
@@ -1,26 +1,22 @@
-import path from 'path';
-import pWaitFor from 'p-wait-for';
-import { commands } from 'vscode';
+import * as assert from 'node:assert/strict';
+
+import { openDocument, executeAutofix, closeAllEditors } from '../helpers';
 
 describe('"stylelint.customSyntax" setting', () => {
+	afterEach(async () => {
+		await closeAllEditors();
+	});
+
 	it('should auto-fix using the specified custom syntax', async () => {
-		const { document } = await openDocument(path.resolve(workspaceDir, 'custom-syntax/test.css'));
+		const { document } = await openDocument('custom-syntax/test.css');
 
-		await pWaitFor(
-			async () => {
-				const names = await commands.getCommands();
+		await executeAutofix();
 
-				return (
-					// cspell:disable-next-line
-					names.includes('stylelint.executeAutofix') && names.includes('stylelint.applyAutoFix')
-				);
-			},
-			{ timeout: 2000 },
+		assert.equal(
+			document.getText(),
+			`/* prettier-ignore */
+.foo .bar
+    color: red`,
 		);
-
-		// cspell:disable-next-line
-		await commands.executeCommand('stylelint.executeAutofix');
-
-		expect(document.getText()).toMatchSnapshot();
 	});
 });
diff --git a/test/e2e/__tests__/format.ts b/test/e2e/__tests__/format.ts
index c46bc25..fd7c53a 100644
--- a/test/e2e/__tests__/format.ts
+++ b/test/e2e/__tests__/format.ts
@@ -1,26 +1,31 @@
-import path from 'path';
+import * as assert from 'node:assert/strict';
+
 import { commands } from 'vscode';
-import { URI } from 'vscode-uri';
-import { ApiEvent } from '../../../src/extension/index';
 
-describe('Document formatting', () => {
-	it('should format document using formatting options', async () => {
-		const documentPath = path.resolve(workspaceDir, 'defaults/format.css');
+import { openDocument, closeAllEditors, setupSettings, sleep } from '../helpers';
 
-		const eventPromise = waitForApiEvent(
-			ApiEvent.DidRegisterDocumentFormattingEditProvider,
-			({ uri }) => path.relative(documentPath, URI.parse(uri).fsPath) === '',
-		);
+describe('Document formatting', () => {
+	setupSettings({ '[css]': { 'editor.defaultFormatter': 'stylelint.vscode-stylelint' } });
 
-		const editor = await openDocument(documentPath);
+	afterEach(async () => {
+		await closeAllEditors();
+	});
 
-		await eventPromise;
+	it('should format document using formatting options', async () => {
+		const editor = await openDocument('defaults/format.css');
 
 		editor.options.tabSize = 4;
 		editor.options.insertSpaces = false;
 
+		await sleep(1000); // HACK: Prevent flaky test.
 		await commands.executeCommand('editor.action.formatDocument');
 
-		expect(editor.document.getText()).toMatchSnapshot();
+		assert.equal(
+			editor.document.getText(),
+			`a {
+	color: red;
+}
+`,
+		);
 	});
 });
diff --git a/test/e2e/__tests__/ignore-disables.ts b/test/e2e/__tests__/ignore-disables.ts
index 12b1af2..9ac36da 100644
--- a/test/e2e/__tests__/ignore-disables.ts
+++ b/test/e2e/__tests__/ignore-disables.ts
@@ -1,11 +1,29 @@
-import path from 'path';
-import { normalizeDiagnostic } from '../utils';
+import { openDocument, waitForDiagnostics, assertDiagnostics, closeAllEditors } from '../helpers';
 
 describe('"stylelint.ignoreDisables" setting', () => {
+	afterEach(async () => {
+		await closeAllEditors();
+	});
+
 	it('should ignore disable directives when enabled', async () => {
-		const { document } = await openDocument(path.resolve(workspaceDir, 'ignore-disables/test.css'));
+		const document = await openDocument('ignore-disables/test.css');
 		const diagnostics = await waitForDiagnostics(document);
 
-		expect(diagnostics.map(normalizeDiagnostic)).toMatchSnapshot();
+		assertDiagnostics(diagnostics, [
+			{
+				code: 'color-hex-case',
+				codeDescription: 'https://stylelint.io/user-guide/rules/color-hex-case',
+				message: 'Expected "#fff" to be "#FFF" (color-hex-case)',
+				range: [3, 9, 3, 10],
+				severity: 'error',
+			},
+			{
+				code: 'indentation',
+				codeDescription: 'https://stylelint.io/user-guide/rules/indentation',
+				message: 'Expected indentation of 4 spaces (indentation)',
+				range: [3, 2, 3, 14],
+				severity: 'error',
+			},
+		]);
 	});
 });
diff --git a/test/e2e/__tests__/ignore.ts b/test/e2e/__tests__/ignore.ts
index c487d5a..4e99235 100644
--- a/test/e2e/__tests__/ignore.ts
+++ b/test/e2e/__tests__/ignore.ts
@@ -1,20 +1,21 @@
-import path from 'path';
-// import { normalizeDiagnostic, getStylelintDiagnostics } from '../utils';
+import * as assert from 'node:assert/strict';
+
+import { openDocument, closeAllEditors, getStylelintDiagnostics } from '../helpers';
 
 describe('.stylelintignore', () => {
+	afterEach(async () => {
+		await closeAllEditors();
+	});
+
 	it('should have syntax highlighting', async () => {
-		const { document } = await openDocument(path.join(workspaceDir, 'defaults/.stylelintignore'));
+		const { document } = await openDocument('defaults/.stylelintignore');
 
-		expect(document.languageId).toBe('ignore');
+		assert.equal(document.languageId, 'ignore');
 	});
 
-	// TODO: Get .stylelintignore to work
-	// eslint-disable-next-line jest/no-commented-out-tests
-	// it('should be respected', async () => {
-	// 	const { document } = await openDocument(path.resolve(workspaceDir, 'defaults/ignored.css'));
+	it('should be respected', async () => {
+		const { document } = await openDocument('defaults/ignored.css');
 
-	// 	// Wait for diagnostics to be computed
-
-	// 	expect(getStylelintDiagnostics(document.uri).map(normalizeDiagnostic)).toEqual([]);
-	// });
+		assert.deepEqual(getStylelintDiagnostics(document.uri), []);
+	});
 });
diff --git a/test/e2e/__tests__/lint.ts b/test/e2e/__tests__/lint.ts
index cc5309e..6aa933d 100644
--- a/test/e2e/__tests__/lint.ts
+++ b/test/e2e/__tests__/lint.ts
@@ -1,18 +1,50 @@
-import path from 'path';
-import { normalizeDiagnostic } from '../utils';
+import { openDocument, waitForDiagnostics, assertDiagnostics, closeAllEditors } from '../helpers';
 
 describe('Linting', () => {
+	afterEach(async () => {
+		await closeAllEditors();
+	});
+
 	it('should lint CSS documents', async () => {
-		const { document } = await openDocument(path.resolve(workspaceDir, 'defaults/lint.css'));
+		const document = await openDocument('defaults/lint.css');
 		const diagnostics = await waitForDiagnostics(document);
 
-		expect(diagnostics.map(normalizeDiagnostic)).toMatchSnapshot();
+		assertDiagnostics(diagnostics, [
+			{
+				code: 'indentation',
+				codeDescription: 'https://stylelint.io/user-guide/rules/indentation',
+				message: 'Expected indentation of 4 spaces (indentation)',
+				range: [2, 2, 2, 13],
+				severity: 'error',
+			},
+		]);
 	});
 
 	it('should display rule documentation links when one is available', async () => {
-		const { document } = await openDocument(path.resolve(workspaceDir, 'defaults/rule-doc.css'));
+		const document = await openDocument('defaults/rule-doc.css');
 		const diagnostics = await waitForDiagnostics(document);
 
-		expect(diagnostics.map(normalizeDiagnostic)).toMatchSnapshot();
+		assertDiagnostics(diagnostics, [
+			{
+				code: 'plugin/foo-bar',
+				message: 'Bar (plugin/foo-bar)',
+				range: [0, 5, 0, 6],
+				severity: 'error',
+			},
+			{
+				code: 'color-no-invalid-hex',
+				codeDescription: 'https://stylelint.io/user-guide/rules/color-no-invalid-hex',
+				message: 'Unexpected invalid hex color "#y3" (color-no-invalid-hex)',
+				range: [6, 11, 6, 14],
+				severity: 'error',
+			},
+			{
+				code: 'indentation',
+				codeDescription: 'https://stylelint.io/user-guide/rules/indentation',
+				message: 'Expected indentation of 4 spaces (indentation)',
+				range: [2, 2, 2, 13],
+				severity: 'error',
+			},
+		]);
 	});
 });
diff --git a/test/e2e/__tests__/report-descriptionless-disables.ts b/test/e2e/__tests__/report-descriptionless-disables.ts
index 7a55834..dd8d767 100644
--- a/test/e2e/__tests__/report-descriptionless-disables.ts
+++ b/test/e2e/__tests__/report-descriptionless-disables.ts
@@ -1,17 +1,21 @@
-import path from 'path';
-import { normalizeDiagnostic } from '../utils';
+import { openDocument, waitForDiagnostics, assertDiagnostics, closeAllEditors } from '../helpers';
 
 describe('"stylelint.reportDescriptionlessDisables" setting', () => {
+	afterEach(async () => {
+		await closeAllEditors();
+	});
+
 	it('should report invalid-scope disables when enabled', async () => {
-		const { document } = await openDocument(
-			path.resolve(workspaceDir, 'descriptionless-disables/test.css'),
-		);
+		const { document } = await openDocument('descriptionless-disables/test.css');
 		const diagnostics = await waitForDiagnostics(document);
 
-		expect(
-			diagnostics
-				.map(normalizeDiagnostic)
-				.filter((diagnostic) => diagnostic?.code === '--report-descriptionless-disables'),
-		).toMatchSnapshot();
+		assertDiagnostics(diagnostics, [
+			{
+				code: '--report-descriptionless-disables',
+				message: 'Disable for "indentation" is missing a description',
+				range: [2, 4, 2, 48],
+				severity: 'error',
+			},
+		]);
 	});
 });
diff --git a/test/e2e/__tests__/report-invalid-scope-disables.ts b/test/e2e/__tests__/report-invalid-scope-disables.ts
index fcc2f6f..4673566 100644
--- a/test/e2e/__tests__/report-invalid-scope-disables.ts
+++ b/test/e2e/__tests__/report-invalid-scope-disables.ts
@@ -1,17 +1,35 @@
-import path from 'path';
-import { normalizeDiagnostic } from '../utils';
+import { openDocument, waitForDiagnostics, assertDiagnostics, closeAllEditors } from '../helpers';
 
 describe('"stylelint.reportInvalidScopeDisables" setting', () => {
+	afterEach(async () => {
+		await closeAllEditors();
+	});
+
 	it('should report invalid-scope disables when enabled', async () => {
-		const { document } = await openDocument(
-			path.resolve(workspaceDir, 'report-disables/invalid-scope.css'),
+		const { document } = await openDocument('report-disables/invalid-scope.css');
+		const diagnostics = (await waitForDiagnostics(document)).filter(
+			({ code }) => code === '--report-invalid-scope-disables',
 		);
-		const diagnostics = await waitForDiagnostics(document);
 
-		expect(
-			diagnostics
-				.map(normalizeDiagnostic)
-				.filter((diagnostic) => diagnostic?.code === '--report-invalid-scope-disables'),
-		).toMatchSnapshot();
+		assertDiagnostics(diagnostics, [
+			{
+				code: '--report-invalid-scope-disables',
+				message: 'Rule "foo" isn\'t enabled',
+				range: [0, 0, 0, 36],
+				severity: 'error',
+			},
+			{
+				code: '--report-invalid-scope-disables',
+				message: 'Rule "foo" isn\'t enabled',
+				range: [2, 0, 2, 31],
+				severity: 'error',
+			},
+			{
+				code: '--report-invalid-scope-disables',
+				message: 'Rule "foo" isn\'t enabled',
+				range: [4, 0, 4, 26],
+				severity: 'error',
+			},
+		]);
 	});
 });
diff --git a/test/e2e/__tests__/report-needless-disables.ts b/test/e2e/__tests__/report-needless-disables.ts
index 5653628..76d96c3 100644
--- a/test/e2e/__tests__/report-needless-disables.ts
+++ b/test/e2e/__tests__/report-needless-disables.ts
@@ -1,17 +1,47 @@
-import path from 'path';
-import { normalizeDiagnostic } from '../utils';
+import { openDocument, waitForDiagnostics, assertDiagnostics, closeAllEditors } from '../helpers';
 
 describe('"stylelint.reportNeedlessDisables" setting', () => {
+	afterEach(async () => {
+		await closeAllEditors();
+	});
+
 	it('should report needless disables when enabled', async () => {
-		const { document } = await openDocument(
-			path.resolve(workspaceDir, 'report-disables/needless.css'),
+		const { document } = await openDocument('report-disables/needless.css');
+		const diagnostics = (await waitForDiagnostics(document)).filter(
+			({ code }) => code === '--report-needless-disables',
 		);
-		const diagnostics = await waitForDiagnostics(document);
 
-		expect(
-			diagnostics
-				.map(normalizeDiagnostic)
-				.filter((diagnostic) => diagnostic?.code === '--report-needless-disables'),
-		).toMatchSnapshot();
+		assertDiagnostics(diagnostics, [
+			{
+				code: '--report-needless-disables',
+				message: 'Needless disable for "indentation"',
+				range: [2, 2, 2, 55],
+				severity: 'error',
+			},
+			{
+				code: '--report-needless-disables',
+				message: 'Needless disable for "indentation"',
+				range: [6, 0, 6, 34],
+				severity: 'error',
+			},
+			{
+				code: '--report-needless-disables',
+				message: 'Needless disable for "indentation"',
+				range: [14, 16, 14, 55],
+				severity: 'error',
+			},
+			{
+				code: '--report-needless-disables',
+				message: 'Needless disable for "indentation"',
+				range: [17, 0, 17, 34],
+				severity: 'error',
+			},
+			{
+				code: '--report-needless-disables',
+				message: 'Needless disable for "unknown"',
+				range: [2, 2, 2, 55],
+				severity: 'error',
+			},
+		]);
 	});
 });
diff --git a/test/e2e/__tests__/restart.ts b/test/e2e/__tests__/restart.ts
index e282c73..907b2d8 100644
--- a/test/e2e/__tests__/restart.ts
+++ b/test/e2e/__tests__/restart.ts
@@ -1,20 +1,24 @@
-import path from 'path';
+import * as assert from 'node:assert/strict';
+
 import { commands, languages } from 'vscode';
 
-// Skipped for now because of https://github.com/microsoft/vscode-languageserver-node/issues/723
-// Should work with LSP 3.17 when released
-// eslint-disable-next-line jest/no-disabled-tests
-describe.skip('Restart command', () => {
+import { openDocument, waitForDiagnostics, closeAllEditors } from '../helpers';
+
+describe('Restart command', () => {
+	afterEach(async () => {
+		await closeAllEditors();
+	});
+
 	it('should restart the language server', async () => {
-		const { document } = await openDocument(path.resolve(workspaceDir, 'defaults/lint.css'));
+		const { document } = await openDocument('defaults/restart.css');
 		const diagnostics1 = await waitForDiagnostics(document);
 
 		await commands.executeCommand('stylelint.restart');
 
-		expect(languages.getDiagnostics(document.uri)).toEqual([]);
+		assert.equal(languages.getDiagnostics(document.uri).length, 0);
 
-		const diagnostics2 = await waitForDiagnostics(document);
+		const diagnostics2 = await waitForDiagnostics(document, { timeout: 20000 });
 
-		expect(diagnostics2).toEqual(diagnostics1);
+		assert.deepEqual(diagnostics2, diagnostics1);
 	});
 });
diff --git a/test/e2e/__tests__/stylelint-resolution.ts b/test/e2e/__tests__/stylelint-resolution.ts
index e4f5820..ba52d9c 100644
--- a/test/e2e/__tests__/stylelint-resolution.ts
+++ b/test/e2e/__tests__/stylelint-resolution.ts
@@ -1,38 +1,65 @@
-import path from 'path';
-import { normalizeDiagnostic } from '../utils';
+import { openDocument, waitForDiagnostics, assertDiagnostics, closeAllEditors } from '../helpers';
 
 describe('Stylelint resolution', () => {
+	afterEach(async () => {
+		await closeAllEditors();
+	});
+
 	it('should resolve Stylelint using local node_modules', async () => {
-		const { document } = await openDocument(
-			path.resolve(workspaceDir, 'defaults/local-stylelint/test.css'),
-		);
+		const document = await openDocument('defaults/local-stylelint/test.css');
 		const diagnostics = await waitForDiagnostics(document);
 
-		expect(diagnostics.map(normalizeDiagnostic)).toMatchSnapshot();
+		assertDiagnostics(diagnostics, [
+			{
+				code: 'fake',
+				message: 'Fake result from resolve-local',
+				range: [0, 0, 0, 1],
+				severity: 'error',
+			},
+		]);
 	});
 
-	it('should resolve Stylelint using "stylelint.stylelintPath"', async () => {
-		const { document } = await openDocument(path.resolve(workspaceDir, 'stylelint-path/test.css'));
-		const diagnostics = await waitForDiagnostics(document);
+	describe('when using "stylelint.stylelintPath"', () => {
+		it('should resolve Stylelint', async () => {
+			const document = await openDocument('stylelint-path/test.css');
+			const diagnostics = await waitForDiagnostics(document);
 
-		expect(diagnostics.map(normalizeDiagnostic)).toMatchSnapshot();
+			assertDiagnostics(diagnostics, [
+				{
+					code: 'fake',
+					message: 'Fake result',
+					range: [0, 0, 0, 1],
+					severity: 'error',
+				},
+			]);
+		});
 	});
 
 	it('should resolve Stylelint using PnP', async () => {
-		const { document } = await openDocument(
-			path.resolve(workspaceDir, 'defaults/yarn-pnp/test.css'),
-		);
+		const document = await openDocument('defaults/yarn-pnp/test.css');
 		const diagnostics = await waitForDiagnostics(document);
 
-		expect(diagnostics.map(normalizeDiagnostic)).toMatchSnapshot();
+		assertDiagnostics(diagnostics, [
+			{
+				code: 'fake',
+				message: 'Fake result from yarn-pnp',
+				range: [0, 0, 0, 1],
+				severity: 'error',
+			},
+		]);
 	});
 
 	it('should resolve Stylelint using Yarn 2.x PnP', async () => {
-		const { document } = await openDocument(
-			path.resolve(workspaceDir, 'defaults/yarn-2-pnp/test.css'),
-		);
+		const document = await openDocument('defaults/yarn-2-pnp/test.css');
 		const diagnostics = await waitForDiagnostics(document);
 
-		expect(diagnostics.map(normalizeDiagnostic)).toMatchSnapshot();
+		assertDiagnostics(diagnostics, [
+			{
+				code: 'fake',
+				message: 'Fake result from yarn-2-pnp',
+				range: [0, 0, 0, 1],
+				severity: 'error',
+			},
+		]);
 	});
 });
diff --git a/test/e2e/__tests__/validate.ts b/test/e2e/__tests__/validate.ts
index 1dd7e89..a073c38 100644
--- a/test/e2e/__tests__/validate.ts
+++ b/test/e2e/__tests__/validate.ts
@@ -1,62 +1,81 @@
-import path from 'path';
-import pWaitFor from 'p-wait-for';
-import { commands } from 'vscode';
-import { normalizeDiagnostic, getStylelintDiagnostics } from '../utils';
+import * as assert from 'node:assert/strict';
 
-describe('"stylelint.validate" setting', () => {
-	beforeAll(async () => {
-		await pWaitFor(
-			async () => {
-				const names = await commands.getCommands();
-
-				return (
-					// cspell:disable-next-line
-					names.includes('stylelint.executeAutofix') && names.includes('stylelint.applyAutoFix')
-				);
-			},
-			{ timeout: 2000 },
-		);
-	});
+import {
+	openDocument,
+	waitForDiagnostics,
+	assertDiagnostics,
+	getStylelintDiagnostics,
+	executeAutofix,
+	closeAllEditors,
+} from '../helpers';
 
+describe('"stylelint.validate" setting', () => {
 	describe('when set to ["scss"]', () => {
-		it("shouldn't lint or fix css", async () => {
-			const { document } = await openDocument(path.resolve(workspaceDir, 'validate/test.css'));
+		afterEach(async () => {
+			await closeAllEditors();
+		});
 
-			// TODO: find a better way to wait for linting to finish
-			await new Promise((resolve) => setTimeout(resolve, 2000));
+		it("shouldn't lint or fix css", async () => {
+			const { document } = await openDocument('validate/test.css');
 
-			expect(getStylelintDiagnostics(document.uri)).toEqual([]);
+			assert.deepEqual(getStylelintDiagnostics(document.uri), []);
 
-			// cspell:disable-next-line
-			await commands.executeCommand('stylelint.executeAutofix');
+			await executeAutofix();
 
-			expect(document.getText()).toMatchSnapshot();
+			assert.equal(
+				document.getText(),
+				`/* prettier-ignore */
+a {
+  color: red;
+}
+`,
+			);
 		});
 
 		it('should lint and auto-fix scss', async () => {
-			const { document } = await openDocument(path.resolve(workspaceDir, 'validate/test.scss'));
+			const { document } = await openDocument('validate/test.scss');
 			const diagnostics = await waitForDiagnostics(document);
 
-			expect(diagnostics.map(normalizeDiagnostic)).toMatchSnapshot();
-
-			// cspell:disable-next-line
-			await commands.executeCommand('stylelint.executeAutofix');
-
-			expect(document.getText()).toMatchSnapshot();
+			assertDiagnostics(diagnostics, [
+				{
+					code: 'indentation',
+					codeDescription: 'https://stylelint.io/user-guide/rules/indentation',
+					message: 'Expected indentation of 4 spaces (indentation)',
+					range: [2, 2, 2, 13],
+					severity: 'error',
+				},
+			]);
+
+			await executeAutofix();
+
+			assert.equal(
+				document.getText(),
+				`/* prettier-ignore */
+a {
+    color: red;
+}
+`,
+			);
 		});
 
 		it("shouldn't lint or fix markdown", async () => {
-			const { document } = await openDocument(path.resolve(workspaceDir, 'validate/test.md'));
+			const { document } = await openDocument('validate/test.md');
 
-			// TODO: find a better way to wait for linting to finish
-			await new Promise((resolve) => setTimeout(resolve, 2000));
+			assert.deepEqual(getStylelintDiagnostics(document.uri), []);
 
-			expect(getStylelintDiagnostics(document.uri)).toEqual([]);
+			await executeAutofix();
 
-			// cspell:disable-next-line
-			await commands.executeCommand('stylelint.executeAutofix');
+			assert.equal(
+				document.getText(),
+				`# title
 
-			expect(document.getText()).toMatchSnapshot();
+\`\`\`css
+a {
+  color: red;
+}
+\`\`\`
+`,
+			);
 		});
 	});
 });
diff --git a/test/e2e/helpers.ts b/test/e2e/helpers.ts
new file mode 100644
index 0000000..df383e2
--- /dev/null
+++ b/test/e2e/helpers.ts
@@ -0,0 +1,237 @@
+/* eslint-disable require-jsdoc */
+import * as assert from 'node:assert/strict';
+
+import {
+	commands,
+	languages,
+	window,
+	workspace,
+	ConfigurationTarget,
+	DiagnosticSeverity,
+	Range,
+	RelativePattern,
+	type CodeAction,
+	type Command,
+	type Diagnostic,
+	type Selection,
+	type TextDocument,
+	type TextEdit,
+	type TextEditor,
+	type Uri,
+} from 'vscode';
+
+export function sleep(ms: number) {
+	return new Promise((resolve) => setTimeout(resolve, ms));
+}
+
+async function getWorkspaceFile(filePath: string): Promise<Uri> {
+	const separatorPosition = filePath.indexOf('/');
+	const workspaceName = filePath.slice(0, separatorPosition);
+	const fileName = filePath.slice(separatorPosition + 1);
+	const workspaceFolder = workspace.workspaceFolders?.find(({ name }) => name === workspaceName);
+
+	assert.ok(workspaceFolder, `A workspace for "${filePath}" is not found`);
+
+	const files = await workspace.findFiles(new RelativePattern(workspaceFolder, fileName));
+
+	assert.equal(files.length, 1);
+
+	return files[0];
+}
+
+export async function openDocument(filePath: string) {
+	const file = await getWorkspaceFile(filePath);
+	const document = await workspace.openTextDocument(file);
+
+	return window.showTextDocument(document);
+}
+
+export async function closeAllEditors() {
+	await commands.executeCommand('workbench.action.closeAllEditors');
+}
+
+export function getStylelintDiagnostics(uri: Uri) {
+	return languages.getDiagnostics(uri).filter((d) => d.source === 'Stylelint');
+}
+
+type WaitForOptions = {
+	timeout?: number;
+	interval?: number;
+};
+
+export async function waitFor<T>(
+	produce: () => T,
+	condition: (result: T) => boolean,
+	{ timeout = 5000, interval = 20 }: WaitForOptions = {},
+): Promise<T> {
+	let intervalRef: NodeJS.Timer;
+	let timeoutRef: NodeJS.Timeout;
+
+	return new Promise((resolve, reject) => {
+		intervalRef = setInterval(() => {
+			const result = produce();
+
+			if (condition(result)) {
+				clearInterval(intervalRef);
+				clearTimeout(timeoutRef);
+				resolve(result);
+			}
+		}, interval);
+
+		timeoutRef = setTimeout(() => {
+			clearInterval(intervalRef);
+			clearTimeout(timeoutRef);
+			reject(new Error(`Timed out in "waitFor" (timeout=${timeout}, interval=${interval})`));
+		}, timeout);
+	});
+}
+
+export function waitForDiagnostics(
+	editorOrDocument: TextEditor | TextDocument,
+	options?: WaitForOptions,
+): Promise<Diagnostic[]> {
+	const uri = 'document' in editorOrDocument ? editorOrDocument.document.uri : editorOrDocument.uri;
+
+	return waitFor(
+		() => getStylelintDiagnostics(uri),
+		(diagnostics) => diagnostics.length > 0,
+		options,
+	);
+}
+
+type ExpectedRange = [number, number, number, number];
+
+type ExpectedDiagnostic = {
+	code: string;
+	codeDescription?: string;
+	message: string;
+	range: ExpectedRange;
+	severity: 'error' | 'warning';
+};
+
+function assertRange(actual: Range, expected: ExpectedRange) {
+	const { start, end } = actual;
+
+	assert.deepEqual([start.line, start.character, end.line, end.character], expected);
+}
+
+export function assertDiagnostic(
+	diagnostic: Diagnostic | undefined,
+	expected: ExpectedDiagnostic | undefined,
+) {
+	assert.ok(diagnostic);
+	assert.ok(expected);
+
+	const { code, codeDescription = '', message, range, severity } = expected;
+
+	assert.equal(diagnostic.source, 'Stylelint');
+
+	if (typeof diagnostic.code === 'string') {
+		assert.equal(diagnostic.code, code);
+	} else if (typeof diagnostic.code === 'object') {
+		assert.equal(diagnostic.code.value, code);
+		assert.equal(diagnostic.code.target.toString(), codeDescription);
+	} else {
+		assert.fail(`"code" is not a string or object: ${diagnostic.code}`);
+	}
+
+	assert.equal(diagnostic.message, message);
+	assertRange(diagnostic.range, range);
+	assert.equal(
+		diagnostic.severity,
+		severity === 'error' ? DiagnosticSeverity.Error : DiagnosticSeverity.Warning,
+	);
+}
+
+export function assertDiagnostics(diagnostics: Diagnostic[], expected: ExpectedDiagnostic[]) {
+	assert.equal(diagnostics.length, expected.length);
+
+	for (let i = 0; i < expected.length; i++) {
+		assertDiagnostic(diagnostics[i], expected[i]);
+	}
+}
+
+type ExpectedTextEdit = {
+	newText: string;
+	range: ExpectedRange;
+};
+
+export function assertTextEdits(actual: TextEdit[] | undefined, expected: ExpectedTextEdit[]) {
+	assert.ok(actual);
+	assert.equal(actual.length, expected.length);
+
+	for (let i = 0; i < expected.length; i++) {
+		assert.equal(actual[i].newText, expected[i].newText);
+		assertRange(actual[i].range, expected[i].range);
+	}
+}
+
+type ExpectedCommand = {
+	title: string;
+	command: string;
+	tooltip?: string;
+	arguments?: unknown[];
+};
+
+export function assertCommand(actual: Command | undefined, expected: ExpectedCommand) {
+	assert.ok(actual);
+	assert.equal(actual.title, expected.title);
+	assert.equal(actual.command, expected.command);
+	assert.equal(actual.tooltip, expected.tooltip);
+	assert.deepEqual(actual.arguments, expected.arguments);
+}
+
+export async function executeAutofix() {
+	await commands.executeCommand('stylelint.executeAutofix');
+}
+
+export function setupSettings(settings: Record<string, unknown>) {
+	const configTarget = ConfigurationTarget.Workspace;
+
+	beforeEach(async () => {
+		const config = workspace.getConfiguration();
+
+		for (const [section, value] of Object.entries(settings)) {
+			await config.update(section, value, configTarget);
+		}
+	});
+
+	afterEach(async () => {
+		const config = workspace.getConfiguration();
+
+		for (const section of Object.keys(settings)) {
+			await config.update(section, undefined, configTarget);
+		}
+	});
+}
+
+export function restoreFile(filePath: string) {
+	let file: Uri;
+	let fileContent: Uint8Array;
+
+	beforeEach(async () => {
+		file = await getWorkspaceFile(filePath);
+		fileContent = await workspace.fs.readFile(file);
+	});
+
+	afterEach(async () => {
+		await workspace.fs.writeFile(file, fileContent);
+	});
+}
+
+export async function getCodeActions(
+	editor: TextEditor,
+	selection: Selection,
+): Promise<CodeAction[]> {
+	editor.selection = selection;
+
+	await sleep(1000); // HACK: Wait the UI for being shown.
+
+	return (
+		(await commands.executeCommand(
+			'vscode.executeCodeActionProvider',
+			editor.document.uri,
+			selection,
+		)) ?? []
+	);
+}
diff --git a/test/e2e/jest-runner-vscode.config.js b/test/e2e/jest-runner-vscode.config.js
deleted file mode 100644
index 50edb16..0000000
--- a/test/e2e/jest-runner-vscode.config.js
+++ /dev/null
@@ -1,31 +0,0 @@
-'use strict';
-
-const path = require('node:path');
-const { readFileSync } = require('node:fs');
-
-const vscodeVersion = '1.82.3';
-
-const pkg = JSON.parse(readFileSync(path.join(__dirname, '../../package.json'), 'utf8'));
-const requiredVscodeVersion = pkg.engines.vscode.match(/\d+\.\d+\.\d+/)?.[0];
-
-if (!requiredVscodeVersion) {
-	throw new Error('Cannot find a VSCode version in package.json');
-}
-
-if (vscodeVersion !== requiredVscodeVersion) {
-	throw new Error(
-		`The required VSCode version in package.json is '${requiredVscodeVersion}', but actually '${vscodeVersion}'`,
-	);
-}
-
-/** @type {import('jest-runner-vscode').RunnerOptions} */
-const config = {
-	version: vscodeVersion,
-	launchArgs: ['--disable-extensions'],
-	openInFolder: true,
-	workspaceDir: path.join(__dirname, 'workspace/workspace.code-workspace'),
-	extensionDevelopmentPath: path.join(__dirname, '../..'),
-	filterOutput: true,
-};
-
-module.exports = config;
diff --git a/test/e2e/jest.config.js b/test/e2e/jest.config.js
deleted file mode 100644
index e9e5fa1..0000000
--- a/test/e2e/jest.config.js
+++ /dev/null
@@ -1,19 +0,0 @@
-'use strict';
-
-/** @type {import('@jest/types').Config.InitialOptions} */
-const config = {
-	testMatch: ['<rootDir>/__tests__/**/*.[jt]s?(x)'],
-	preset: 'ts-jest',
-	globals: { 'ts-jest': { tsconfig: '<rootDir>/../../tsconfig.test.json' } },
-	testPathIgnorePatterns: ['.*jest-runner-vscode.config.js'],
-	verbose: true,
-	modulePathIgnorePatterns: [
-		'<rootDir>/../../.vscode-test',
-		'<rootDir>/workspace/defaults/yarn-[^/]+/stylelint',
-		'<rootDir>/workspace/defaults/local-stylelint/node_modules',
-	],
-	setupFilesAfterEnv: ['<rootDir>/setup.ts'],
-	runner: 'vscode',
-};
-
-module.exports = config;
diff --git a/test/e2e/setup.ts b/test/e2e/setup.ts
deleted file mode 100644
index b24a3ef..0000000
--- a/test/e2e/setup.ts
+++ /dev/null
@@ -1,129 +0,0 @@
-import pWaitFor from 'p-wait-for';
-import path from 'path';
-
-import {
-	commands,
-	extensions,
-	window,
-	workspace,
-	TextEditor,
-	TextDocument,
-	Diagnostic,
-} from 'vscode';
-import type { ExtensionEvents, PublicApi } from '../../src/extension/index';
-import { getStylelintDiagnostics } from './utils';
-
-Object.defineProperty(global, 'workspaceDir', {
-	get() {
-		return path.join(__dirname, 'workspace');
-	},
-});
-
-const openEditors: TextEditor[] = [];
-
-Object.defineProperty(global, 'openDocument', {
-	get() {
-		return async (fsPath: string) => {
-			const document = await workspace.openTextDocument(fsPath);
-			const editor = await window.showTextDocument(document);
-
-			openEditors.push(editor);
-
-			return editor;
-		};
-	},
-});
-
-Object.defineProperty(global, 'waitForDiagnostics', {
-	get() {
-		return (
-			editorOrDocument: TextEditor | TextDocument,
-			timeout = 5000,
-			interval = 20,
-		): Promise<Diagnostic[]> => {
-			const uri =
-				(editorOrDocument as TextEditor)?.document?.uri ?? (editorOrDocument as TextDocument).uri;
-
-			return new Promise((resolve, reject) => {
-				// eslint-disable-next-line prefer-const
-				let timeoutRef: NodeJS.Timeout;
-
-				const intervalRef = setInterval(() => {
-					const diagnostics = getStylelintDiagnostics(uri);
-
-					if (diagnostics.length > 0) {
-						clearInterval(intervalRef);
-						clearTimeout(timeoutRef);
-						resolve(diagnostics);
-					}
-				}, interval);
-
-				timeoutRef = setTimeout(() => {
-					clearInterval(intervalRef);
-					clearTimeout(timeoutRef);
-					reject(new Error('Timed out waiting for Stylelint diagnostics'));
-				}, timeout);
-			});
-		};
-	},
-});
-
-Object.defineProperty(global, 'waitForApiEvent', {
-	get() {
-		return <T extends keyof ExtensionEvents>(
-			event: T,
-			shouldResolve?: (...params: Parameters<ExtensionEvents[T]>) => boolean,
-			timeout = 5000,
-		): Promise<Parameters<ExtensionEvents[T]>> => {
-			const api = extensions.getExtension('stylelint.vscode-stylelint')?.exports as PublicApi;
-
-			return new Promise((resolve, reject) => {
-				const timeoutRef = setTimeout(() => {
-					reject(new Error(`Timed out waiting for ${event} event`));
-				}, timeout);
-
-				const listener = (...params: unknown[]) => {
-					if (shouldResolve) {
-						if (shouldResolve(...(params as Parameters<ExtensionEvents[T]>))) {
-							clearTimeout(timeoutRef);
-							api.off(event, listener);
-							resolve(params as Parameters<ExtensionEvents[T]>);
-						}
-					} else {
-						clearTimeout(timeoutRef);
-						api.off(event, listener);
-						resolve(params as Parameters<ExtensionEvents[T]>);
-					}
-				};
-
-				api.on(event, listener);
-
-				setTimeout(() => {
-					api.off(event, listener);
-					reject(new Error(`Timed out waiting for ${event} event`));
-				}, timeout);
-			});
-		};
-	},
-});
-
-global.beforeAll(async () => {
-	const vscodeStylelint = extensions.getExtension('stylelint.vscode-stylelint');
-
-	if (!vscodeStylelint) {
-		throw new Error('Unable to find Stylelint extension');
-	}
-
-	await pWaitFor(() => vscodeStylelint.isActive, { timeout: 2000 });
-});
-
-global.afterEach(async () => {
-	for (const editor of openEditors) {
-		await window.showTextDocument(editor.document);
-		await commands.executeCommand('workbench.action.files.revert');
-	}
-
-	openEditors.length = 0;
-
-	await commands.executeCommand('workbench.action.closeAllEditors');
-});
diff --git a/test/e2e/utils.ts b/test/e2e/utils.ts
deleted file mode 100644
index f33bf15..0000000
--- a/test/e2e/utils.ts
+++ /dev/null
@@ -1,98 +0,0 @@
-import { languages, default as vscode } from 'vscode';
-import { URI } from 'vscode-uri';
-import type LSP from 'vscode-languageserver-protocol';
-
-/**
- * @param {tests.Range} range
- * @returns {tests.Range}
- */
-function normalizeRange(range: tests.Range): tests.Range {
-	/** @type {tests.Range} */
-	const obj: tests.Range = {
-		start: {
-			line: range.start.line,
-			character: range.start.character,
-		},
-	};
-
-	if (range.end !== undefined) {
-		obj.end = {
-			line: range.end.line,
-			character: range.end.character,
-		};
-	}
-
-	return obj;
-}
-
-/**
- * @param {vscode.DiagnosticRelatedInformation[]} relatedInformation
- * @returns {tests.DiagnosticRelatedInformation[]}
- */
-function normalizeRelatedInformation(
-	relatedInformation: vscode.DiagnosticRelatedInformation[],
-): tests.DiagnosticRelatedInformation[] {
-	return relatedInformation.map(({ location, message }) => ({
-		location: {
-			range: normalizeRange(location.range),
-			uri: URI.from(location.uri).toString(),
-		},
-		message,
-	}));
-}
-
-/**
- * @param {vscode.Diagnostic} message
- * @returns {tests.CodePart}
- */
-function normalizeCode(message: vscode.Diagnostic): tests.CodePart {
-	return !message.code || typeof message.code === 'string' || typeof message.code === 'number'
-		? { code: message.code }
-		: {
-				code: message.code.value,
-				codeDescription: { href: URI.from(message.code.target).toString() },
-			};
-}
-
-/**
- * Converts a VS Code diagnostic to a partial LSP diagnostic.
- */
-export function normalizeDiagnostic(message: vscode.Diagnostic): tests.Diagnostic {
-	const { code, codeDescription } = normalizeCode(message);
-
-	const diagnostic: tests.Diagnostic = {
-		message: message.message,
-		range: normalizeRange(message.range),
-		severity: (message.severity + 1) as LSP.DiagnosticSeverity,
-	};
-
-	if (code !== undefined) {
-		diagnostic.code = code;
-	}
-
-	if (codeDescription !== undefined) {
-		diagnostic.codeDescription = codeDescription;
-	}
-
-	if (message.relatedInformation !== undefined) {
-		diagnostic.relatedInformation = normalizeRelatedInformation(message.relatedInformation);
-	}
-
-	if (message.source !== undefined) {
-		diagnostic.source = message.source;
-	}
-
-	if (message.tags !== undefined) {
-		diagnostic.tags = message.tags;
-	}
-
-	return diagnostic;
-}
-
-/**
- * @param {vscode.Uri} uri
- * @returns {vscode.Diagnostic[]}
- */
-export function getStylelintDiagnostics(uri: vscode.Uri): vscode.Diagnostic[] {
-	return languages.getDiagnostics(uri).filter((d) => d.source === 'Stylelint');
-}
diff --git a/test/e2e/workspace/defaults/restart.css b/test/e2e/workspace/defaults/restart.css
new file mode 100644
index 0000000..077f6dd
--- /dev/null
+++ b/test/e2e/workspace/defaults/restart.css
@@ -0,0 +1,1 @@
+a {}
diff --git a/test/e2e/workspace/defaults/stylelint.config.js b/test/e2e/workspace/defaults/stylelint.config.js
index e2b946f..d389f27 100644
--- a/test/e2e/workspace/defaults/stylelint.config.js
+++ b/test/e2e/workspace/defaults/stylelint.config.js
@@ -16,6 +16,10 @@ const config = {
 				'plugin/foo-bar': true,
 			},
 		},
+		{
+			files: ['restart.css'],
+			rules: { 'block-no-empty': true },
+		},
 	],
 };
 
diff --git a/test/e2e/workspace/workspace.code-workspace b/test/e2e/workspace/workspace.code-workspace
index 2360ce5..bc4557e 100644
--- a/test/e2e/workspace/workspace.code-workspace
+++ b/test/e2e/workspace/workspace.code-workspace
@@ -10,4 +10,5 @@
 		{ "path": "stylelint-path" },
 		{ "path": "validate" },
 	],
+	"settings": {},
 }
diff --git a/test/types.d.ts b/test/types.d.ts
index f7aae9b..721ff70 100644
--- a/test/types.d.ts
+++ b/test/types.d.ts
@@ -14,11 +14,6 @@ import type { ConnectionManager as ConnectionManagerConstructor } from './integr
 import type { ExtensionEvents } from '../src/extension/index';
 
 declare global {
-	/**
-	 * The test workspace directory. Only available in end-to-end tests.
-	 */
-	const workspaceDir: string;
-
 	/**
 	 * Server mocks. Only available in unit tests.
 	 */
@@ -29,44 +24,6 @@ declare global {
 	 */
 	const ConnectionManager: typeof ConnectionManagerConstructor;
 
-	/**
-	 * Opens a file in the editor. Only available in end-to-end tests.
-	 * @param fsPath The file path.
-	 */
-	const openDocument: (fsPath: string) => Promise<vscode.TextEditor>;
-
-	/**
-	 * Waits until the extension emits any diagnostics for a document. Resolves
-	 * to the diagnostics. Rejects if the timeout is reached. Only available in
-	 * end-to-end tests.
-	 * @param editorOrDocument The editor or document for which to wait.
-	 * @param timeout The timeout in milliseconds. Defaults to 5000.
-	 * @param interval The interval between checks in milliseconds. Defaults to
-	 * 20.
-	 */
-	const waitForDiagnostics: (
-		editorOrDocument: vscode.TextEditor | vscode.TextDocument,
-		timeout?: number,
-		interval?: number,
-	) => Promise<vscode.Diagnostic[]>;
-
-	/**
-	 * Waits until the extension emits the specified event. If `shouldResolve`
-	 * is provided, the promise will only resolve if `shouldResolve` returns
-	 * true. Resolves with the event's parameters. Rejects if the timeout is
-	 * reached. Only available in end-to-end tests.
-	 * @param event The event name.
-	 * @param shouldResolve A function that returns true if the event should
-	 * resolve the promise. If not provided, the promise will resolve on any
-	 * emitted event regardless of its parameters.
-	 * @param timeout The timeout in milliseconds. Defaults to 5000.
-	 */
-	const waitForApiEvent: <T extends keyof ExtensionEvents>(
-		event: T,
-		shouldResolve?: (...params: Parameters<ExtensionEvents[T]>) => boolean,
-		timeout?: number,
-	) => Promise<Parameters<ExtensionEvents[T]>>;
-
 	/**
 	 * Types used in tests.
 	 */