This is an automated email from the ASF dual-hosted git repository.
bchapuis pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-baremaps.git
The following commit(s) were added to refs/heads/main by this push:
new b5f598c3 Add renderer and integration tests for the basemap (#659)
b5f598c3 is described below
commit b5f598c32386a7afdb16061021f3defebc0c7e52
Author: Leonard <[email protected]>
AuthorDate: Sun May 21 20:15:15 2023 +0200
Add renderer and integration tests for the basemap (#659)
---
baremaps-renderer/.gitignore | 2 +
baremaps-renderer/.prettierignore | 3 +
baremaps-renderer/.prettierrc.json | 4 +
baremaps-renderer/README.md | 78 +
baremaps-renderer/assets/report-template.html | 334 ++++
baremaps-renderer/declaration.d.ts | 13 +
baremaps-renderer/package-lock.json | 1722 ++++++++++++++++++++
baremaps-renderer/package.json | 34 +
baremaps-renderer/src/index.ts | 129 ++
baremaps-renderer/src/lib/browserPool.ts | 86 +
.../src/lib/maplibreBrowserHelpers.ts | 128 ++
baremaps-renderer/src/lib/reportGenerator.ts | 124 ++
baremaps-renderer/src/lib/runnableTask.ts | 24 +
baremaps-renderer/src/lib/test.ts | 178 ++
baremaps-renderer/src/lib/testManager.ts | 90 +
baremaps-renderer/src/lib/testsLogger.ts | 112 ++
baremaps-renderer/src/types/index.ts | 36 +
baremaps-renderer/tsconfig.json | 20 +
basemap/tests/.gitignore | 2 +
basemap/tests/integration/lausanne/metadata.json | 6 +
20 files changed, 3125 insertions(+)
diff --git a/baremaps-renderer/.gitignore b/baremaps-renderer/.gitignore
new file mode 100644
index 00000000..9e87c054
--- /dev/null
+++ b/baremaps-renderer/.gitignore
@@ -0,0 +1,2 @@
+node_modules
+bin
\ No newline at end of file
diff --git a/baremaps-renderer/.prettierignore
b/baremaps-renderer/.prettierignore
new file mode 100644
index 00000000..e5f8ad9a
--- /dev/null
+++ b/baremaps-renderer/.prettierignore
@@ -0,0 +1,3 @@
+node_modules
+package-lock.json
+bin
diff --git a/baremaps-renderer/.prettierrc.json
b/baremaps-renderer/.prettierrc.json
new file mode 100644
index 00000000..6e778b4f
--- /dev/null
+++ b/baremaps-renderer/.prettierrc.json
@@ -0,0 +1,4 @@
+{
+ "trailingComma": "all",
+ "singleQuote": true
+}
diff --git a/baremaps-renderer/README.md b/baremaps-renderer/README.md
new file mode 100644
index 00000000..55bc8a98
--- /dev/null
+++ b/baremaps-renderer/README.md
@@ -0,0 +1,78 @@
+<!--
+ Licensed under the Apache License, Version 2.0 (the "License"); you may not
use this file except
+ in compliance with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
distributed under the License
+ is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express
+ or implied. See the License for the specific language governing permissions
and limitations under
+ the License.
+ -->
+
+<h1>Baremaps Renderer</h1>
+
+- [Installation](#installation)
+- [Usage](#usage)
+ - [Running the Tests](#running-the-tests)
+- [Creating a New Test](#creating-a-new-test)
+
+## Installation
+
+First, install the required dependencies:
+
+```bash
+npm install
+```
+
+Then, build the project:
+
+```bash
+npm run build
+```
+
+You can then link the script to your path in order to use it as a command line
tool:
+
+```bash
+npm link
+```
+
+## Usage
+
+In order to get a list of the available commands, run:
+
+```bash
+baremaps-renderer --help
+```
+
+### Running the Tests
+
+Within a directory containing a `tests` folder, you can run the tests by
running:
+
+```bash
+baremaps-renderer run -s <styleUrl>
+```
+
+After the tests are run, a report is generated `tests/report.html`. You can
view the report by opening the file in your browser or by running:
+
+```bash
+baremaps-renderer report --open
+```
+
+## Creating a New Test
+
+Within a directory containing a `tests` folder, you can add a new test by
creating a folder in the `tests/integration` directory. Each test is a folder
containing a metadata file `metadata.json`. The metadata file is as follows:
+
+```json
+{
+ "width": 512,
+ "height": 512,
+ "center": [6.6323, 46.5197],
+ "zoom": 14
+}
+```
+
+- `width:` the width of the image in pixels
+- `height:` the height of the image in pixels
+- `center:` the center of the map longitude, latitude
+- `zoom:` the zoom level of the map
diff --git a/baremaps-renderer/assets/report-template.html
b/baremaps-renderer/assets/report-template.html
new file mode 100644
index 00000000..696931bb
--- /dev/null
+++ b/baremaps-renderer/assets/report-template.html
@@ -0,0 +1,334 @@
+<!--
+ Licensed under the Apache License, Version 2.0 (the "License"); you may not
use this file except
+ in compliance with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
distributed under the License
+ is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express
+ or implied. See the License for the specific language governing permissions
and limitations under
+ the License.
+ -->
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Baremaps Renderer</title>
+ <style>
+ :root {
+ --color-pass: rgb(40, 156, 40);
+ --color-pass-transparent: rgba(40, 156, 40, 0.05);
+ --color-error: rgb(255, 59, 59);
+ --color-error-transparent: rgba(255, 59, 59, 0.05);
+ }
+
+ body {
+ font-family: sans-serif;
+ padding: 4rem 8rem;
+ }
+
+ @media (max-width: 768px) {
+ body {
+ padding: 4rem 0.5rem;
+ }
+
+ .results {
+ gap: 0.5rem !important;
+ }
+ }
+
+ .title {
+ margin-bottom: 3rem;
+ }
+
+ h1 {
+ font-size: 3rem;
+ margin-top: 0;
+ margin-bottom: 1rem;
+ }
+
+ h2 {
+ font-size: 1.5rem;
+ margin-top: 0;
+ margin-bottom: 1rem;
+ text-transform: uppercase;
+ font-weight: 500;
+ }
+
+ h3 {
+ font-size: 2rem;
+ margin: 1rem 0;
+ }
+
+ h4 {
+ font-size: 1.25rem;
+ margin-bottom: 0;
+ }
+
+ h4.fail {
+ margin-top: 0;
+ text-transform: uppercase;
+ color: var(--color-error);
+ }
+
+ h4.pass {
+ margin-top: 0;
+ text-transform: uppercase;
+ color: var(--color-pass);
+ }
+
+ a {
+ position: relative;
+ text-decoration: underline;
+ color: inherit;
+ }
+
+ pre {
+ font-size: 1.25rem;
+ }
+
+ button {
+ border: none;
+ background-color: transparent;
+ cursor: pointer;
+ font-size: 1rem;
+ padding: 0.25rem 0.5rem;
+ border-radius: 0.25rem;
+ box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.075);
+ }
+
+ .sort {
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+ margin-bottom: 1rem;
+ }
+
+ .sort p {
+ margin-right: 0.5rem;
+ }
+
+ .sort button.active {
+ box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.1);
+ background-color: rgba(0, 0, 0, 0.075);
+ }
+
+ .results {
+ display: flex;
+ flex-direction: column;
+ gap: 2rem;
+ }
+
+ .summary {
+ border-left: 3px solid black;
+ padding: 1rem 2rem;
+ background-color: rgba(0, 0, 0, 0.05);
+ }
+
+ .summary h4 {
+ text-transform: uppercase;
+ margin-top: 1rem;
+ }
+
+ .p-pass {
+ color: var(--color-pass);
+ font-weight: bold;
+ }
+
+ .p-fail {
+ color: var(--color-error);
+ font-weight: bold;
+ }
+
+ .result {
+ padding: 1rem 2rem;
+ }
+
+ .result.fail {
+ border-left: 3px solid var(--color-error);
+ background-color: var(--color-error-transparent);
+ }
+
+ .result.pass {
+ border-left: 3px solid var(--color-pass);
+ background-color: var(--color-pass-transparent);
+ }
+
+ pre {
+ margin: 0;
+ }
+
+ .images {
+ display: grid;
+ grid-template-columns: 1fr 1fr 1fr;
+ gap: 1rem;
+ }
+
+ img {
+ width: 100%;
+ aspect-ratio: 1;
+ }
+ </style>
+ </head>
+ <body>
+ <div class="title">
+ <h1>Baremaps Renderer</h1>
+ <h2>Integration Testing Report</h2>
+ </div>
+ <div>
+ <!-- Sort by test status, test diff, test name -->
+ <div class="sort">
+ <p><strong>Sort by:</strong></p>
+ <button onClick="sortBy(this, 'status')">Test status</button>
+ <button onClick="sortBy(this, 'diff')">Test diff</button>
+ <button onClick="sortBy(this, 'name')">Test name</button>
+ </div>
+ </div>
+ <!-- Test results container (incl. the summary) -->
+ <div class="results" id="testResults"></div>
+ </body>
+ <script>
+ /** Template for the test summary */
+ const SUMMARY_TEMPLATE = `<div class="summary">
+<h4>Summary</h4>
+<p>Out of <strong>{{ TOT_TESTS }}</strong> tests:</p>
+<ul>
+ <li><span class="p-pass">{{ TOT_PASS_TESTS }} tests passed</span></li>
+ <li><span class="p-fail">{{ TOT_FAIL_TESTS }} tests failed</span></li>
+</ul>
+</div>`;
+
+ /**
+ * Helper function to get the test template
+ *
+ * @param {boolean} passed - Whether the test passed or not
+ * @param {number} index - The index of the test
+ * @returns {string} - The test template
+ */
+ const getTestTemplate = (passed, index) => {
+ const loading = index > 2 ? 'lazy' : 'eager';
+ return `<div class="result ${passed ? 'pass' : 'fail'}">
+ <h4 class="${passed ? 'pass' : 'fail'}">${passed ? 'PASSED' : 'FAILED'}</h4>
+ <h3>{{ TEST_NAME }}</h3>
+ <p>
+ {{ TEST_PATH }}
+ </p>
+ <pre>
+ <code>
+{{ METADATA }}</code>
+ </pre>
+ <div class="images">
+ <h4>Expected</h4>
+ <h4>Actual</h4>
+ <h4>Difference ({{ DIFF }})</h4>
+ <img src="{{ EXPECTED_IMG_PATH }}" loading="${loading}" />
+ <img src="{{ ACTUAL_IMG_PATH }}" loading="${loading}" />
+ <img src="{{ DIFF_IMG_PATH }}" loading="${loading}" />
+ </div>
+</div>`;
+ };
+
+ const testData = JSON.parse(`{{ TESTS_DATA }}`);
+
+ /**
+ * Updates the sort parameters and re-renders the test results
+ *
+ * @param {HTMLElement} el - The element that was clicked
+ * @param {string} key - The key to sort by
+ */
+ const sortBy = (el, key) => {
+ if (!sortedBy[key].active) {
+ el.classList.add('active');
+ el.innerText = `${el.innerText} ▲`;
+ sortedBy[key].asc = true;
+ sortedBy[key].active = true;
+ } else if (sortedBy[key].active && sortedBy[key].asc) {
+ el.classList.remove('asc');
+ el.innerText = `${el.innerText.replace(' ▲', '')} ▼`;
+ sortedBy[key].asc = false;
+ } else if (sortedBy[key].active && !sortedBy[key].asc) {
+ el.classList.remove('active');
+ el.innerText = el.innerText.replace(' ▼', '');
+ sortedBy[key].active = false;
+ }
+ render();
+ };
+
+ let sortedBy = {
+ status: {
+ asc: false,
+ active: false,
+ },
+ diff: {
+ asc: false,
+ active: false,
+ },
+ name: {
+ asc: false,
+ active: false,
+ },
+ };
+
+ /** Render the test results */
+ const render = () => {
+ const testResultsDiv = document.getElementById('testResults');
+ testResultsDiv.innerHTML = '';
+ const summary = document.createElement('div');
+ summary.innerHTML = SUMMARY_TEMPLATE.replace(
+ '{{ TOT_TESTS }}',
+ testData.length,
+ )
+ .replace(
+ '{{ TOT_PASS_TESTS }}',
+ testData.filter((test) => test.success).length,
+ )
+ .replace(
+ '{{ TOT_FAIL_TESTS }}',
+ testData.filter((test) => !test.success).length,
+ );
+ testResultsDiv.appendChild(summary);
+ const sortedTestData = [...testData];
+ // sort the test data
+ if (sortedBy.name.active) {
+ sortedBy.name.asc
+ ? sortedTestData.sort((a, b) => (a.name > b.name ? 1 : -1))
+ : sortedTestData.sort((a, b) => (a.name < b.name ? 1 : -1));
+ }
+ if (sortedBy.diff.active) {
+ sortedBy.diff.asc
+ ? sortedTestData.sort((a, b) => a.diff - b.diff)
+ : sortedTestData.sort((a, b) => b.diff - a.diff);
+ }
+ if (sortedBy.status.active) {
+ sortedBy.status.asc
+ ? sortedTestData.sort((a, b) =>
+ a.success === b.success ? 0 : a.success ? -1 : 1,
+ )
+ : sortedTestData.sort((a, b) =>
+ a.success === b.success ? 0 : a.success ? 1 : -1,
+ );
+ }
+
+ sortedTestData.forEach((test, index) => {
+ const testDiv = document.createElement('div');
+ testDiv.innerHTML = getTestTemplate(test.success, index);
+ testDiv.innerHTML = testDiv.innerHTML
+ .replace('{{ TEST_NAME }}', test.name)
+ .replace('{{ TEST_PATH }}', test.path)
+ .replace('{{ METADATA }}', JSON.stringify(test.metadata, null, 4))
+ .replace('{{ DIFF }}', test.diff)
+ .replace(
+ '{{ EXPECTED_IMG_PATH }}',
+ test.expectedImagePath,
+ )
+ .replace(
+ '{{ ACTUAL_IMG_PATH }}',
+ test.actualImagePath,
+ )
+ .replace('{{ DIFF_IMG_PATH }}', test.diffImagePath);
+ testResultsDiv.appendChild(testDiv);
+ });
+ };
+
+ render();
+ </script>
+</html>
diff --git a/baremaps-renderer/declaration.d.ts
b/baremaps-renderer/declaration.d.ts
new file mode 100644
index 00000000..42b0c2c3
--- /dev/null
+++ b/baremaps-renderer/declaration.d.ts
@@ -0,0 +1,13 @@
+/**
+ Licensed under the Apache License, Version 2.0 (the "License"); you may not
use this file except
+ in compliance with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
distributed under the License
+ is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express
+ or implied. See the License for the specific language governing permissions
and limitations under
+ the License.
+ **/
+
+declare module 'st';
diff --git a/baremaps-renderer/package-lock.json
b/baremaps-renderer/package-lock.json
new file mode 100644
index 00000000..94f405dc
--- /dev/null
+++ b/baremaps-renderer/package-lock.json
@@ -0,0 +1,1722 @@
+{
+ "name": "baremaps-renderer",
+ "version": "0.0.2",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "baremaps-renderer",
+ "version": "0.0.2",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "chalk": "^4",
+ "commander": "^10.0.1",
+ "http": "^0.0.1-security",
+ "maplibre-gl": "^2.4.0",
+ "pixelmatch": "^5.3.0",
+ "pngjs": "^7.0.0",
+ "puppeteer": "^20.2.0",
+ "st": "^3.0.0"
+ },
+ "bin": {
+ "baremaps-renderer": "bin/index.js"
+ },
+ "devDependencies": {
+ "@types/node": "^20.1.4",
+ "@types/pixelmatch": "^5.2.4",
+ "@types/pngjs": "^6.0.1",
+ "@types/serve-handler": "^6.1.1",
+ "copyfiles": "^2.4.1",
+ "prettier": "^2.8.8",
+ "typescript": "^5.0.4"
+ }
+ },
+ "node_modules/@babel/code-frame": {
+ "version": "7.21.4",
+ "resolved":
"https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.21.4.tgz",
+ "integrity":
"sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g==",
+ "dependencies": {
+ "@babel/highlight": "^7.18.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.19.1",
+ "resolved":
"https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz",
+ "integrity":
"sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/highlight": {
+ "version": "7.18.6",
+ "resolved":
"https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz",
+ "integrity":
"sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==",
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.18.6",
+ "chalk": "^2.0.0",
+ "js-tokens": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/highlight/node_modules/chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity":
"sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "dependencies": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/@mapbox/geojson-rewind": {
+ "version": "0.5.2",
+ "resolved":
"https://registry.npmjs.org/@mapbox/geojson-rewind/-/geojson-rewind-0.5.2.tgz",
+ "integrity":
"sha512-tJaT+RbYGJYStt7wI3cq4Nl4SXxG8W7JDG5DMJu97V25RnbNg3QtQtf+KD+VLjNpWKYsRvXDNmNrBgEETr1ifA==",
+ "dependencies": {
+ "get-stream": "^6.0.1",
+ "minimist": "^1.2.6"
+ },
+ "bin": {
+ "geojson-rewind": "geojson-rewind"
+ }
+ },
+ "node_modules/@mapbox/geojson-rewind/node_modules/get-stream": {
+ "version": "6.0.1",
+ "resolved":
"https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
+ "integrity":
"sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@mapbox/jsonlint-lines-primitives": {
+ "version": "2.0.2",
+ "resolved":
"https://registry.npmjs.org/@mapbox/jsonlint-lines-primitives/-/jsonlint-lines-primitives-2.0.2.tgz",
+ "integrity":
"sha512-rY0o9A5ECsTQRVhv7tL/OyDpGAoUB4tTvLiW1DSzQGq4bvTPhNw1VpSNjDJc5GFZ2XuyOtSWSVN05qOtcD71qQ==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/@mapbox/mapbox-gl-supported": {
+ "version": "2.0.1",
+ "resolved":
"https://registry.npmjs.org/@mapbox/mapbox-gl-supported/-/mapbox-gl-supported-2.0.1.tgz",
+ "integrity":
"sha512-HP6XvfNIzfoMVfyGjBckjiAOQK9WfX0ywdLubuPMPv+Vqf5fj0uCbgBQYpiqcWZT6cbyyRnTSXDheT1ugvF6UQ=="
+ },
+ "node_modules/@mapbox/point-geometry": {
+ "version": "0.1.0",
+ "resolved":
"https://registry.npmjs.org/@mapbox/point-geometry/-/point-geometry-0.1.0.tgz",
+ "integrity":
"sha512-6j56HdLTwWGO0fJPlrZtdU/B13q8Uwmo18Ck2GnGgN9PCFyKTZ3UbXeEdRFh18i9XQ92eH2VdtpJHpBD3aripQ=="
+ },
+ "node_modules/@mapbox/tiny-sdf": {
+ "version": "2.0.6",
+ "resolved":
"https://registry.npmjs.org/@mapbox/tiny-sdf/-/tiny-sdf-2.0.6.tgz",
+ "integrity":
"sha512-qMqa27TLw+ZQz5Jk+RcwZGH7BQf5G/TrutJhspsca/3SHwmgKQ1iq+d3Jxz5oysPVYTGP6aXxCo5Lk9Er6YBAA=="
+ },
+ "node_modules/@mapbox/unitbezier": {
+ "version": "0.0.1",
+ "resolved":
"https://registry.npmjs.org/@mapbox/unitbezier/-/unitbezier-0.0.1.tgz",
+ "integrity":
"sha512-nMkuDXFv60aBr9soUG5q+GvZYL+2KZHVvsqFCzqnkGEf46U2fvmytHaEVc1/YZbiLn8X+eR3QzX1+dwDO1lxlw=="
+ },
+ "node_modules/@mapbox/vector-tile": {
+ "version": "1.3.1",
+ "resolved":
"https://registry.npmjs.org/@mapbox/vector-tile/-/vector-tile-1.3.1.tgz",
+ "integrity":
"sha512-MCEddb8u44/xfQ3oD+Srl/tNcQoqTw3goGk2oLsrFxOTc3dUp+kAnby3PvAeeBYSMSjSPD1nd1AJA6W49WnoUw==",
+ "dependencies": {
+ "@mapbox/point-geometry": "~0.1.0"
+ }
+ },
+ "node_modules/@mapbox/whoots-js": {
+ "version": "3.1.0",
+ "resolved":
"https://registry.npmjs.org/@mapbox/whoots-js/-/whoots-js-3.1.0.tgz",
+ "integrity":
"sha512-Es6WcD0nO5l+2BOQS4uLfNPYQaNDfbot3X1XUoloz+x0mPDS3eeORZJl06HXjwBG1fOGwCRnzK88LMdxKRrd6Q==",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@puppeteer/browsers": {
+ "version": "1.2.0",
+ "resolved":
"https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.2.0.tgz",
+ "integrity":
"sha512-F2ygRTaNKq2HQQGsvypvy2S/Dg7aqqp2zxv4uolkxxTQvdbYfI0DcLPFNdqenaC+rZX5ldSPs/s39yAPpTVZ0A==",
+ "dependencies": {
+ "debug": "4.3.4",
+ "extract-zip": "2.0.1",
+ "http-proxy-agent": "5.0.0",
+ "https-proxy-agent": "5.0.1",
+ "progress": "2.0.3",
+ "proxy-from-env": "1.1.0",
+ "tar-fs": "2.1.1",
+ "unbzip2-stream": "1.4.3",
+ "yargs": "17.7.1"
+ },
+ "bin": {
+ "browsers": "lib/cjs/main-cli.js"
+ },
+ "engines": {
+ "node": ">=16.0.0"
+ },
+ "peerDependencies": {
+ "typescript": ">= 4.7.4"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@tootallnate/once": {
+ "version": "2.0.0",
+ "resolved":
"https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz",
+ "integrity":
"sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==",
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@types/geojson": {
+ "version": "7946.0.10",
+ "resolved":
"https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.10.tgz",
+ "integrity":
"sha512-Nmh0K3iWQJzniTuPRcJn5hxXkfB1T1pgB89SBig5PlJQU5yocazeu4jATJlaA0GYFKWMqDdvYemoSnF2pXgLVA=="
+ },
+ "node_modules/@types/mapbox__point-geometry": {
+ "version": "0.1.2",
+ "resolved":
"https://registry.npmjs.org/@types/mapbox__point-geometry/-/mapbox__point-geometry-0.1.2.tgz",
+ "integrity":
"sha512-D0lgCq+3VWV85ey1MZVkE8ZveyuvW5VAfuahVTQRpXFQTxw03SuIf1/K4UQ87MMIXVKzpFjXFiFMZzLj2kU+iA=="
+ },
+ "node_modules/@types/mapbox__vector-tile": {
+ "version": "1.3.0",
+ "resolved":
"https://registry.npmjs.org/@types/mapbox__vector-tile/-/mapbox__vector-tile-1.3.0.tgz",
+ "integrity":
"sha512-kDwVreQO5V4c8yAxzZVQLE5tyWF+IPToAanloQaSnwfXmIcJ7cyOrv8z4Ft4y7PsLYmhWXmON8MBV8RX0Rgr8g==",
+ "dependencies": {
+ "@types/geojson": "*",
+ "@types/mapbox__point-geometry": "*",
+ "@types/pbf": "*"
+ }
+ },
+ "node_modules/@types/node": {
+ "version": "20.1.4",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.1.4.tgz",
+ "integrity":
"sha512-At4pvmIOki8yuwLtd7BNHl3CiWNbtclUbNtScGx4OHfBd4/oWoJC8KRCIxXwkdndzhxOsPXihrsOoydxBjlE9Q==",
+ "devOptional": true
+ },
+ "node_modules/@types/pbf": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/@types/pbf/-/pbf-3.0.2.tgz",
+ "integrity":
"sha512-EDrLIPaPXOZqDjrkzxxbX7UlJSeQVgah3i0aA4pOSzmK9zq3BIh7/MZIQxED7slJByvKM4Gc6Hypyu2lJzh3SQ=="
+ },
+ "node_modules/@types/pixelmatch": {
+ "version": "5.2.4",
+ "resolved":
"https://registry.npmjs.org/@types/pixelmatch/-/pixelmatch-5.2.4.tgz",
+ "integrity":
"sha512-HDaSHIAv9kwpMN7zlmwfTv6gax0PiporJOipcrGsVNF3Ba+kryOZc0Pio5pn6NhisgWr7TaajlPEKTbTAypIBQ==",
+ "dev": true,
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/pngjs": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/@types/pngjs/-/pngjs-6.0.1.tgz",
+ "integrity":
"sha512-J39njbdW1U/6YyVXvC9+1iflZghP8jgRf2ndYghdJb5xL49LYDB+1EuAxfbuJ2IBbWIL3AjHPQhgaTxT3YaYeg==",
+ "dev": true,
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/serve-handler": {
+ "version": "6.1.1",
+ "resolved":
"https://registry.npmjs.org/@types/serve-handler/-/serve-handler-6.1.1.tgz",
+ "integrity":
"sha512-bIwSmD+OV8w0t2e7EWsuQYlGoS1o5aEdVktgkXaa43Zm0qVWi21xaSRb3DQA1UXD+DJ5bRq1Rgu14ZczB+CjIQ==",
+ "dev": true,
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/yauzl": {
+ "version": "2.10.0",
+ "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.0.tgz",
+ "integrity":
"sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==",
+ "optional": true,
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/agent-base": {
+ "version": "6.0.2",
+ "resolved":
"https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
+ "integrity":
"sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
+ "dependencies": {
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 6.0.0"
+ }
+ },
+ "node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved":
"https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity":
"sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "3.2.1",
+ "resolved":
"https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity":
"sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dependencies": {
+ "color-convert": "^1.9.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity":
"sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
+ },
+ "node_modules/async-cache": {
+ "version": "1.1.0",
+ "resolved":
"https://registry.npmjs.org/async-cache/-/async-cache-1.1.0.tgz",
+ "integrity":
"sha512-YDQc4vBn5NFhY6g6HhVshyi3Fy9+SQ5ePnE7JLDJn1DoL+i7ER+vMwtTNOYk9leZkYMnOwpBCWqyLDPw8Aig8g==",
+ "deprecated": "No longer maintained. Use
[lru-cache](http://npm.im/lru-cache) version 7.6 or higher, and provide an
asynchronous `fetchMethod` option.",
+ "dependencies": {
+ "lru-cache": "^4.0.0"
+ }
+ },
+ "node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved":
"https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity":
"sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true
+ },
+ "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==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ]
+ },
+ "node_modules/bl": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
+ "integrity":
"sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
+ "dependencies": {
+ "buffer": "^5.5.0",
+ "inherits": "^2.0.4",
+ "readable-stream": "^3.4.0"
+ }
+ },
+ "node_modules/brace-expansion": {
+ "version": "1.1.11",
+ "resolved":
"https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+ "integrity":
"sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "dev": true,
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/buffer": {
+ "version": "5.7.1",
+ "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
+ "integrity":
"sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "dependencies": {
+ "base64-js": "^1.3.1",
+ "ieee754": "^1.1.13"
+ }
+ },
+ "node_modules/buffer-crc32": {
+ "version": "0.2.13",
+ "resolved":
"https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
+ "integrity":
"sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==",
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/callsites": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+ "integrity":
"sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity":
"sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "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/chalk/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==",
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/chalk/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==",
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/chalk/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=="
+ },
+ "node_modules/chalk/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==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/chalk/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==",
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/chownr": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
+ "integrity":
"sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="
+ },
+ "node_modules/chromium-bidi": {
+ "version": "0.4.7",
+ "resolved":
"https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.7.tgz",
+ "integrity":
"sha512-6+mJuFXwTMU6I3vYLs6IL8A1DyQTPjCfIL971X0aMPVGRbGnNfl6i6Cl0NMbxi2bRYLGESt9T2ZIMRM5PAEcIQ==",
+ "dependencies": {
+ "mitt": "3.0.0"
+ },
+ "peerDependencies": {
+ "devtools-protocol": "*"
+ }
+ },
+ "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==",
+ "dependencies": {
+ "string-width": "^4.2.0",
+ "strip-ansi": "^6.0.1",
+ "wrap-ansi": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/color-convert": {
+ "version": "1.9.3",
+ "resolved":
"https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
+ "integrity":
"sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+ "dependencies": {
+ "color-name": "1.1.3"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.3",
+ "resolved":
"https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+ "integrity":
"sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="
+ },
+ "node_modules/commander": {
+ "version": "10.0.1",
+ "resolved":
"https://registry.npmjs.org/commander/-/commander-10.0.1.tgz",
+ "integrity":
"sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==",
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/concat-map": {
+ "version": "0.0.1",
+ "resolved":
"https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity":
"sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+ "dev": true
+ },
+ "node_modules/copyfiles": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/copyfiles/-/copyfiles-2.4.1.tgz",
+ "integrity":
"sha512-fereAvAvxDrQDOXybk3Qu3dPbOoKoysFMWtkY3mv5BsL8//OSZVL5DCLYqgRfY5cWirgRzlC+WSrxp6Bo3eNZg==",
+ "dev": true,
+ "dependencies": {
+ "glob": "^7.0.5",
+ "minimatch": "^3.0.3",
+ "mkdirp": "^1.0.4",
+ "noms": "0.0.0",
+ "through2": "^2.0.1",
+ "untildify": "^4.0.0",
+ "yargs": "^16.1.0"
+ },
+ "bin": {
+ "copyfiles": "copyfiles",
+ "copyup": "copyfiles"
+ }
+ },
+ "node_modules/copyfiles/node_modules/cliui": {
+ "version": "7.0.4",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
+ "integrity":
"sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==",
+ "dev": true,
+ "dependencies": {
+ "string-width": "^4.2.0",
+ "strip-ansi": "^6.0.0",
+ "wrap-ansi": "^7.0.0"
+ }
+ },
+ "node_modules/copyfiles/node_modules/yargs": {
+ "version": "16.2.0",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz",
+ "integrity":
"sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==",
+ "dev": true,
+ "dependencies": {
+ "cliui": "^7.0.2",
+ "escalade": "^3.1.1",
+ "get-caller-file": "^2.0.5",
+ "require-directory": "^2.1.1",
+ "string-width": "^4.2.0",
+ "y18n": "^5.0.5",
+ "yargs-parser": "^20.2.2"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/copyfiles/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/core-util-is": {
+ "version": "1.0.3",
+ "resolved":
"https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
+ "integrity":
"sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==",
+ "dev": true
+ },
+ "node_modules/cosmiconfig": {
+ "version": "8.1.3",
+ "resolved":
"https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.1.3.tgz",
+ "integrity":
"sha512-/UkO2JKI18b5jVMJUp0lvKFMpa/Gye+ZgZjKD+DGEN9y7NRcf/nK1A0sp67ONmKtnDCNMS44E6jrk0Yc3bDuUw==",
+ "dependencies": {
+ "import-fresh": "^3.2.1",
+ "js-yaml": "^4.1.0",
+ "parse-json": "^5.0.0",
+ "path-type": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/d-fischer"
+ }
+ },
+ "node_modules/cross-fetch": {
+ "version": "3.1.5",
+ "resolved":
"https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz",
+ "integrity":
"sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==",
+ "dependencies": {
+ "node-fetch": "2.6.7"
+ }
+ },
+ "node_modules/csscolorparser": {
+ "version": "1.0.3",
+ "resolved":
"https://registry.npmjs.org/csscolorparser/-/csscolorparser-1.0.3.tgz",
+ "integrity":
"sha512-umPSgYwZkdFoUrH5hIq5kf0wPSXiro51nPw0j2K/c83KflkPSTBGMz6NJvMB+07VlL0y7VPo6QJcDjcgKTTm3w=="
+ },
+ "node_modules/debug": {
+ "version": "4.3.4",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+ "integrity":
"sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+ "dependencies": {
+ "ms": "2.1.2"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/devtools-protocol": {
+ "version": "0.0.1120988",
+ "resolved":
"https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1120988.tgz",
+ "integrity":
"sha512-39fCpE3Z78IaIPChJsP6Lhmkbf4dWXOmzLk/KFTdRkNk/0JymRIfUynDVRndV9HoDz8PyalK1UH21ST/ivwW5Q=="
+ },
+ "node_modules/earcut": {
+ "version": "2.2.4",
+ "resolved": "https://registry.npmjs.org/earcut/-/earcut-2.2.4.tgz",
+ "integrity":
"sha512-/pjZsA1b4RPHbeWZQn66SWS8nZZWLQQ23oE3Eam7aroEFGEvwKAsJfZ9ytiEMycfzXWpca4FA9QIOehf7PocBQ=="
+ },
+ "node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved":
"https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity":
"sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
+ },
+ "node_modules/end-of-stream": {
+ "version": "1.4.4",
+ "resolved":
"https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
+ "integrity":
"sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==",
+ "dependencies": {
+ "once": "^1.4.0"
+ }
+ },
+ "node_modules/error-ex": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
+ "integrity":
"sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
+ "dependencies": {
+ "is-arrayish": "^0.2.1"
+ }
+ },
+ "node_modules/escalade": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
+ "integrity":
"sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "1.0.5",
+ "resolved":
"https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+ "integrity":
"sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
+ "node_modules/extract-zip": {
+ "version": "2.0.1",
+ "resolved":
"https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz",
+ "integrity":
"sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==",
+ "dependencies": {
+ "debug": "^4.1.1",
+ "get-stream": "^5.1.0",
+ "yauzl": "^2.10.0"
+ },
+ "bin": {
+ "extract-zip": "cli.js"
+ },
+ "engines": {
+ "node": ">= 10.17.0"
+ },
+ "optionalDependencies": {
+ "@types/yauzl": "^2.9.1"
+ }
+ },
+ "node_modules/fd": {
+ "version": "0.0.3",
+ "resolved": "https://registry.npmjs.org/fd/-/fd-0.0.3.tgz",
+ "integrity":
"sha512-iAHrIslQb3U68OcMSP0kkNWabp7sSN6d2TBSb2JO3gcLJVDd4owr/hKM4SFJovFOUeeXeItjYgouEDTMWiVAnA=="
+ },
+ "node_modules/fd-slicer": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz",
+ "integrity":
"sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==",
+ "dependencies": {
+ "pend": "~1.2.0"
+ }
+ },
+ "node_modules/fs-constants": {
+ "version": "1.0.0",
+ "resolved":
"https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
+ "integrity":
"sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="
+ },
+ "node_modules/fs.realpath": {
+ "version": "1.0.0",
+ "resolved":
"https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity":
"sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
+ "dev": true
+ },
+ "node_modules/geojson-vt": {
+ "version": "3.2.1",
+ "resolved":
"https://registry.npmjs.org/geojson-vt/-/geojson-vt-3.2.1.tgz",
+ "integrity":
"sha512-EvGQQi/zPrDA6zr6BnJD/YhwAkBP8nnJ9emh3EnHQKVMfg/MRVtPbMYdgVy/IaEmn4UfagD2a6fafPDL5hbtwg=="
+ },
+ "node_modules/get-caller-file": {
+ "version": "2.0.5",
+ "resolved":
"https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
+ "integrity":
"sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
+ "engines": {
+ "node": "6.* || 8.* || >= 10.*"
+ }
+ },
+ "node_modules/get-stream": {
+ "version": "5.2.0",
+ "resolved":
"https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz",
+ "integrity":
"sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==",
+ "dependencies": {
+ "pump": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/gl-matrix": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/gl-matrix/-/gl-matrix-3.4.3.tgz",
+ "integrity":
"sha512-wcCp8vu8FT22BnvKVPjXa/ICBWRq/zjFfdofZy1WSpQZpphblv12/bOQLBC1rMM7SGOFS9ltVmKOHil5+Ml7gA=="
+ },
+ "node_modules/glob": {
+ "version": "7.2.3",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
+ "integrity":
"sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
+ "dev": true,
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.1.1",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/global-prefix": {
+ "version": "3.0.0",
+ "resolved":
"https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz",
+ "integrity":
"sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==",
+ "dependencies": {
+ "ini": "^1.3.5",
+ "kind-of": "^6.0.2",
+ "which": "^1.3.1"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/graceful-fs": {
+ "version": "4.2.11",
+ "resolved":
"https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
+ "integrity":
"sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
+ "optional": true
+ },
+ "node_modules/has-flag": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+ "integrity":
"sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/http": {
+ "version": "0.0.1-security",
+ "resolved": "https://registry.npmjs.org/http/-/http-0.0.1-security.tgz",
+ "integrity":
"sha512-RnDvP10Ty9FxqOtPZuxtebw1j4L/WiqNMDtuc1YMH1XQm5TgDRaR1G9u8upL6KD1bXHSp9eSXo/ED+8Q7FAr+g=="
+ },
+ "node_modules/http-proxy-agent": {
+ "version": "5.0.0",
+ "resolved":
"https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz",
+ "integrity":
"sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==",
+ "dependencies": {
+ "@tootallnate/once": "2",
+ "agent-base": "6",
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/https-proxy-agent": {
+ "version": "5.0.1",
+ "resolved":
"https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
+ "integrity":
"sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
+ "dependencies": {
+ "agent-base": "6",
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/ieee754": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
+ "integrity":
"sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ]
+ },
+ "node_modules/import-fresh": {
+ "version": "3.3.0",
+ "resolved":
"https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
+ "integrity":
"sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
+ "dependencies": {
+ "parent-module": "^1.0.0",
+ "resolve-from": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/inflight": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+ "integrity":
"sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
+ "dev": true,
+ "dependencies": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity":
"sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
+ },
+ "node_modules/ini": {
+ "version": "1.3.8",
+ "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
+ "integrity":
"sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="
+ },
+ "node_modules/is-arrayish": {
+ "version": "0.2.1",
+ "resolved":
"https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
+ "integrity":
"sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg=="
+ },
+ "node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved":
"https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity":
"sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/isarray": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
+ "integrity":
"sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==",
+ "dev": true
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity":
"sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity":
"sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
+ },
+ "node_modules/js-yaml": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
+ "integrity":
"sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+ "dependencies": {
+ "argparse": "^2.0.1"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/json-parse-even-better-errors": {
+ "version": "2.3.1",
+ "resolved":
"https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
+ "integrity":
"sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w=="
+ },
+ "node_modules/kdbush": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/kdbush/-/kdbush-3.0.0.tgz",
+ "integrity":
"sha512-hRkd6/XW4HTsA9vjVpY9tuXJYLSlelnkTmVFu4M9/7MIYQtFcHpbugAU7UbOfjOiVSVYl2fqgBuJ32JUmRo5Ew=="
+ },
+ "node_modules/kind-of": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
+ "integrity":
"sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/lines-and-columns": {
+ "version": "1.2.4",
+ "resolved":
"https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
+ "integrity":
"sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="
+ },
+ "node_modules/lru-cache": {
+ "version": "4.1.5",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz",
+ "integrity":
"sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==",
+ "dependencies": {
+ "pseudomap": "^1.0.2",
+ "yallist": "^2.1.2"
+ }
+ },
+ "node_modules/maplibre-gl": {
+ "version": "2.4.0",
+ "resolved":
"https://registry.npmjs.org/maplibre-gl/-/maplibre-gl-2.4.0.tgz",
+ "integrity":
"sha512-csNFylzntPmHWidczfgCZpvbTSmhaWvLRj9e1ezUDBEPizGgshgm3ea1T5TCNEEBq0roauu7BPuRZjA3wO4KqA==",
+ "hasInstallScript": true,
+ "dependencies": {
+ "@mapbox/geojson-rewind": "^0.5.2",
+ "@mapbox/jsonlint-lines-primitives": "^2.0.2",
+ "@mapbox/mapbox-gl-supported": "^2.0.1",
+ "@mapbox/point-geometry": "^0.1.0",
+ "@mapbox/tiny-sdf": "^2.0.5",
+ "@mapbox/unitbezier": "^0.0.1",
+ "@mapbox/vector-tile": "^1.3.1",
+ "@mapbox/whoots-js": "^3.1.0",
+ "@types/geojson": "^7946.0.10",
+ "@types/mapbox__point-geometry": "^0.1.2",
+ "@types/mapbox__vector-tile": "^1.3.0",
+ "@types/pbf": "^3.0.2",
+ "csscolorparser": "~1.0.3",
+ "earcut": "^2.2.4",
+ "geojson-vt": "^3.2.1",
+ "gl-matrix": "^3.4.3",
+ "global-prefix": "^3.0.0",
+ "murmurhash-js": "^1.0.0",
+ "pbf": "^3.2.1",
+ "potpack": "^1.0.2",
+ "quickselect": "^2.0.0",
+ "supercluster": "^7.1.5",
+ "tinyqueue": "^2.0.3",
+ "vt-pbf": "^3.1.3"
+ }
+ },
+ "node_modules/mime": {
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz",
+ "integrity":
"sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==",
+ "bin": {
+ "mime": "cli.js"
+ },
+ "engines": {
+ "node": ">=4.0.0"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity":
"sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/minimist": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
+ "integrity":
"sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/mitt": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.0.tgz",
+ "integrity":
"sha512-7dX2/10ITVyqh4aOSVI9gdape+t9l2/8QxHrFmUXu4EEUpdlxl6RudZUPZoc+zuY2hk1j7XxVroIVIan/pD/SQ=="
+ },
+ "node_modules/mkdirp": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
+ "integrity":
"sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
+ "dev": true,
+ "bin": {
+ "mkdirp": "bin/cmd.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/mkdirp-classic": {
+ "version": "0.5.3",
+ "resolved":
"https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
+ "integrity":
"sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A=="
+ },
+ "node_modules/ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity":
"sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+ },
+ "node_modules/murmurhash-js": {
+ "version": "1.0.0",
+ "resolved":
"https://registry.npmjs.org/murmurhash-js/-/murmurhash-js-1.0.0.tgz",
+ "integrity":
"sha512-TvmkNhkv8yct0SVBSy+o8wYzXjE4Zz3PCesbfs8HiCXXdcTuocApFv11UWlNFWKYsP2okqrhb7JNlSm9InBhIw=="
+ },
+ "node_modules/negotiator": {
+ "version": "0.6.3",
+ "resolved":
"https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
+ "integrity":
"sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/node-fetch": {
+ "version": "2.6.7",
+ "resolved":
"https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
+ "integrity":
"sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==",
+ "dependencies": {
+ "whatwg-url": "^5.0.0"
+ },
+ "engines": {
+ "node": "4.x || >=6.0.0"
+ },
+ "peerDependencies": {
+ "encoding": "^0.1.0"
+ },
+ "peerDependenciesMeta": {
+ "encoding": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/noms": {
+ "version": "0.0.0",
+ "resolved": "https://registry.npmjs.org/noms/-/noms-0.0.0.tgz",
+ "integrity":
"sha512-lNDU9VJaOPxUmXcLb+HQFeUgQQPtMI24Gt6hgfuMHRJgMRHMF/qZ4HJD3GDru4sSw9IQl2jPjAYnQrdIeLbwow==",
+ "dev": true,
+ "dependencies": {
+ "inherits": "^2.0.1",
+ "readable-stream": "~1.0.31"
+ }
+ },
+ "node_modules/noms/node_modules/readable-stream": {
+ "version": "1.0.34",
+ "resolved":
"https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
+ "integrity":
"sha512-ok1qVCJuRkNmvebYikljxJA/UEsKwLl2nI1OmaqAu4/UE+h0wKCHok4XkL/gvi39OacXvw59RJUOFUkDib2rHg==",
+ "dev": true,
+ "dependencies": {
+ "core-util-is": "~1.0.0",
+ "inherits": "~2.0.1",
+ "isarray": "0.0.1",
+ "string_decoder": "~0.10.x"
+ }
+ },
+ "node_modules/noms/node_modules/string_decoder": {
+ "version": "0.10.31",
+ "resolved":
"https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
+ "integrity":
"sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==",
+ "dev": true
+ },
+ "node_modules/once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity":
"sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "node_modules/parent-module": {
+ "version": "1.0.1",
+ "resolved":
"https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
+ "integrity":
"sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+ "dependencies": {
+ "callsites": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/parse-json": {
+ "version": "5.2.0",
+ "resolved":
"https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
+ "integrity":
"sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
+ "dependencies": {
+ "@babel/code-frame": "^7.0.0",
+ "error-ex": "^1.3.1",
+ "json-parse-even-better-errors": "^2.3.0",
+ "lines-and-columns": "^1.1.6"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/path-is-absolute": {
+ "version": "1.0.1",
+ "resolved":
"https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity":
"sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/path-type": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
+ "integrity":
"sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/pbf": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/pbf/-/pbf-3.2.1.tgz",
+ "integrity":
"sha512-ClrV7pNOn7rtmoQVF4TS1vyU0WhYRnP92fzbfF75jAIwpnzdJXf8iTd4CMEqO4yUenH6NDqLiwjqlh6QgZzgLQ==",
+ "dependencies": {
+ "ieee754": "^1.1.12",
+ "resolve-protobuf-schema": "^2.1.0"
+ },
+ "bin": {
+ "pbf": "bin/pbf"
+ }
+ },
+ "node_modules/pend": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
+ "integrity":
"sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg=="
+ },
+ "node_modules/pixelmatch": {
+ "version": "5.3.0",
+ "resolved":
"https://registry.npmjs.org/pixelmatch/-/pixelmatch-5.3.0.tgz",
+ "integrity":
"sha512-o8mkY4E/+LNUf6LzX96ht6k6CEDi65k9G2rjMtBe9Oo+VPKSvl+0GKHuH/AlG+GA5LPG/i5hrekkxUc3s2HU+Q==",
+ "dependencies": {
+ "pngjs": "^6.0.0"
+ },
+ "bin": {
+ "pixelmatch": "bin/pixelmatch"
+ }
+ },
+ "node_modules/pixelmatch/node_modules/pngjs": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-6.0.0.tgz",
+ "integrity":
"sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg==",
+ "engines": {
+ "node": ">=12.13.0"
+ }
+ },
+ "node_modules/pngjs": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-7.0.0.tgz",
+ "integrity":
"sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==",
+ "engines": {
+ "node": ">=14.19.0"
+ }
+ },
+ "node_modules/potpack": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/potpack/-/potpack-1.0.2.tgz",
+ "integrity":
"sha512-choctRBIV9EMT9WGAZHn3V7t0Z2pMQyl0EZE6pFc/6ml3ssw7Dlf/oAOvFwjm1HVsqfQN8GfeFyJ+d8tRzqueQ=="
+ },
+ "node_modules/prettier": {
+ "version": "2.8.8",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz",
+ "integrity":
"sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==",
+ "dev": true,
+ "bin": {
+ "prettier": "bin-prettier.js"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ },
+ "funding": {
+ "url": "https://github.com/prettier/prettier?sponsor=1"
+ }
+ },
+ "node_modules/process-nextick-args": {
+ "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
+ },
+ "node_modules/progress": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
+ "integrity":
"sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/protocol-buffers-schema": {
+ "version": "3.6.0",
+ "resolved":
"https://registry.npmjs.org/protocol-buffers-schema/-/protocol-buffers-schema-3.6.0.tgz",
+ "integrity":
"sha512-TdDRD+/QNdrCGCE7v8340QyuXd4kIWIgapsE2+n/SaGiSSbomYl4TjHlvIoCWRpE7wFt02EpB35VVA2ImcBVqw=="
+ },
+ "node_modules/proxy-from-env": {
+ "version": "1.1.0",
+ "resolved":
"https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
+ "integrity":
"sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
+ },
+ "node_modules/pseudomap": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
+ "integrity":
"sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ=="
+ },
+ "node_modules/pump": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
+ "integrity":
"sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
+ "dependencies": {
+ "end-of-stream": "^1.1.0",
+ "once": "^1.3.1"
+ }
+ },
+ "node_modules/puppeteer": {
+ "version": "20.2.0",
+ "resolved":
"https://registry.npmjs.org/puppeteer/-/puppeteer-20.2.0.tgz",
+ "integrity":
"sha512-mGiCtWGq7S8MRUefZQV0AUiYGSPGmVlL/eYKPB4X9s9x0kuhzJmbSXSoWYFyo8MXaFmjUOqeMNP/3KeLZw207A==",
+ "hasInstallScript": true,
+ "dependencies": {
+ "@puppeteer/browsers": "1.2.0",
+ "cosmiconfig": "8.1.3",
+ "puppeteer-core": "20.2.0"
+ }
+ },
+ "node_modules/puppeteer-core": {
+ "version": "20.2.0",
+ "resolved":
"https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.2.0.tgz",
+ "integrity":
"sha512-QKLu4ELW/609vlLqp/NvQA564LzvLaF6x1ZH+4BhPhxPgreoY8WR/AYOzXS12hgtPPjNCqPKiRLxmyXGnlIK8A==",
+ "dependencies": {
+ "@puppeteer/browsers": "1.2.0",
+ "chromium-bidi": "0.4.7",
+ "cross-fetch": "3.1.5",
+ "debug": "4.3.4",
+ "devtools-protocol": "0.0.1120988",
+ "ws": "8.13.0"
+ },
+ "engines": {
+ "node": ">=16.0.0"
+ },
+ "peerDependencies": {
+ "typescript": ">= 4.7.4"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/quickselect": {
+ "version": "2.0.0",
+ "resolved":
"https://registry.npmjs.org/quickselect/-/quickselect-2.0.0.tgz",
+ "integrity":
"sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw=="
+ },
+ "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==",
+ "dependencies": {
+ "inherits": "^2.0.3",
+ "string_decoder": "^1.1.1",
+ "util-deprecate": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/require-directory": {
+ "version": "2.1.1",
+ "resolved":
"https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
+ "integrity":
"sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/resolve-from": {
+ "version": "4.0.0",
+ "resolved":
"https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+ "integrity":
"sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/resolve-protobuf-schema": {
+ "version": "2.1.0",
+ "resolved":
"https://registry.npmjs.org/resolve-protobuf-schema/-/resolve-protobuf-schema-2.1.0.tgz",
+ "integrity":
"sha512-kI5ffTiZWmJaS/huM8wZfEMer1eRd7oJQhDuxeCLe3t7N7mX3z94CN0xPxBQxFYQTSNz9T0i+v6inKqSdK8xrQ==",
+ "dependencies": {
+ "protocol-buffers-schema": "^3.3.1"
+ }
+ },
+ "node_modules/safe-buffer": {
+ "version": "5.2.1",
+ "resolved":
"https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity":
"sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ]
+ },
+ "node_modules/st": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/st/-/st-3.0.0.tgz",
+ "integrity":
"sha512-UEUi8P8Y5GOewlJbE5vrhsaQRwmbNVMUr6PLxRZHH4Cwz8CkHhnBqlqGtE3egXQd+ceUwNxdOVjsC/IsgN2Pww==",
+ "dependencies": {
+ "async-cache": "^1.1.0",
+ "bl": "^5.0.0",
+ "fd": "~0.0.3",
+ "mime": "^2.5.2",
+ "negotiator": "~0.6.2"
+ },
+ "bin": {
+ "st": "bin/server.js"
+ },
+ "optionalDependencies": {
+ "graceful-fs": "^4.2.3"
+ }
+ },
+ "node_modules/st/node_modules/bl": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/bl/-/bl-5.1.0.tgz",
+ "integrity":
"sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==",
+ "dependencies": {
+ "buffer": "^6.0.3",
+ "inherits": "^2.0.4",
+ "readable-stream": "^3.4.0"
+ }
+ },
+ "node_modules/st/node_modules/buffer": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
+ "integrity":
"sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "dependencies": {
+ "base64-js": "^1.3.1",
+ "ieee754": "^1.2.1"
+ }
+ },
+ "node_modules/string_decoder": {
+ "version": "1.3.0",
+ "resolved":
"https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
+ "integrity":
"sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
+ "dependencies": {
+ "safe-buffer": "~5.2.0"
+ }
+ },
+ "node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved":
"https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity":
"sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved":
"https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity":
"sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/supercluster": {
+ "version": "7.1.5",
+ "resolved":
"https://registry.npmjs.org/supercluster/-/supercluster-7.1.5.tgz",
+ "integrity":
"sha512-EulshI3pGUM66o6ZdH3ReiFcvHpM3vAigyK+vcxdjpJyEbIIrtbmBdY23mGgnI24uXiGFvrGq9Gkum/8U7vJWg==",
+ "dependencies": {
+ "kdbush": "^3.0.0"
+ }
+ },
+ "node_modules/supports-color": {
+ "version": "5.5.0",
+ "resolved":
"https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity":
"sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dependencies": {
+ "has-flag": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/tar-fs": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz",
+ "integrity":
"sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==",
+ "dependencies": {
+ "chownr": "^1.1.1",
+ "mkdirp-classic": "^0.5.2",
+ "pump": "^3.0.0",
+ "tar-stream": "^2.1.4"
+ }
+ },
+ "node_modules/tar-stream": {
+ "version": "2.2.0",
+ "resolved":
"https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
+ "integrity":
"sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
+ "dependencies": {
+ "bl": "^4.0.3",
+ "end-of-stream": "^1.4.1",
+ "fs-constants": "^1.0.0",
+ "inherits": "^2.0.3",
+ "readable-stream": "^3.1.1"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/through": {
+ "version": "2.3.8",
+ "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
+ "integrity":
"sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg=="
+ },
+ "node_modules/through2": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz",
+ "integrity":
"sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==",
+ "dev": true,
+ "dependencies": {
+ "readable-stream": "~2.3.6",
+ "xtend": "~4.0.1"
+ }
+ },
+ "node_modules/through2/node_modules/isarray": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+ "integrity":
"sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
+ "dev": true
+ },
+ "node_modules/through2/node_modules/readable-stream": {
+ "version": "2.3.8",
+ "resolved":
"https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
+ "integrity":
"sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
+ "dev": true,
+ "dependencies": {
+ "core-util-is": "~1.0.0",
+ "inherits": "~2.0.3",
+ "isarray": "~1.0.0",
+ "process-nextick-args": "~2.0.0",
+ "safe-buffer": "~5.1.1",
+ "string_decoder": "~1.1.1",
+ "util-deprecate": "~1.0.1"
+ }
+ },
+ "node_modules/through2/node_modules/safe-buffer": {
+ "version": "5.1.2",
+ "resolved":
"https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "integrity":
"sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+ "dev": true
+ },
+ "node_modules/through2/node_modules/string_decoder": {
+ "version": "1.1.1",
+ "resolved":
"https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+ "integrity":
"sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+ "dev": true,
+ "dependencies": {
+ "safe-buffer": "~5.1.0"
+ }
+ },
+ "node_modules/tinyqueue": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/tinyqueue/-/tinyqueue-2.0.3.tgz",
+ "integrity":
"sha512-ppJZNDuKGgxzkHihX8v9v9G5f+18gzaTfrukGrq6ueg0lmH4nqVnA2IPG0AEH3jKEk2GRJCUhDoqpoiw3PHLBA=="
+ },
+ "node_modules/tr46": {
+ "version": "0.0.3",
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
+ "integrity":
"sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
+ },
+ "node_modules/typescript": {
+ "version": "5.0.4",
+ "resolved":
"https://registry.npmjs.org/typescript/-/typescript-5.0.4.tgz",
+ "integrity":
"sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==",
+ "devOptional": true,
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=12.20"
+ }
+ },
+ "node_modules/unbzip2-stream": {
+ "version": "1.4.3",
+ "resolved":
"https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz",
+ "integrity":
"sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==",
+ "dependencies": {
+ "buffer": "^5.2.1",
+ "through": "^2.3.8"
+ }
+ },
+ "node_modules/untildify": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz",
+ "integrity":
"sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/util-deprecate": {
+ "version": "1.0.2",
+ "resolved":
"https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+ "integrity":
"sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
+ },
+ "node_modules/vt-pbf": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/vt-pbf/-/vt-pbf-3.1.3.tgz",
+ "integrity":
"sha512-2LzDFzt0mZKZ9IpVF2r69G9bXaP2Q2sArJCmcCgvfTdCCZzSyz4aCLoQyUilu37Ll56tCblIZrXFIjNUpGIlmA==",
+ "dependencies": {
+ "@mapbox/point-geometry": "0.1.0",
+ "@mapbox/vector-tile": "^1.3.1",
+ "pbf": "^3.2.1"
+ }
+ },
+ "node_modules/webidl-conversions": {
+ "version": "3.0.1",
+ "resolved":
"https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
+ "integrity":
"sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
+ },
+ "node_modules/whatwg-url": {
+ "version": "5.0.0",
+ "resolved":
"https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
+ "integrity":
"sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
+ "dependencies": {
+ "tr46": "~0.0.3",
+ "webidl-conversions": "^3.0.0"
+ }
+ },
+ "node_modules/which": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
+ "integrity":
"sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "which": "bin/which"
+ }
+ },
+ "node_modules/wrap-ansi": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+ "integrity":
"sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/wrap-ansi/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==",
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/wrap-ansi/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==",
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/wrap-ansi/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=="
+ },
+ "node_modules/wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity":
"sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
+ },
+ "node_modules/ws": {
+ "version": "8.13.0",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz",
+ "integrity":
"sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==",
+ "engines": {
+ "node": ">=10.0.0"
+ },
+ "peerDependencies": {
+ "bufferutil": "^4.0.1",
+ "utf-8-validate": ">=5.0.2"
+ },
+ "peerDependenciesMeta": {
+ "bufferutil": {
+ "optional": true
+ },
+ "utf-8-validate": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/xtend": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
+ "integrity":
"sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.4"
+ }
+ },
+ "node_modules/y18n": {
+ "version": "5.0.8",
+ "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
+ "integrity":
"sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/yallist": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",
+ "integrity":
"sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A=="
+ },
+ "node_modules/yargs": {
+ "version": "17.7.1",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz",
+ "integrity":
"sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==",
+ "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/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==",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/yauzl": {
+ "version": "2.10.0",
+ "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz",
+ "integrity":
"sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==",
+ "dependencies": {
+ "buffer-crc32": "~0.2.3",
+ "fd-slicer": "~1.1.0"
+ }
+ }
+ }
+}
diff --git a/baremaps-renderer/package.json b/baremaps-renderer/package.json
new file mode 100644
index 00000000..49844dbe
--- /dev/null
+++ b/baremaps-renderer/package.json
@@ -0,0 +1,34 @@
+{
+ "name": "baremaps-renderer",
+ "version": "0.0.2",
+ "description": "Integration tests for pixel to pixel matching",
+ "bin": {
+ "baremaps-renderer": "./bin/index.js"
+ },
+ "scripts": {
+ "format": "prettier --write .",
+ "build": "tsc && copyfiles ./assets/**/* ./bin && npm run chmod",
+ "chmod": "chmod u+x ./bin/index.js"
+ },
+ "author": "Leonard Cseres <[email protected]>",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "chalk": "^4",
+ "commander": "^10.0.1",
+ "http": "^0.0.1-security",
+ "maplibre-gl": "^2.4.0",
+ "pixelmatch": "^5.3.0",
+ "pngjs": "^7.0.0",
+ "puppeteer": "^20.2.0",
+ "st": "^3.0.0"
+ },
+ "devDependencies": {
+ "@types/node": "^20.1.4",
+ "@types/pixelmatch": "^5.2.4",
+ "@types/pngjs": "^6.0.1",
+ "@types/serve-handler": "^6.1.1",
+ "copyfiles": "^2.4.1",
+ "prettier": "^2.8.8",
+ "typescript": "^5.0.4"
+ }
+}
diff --git a/baremaps-renderer/src/index.ts b/baremaps-renderer/src/index.ts
new file mode 100644
index 00000000..59f1c43c
--- /dev/null
+++ b/baremaps-renderer/src/index.ts
@@ -0,0 +1,129 @@
+#!/usr/bin/env node
+
+/**
+ Licensed under the Apache License, Version 2.0 (the "License"); you may not
use this file except
+ in compliance with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
distributed under the License
+ is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express
+ or implied. See the License for the specific language governing permissions
and limitations under
+ the License.
+ **/
+
+import commander from 'commander';
+import http from 'http';
+import st from 'st';
+import { exec } from 'child_process';
+import { BrowserPool } from './lib/browserPool';
+import { TestsLogger } from './lib/testsLogger';
+import { MaplibreBrowserHelpers } from './lib/maplibreBrowserHelpers';
+import { TestManager } from './lib/testManager';
+import { ReportGenerator } from './lib/reportGenerator';
+
+/** Subcommand for running the tests */
+const run = async (options: any) => {
+ // Parse options
+ const instances = parseInt(options.instances);
+ const threshold = parseFloat(options.threshold);
+
+ // Discover tests
+ const testLogger = new TestsLogger();
+ const testManager = new TestManager(
+ options.path,
+ 'integration',
+ options.style,
+ options.refStyle,
+ testLogger,
+ threshold,
+ );
+
+ // Setup browser pool
+ const maplibreHelpers = new MaplibreBrowserHelpers(options.debug);
+ const browserPool = new BrowserPool(
+ instances,
+ true,
+ maplibreHelpers.initPage,
+ );
+ await browserPool.init();
+
+ // Queue tests
+ for (const test of testManager.getTests()) {
+ browserPool.queueTask(test);
+ }
+
+ // Run tests
+ const testResults: boolean[] = await browserPool.run();
+ await browserPool.close();
+
+ // Generate summary and report
+ testLogger.logSummary();
+ new ReportGenerator(options.path, testManager.getTests()).generate();
+
+ if (testResults.some((res) => !res)) {
+ process.exit(1);
+ }
+};
+
+/** Subcommand for viewing the report */
+const viewReport = async (options: any) => {
+ const server = http.createServer(
+ st({ path: options.path, index: ReportGenerator.reportFilename }),
+ );
+
+ const port = 3000;
+ const url = `http://localhost:${port}`;
+ server.listen(port, () => {
+ console.log(`INFO: Running at ${url}`);
+ });
+ // open the browser
+ if (options.open) {
+ if (process.platform === 'linux') {
+ exec(`xdg-open ${url}`);
+ } else if (process.platform === 'darwin') {
+ exec(`open ${url}`);
+ } else if (process.platform === 'win32') {
+ exec(`start ${url}`);
+ }
+ }
+};
+
+/** Main entrypoint */
+(async () => {
+ const program = new commander.Command();
+
+ program.version('0.0.2').description('Baremaps renderer utility tool');
+
+ program
+ .command('run')
+ .description('run integration tests defined in the tests folder')
+ .requiredOption('-s, --style <style>', 'style url to use')
+ .option(
+ '-r, --refStyle <refStyle>',
+ 'reference style url to use',
+ 'https://demo.baremaps.com/style.json',
+ )
+ .option(
+ '-t, --threshold <threshold>',
+ 'threshold to use for comparing images',
+ '0.1',
+ )
+ .option('-p, --path <testsPath>', 'tests folder path to use', 'tests')
+ .option('-d, --debug', 'debug output', false)
+ .option(
+ '-i, --instances <instances>',
+ 'number of concurrent browsers to use',
+ '2',
+ )
+ .action(run);
+
+ program
+ .command('report')
+ .description('view the report of the latest test run')
+ .option('-o, --open', 'open the report in the browser', false)
+ .option('-p, --path <testsPath>', 'tests folder path to use', 'tests')
+ .action(viewReport);
+
+ program.parse(process.argv);
+})();
diff --git a/baremaps-renderer/src/lib/browserPool.ts
b/baremaps-renderer/src/lib/browserPool.ts
new file mode 100644
index 00000000..9f094bb3
--- /dev/null
+++ b/baremaps-renderer/src/lib/browserPool.ts
@@ -0,0 +1,86 @@
+/**
+ Licensed under the Apache License, Version 2.0 (the "License"); you may not
use this file except
+ in compliance with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
distributed under the License
+ is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express
+ or implied. See the License for the specific language governing permissions
and limitations under
+ the License.
+ **/
+
+import puppeteer, { Page, Browser } from 'puppeteer';
+import { RunnableTask } from './runnableTask';
+
+/** Pool of browsers that can be used to run tasks in parallel */
+export class BrowserPool {
+ private instances: number;
+ private headless: boolean;
+ private initPage: (page: Page) => Promise<any>;
+ private pool: Browser[];
+ private queue: RunnableTask[] = [];
+
+ /**
+ * Create a new BrowserPool
+ *
+ * @param instances number of browsers to create
+ * @param headless whether to run the browsers in headless mode
+ * @param initPage function to run on each page before running a task
+ * @returns a new BrowserPool
+ */
+ constructor(
+ instances: number,
+ headless: boolean = true,
+ initPage?: (page: Page) => Promise<any>,
+ ) {
+ this.instances = instances;
+ this.headless = headless;
+ this.pool = [];
+ this.initPage = initPage || (async (page: Page) => {});
+ }
+
+ /** Initialize the browser pool */
+ public async init() {
+ for (let i = 0; i < this.instances; i++) {
+ const browser = await puppeteer.launch({
+ headless: this.headless ? 'new' : false,
+ args: ['--enable-webgl', '--no-sandbox', '--disable-web-security'],
+ });
+ const [page] = await browser.pages();
+ await this.initPage(page);
+ this.pool.push(browser);
+ }
+ }
+
+ /**
+ * Queue a task to be run
+ *
+ * @param task task to run
+ */
+ public async queueTask(task: RunnableTask) {
+ this.queue.push(task);
+ }
+
+ /** Run all queued tasks in parallel */
+ public async run() {
+ const results = [];
+ // run the queue in parallel with only one browser per task at max
+ while (this.queue.length) {
+ const res = await Promise.all(
+ this.queue.splice(0, this.instances).map(async (task, index) => {
+ const browser = this.pool[index];
+ const [page] = await browser.pages();
+ return await task.run(page);
+ }),
+ );
+ results.push(...res);
+ }
+ return results;
+ }
+
+ /** Close all browsers in the pool */
+ public async close() {
+ await Promise.all(this.pool.map((browser) => browser.close()));
+ }
+}
diff --git a/baremaps-renderer/src/lib/maplibreBrowserHelpers.ts
b/baremaps-renderer/src/lib/maplibreBrowserHelpers.ts
new file mode 100644
index 00000000..8407cc66
--- /dev/null
+++ b/baremaps-renderer/src/lib/maplibreBrowserHelpers.ts
@@ -0,0 +1,128 @@
+/**
+ Licensed under the Apache License, Version 2.0 (the "License"); you may not
use this file except
+ in compliance with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
distributed under the License
+ is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express
+ or implied. See the License for the specific language governing permissions
and limitations under
+ the License.
+ **/
+
+import { Page } from 'puppeteer';
+import { Metadata } from '../types';
+
+/** Helper class for running maplibre-gl in a browser */
+export class MaplibreBrowserHelpers {
+ private debug: boolean;
+
+ /**
+ * Create a new MaplibreBrowserHelpers
+ *
+ * @param debug whether to enable debug logging
+ */
+ constructor(debug: boolean = false) {
+ this.debug = debug;
+ }
+
+ /**
+ * Initialize a page for running maplibre-gl
+ *
+ * @param page puppeteer page
+ */
+ public async initPage(page: Page) {
+ if (this.debug) {
+ page
+ .on('console', (message: any) =>
+ console.log(
+ `${message.type().slice(0, 3).toUpperCase()} ${message.text()}`,
+ ),
+ )
+ .on('pageerror', ({ message }: any) => console.log(message))
+ .on('response', (response: any) =>
+ console.log(`${response.status()} ${response.url()}`),
+ )
+ .on('requestfailed', (request: any) =>
+ console.log(`${request.failure().errorText} ${request.url()}`),
+ );
+ }
+
+ await page.addScriptTag({
+ url: 'https://unpkg.com/[email protected]/dist/maplibre-gl.js',
+ });
+ await page.addStyleTag({
+ url: 'https://unpkg.com/[email protected]/dist/maplibre-gl.css',
+ });
+ }
+
+ /**
+ * Get an image from a maplibre-gl style
+ *
+ * @param page puppeteer page
+ * @param metadata metadata for the map
+ * @param styleUrl url to the maplibre-gl style
+ * @returns image as a base64 encoded string
+ */
+ public static async getImageFromMetadata(
+ page: Page,
+ metadata: Metadata,
+ styleUrl: string,
+ ) {
+ const width = metadata.width;
+ const height = metadata.height;
+
+ await page.setViewport({ width, height, deviceScaleFactor: 1 });
+ await page.setContent(`
+ <!DOCTYPE html>
+ <html lang="en">
+ <head>
+ <title>Query Test Page</title>
+ <meta charset='utf-8'>
+ <link rel="icon" href="about:blank">
+ <style>#map {
+ box-sizing:content-box;
+ width:${width}px;
+ height:${height}px;
+ }</style>
+ </head>
+ <body>
+ <div id='map'></div>
+ </body>
+ </html>`);
+
+ // create map
+ await page.evaluate(
+ (metadata: Metadata, styleUrl: string) => {
+ (window as any).map = new (window as any).maplibregl.Map({
+ container: 'map',
+ style: styleUrl,
+ center: metadata.center,
+ zoom: metadata.zoom,
+ interactive: false,
+ attributionControl: false,
+ // If true, the map's canvas can be exported to a PNG using
map.getCanvas().toDataURL(). This is false by default as a performance
optimization.
+ preserveDrawingBuffer: true,
+ // Prevents the fading-in of layers after the style has loaded.
+ fadeDuration: 0,
+ });
+ },
+ metadata,
+ styleUrl,
+ );
+
+ // wait for map to load
+ await page.waitForFunction(() => {
+ return (
+ (window as any).map?.loaded() && (window as any).map?.isStyleLoaded()
+ );
+ });
+
+ // export image from map
+ const image = await page.evaluate(() => {
+ return (window as any).map.getCanvas().toDataURL();
+ });
+
+ return Buffer.from(image.split(',')[1], 'base64');
+ }
+}
diff --git a/baremaps-renderer/src/lib/reportGenerator.ts
b/baremaps-renderer/src/lib/reportGenerator.ts
new file mode 100644
index 00000000..a30aa873
--- /dev/null
+++ b/baremaps-renderer/src/lib/reportGenerator.ts
@@ -0,0 +1,124 @@
+/**
+ Licensed under the Apache License, Version 2.0 (the "License"); you may not
use this file except
+ in compliance with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
distributed under the License
+ is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express
+ or implied. See the License for the specific language governing permissions
and limitations under
+ the License.
+ **/
+
+import fs from 'fs';
+import path from 'path';
+import { Test } from './test';
+import { TestData } from '../types';
+
+/**
+ * ReportGenerator class
+ *
+ * Generates an HTML report from generated images during tests
+ */
+export class ReportGenerator {
+ public static readonly reportFilename = 'report.html';
+ private static readonly assetsPath = 'assets';
+ private static readonly reportTemplateFilename = 'report-template.html';
+
+ private testFolder: string;
+ private tests: Test[];
+ private htmlTemplate: string;
+
+ /**
+ * Creates a new ReportGenerator
+ *
+ * @param testFolder Path to the test folder
+ * @param tests Array of tests
+ * @returns A new ReportGenerator
+ */
+ constructor(testFolder: string, tests: Test[]) {
+ this.testFolder = testFolder;
+ if (!path.isAbsolute(this.testFolder)) {
+ this.testFolder = path.join(process.cwd(), this.testFolder);
+ }
+ this.tests = tests;
+ // root dir of the package
+ const rootDirname = path.dirname(__dirname);
+ this.htmlTemplate = fs.readFileSync(
+ path.join(
+ rootDirname,
+ ReportGenerator.assetsPath,
+ ReportGenerator.reportTemplateFilename,
+ ),
+ 'utf8',
+ );
+ }
+
+ /** Generates and saves the HTML report */
+ public generate() {
+ const testsData = this.tests
+ .map((test) => this.getTestData(test))
+ .filter((test) => test !== null);
+
+ const html = this.htmlTemplate.replace(
+ '{{ TESTS_DATA }}',
+ JSON.stringify(testsData, null, 2),
+ );
+ this.writeHtml(html);
+
+ console.log(
+ "INFO: Report generated at '" +
+ path.join(this.testFolder, ReportGenerator.reportFilename) +
+ "'",
+ );
+ }
+
+ /**
+ * Returns an object with the test images paths and metadata for a given test
+ *
+ * @param test The test to get the data from
+ * @returns An object with the test images paths and metadata
+ */
+ private getTestData(test: Test): TestData | null {
+ // return if the test is not completed
+ if (test.success === undefined) {
+ return null;
+ }
+ // check if the images exist
+ if (
+ !fs.existsSync(path.join(test.testPath, Test.expectedFilename)) ||
+ !fs.existsSync(path.join(test.testPath, Test.actualFilename)) ||
+ !fs.existsSync(path.join(test.testPath, Test.diffFilename))
+ ) {
+ console.log(
+ 'WARN: Missing images for test ' + test.testPath + ', skipping...',
+ );
+ return null;
+ }
+ const testName = path.basename(test.testPath);
+ const testsRelativePath = path.relative(this.testFolder, test.testPath);
+ return {
+ path: test.testPath,
+ name: testName,
+ metadata: test.metadata,
+ success: test.success,
+ expectedImagePath: path.join(testsRelativePath, Test.expectedFilename),
+ actualImagePath: path.join(testsRelativePath, Test.actualFilename),
+ diffImagePath: path.join(testsRelativePath, Test.diffFilename),
+ // The diff is not undefined because the test is completed
+ diff: test.diff as number,
+ };
+ }
+
+ /**
+ * Writes the HTML report to disk
+ *
+ * @param html The HTML report
+ */
+ private writeHtml(html: string) {
+ fs.writeFileSync(
+ path.join(this.testFolder, ReportGenerator.reportFilename),
+ html,
+ );
+ }
+}
diff --git a/baremaps-renderer/src/lib/runnableTask.ts
b/baremaps-renderer/src/lib/runnableTask.ts
new file mode 100644
index 00000000..e860f216
--- /dev/null
+++ b/baremaps-renderer/src/lib/runnableTask.ts
@@ -0,0 +1,24 @@
+/**
+ Licensed under the Apache License, Version 2.0 (the "License"); you may not
use this file except
+ in compliance with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
distributed under the License
+ is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express
+ or implied. See the License for the specific language governing permissions
and limitations under
+ the License.
+ **/
+
+import { Page } from 'puppeteer';
+
+/** Abstract class for a task that can be run on a puppeteer page */
+export abstract class RunnableTask {
+ /**
+ * Abstract function for running the task
+ *
+ * @param page puppeteer page
+ * @returns true if the task was successful, false otherwise
+ */
+ abstract run(page: Page): Promise<boolean>;
+}
diff --git a/baremaps-renderer/src/lib/test.ts
b/baremaps-renderer/src/lib/test.ts
new file mode 100644
index 00000000..340226a3
--- /dev/null
+++ b/baremaps-renderer/src/lib/test.ts
@@ -0,0 +1,178 @@
+/**
+ Licensed under the Apache License, Version 2.0 (the "License"); you may not
use this file except
+ in compliance with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
distributed under the License
+ is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express
+ or implied. See the License for the specific language governing permissions
and limitations under
+ the License.
+ **/
+
+import fs from 'fs';
+import path from 'path';
+import pixelmatch from 'pixelmatch';
+import { PNG } from 'pngjs';
+import { Page } from 'puppeteer';
+import { MaplibreBrowserHelpers } from './maplibreBrowserHelpers';
+import { TestsLogger } from './testsLogger';
+import { Metadata } from '../types';
+import { RunnableTask } from './runnableTask';
+
+/** Test is responsible for running a single test */
+export class Test implements RunnableTask {
+ public static readonly metadataFilename = 'metadata.json';
+ public static readonly expectedFilename = 'expected.png';
+ public static readonly actualFilename = 'actual.png';
+ public static readonly diffFilename = 'diff.png';
+
+ private _testPath: string;
+ private styleUrl: string;
+ private refStyleUrl: string;
+ private testLogger: TestsLogger;
+ private threshold: number;
+
+ private _metadata: Metadata;
+
+ private _success?: boolean;
+ private _diff?: number;
+
+ /**
+ * Create a new Test
+ *
+ * @param testPath path to the test
+ * @param styleUrl URL to the style
+ * @param refStyleUrl URL to the reference style
+ * @param testLogger logger for tests
+ * @param threshold threshold for image comparison
+ * @returns a new Test
+ * @throws if the test is not setup correctly
+ */
+ constructor(
+ testPath: string,
+ styleUrl: string,
+ refStyleUrl: string,
+ testLogger: TestsLogger,
+ threshold: number,
+ ) {
+ this._testPath = testPath;
+ this.styleUrl = styleUrl;
+ this.refStyleUrl = refStyleUrl;
+ this.testLogger = testLogger;
+ this.threshold = threshold;
+ try {
+ this._metadata = JSON.parse(
+ fs
+ .readFileSync(path.join(this.testPath, Test.metadataFilename))
+ .toString(),
+ );
+ } catch (e) {
+ throw new Error(
+ `Could not read metadata in test '${this.testPath}'. ` +
+ `Make sure the file '${Test.metadataFilename}' exists and is valid
JSON`,
+ );
+ }
+ // check if metadata is valid
+ if (
+ !this.metadata ||
+ !this.metadata.width ||
+ !this.metadata.height ||
+ !this.metadata.center ||
+ !this.metadata.zoom
+ ) {
+ throw new Error(
+ `Invalid metadata in test '${this.testPath}'. ` +
+ 'Metadata must contain width, height, center and zoom',
+ );
+ }
+ }
+
+ public get testPath(): string {
+ return this._testPath;
+ }
+
+ public get metadata(): Metadata {
+ return this._metadata;
+ }
+
+ public get success(): boolean | undefined {
+ return this._success;
+ }
+
+ public get diff(): number | undefined {
+ return this._diff;
+ }
+
+ /**
+ * Abstraction function for running the test
+ *
+ * @param page puppeteer page
+ * @returns true if the test was successful, false otherwise
+ * @throws if the test fails to run
+ */
+ public async run(page: Page): Promise<boolean> {
+ const success = await this.runTest(page)
+ .then((s: boolean) => {
+ this.testLogger.logTest(this.testPath, s);
+ return s;
+ })
+ .catch((e) => {
+ this.testLogger.logError(this.testPath, e);
+ return false;
+ });
+ this._success = success;
+ return success;
+ }
+
+ /**
+ * Run the test
+ *
+ * @param page puppeteer page
+ * @returns true if the test was successful, false otherwise
+ */
+ private async runTest(page: Page): Promise<boolean> {
+ const image = await MaplibreBrowserHelpers.getImageFromMetadata(
+ page,
+ this.metadata,
+ this.styleUrl,
+ );
+
+ const refImage = await MaplibreBrowserHelpers.getImageFromMetadata(
+ page,
+ this.metadata,
+ this.refStyleUrl,
+ );
+
+ // compare image to reference image
+ const width = this.metadata.width;
+ const height = this.metadata.height;
+
+ const diffImg = new PNG({ width, height });
+
+ const diff = pixelmatch(
+ PNG.sync.read(refImage).data,
+ PNG.sync.read(image).data,
+ diffImg.data,
+ width,
+ height,
+ { threshold: this.threshold / (width * height) },
+ );
+ this._diff = diff;
+
+ // save actual image
+ fs.writeFileSync(path.join(this.testPath, Test.actualFilename), image);
+ // save expected image
+ fs.writeFileSync(path.join(this.testPath, Test.expectedFilename),
refImage);
+ // save diff image
+ fs.writeFileSync(
+ path.join(this.testPath, Test.diffFilename),
+ PNG.sync.write(diffImg, { filterType: 4 }),
+ );
+
+ if (diff > 0) {
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/baremaps-renderer/src/lib/testManager.ts
b/baremaps-renderer/src/lib/testManager.ts
new file mode 100644
index 00000000..e2a65af2
--- /dev/null
+++ b/baremaps-renderer/src/lib/testManager.ts
@@ -0,0 +1,90 @@
+/**
+ Licensed under the Apache License, Version 2.0 (the "License"); you may not
use this file except
+ in compliance with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
distributed under the License
+ is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express
+ or implied. See the License for the specific language governing permissions
and limitations under
+ the License.
+ **/
+
+import fs from 'fs';
+import path from 'path';
+import { Test } from './test';
+import { TestsLogger } from './testsLogger';
+
+/** TestManager is responsible for discovering and creating tests */
+export class TestManager {
+ private testsPath: string;
+ private tests: Test[];
+
+ /**
+ * Create a new TestManager
+ *
+ * @param testsFolder path to the tests folder
+ * @param integrationFolder path to the integration folder
+ * @param styleUrl URL to the style
+ * @param refStyleUrl URL to the reference style
+ * @param testLogger logger for tests
+ * @param threshold threshold for image comparison
+ * @returns a new TestManager
+ * @throws if the test is not setup correctly
+ */
+ constructor(
+ testsFolder: string,
+ integrationFolder: string,
+ styleUrl: string,
+ refStyleUrl: string,
+ testLogger: TestsLogger,
+ threshold: number,
+ ) {
+ this.testsPath = path.join(testsFolder, integrationFolder);
+ // check if this.testsPath is absolute
+ if (!path.isAbsolute(this.testsPath)) {
+ this.testsPath = path.join(process.cwd(), this.testsPath);
+ }
+ this.tests = [] as Test[];
+ const testNames = this.discoverTests();
+ for (const testName of testNames) {
+ try {
+ this.tests.push(
+ new Test(
+ path.join(this.testsPath, testName),
+ styleUrl,
+ refStyleUrl,
+ testLogger,
+ threshold,
+ ),
+ );
+ } catch (e) {
+ testLogger.logError(testName, e);
+ }
+ }
+ }
+
+ public getTests(): Test[] {
+ return this.tests;
+ }
+
+ /**
+ * Discover tests in the tests folder
+ * @returns a list of test names
+ * @throws if the tests folder is not found
+ */
+ public discoverTests() {
+ let testNames: string[];
+ try {
+ // filter by directories
+ testNames = fs
+ .readdirSync(this.testsPath)
+ .filter((file) =>
+ fs.statSync(path.join(this.testsPath, file)).isDirectory(),
+ );
+ } catch (e) {
+ throw new Error(`Tests folder '${this.testsPath}' could not be found`);
+ }
+ return testNames;
+ }
+}
diff --git a/baremaps-renderer/src/lib/testsLogger.ts
b/baremaps-renderer/src/lib/testsLogger.ts
new file mode 100644
index 00000000..3b66558a
--- /dev/null
+++ b/baremaps-renderer/src/lib/testsLogger.ts
@@ -0,0 +1,112 @@
+/**
+ Licensed under the Apache License, Version 2.0 (the "License"); you may not
use this file except
+ in compliance with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
distributed under the License
+ is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express
+ or implied. See the License for the specific language governing permissions
and limitations under
+ the License.
+ **/
+
+import path from 'path';
+import chalk from 'chalk';
+import { TestResults } from '../types';
+
+/** Class to log tests */
+export class TestsLogger {
+ private tests: TestResults;
+ private startTime: number;
+
+ /**
+ * Create a new TestsLogger
+ *
+ * @returns a new TestsLogger
+ */
+ constructor() {
+ this.tests = {} as TestResults;
+ this.startTime = Date.now();
+ }
+
+ /**
+ * Log a test to the console
+ *
+ * @param testPath path to the test
+ * @param success whether the test was successful
+ */
+ public logTest(testPath: string, success: boolean) {
+ const testName = path.basename(testPath);
+ this.tests[testName] = success;
+
+ const testRepr = this.getTestRepr(testPath);
+ console.log(
+ success
+ ? chalk.bgGreenBright(chalk.whiteBright(chalk.bold(' PASS '))) +
+ testRepr
+ : chalk.bgRedBright(chalk.whiteBright(chalk.bold(' FAIL '))) +
testRepr,
+ );
+ }
+
+ /**
+ * Log an error to the console
+ *
+ * @param testPath path to the test
+ * @param error error to log
+ */
+ public logError(testPath: string, error: Error | unknown) {
+ const testName = path.basename(testPath);
+ this.tests[testName] = false;
+
+ const testRepr = this.getTestRepr(testPath);
+ console.log(
+ chalk.bgRedBright(chalk.whiteBright(chalk.bold(' ERROR '))) + testRepr,
+ );
+ console.log(error);
+ }
+
+ /** Log a summary of the tests to the console */
+ public logSummary() {
+ // add empty line
+ console.log();
+ console.log(
+ chalk.bgWhiteBright(chalk.blackBright(chalk.bold(' SUMMARY '))),
+ );
+
+ // log time taken
+ const timeTaken = Date.now() - this.startTime;
+ console.log(
+ 'Time taken: ' +
+ chalk.bold(`${Math.round(timeTaken / 1000 / 60)}m`) +
+ chalk.bold(`${(timeTaken / 1000).toFixed(2)}s`),
+ );
+
+ // print summary total failed and total passed
+ const totPassed = Object.values(this.tests).filter(
+ (success) => success,
+ ).length;
+ const totFailed = Object.values(this.tests).length - totPassed;
+
+ console.log(
+ 'Out of ' +
+ chalk.bold(Object.values(this.tests).length) +
+ ' tests: ' +
+ chalk.greenBright(chalk.bold(`${totPassed} passed`)) +
+ ', ' +
+ chalk.redBright(chalk.bold(`${totFailed} failed `)),
+ );
+
+ // add empty line
+ console.log();
+ }
+
+ /**
+ * Helper function to get a string representation for a test
+ *
+ * @param testPath path to the test
+ * @returns string representation of the test
+ */
+ private getTestRepr(testPath: string) {
+ return ` Ran test: ${path.basename(testPath)} (${testPath})`;
+ }
+}
diff --git a/baremaps-renderer/src/types/index.ts
b/baremaps-renderer/src/types/index.ts
new file mode 100644
index 00000000..99fe9e90
--- /dev/null
+++ b/baremaps-renderer/src/types/index.ts
@@ -0,0 +1,36 @@
+/**
+ Licensed under the Apache License, Version 2.0 (the "License"); you may not
use this file except
+ in compliance with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
distributed under the License
+ is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express
+ or implied. See the License for the specific language governing permissions
and limitations under
+ the License.
+ **/
+
+/** Represent the result of tests */
+export interface TestResults {
+ [key: string]: boolean | undefined;
+}
+
+/** Metadata file spec for tests */
+export interface Metadata {
+ width: number;
+ height: number;
+ center: [number, number];
+ zoom: number;
+}
+
+/** Data spec for the HTML generator */
+export interface TestData {
+ path: string;
+ name: string;
+ metadata: Metadata;
+ success: boolean;
+ expectedImagePath: string;
+ actualImagePath: string;
+ diffImagePath: string;
+ diff: number;
+}
diff --git a/baremaps-renderer/tsconfig.json b/baremaps-renderer/tsconfig.json
new file mode 100644
index 00000000..78978a07
--- /dev/null
+++ b/baremaps-renderer/tsconfig.json
@@ -0,0 +1,20 @@
+{
+ "compilerOptions": {
+ /* Language and Environment */
+ "target": "es2016" /* Set the JavaScript language version for emitted
JavaScript and include compatible library declarations. */,
+
+ /* Modules */
+ "module": "commonjs" /* Specify what module code is generated. */,
+ "outDir": "./bin" /* Specify an output folder for all emitted files. */,
+ "esModuleInterop": true /* Emit additional JavaScript to ease support for
importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for
type compatibility. */,
+ "forceConsistentCasingInFileNames": true /* Ensure that casing is correct
in imports. */,
+
+ /* Type Checking */
+ "strict": true /* Enable all strict type-checking options. */,
+ "noImplicitAny": true /* Enable error reporting for expressions and
declarations with an implied 'any' type. */,
+ "skipLibCheck": true /* Skip type checking all .d.ts files. */,
+ "noUnusedLocals": true
+ },
+ "include": ["src/**/*", "declaration.d.ts"],
+ "exclude": ["node_modules", "bin"]
+}
diff --git a/basemap/tests/.gitignore b/basemap/tests/.gitignore
new file mode 100644
index 00000000..c1775f0a
--- /dev/null
+++ b/basemap/tests/.gitignore
@@ -0,0 +1,2 @@
+**/*.png
+*.html
\ No newline at end of file
diff --git a/basemap/tests/integration/lausanne/metadata.json
b/basemap/tests/integration/lausanne/metadata.json
new file mode 100644
index 00000000..411cd146
--- /dev/null
+++ b/basemap/tests/integration/lausanne/metadata.json
@@ -0,0 +1,6 @@
+{
+ "width": 512,
+ "height": 512,
+ "center": [6.6323, 46.5197],
+ "zoom": 14
+}