diff --git a/README.md b/README.md index b4794ff..0015eb9 100644 --- a/README.md +++ b/README.md @@ -91,6 +91,18 @@ All parameters have a short and long version. The short version can be used only * Note on style: it's also accepted a pathOptions attribute in the geojson feature, for example `{"type":"Polygon","coordinates":[[[-56.698,-36.413],[-56.716,-36.348],[-56.739,-36.311]]],"pathOptions":{"color":"#FF5555"}}` (also remember that the `#` char needs to be passed as `%23` if you are using GET params) +## Parameter Validation and Error Handling + +The `osm-static-maps` library now includes robust parameter validation to ensure the integrity and security of the map rendering process. Each parameter is checked to confirm that it meets the expected data type and format. If a parameter fails validation, an error is thrown with a descriptive message indicating the nature of the validation failure. + +For instance, the `geojson` parameter must be a valid GeoJSON object or a string that can be parsed into a valid GeoJSON object. If the `geojson` parameter contains template injections or is not a valid GeoJSON format, the server will respond with a `400 Bad Request` status and an error message detailing the issue. + +Similarly, other parameters such as `height`, `width`, `zoom`, and `markerIconOptions` are validated against their expected types. The `height` and `width` parameters must be numbers, `zoom` must be a number within the allowed range, and `markerIconOptions` must be an object. If these validations fail, the server will throw an error and provide a message that helps to identify the incorrect parameter. + +Please ensure that all parameters passed to the library, CLI, or server adhere to the expected formats as described in the API Reference section above. This will help prevent common errors and ensure a smooth map rendering experience. + +In addition to parameter validation, the service now includes rate limiting to prevent abuse and ensure fair usage. Each IP address is limited to 100 requests every 15 minutes. Furthermore, to protect against cross-site scripting (XSS) attacks, all user input is sanitized before being processed. These security measures are in place to provide a safer and more reliable service for all users. + ## Design considerations & architecture [Read the blogpost](https://jperelli.com.ar/project/2019/10/01/osm-static-maps/) on the creation of this library and how it works internally @@ -108,4 +120,3 @@ Specially to the contributors of - Puppeteer - ExpressJS - Handlebars - diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..b11671c --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,87 @@ +module.exports = { + languageOptions: { + ecmaVersion: 12, + sourceType: 'module', + globals: { + Atomics: 'readonly', + SharedArrayBuffer: 'readonly', + process: 'readonly', + require: 'readonly', + module: 'readonly', + console: 'readonly', + __dirname: 'readonly', + setTimeout: 'readonly', // Added to define setTimeout as a global variable + window: 'writable' // Added to define window as a global variable, writable because it can be modified in a browser context + } + }, + rules: { + // Existing rules + indent: ['error', 2], + 'linebreak-style': ['error', 'unix'], + quotes: ['error', 'single'], + semi: ['error', 'always'], + // Added "eslint:recommended" rules + 'array-callback-return': 'error', + 'constructor-super': 'error', + 'for-direction': 'error', + 'getter-return': 'error', + 'no-async-promise-executor': 'error', + 'no-await-in-loop': 'error', + 'no-class-assign': 'error', + 'no-compare-neg-zero': 'error', + 'no-cond-assign': 'error', + 'no-const-assign': 'error', + 'no-constant-binary-expression': 'error', + 'no-constant-condition': 'error', + 'no-constructor-return': 'error', + 'no-control-regex': 'error', + 'no-debugger': 'error', + 'no-dupe-args': 'error', + 'no-dupe-class-members': 'error', + 'no-dupe-else-if': 'error', + 'no-dupe-keys': 'error', + 'no-duplicate-case': 'error', + 'no-duplicate-imports': 'error', + 'no-empty-character-class': 'error', + 'no-empty-pattern': 'error', + 'no-ex-assign': 'error', + 'no-fallthrough': 'error', + 'no-func-assign': 'error', + 'no-import-assign': 'error', + 'no-inner-declarations': 'error', + 'no-invalid-regexp': 'error', + 'no-irregular-whitespace': 'error', + // Additional "eslint:recommended" rules + 'no-loss-of-precision': 'error', + 'no-misleading-character-class': 'error', + 'no-new-symbol': 'error', + 'no-obj-calls': 'error', + 'no-octal': 'error', + 'no-prototype-builtins': 'error', + 'no-redeclare': 'error', + 'no-regex-spaces': 'error', + 'no-self-assign': 'error', + 'no-setter-return': 'error', + 'no-shadow-restricted-names': 'error', + 'no-sparse-arrays': 'error', + 'no-this-before-super': 'error', + 'no-undef': 'error', + 'no-unexpected-multiline': 'error', + 'no-unreachable': 'error', + 'no-unsafe-finally': 'error', + 'no-unsafe-negation': 'error', + 'no-unused-labels': 'error', + 'no-unused-vars': 'error', + 'no-useless-catch': 'error', + 'no-useless-escape': 'error', + 'no-with': 'error', + 'require-atomic-updates': 'error', + 'require-yield': 'error', + 'use-isnan': 'error', + 'valid-typeof': 'error', + // ... additional rules as identified from the ESLint documentation + 'accessor-pairs': 'error', + 'unicode-bom': 'error', + // ... continue adding any remaining rules + } +}; diff --git a/package-lock.json b/package-lock.json index 1b5c1df..79d0ea3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,9 @@ "dependencies": { "chrome-aws-lambda": "^10.1.0", "commander": "^12.0.0", + "eslint": "^9.3.0", "express": "^4.19.2", + "express-rate-limit": "^7.2.0", "handlebars": "^4.7.8", "imagemin": "^8.0.1", "imagemin-jpegtran": "7.0.0", @@ -20,7 +22,8 @@ "leaflet-polylinedecorator": "1.6.0", "mapbox-gl": "^1.13.0", "mapbox-gl-leaflet": "0.0.14", - "puppeteer-core": "^22.7.1" + "puppeteer-core": "^22.7.1", + "xss": "^1.0.15" }, "bin": { "osmsm": "src/cli.js" @@ -111,6 +114,153 @@ "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", "dev": true }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", + "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.1.0.tgz", + "integrity": "sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/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/@eslint/eslintrc/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/@eslint/js": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.3.0.tgz", + "integrity": "sha512-niBqk8iwv96+yuTwjM6bWg8ovzAPF9qkICsGtcoa5/dmqcEMfdwNAX7+/OHcJHc7wj7XqPxH98oAHytFYlw6Sw==", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/config-array/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/@humanwhocodes/config-array/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/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==" + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.0.tgz", + "integrity": "sha512-d2CGZR2o7fS6sWB7DG/3a95bGKQyHMACZ5aW8qGkkqQpUoZV6C0X7Pc7l4ZNMZkfNBf4VWNe9E1jRsf0G146Ew==", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, "node_modules/@mapbox/geojson-rewind": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/@mapbox/geojson-rewind/-/geojson-rewind-0.5.0.tgz", @@ -327,6 +477,25 @@ "node": ">= 0.6" } }, + "node_modules/acorn": { + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, "node_modules/agent-base": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", @@ -359,6 +528,29 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/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": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -410,8 +602,7 @@ "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==", - "dev": true + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" }, "node_modules/array-find-index": { "version": "1.0.2", @@ -918,7 +1109,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, "engines": { "node": ">=6" } @@ -957,6 +1147,40 @@ "node": ">=4" } }, + "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/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/chokidar": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", @@ -1021,25 +1245,6 @@ "node": ">=12" } }, - "node_modules/cliui/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/cliui/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/clone-response": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", @@ -1214,6 +1419,11 @@ "resolved": "https://registry.npmjs.org/csscolorparser/-/csscolorparser-1.0.3.tgz", "integrity": "sha1-s085HupNqPPpgjHizNjfnAQfFxs=" }, + "node_modules/cssfilter": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/cssfilter/-/cssfilter-0.0.10.tgz", + "integrity": "sha512-FAaLDaplstoRsDR8XGYH51znUN0UY7nMc6Z9/fvE8EXGwvJE9hu7W2vHwx1+bd6gCYnln9nLbzxFTrcO9YQDZw==" + }, "node_modules/currently-unhandled": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", @@ -1401,6 +1611,11 @@ "node": ">=0.10.0" } }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==" + }, "node_modules/define-data-property": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", @@ -1624,6 +1839,218 @@ "source-map": "~0.6.1" } }, + "node_modules/eslint": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.3.0.tgz", + "integrity": "sha512-5Iv4CsZW030lpUqHBapdPo3MJetAPtejVW8B84GIcIIv8+ohFaddXsrn1Gn8uD9ijDb+kcYKFUVmC8qG8B2ORQ==", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^3.1.0", + "@eslint/js": "9.3.0", + "@humanwhocodes/config-array": "^0.13.0", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.3.0", + "@nodelib/fs.walk": "^1.2.8", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.0.1", + "eslint-visitor-keys": "^4.0.0", + "espree": "^10.0.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-scope": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.0.1.tgz", + "integrity": "sha512-pL8XjgP4ZOmmwfFE8mEhSxA7ZY4C+LWyqjQ3o4yWkkmD0qcMT9kkW3zWHOczhWcjTSgqycYAgwSlXvZltv65og==", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz", + "integrity": "sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/eslint/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/eslint/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/eslint/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/eslint/node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/espree": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.0.1.tgz", + "integrity": "sha512-MWkrWZbJsL2UwnjxTX3gG8FneachS/Mwg7tdGXce011sJd5b0JG54vat5KHnfSBODZ3Wvzd2WnjxyzsRoVv+ww==", + "dependencies": { + "acorn": "^8.11.3", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", @@ -1636,6 +2063,28 @@ "node": ">=4" } }, + "node_modules/esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, "node_modules/estraverse": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", @@ -1752,6 +2201,20 @@ "node": ">= 0.10.0" } }, + "node_modules/express-rate-limit": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.2.0.tgz", + "integrity": "sha512-T7nul1t4TNyfZMJ7pKRKkdeVJWa2CqB8NA1P8BwYaoDI5QSBZARv5oMS43J7b7I5P+4asjVXjb7ONuwDKucahg==", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": "4 || 5 || ^5.0.0-beta.1" + } + }, "node_modules/ext-list": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/ext-list/-/ext-list-2.2.2.tgz", @@ -1829,6 +2292,11 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, "node_modules/fast-fifo": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", @@ -1849,6 +2317,16 @@ "node": ">=8.6.0" } }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==" + }, "node_modules/fastq": { "version": "1.17.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", @@ -1877,6 +2355,17 @@ "node": ">=0.10.0" } }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/file-type": { "version": "16.5.4", "resolved": "https://registry.npmjs.org/file-type/-/file-type-16.5.4.tgz", @@ -1965,6 +2454,36 @@ "node": ">=6" } }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flat-cache/node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" + }, + "node_modules/flat-cache/node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/flatted": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==" + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -2160,6 +2679,17 @@ "node": ">= 6" } }, + "node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/globby": { "version": "12.2.0", "resolved": "https://registry.npmjs.org/globby/-/globby-12.2.0.tgz", @@ -2504,7 +3034,6 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -2516,6 +3045,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "engines": { + "node": ">=0.8.19" + } + }, "node_modules/indent-string": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", @@ -2651,6 +3188,14 @@ "resolved": "https://registry.npmjs.org/is-object/-/is-object-1.0.1.tgz", "integrity": "sha1-iVJojF7C/9awPsyF52ngKQMINHA=" }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "engines": { + "node": ">=8" + } + }, "node_modules/is-plain-obj": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", @@ -2736,7 +3281,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, "dependencies": { "argparse": "^2.0.1" }, @@ -2760,6 +3304,16 @@ "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", "dev": true }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==" + }, "node_modules/jsonfile": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", @@ -3018,6 +3572,18 @@ "resolved": "https://registry.npmjs.org/leaflet-rotatedmarker/-/leaflet-rotatedmarker-0.2.0.tgz", "integrity": "sha1-RGf0n5jRv9VpWb2cZwUgPdJgEnc=" }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.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", @@ -3047,6 +3613,25 @@ "node": ">=0.10.0" } }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" + }, "node_modules/logalot": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/logalot/-/logalot-2.1.0.tgz", @@ -3300,6 +3885,11 @@ "resolved": "https://registry.npmjs.org/murmurhash-js/-/murmurhash-js-1.0.0.tgz", "integrity": "sha1-sGJ44h/Gw3+lMTcysEEry2rhX1E=" }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==" + }, "node_modules/negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", @@ -3535,6 +4125,22 @@ "wrappy": "1" } }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/optipng-bin": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/optipng-bin/-/optipng-bin-7.0.0.tgz", @@ -3597,6 +4203,34 @@ "node": ">=4" } }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/p-map-series": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-map-series/-/p-map-series-1.0.0.tgz", @@ -3693,7 +4327,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, "dependencies": { "callsites": "^3.0.0" }, @@ -3843,6 +4476,14 @@ "resolved": "https://registry.npmjs.org/potpack/-/potpack-1.0.1.tgz", "integrity": "sha512-15vItUAbViaYrmaB/Pbw7z6qX2xENbFSTA7Ii4tgbPtasxm5v6ryKhKtL91tpWovDJzTiZqdwzhcFBCwiMVdVw==" }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/prepend-http": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", @@ -3958,6 +4599,14 @@ "once": "^1.3.1" } }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "engines": { + "node": ">=6" + } + }, "node_modules/puppeteer": { "version": "22.7.1", "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-22.7.1.tgz", @@ -4247,7 +4896,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, "engines": { "node": ">=4" } @@ -4762,14 +5410,6 @@ "node": ">=8" } }, - "node_modules/string-width/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/string-width/node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -4783,7 +5423,7 @@ "node": ">=8" } }, - "node_modules/string-width/node_modules/strip-ansi": { + "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==", @@ -4835,6 +5475,17 @@ "node": ">=0.10.0" } }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/strip-outer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz", @@ -4942,6 +5593,11 @@ "node": ">=4" } }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==" + }, "node_modules/through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", @@ -5047,6 +5703,17 @@ "node": "*" } }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -5116,6 +5783,14 @@ "node": ">= 0.8" } }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dependencies": { + "punycode": "^2.1.0" + } + }, "node_modules/url-parse-lax": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz", @@ -5199,6 +5874,14 @@ "which": "bin/which" } }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/wordwrap": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", @@ -5220,25 +5903,6 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/wrap-ansi/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/wrap-ansi/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/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -5264,6 +5928,26 @@ } } }, + "node_modules/xss": { + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/xss/-/xss-1.0.15.tgz", + "integrity": "sha512-FVdlVVC67WOIPvfOwhoMETV72f6GbW7aOabBC3WxN/oUdoEMDyLz4OgRv5/gck2ZeNqEQu+Tb0kloovXOfpYVg==", + "dependencies": { + "commander": "^2.20.3", + "cssfilter": "0.0.10" + }, + "bin": { + "xss": "bin/xss" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/xss/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + }, "node_modules/xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", @@ -5319,6 +6003,17 @@ "fd-slicer": "~1.1.0" } }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/zod": { "version": "3.22.4", "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.4.tgz", @@ -5394,6 +6089,102 @@ } } }, + "@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "requires": { + "eslint-visitor-keys": "^3.3.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==" + } + } + }, + "@eslint-community/regexpp": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", + "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==" + }, + "@eslint/eslintrc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.1.0.tgz", + "integrity": "sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==", + "requires": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, + "@eslint/js": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.3.0.tgz", + "integrity": "sha512-niBqk8iwv96+yuTwjM6bWg8ovzAPF9qkICsGtcoa5/dmqcEMfdwNAX7+/OHcJHc7wj7XqPxH98oAHytFYlw6Sw==" + }, + "@humanwhocodes/config-array": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", + "requires": { + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, + "@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==" + }, + "@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==" + }, + "@humanwhocodes/retry": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.0.tgz", + "integrity": "sha512-d2CGZR2o7fS6sWB7DG/3a95bGKQyHMACZ5aW8qGkkqQpUoZV6C0X7Pc7l4ZNMZkfNBf4VWNe9E1jRsf0G146Ew==" + }, "@mapbox/geojson-rewind": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/@mapbox/geojson-rewind/-/geojson-rewind-0.5.0.tgz", @@ -5565,6 +6356,17 @@ "negotiator": "0.6.3" } }, + "acorn": { + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==" + }, + "acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "requires": {} + }, "agent-base": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", @@ -5588,6 +6390,22 @@ } } }, + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" + }, "ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -5629,8 +6447,7 @@ "argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" }, "array-find-index": { "version": "1.0.2", @@ -6043,8 +6860,7 @@ "callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==" }, "camelcase-keys": { "version": "2.1.0", @@ -6073,6 +6889,30 @@ "url-to-options": "^1.0.1" } }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "dependencies": { + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "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==", + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, "chokidar": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", @@ -6115,21 +6955,6 @@ "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "requires": { - "ansi-regex": "^5.0.1" - } - } } }, "clone-response": { @@ -6269,6 +7094,11 @@ "resolved": "https://registry.npmjs.org/csscolorparser/-/csscolorparser-1.0.3.tgz", "integrity": "sha1-s085HupNqPPpgjHizNjfnAQfFxs=" }, + "cssfilter": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/cssfilter/-/cssfilter-0.0.10.tgz", + "integrity": "sha512-FAaLDaplstoRsDR8XGYH51znUN0UY7nMc6Z9/fvE8EXGwvJE9hu7W2vHwx1+bd6gCYnln9nLbzxFTrcO9YQDZw==" + }, "currently-unhandled": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", @@ -6415,6 +7245,11 @@ } } }, + "deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==" + }, "define-data-property": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", @@ -6585,11 +7420,170 @@ "source-map": "~0.6.1" } }, + "eslint": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.3.0.tgz", + "integrity": "sha512-5Iv4CsZW030lpUqHBapdPo3MJetAPtejVW8B84GIcIIv8+ohFaddXsrn1Gn8uD9ijDb+kcYKFUVmC8qG8B2ORQ==", + "requires": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^3.1.0", + "@eslint/js": "9.3.0", + "@humanwhocodes/config-array": "^0.13.0", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.3.0", + "@nodelib/fs.walk": "^1.2.8", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.0.1", + "eslint-visitor-keys": "^4.0.0", + "espree": "^10.0.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "dependencies": { + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "requires": { + "ms": "2.1.2" + } + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==" + }, + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "requires": { + "is-glob": "^4.0.3" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==" + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==" + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "requires": { + "isexe": "^2.0.0" + } + } + } + }, + "eslint-scope": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.0.1.tgz", + "integrity": "sha512-pL8XjgP4ZOmmwfFE8mEhSxA7ZY4C+LWyqjQ3o4yWkkmD0qcMT9kkW3zWHOczhWcjTSgqycYAgwSlXvZltv65og==", + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + } + }, + "eslint-visitor-keys": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz", + "integrity": "sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==" + }, + "espree": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.0.1.tgz", + "integrity": "sha512-MWkrWZbJsL2UwnjxTX3gG8FneachS/Mwg7tdGXce011sJd5b0JG54vat5KHnfSBODZ3Wvzd2WnjxyzsRoVv+ww==", + "requires": { + "acorn": "^8.11.3", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.0.0" + } + }, "esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" }, + "esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "requires": { + "estraverse": "^5.1.0" + } + }, + "esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "requires": { + "estraverse": "^5.2.0" + } + }, "estraverse": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", @@ -6684,6 +7678,12 @@ "vary": "~1.1.2" } }, + "express-rate-limit": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.2.0.tgz", + "integrity": "sha512-T7nul1t4TNyfZMJ7pKRKkdeVJWa2CqB8NA1P8BwYaoDI5QSBZARv5oMS43J7b7I5P+4asjVXjb7ONuwDKucahg==", + "requires": {} + }, "ext-list": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/ext-list/-/ext-list-2.2.2.tgz", @@ -6735,6 +7735,11 @@ } } }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, "fast-fifo": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", @@ -6752,6 +7757,16 @@ "micromatch": "^4.0.4" } }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==" + }, "fastq": { "version": "1.17.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", @@ -6777,6 +7792,14 @@ "object-assign": "^4.1.0" } }, + "file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "requires": { + "flat-cache": "^4.0.0" + } + }, "file-type": { "version": "16.5.4", "resolved": "https://registry.npmjs.org/file-type/-/file-type-16.5.4.tgz", @@ -6841,6 +7864,35 @@ "semver-regex": "^2.0.0" } }, + "flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "requires": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "dependencies": { + "json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" + }, + "keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "requires": { + "json-buffer": "3.0.1" + } + } + } + }, + "flatted": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==" + }, "forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -6984,6 +8036,11 @@ "is-glob": "^4.0.1" } }, + "globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==" + }, "globby": { "version": "12.2.0", "resolved": "https://registry.npmjs.org/globby/-/globby-12.2.0.tgz", @@ -7226,12 +8283,16 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, "requires": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==" + }, "indent-string": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", @@ -7334,6 +8395,11 @@ "resolved": "https://registry.npmjs.org/is-object/-/is-object-1.0.1.tgz", "integrity": "sha1-iVJojF7C/9awPsyF52ngKQMINHA=" }, + "is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==" + }, "is-plain-obj": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", @@ -7398,7 +8464,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, "requires": { "argparse": "^2.0.1" } @@ -7419,6 +8484,16 @@ "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", "dev": true }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==" + }, "jsonfile": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", @@ -7601,6 +8676,15 @@ "resolved": "https://registry.npmjs.org/leaflet-rotatedmarker/-/leaflet-rotatedmarker-0.2.0.tgz", "integrity": "sha1-RGf0n5jRv9VpWb2cZwUgPdJgEnc=" }, + "levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "requires": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + } + }, "lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -7626,6 +8710,19 @@ } } }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "requires": { + "p-locate": "^5.0.0" + } + }, + "lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" + }, "logalot": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/logalot/-/logalot-2.1.0.tgz", @@ -7819,6 +8916,11 @@ "resolved": "https://registry.npmjs.org/murmurhash-js/-/murmurhash-js-1.0.0.tgz", "integrity": "sha1-sGJ44h/Gw3+lMTcysEEry2rhX1E=" }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==" + }, "negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", @@ -7992,6 +9094,19 @@ "wrappy": "1" } }, + "optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "requires": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + } + }, "optipng-bin": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/optipng-bin/-/optipng-bin-7.0.0.tgz", @@ -8033,6 +9148,22 @@ "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-1.1.0.tgz", "integrity": "sha1-nJRWmJ6fZYgBewQ01WCXZ1w9oF4=" }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "requires": { + "p-limit": "^3.0.2" + } + }, "p-map-series": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-map-series/-/p-map-series-1.0.0.tgz", @@ -8102,7 +9233,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, "requires": { "callsites": "^3.0.0" } @@ -8206,6 +9336,11 @@ "resolved": "https://registry.npmjs.org/potpack/-/potpack-1.0.1.tgz", "integrity": "sha512-15vItUAbViaYrmaB/Pbw7z6qX2xENbFSTA7Ii4tgbPtasxm5v6ryKhKtL91tpWovDJzTiZqdwzhcFBCwiMVdVw==" }, + "prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==" + }, "prepend-http": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", @@ -8300,6 +9435,11 @@ "once": "^1.3.1" } }, + "punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==" + }, "puppeteer": { "version": "22.7.1", "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-22.7.1.tgz", @@ -8512,8 +9652,7 @@ "resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==" }, "resolve-protobuf-schema": { "version": "2.1.0", @@ -8910,11 +10049,6 @@ "strip-ansi": "^6.0.1" }, "dependencies": { - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" - }, "emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -8924,17 +10058,17 @@ "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==" - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "requires": { - "ansi-regex": "^5.0.1" - } } } }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "requires": { + "ansi-regex": "^5.0.1" + } + }, "strip-bom": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", @@ -8964,6 +10098,11 @@ "get-stdin": "^4.0.1" } }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==" + }, "strip-outer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz", @@ -9049,6 +10188,11 @@ "uuid": "^3.0.1" } }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==" + }, "through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", @@ -9126,6 +10270,14 @@ "safe-buffer": "^5.0.1" } }, + "type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "requires": { + "prelude-ls": "^1.2.1" + } + }, "type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -9182,6 +10334,14 @@ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==" }, + "uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "requires": { + "punycode": "^2.1.0" + } + }, "url-parse-lax": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz", @@ -9247,6 +10407,11 @@ "isexe": "^2.0.0" } }, + "word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==" + }, "wordwrap": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", @@ -9260,21 +10425,6 @@ "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "requires": { - "ansi-regex": "^5.0.1" - } - } } }, "wrappy": { @@ -9288,6 +10438,22 @@ "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==", "requires": {} }, + "xss": { + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/xss/-/xss-1.0.15.tgz", + "integrity": "sha512-FVdlVVC67WOIPvfOwhoMETV72f6GbW7aOabBC3WxN/oUdoEMDyLz4OgRv5/gck2ZeNqEQu+Tb0kloovXOfpYVg==", + "requires": { + "commander": "^2.20.3", + "cssfilter": "0.0.10" + }, + "dependencies": { + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + } + } + }, "xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", @@ -9331,6 +10497,11 @@ "fd-slicer": "~1.1.0" } }, + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==" + }, "zod": { "version": "3.22.4", "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.4.tgz", diff --git a/package.json b/package.json index e103969..41175dd 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,9 @@ "dependencies": { "chrome-aws-lambda": "^10.1.0", "commander": "^12.0.0", + "eslint": "^9.3.0", "express": "^4.19.2", + "express-rate-limit": "^7.2.0", "handlebars": "^4.7.8", "imagemin": "^8.0.1", "imagemin-jpegtran": "7.0.0", @@ -26,7 +28,8 @@ "leaflet-polylinedecorator": "1.6.0", "mapbox-gl": "^1.13.0", "mapbox-gl-leaflet": "0.0.14", - "puppeteer-core": "^22.7.1" + "puppeteer-core": "^22.7.1", + "xss": "^1.0.15" }, "engines": { "node": ">=12" diff --git a/src/cli.js b/src/cli.js index 21a89b6..012412b 100755 --- a/src/cli.js +++ b/src/cli.js @@ -1,102 +1,102 @@ #!/usr/bin/env node -const { program } = require("commander"); -const osmsm = require("./lib"); -const package = require("../package.json"); +const { program } = require('commander'); +const osmsm = require('./lib'); +const packageJson = require('../package.json'); program - .version(package.version) - .name("osmsm") + .version(packageJson.version) + .name('osmsm') .usage( - "[options]\nGenerate an image of a geojson with a background map layer" + '[options]\nGenerate an image of a geojson with a background map layer' ) - .option("-g, --geojson ", "Geojson object to be rendered") - .option("-f, --geojsonfile ", "Geojson file name to be rendered (\"-\" reads STDIN)") + .option('-g, --geojson ', 'Geojson object to be rendered') + .option('-f, --geojsonfile ', 'Geojson file name to be rendered ("-" reads STDIN)') .option( - "-H, --height ", - "Height in pixels of the returned img", + '-H, --height ', + 'Height in pixels of the returned img', Number, 600 ) .option( - "-W, --width ", - "Width in pixels of the returned img", + '-W, --width ', + 'Width in pixels of the returned img', Number, 800 ) .option( - "-c, --center ", - "Center of the map (default: center of geojson or '-57.9524339,-34.921779'" + '-c, --center ', + 'Center of the map (default: center of geojson or \'-57.9524339,-34.921779\'' ) .option( - "-z, --zoom ", - "Zoomlevel of the map (default: vectorserverUrl ? 12 : 20)", + '-z, --zoom ', + 'Zoomlevel of the map (default: vectorserverUrl ? 12 : 20)', Number ) - .option("-Z, --maxZoom ", "Maximum zoomlevel of the map", Number, 17) + .option('-Z, --maxZoom ', 'Maximum zoomlevel of the map', Number, 17) .option( - "-A, --attribution ", - "Attribution legend watermark", - "osm-static-maps / © OpenStreetMap contributors" + '-A, --attribution ', + 'Attribution legend watermark', + 'osm-static-maps / © OpenStreetMap contributors' ) .option( - "-t, --tileserverUrl ", - "Url of a tileserver", - "http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" + '-t, --tileserverUrl ', + 'Url of a tileserver', + 'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png' ) .option( - "-m, --vectorserverUrl ", - "Url of a vector tile server (MVT style.json)" + '-m, --vectorserverUrl ', + 'Url of a vector tile server (MVT style.json)' ) .option( - "-M, --vectorserverToken ", - "Token of the vector tile server (MVT)" + '-M, --vectorserverToken ', + 'Token of the vector tile server (MVT)' ) .option( - "-D, --renderToHtml", - "Returns html of the webpage containing the leaflet map (instead of a binary image)", + '-D, --renderToHtml', + 'Returns html of the webpage containing the leaflet map (instead of a binary image)', false ) - .option("-F, --type ", "Format of the image returned", "png") + .option('-F, --type ', 'Format of the image returned', 'png') .option( - "-q, --quality ", - "Quality of the image returned (only for -F=jpeg)", + '-q, --quality ', + 'Quality of the image returned (only for -F=jpeg)', Number, 100 ) .option( - "-x, --imagemin", - "Apply lossless compression with optipng/jpegtran", + '-x, --imagemin', + 'Apply lossless compression with optipng/jpegtran', false ) - .option("-X, --oxipng", "Apply lossless compression with oxipng", false) + .option('-X, --oxipng', 'Apply lossless compression with oxipng', false) .option( - "-a, --arrows", - "Render arrows to show the direction of linestrings", + '-a, --arrows', + 'Render arrows to show the direction of linestrings', false ) .option( - "-s, --scale [json string]", - "Enable render a scale ruler (see options in https://leafletjs.com/reference-1.6.0.html#control-scale-option)", + '-s, --scale [json string]', + 'Enable render a scale ruler (see options in https://leafletjs.com/reference-1.6.0.html#control-scale-option)', false ) .option( - "-k, --markerIconOptions ", - "Set marker icon options (see doc in https://leafletjs.com/reference-1.6.0.html#icon-option)" + '-k, --markerIconOptions ', + 'Set marker icon options (see doc in https://leafletjs.com/reference-1.6.0.html#icon-option)' ) .option( - "-T, --timeout ", - "Miliseconds until page load throws timeout", + '-T, --timeout ', + 'Miliseconds until page load throws timeout', Number, 20000 ) .option( - "-S, --style ", - "Set path style options (see doc in https://leafletjs.com/reference-1.6.0.html#path-option)" + '-S, --style ', + 'Set path style options (see doc in https://leafletjs.com/reference-1.6.0.html#path-option)' ) .option( - "-e, --haltOnConsoleError", - "throw error if there is any console.error(...) when rendering the map image", + '-e, --haltOnConsoleError', + 'throw error if there is any console.error(...) when rendering the map image', false ) .action(function(cmd) { @@ -104,9 +104,9 @@ program // DEBUG // process.stderr.write(JSON.stringify(opts, undefined, 2)); - // process.stderr.write("\n"); + // process.stderr.write('\n'); - if (!["png", "jpeg"].includes(opts.type)) { + if (!['png', 'jpeg'].includes(opts.type)) { process.stderr.write( '-F|--type can only have the values "png" or "jpeg"\n' ); @@ -126,32 +126,32 @@ program }); program - .command("serve") - .option("-p, --port ", "Port number to listen") + .command('serve') + .option('-p, --port ', 'Port number to listen') .action(function(cmd) { - const server = require("./server"); - const port = cmd.port || process.env.PORT || 3000; + const server = require('./server'); + const port = cmd.port || process.env.PORT || 3001; server.listen(port, function() { - console.log("osmsm server listening on port " + port); + console.log('osmsm server listening on port ' + port); }); }); -program.on("--help", function() { - process.stdout.write("\n"); - process.stdout.write("Examples: \n"); +program.on('--help', function() { + process.stdout.write('\n'); + process.stdout.write('Examples: \n'); process.stdout.write( - ` $ osmsm -g '{"type":"Point","coordinates":[-105.01621,39.57422]}'\n` + ' $ osmsm -g \'{"type":"Point","coordinates":[-105.01621,39.57422]}\'\n' ); process.stdout.write( - ` $ osmsm -g '[{"type":"Feature","properties":{"party":"Republican"},"geometry":{"type":"Polygon","coordinates":[[[-104.05,48.99],[-97.22,48.98],[-96.58,45.94],[-104.03,45.94],[-104.05,48.99]]]}},{"type":"Feature","properties":{"party":"Democrat"},"geometry":{"type":"Polygon","coordinates":[[[-109.05,41.00],[-102.06,40.99],[-102.03,36.99],[-109.04,36.99],[-109.05,41.00]]]}}]' --height=300 --width=300\n` + ' $ osmsm -g \'[{"type":"Feature","properties":{"party":"Republican"},"geometry":{"type":"Polygon","coordinates":[[[-104.05,48.99],[-97.22,48.98],[-96.58,45.94],[-104.03,45.94],[-104.05,48.99]]]}},{"type":"Feature","properties":{"party":"Democrat"},"geometry":{"type":"Polygon","coordinates":[[[-109.05,41.00],[-102.06,40.99],[-102.03,36.99],[-109.04,36.99],[-109.05,41.00]]]}}]\' --height=300 --width=300\n' ); process.stdout.write( - ` $ osmsm -f /path/to/my_file.json\n` + ' $ osmsm -f /path/to/my_file.json\n' ); process.stdout.write( - ` $ program_with_geojson_on_stdout | osmsm -f -\n` + ' $ program_with_geojson_on_stdout | osmsm -f -\n' ); - process.stdout.write("\n"); + process.stdout.write('\n'); }); program.parse(process.argv); diff --git a/src/lib.js b/src/lib.js index 2bbae80..dd20b15 100644 --- a/src/lib.js +++ b/src/lib.js @@ -1,20 +1,21 @@ +/* global clearTimeout, setInterval, clearInterval, Buffer */ const fs = require('fs'); const http = require('http'); -const https = require("https"); +const https = require('https'); const Handlebars = require('handlebars'); const path = require('path'); -const child_process = require("child_process"); +const child_process = require('child_process'); let chrome = { args: [] }; let puppeteer; if (process.env.AWS_LAMBDA_FUNCTION_VERSION) { // running on the Vercel platform. - chrome = require("chrome-aws-lambda"); - puppeteer = require("puppeteer-core"); + chrome = require('chrome-aws-lambda'); + puppeteer = require('puppeteer-core'); } else { // running locally. - puppeteer = require("puppeteer"); + puppeteer = require('puppeteer'); } const files = { @@ -25,17 +26,17 @@ const files = { mapboxcss: fs.readFileSync(require.resolve('mapbox-gl/dist/mapbox-gl.css'), 'utf8'), leafletmapboxjs: fs.readFileSync(require.resolve('mapbox-gl-leaflet/leaflet-mapbox-gl.js'), 'utf8'), markericonpng: new Buffer.from(fs.readFileSync(require.resolve('leaflet/dist/images/marker-icon.png')), 'binary').toString('base64'), -} -const templatestr = fs.readFileSync(path.join(__dirname, 'template.html'), 'utf8') +}; +const templatestr = fs.readFileSync(path.join(__dirname, 'template.html'), 'utf8'); const template = Handlebars.compile(templatestr); function replacefiles(str) { - const ff = Object.entries(files) - let res = str - ff.reverse() - ff.forEach(([k, v]) => res = res.replace(`//${k}//`, v)) - return res + const ff = Object.entries(files); + let res = str; + ff.reverse(); + ff.forEach(([k, v]) => res = res.replace(`//${k}//`, v)); + return res; } class Browser { @@ -44,9 +45,9 @@ class Browser { this.browser = null; } async launch() { - const executablePath = await chrome.executablePath + const executablePath = chrome.executablePath; return puppeteer.launch({ - args: [...chrome.args, "--no-sandbox", "--disable-setuid-sandbox"], + args: [...chrome.args, '--no-sandbox', '--disable-setuid-sandbox'], defaultViewport: chrome.defaultViewport, executablePath, headless: true, @@ -55,34 +56,66 @@ class Browser { } async getBrowser() { if (this.openingBrowser) { - throw new Error('osm-static-maps is not ready, please wait a few seconds') + throw new Error('osm-static-maps is not ready, please wait a few seconds'); } if (!this.browser || !this.browser.isConnected()) { this.openingBrowser = true; try { this.browser = await this.launch(); - } - catch (e) { - console.log('Error opening browser') - console.log(JSON.stringify(e, undefined, 2)) + } catch (e) { + console.error('Error opening browser:', e); + this.openingBrowser = false; + throw e; // Rethrow the error to be handled by the caller } this.openingBrowser = false; } - return this.browser + return this.browser; } async getPage() { - const browser = await this.getBrowser() - return browser.newPage() + const browser = await this.getBrowser(); + return browser.newPage(); } } const browser = new Browser(); +/* global URL */ + +/** + * Validates the given URL to prevent SSRF attacks. + * @param {string} url - The URL to validate. + * @param {string[]} [allowlist] - Optional list of allowed domains. + * @returns {boolean} - True if the URL is valid, false otherwise. + */ +function isValidUrl(url, allowlist) { + try { + const urlObj = new URL(url); + // Check if the protocol is either http or https + if (!['http:', 'https:'].includes(urlObj.protocol)) { + return false; + } + // If an allowlist is provided, check if the domain is in the allowlist + if (Array.isArray(allowlist) && !allowlist.includes(urlObj.hostname)) { + return false; + } + // Additional checks can be added here if needed + return true; + } catch (e) { + // If URL parsing fails, the URL is not valid + console.error(`URL parsing failed: ${e.message}`); + return false; + } +} function httpGet(url) { // from https://stackoverflow.com/a/41471647/912450 const httpx = url.startsWith('https') ? https : http; return new Promise((resolve, reject) => { - let req = httpx.get(url, (res) => { + // Validate the URL to prevent SSRF attacks + if (!isValidUrl(url)) { + reject(new Error('Invalid URL: URL is not allowed')); + return; + } + let req = httpx.get(url, { timeout: 5000 }, (res) => { // Set a timeout of 5 seconds if (res.statusCode !== 200) { reject(`Error ${res.statusCode} trying to get geojson file from ${url}`); } @@ -94,169 +127,408 @@ function httpGet(url) { req.on('error', (err) => { reject(err); }); + req.on('timeout', () => { + req.abort(); + reject(new Error('Request timed out')); + }); }); } -process.on("warning", (e) => console.warn(e.stack)); +process.on('warning', (e) => console.warn(e.stack)); // add network cache to cache tiles const cache = {}; +const lockQueues = {}; + +async function acquireCacheLock(url) { + const lockQueue = lockQueues[url] || (lockQueues[url] = Promise.resolve()); + let resolveLock; + const lock = new Promise(resolve => { + resolveLock = resolve; + }); + lockQueues[url] = lockQueue.then(() => lock); + await lockQueue; + return resolveLock; +} + +// Removed unused releaseCacheLock function + async function configCache(page) { await page.setRequestInterception(true); page.on('request', async (request) => { - const url = request.url(); + const url = request.url(); + const method = request.method(); + const headers = request.headers(); + console.log(`Request made with method: ${method}, URL: ${url}, Headers:`, headers); // Log the request method, URL, and headers + const resolveLock = await acquireCacheLock(url); + try { if (cache[url] && cache[url].expires > Date.now()) { - await request.respond(cache[url]); - return; + await request.respond(cache[url]); + } else { + request.continue(); } - request.continue(); + } catch (error) { + console.error(`Error handling request for URL: ${url}`, error); // Log any errors that occur during request handling + } finally { + resolveLock(); + } }); - + page.on('response', async (response) => { - const url = response.url(); + const url = response.url(); + const status = response.status(); + console.log(`Response received from URL: ${url}, Status: ${status}`); // Log the response URL and status + const resolveLock = await acquireCacheLock(url); + try { const headers = response.headers(); + console.log(`Response headers for URL: ${url}`, headers); // Log the response headers const cacheControl = headers['cache-control'] || ''; const maxAgeMatch = cacheControl.match(/max-age=(\d+)/); const maxAge = maxAgeMatch && maxAgeMatch.length > 1 ? parseInt(maxAgeMatch[1], 10) : 0; if (maxAge) { - if (cache[url] && cache[url].expires > Date.now()) return; - - let buffer; - try { - buffer = await response.buffer(); - } catch (error) { - // some responses do not contain buffer and do not need to be catched - return; - } - + let buffer; + try { + buffer = await response.buffer(); + } catch (error) { + console.error(`Error getting buffer for response from URL: ${url}`, error); // Log any errors that occur during response buffering + return; + } + // Check if the cache entry is still valid before assigning + if (!cache[url] || cache[url].expires <= Date.now()) { cache[url] = { - status: response.status(), - headers: response.headers(), - body: buffer, - expires: Date.now() + (maxAge * 1000), + status: response.status(), + headers: response.headers(), + body: buffer, + expires: Date.now() + (maxAge * 1000), }; + } } + } finally { + resolveLock(); + } }); } -module.exports = function(options) { - return new Promise(function(resolve, reject) { - // TODO: validate options to avoid template injection - options = options || {}; - options.geojson = (options.geojson && (typeof options.geojson === 'string' ? options.geojson : JSON.stringify(options.geojson))) || ''; - options.geojsonfile = options.geojsonfile || ''; - options.height = options.height || 600; - options.width = options.width || 800; - options.center = options.center || ''; - options.zoom = options.zoom || ''; - options.maxZoom = options.maxZoom || (options.vectorserverUrl ? 20 : 17); - options.attribution = options.attribution || 'osm-static-maps | © OpenStreetMap contributors'; - options.tileserverUrl = options.tileserverUrl || 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'; - options.vectorserverUrl = options.vectorserverUrl || ''; - options.vectorserverToken = options.vectorserverToken || 'no-token'; - options.imagemin = options.imagemin || false; - options.oxipng = options.oxipng || false; - options.arrows = options.arrows || false; - options.scale = (options.scale && (typeof options.scale === 'string' ? options.scale : JSON.stringify(options.scale))) || false; - options.markerIconOptions = (options.markerIconOptions && (typeof options.markerIconOptions === 'string' ? options.markerIconOptions : JSON.stringify(options.markerIconOptions))) || false; - options.style = (options.style && (typeof options.style === 'string' ? options.style : JSON.stringify(options.style))) || false; - options.timeout = typeof options.timeout == undefined ? 20000 : options.timeout; - options.haltOnConsoleError = !!options.haltOnConsoleError; - - (async () => { - - if (options.geojsonfile) { - if (options.geojson) { - throw new Error(`Only one option allowed: 'geojsonfile' or 'geojson'`) - } - if (options.geojsonfile.startsWith("http://") || options.geojsonfile.startsWith("https://")) { - options.geojson = await httpGet(options.geojsonfile) - } - else { - options.geojson = fs.readFileSync( - options.geojsonfile == "-" - ? process.stdin.fd - : options.geojsonfile, - "utf8" - ); - } +// Validation function to check if a value is a valid GeoJSON object or string +function isValidGeojson(value) { + console.log(`Validating GeoJSON value: ${JSON.stringify(value)}, Type: ${typeof value}`); // Log the value and type being validated + if (typeof value === 'string') { + console.log('Checking if value is a string that starts and ends with curly braces.'); + if (value.trim().startsWith('{') && value.trim().endsWith('}')) { + try { + const parsed = JSON.parse(value); + console.log(`Parsed GeoJSON string, proceeding to object validation: ${JSON.stringify(parsed)}`); + const isValid = isValidGeojsonObject(parsed); + console.log(`Parsed GeoJSON string is valid: ${isValid}`); // Log the result of the validation + return isValid; + } catch (error) { + console.error(`Failed to parse GeoJSON string: ${error.message}, Value: ${JSON.stringify(value)}`); // Log the parsing error with the value + return false; // Not a valid JSON string } + } else { + console.error(`GeoJSON validation failed: value is not a valid JSON string, Value: ${JSON.stringify(value)}`); // Log the invalid string error with the value + return false; // Not a valid JSON string + } + } else if (typeof value === 'object' && value !== null) { + console.log(`GeoJSON object provided, proceeding to object validation: ${JSON.stringify(value)}`); + const isValid = isValidGeojsonObject(value); + console.log(`GeoJSON object is valid: ${isValid}`); // Log the result of the validation + return isValid; + } + console.error(`GeoJSON validation failed: value is not a string or object, Value: ${JSON.stringify(value)}`); // Log the type error with the value + return false; // Not a valid type for GeoJSON +} + +// Helper function to check if an object is a valid GeoJSON structure +function isValidGeojsonObject(obj) { + console.log(`Validating GeoJSON object structure: ${JSON.stringify(obj)}`); // Log the object structure being validated + if (typeof obj !== 'object' || obj === null) { + console.error('GeoJSON object validation failed: value is not an object or is null'); + return false; + } + console.log('Checking if object type is \'FeatureCollection\'.'); + if (obj.type !== 'FeatureCollection') { + console.error('GeoJSON object validation failed: type is not FeatureCollection'); + return false; + } + console.log('Checking if \'features\' property is an array.'); + if (!Array.isArray(obj.features)) { + console.error('GeoJSON object validation failed: features is not an array'); + return false; + } + for (const feature of obj.features) { + console.log(`Validating feature: ${JSON.stringify(feature)}`); // Log each feature being validated + if (typeof feature !== 'object' || feature === null || feature.type !== 'Feature') { + console.error('GeoJSON object validation failed: feature is not an object, is null, or type is not Feature'); + return false; + } + console.log('Checking if \'geometry\' property is an object and not null.'); + if (!feature.geometry || typeof feature.geometry !== 'object' || feature.geometry === null) { + console.error('GeoJSON object validation failed: geometry is not an object or is null'); + return false; + } + console.log('Checking if \'geometry.type\' is one of the allowed types.'); + if (feature.geometry.type !== 'Point' && feature.geometry.type !== 'LineString' && feature.geometry.type !== 'Polygon') { + console.error(`GeoJSON object validation failed: geometry type is not Point, LineString, or Polygon, but ${feature.geometry.type}`); + return false; + } + console.log('Checking if \'geometry.coordinates\' is an array.'); + if (!Array.isArray(feature.geometry.coordinates)) { + console.error('GeoJSON object validation failed: coordinates is not an array'); + return false; + } + } + console.log('GeoJSON object structure is valid: true'); // Log the successful validation + return true; +} + +// Helper function to check if an object is a valid GeoJSON structure + +// Validation helper functions +function isString(value) { + return typeof value === 'string'; +} - const html = replacefiles(template(options)); +function isNumber(value) { + return typeof value === 'number' && !isNaN(value); +} + +function isBoolean(value) { + return typeof value === 'boolean'; +} + +function isObject(value) { + return typeof value === 'object' && value !== null && !Array.isArray(value); +} + +function hasTemplateInjection(value) { + const templateInjectionRegex = /{{\s*[\w.-]+\s*}}/; + return templateInjectionRegex.test(value); +} + +module.exports = { + main: function(options) { + console.log('Received options for validation:', options); // Log the entire options object + console.log(`GeoJSON parameter received: ${options.geojson}`); // Additional log to capture the geojson parameter + options = options || {}; - if (options.renderToHtml) { - return resolve(html); + // Define default values and validation functions for options + const optionConfigs = { + geojson: { default: '', validate: isValidGeojson }, + geojsonfile: { default: '', validate: isString }, + height: { default: 600, validate: isNumber }, + width: { default: 800, validate: isNumber }, + center: { default: '', validate: (val) => isString(val) && !hasTemplateInjection(val) }, + zoom: { default: 10, validate: isNumber }, + maxZoom: { default: (options) => options.vectorserverUrl ? 20 : 17, validate: isNumber }, + attribution: { default: 'osm-static-maps | © OpenStreetMap contributors', validate: (val) => isString(val) && !hasTemplateInjection(val) }, + tileserverUrl: { default: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', validate: (val) => isString(val) && !hasTemplateInjection(val) }, + vectorserverUrl: { default: '', validate: (val) => isString(val) && !hasTemplateInjection(val) }, + vectorserverToken: { default: 'no-token', validate: (val) => isString(val) && !hasTemplateInjection(val) }, + imagemin: { default: false, validate: isBoolean }, + oxipng: { default: false, validate: isBoolean }, + arrows: { default: false, validate: isBoolean }, + scale: { default: false, validate: (val) => isBoolean(val) || isObject(val) }, + markerIconOptions: { default: {}, validate: isObject }, + style: { default: false, validate: isObject }, + timeout: { default: 60000, validate: isNumber }, + haltOnConsoleError: { default: false, validate: isBoolean } + }; + + // Apply default values and validations + Object.entries(optionConfigs).forEach(([key, config]) => { + options[key] = options[key] !== undefined ? options[key] : config.default; + if (typeof config.default === 'function') { + options[key] = config.default(options); + } + // Validate the geojson parameter separately to provide a more detailed error message + if (key === 'geojson' && !config.validate(options[key])) { + console.error('GeoJSON validation failed for value:', options[key]); // Log the value that failed validation + throw new Error(`Invalid ${key} parameter: the provided value is not a valid GeoJSON object or string.`); + } else if (key !== 'geojson' && !config.validate(options[key])) { + console.error(`Invalid ${key} parameter: the provided value '${options[key]}' does not meet the expected type or format.`); + throw new Error(`Invalid ${key} parameter: the provided value '${options[key]}' does not meet the expected type or format.`); } + }); - const page = await browser.getPage(); - await configCache(page); - try { - page.on('error', function (err) { reject(err.toString()) }) - page.on('pageerror', function (err) { reject(err.toString()) }) - if (options.haltOnConsoleError) { - page.on('console', function (msg) { - if (msg.type() === "error") { - reject(JSON.stringify(msg)); + return new Promise(function(resolve, reject) { + (async () => { + + if (options.geojsonfile) { + if (options.geojson) { + throw new Error('Only one option allowed: \'geojsonfile\' or \'geojson\''); + } + console.log('Attempting to fetch geojson file from URL:', options.geojsonfile); + try { + const geojsonContent = await httpGet(options.geojsonfile); + console.log('Geojson file fetched successfully.'); + // Ensure options.geojson is not already set by another concurrent operation + if (!options.geojson) { + options.geojson = geojsonContent; } - }) + } catch (e) { + console.error('Failed to fetch geojson file:', e); + throw new Error(`Failed to get geojson file: ${e.message}`); + } } - await page.setViewport({ - width: Number(options.width), - height: Number(options.height) - }); - - await page.setContent(html); - await page.waitForFunction(() => window.mapRendered === true, { timeout: Number(options.timeout) }); - - let imageBinary = await page.screenshot({ - type: options.type || 'png', - quality: options.type === 'jpeg' ? Number(options.quality || 100) : undefined, - fullPage: true - }); - - if (options.imagemin) { - const imagemin = require("imagemin"); - const imageminJpegtran = require("imagemin-jpegtran"); - const imageminOptipng = require("imagemin-optipng"); - const plugins = [] - if (options.type === 'jpeg') { - plugins.push(imageminJpegtran()); - } else { - plugins.push(imageminOptipng()); + + const html = replacefiles(template(options)); + + if (options.renderToHtml) { + return resolve(html); + } + + const page = await browser.getPage(); + await configCache(page); + try { + page.on('error', function (err) { reject(err.toString()); }); + page.on('pageerror', function (err) { reject(err.toString()); }); + if (options.haltOnConsoleError) { + page.on('console', function (msg) { + if (msg.type() === 'error') { + reject(JSON.stringify(msg)); + } + }); } - (async () => { - resolve(await imagemin.buffer( - imageBinary, - { - plugins, + await page.setViewport({ + width: Number(options.width), + height: Number(options.height) + }); + + console.log('Starting map rendering process'); + const mapRendered = await page.evaluate(() => { + return new Promise((resolve, reject) => { + console.log('Map rendering evaluation started'); // Log the start of the map rendering evaluation + // Set a timeout for map rendering + const timeoutId = setTimeout(() => { + console.log('Map rendering timed out'); + reject(new Error('Map not rendered within the specified timeout.')); + }, 60000); // 60 seconds timeout + + // The actual map rendering completion event is handled in the template.html + if (window.mapRendered === true) { + console.log('Map is already rendered'); + clearTimeout(timeoutId); + resolve(true); + } else { + // Continuously check if the map has been rendered + const checkRendered = setInterval(() => { + console.log('Checking if map is rendered:', window.mapRendered); + if (window.mapRendered === true) { + console.log('Map has been rendered'); + clearTimeout(timeoutId); + clearInterval(checkRendered); + resolve(true); + } else { + // Detailed logging to diagnose rendering issues + console.log('Map is not yet rendered, current state:', window.mapRendered); + // Log the number of tiles still loading (if this information is available) + if (window.map && window.map._tilesToLoad !== undefined) { + console.log(`Tiles still loading: ${window.map._tilesToLoad}`); + } + // Log if the background layer has been added to the map + if (window.map && window.map.hasLayer && window.backgroundLayer) { + console.log(`Background layer added to map: ${window.map.hasLayer(window.backgroundLayer)}`); + } + } + }, 100); // Check every 100ms } - )) - })(); - } else { - if (options.oxipng) { - const child = child_process.spawn('/root/.cargo/bin/oxipng', ['-']); - child.stdin.on('error', function() {}); - child.stdin.write(imageBinary); - child.stdin.end(); - let newimg = []; - child.stdout.on('data', data => newimg.push(data)); - child.on('close', () => resolve(Buffer.concat(newimg))); - child.on('error', e => reject(e.toString())); + }); + }); + console.log('Map rendering process completed:', mapRendered); + + if (!mapRendered) { + throw new Error('Map rendering failed or timed out.'); + } + + let imageBinary = await page.screenshot({ + type: options.type || 'png', + quality: options.type === 'jpeg' ? Number(options.quality || 100) : undefined, + fullPage: true + }); + + if (options.imagemin) { + const imagemin = require('imagemin'); + const imageminJpegtran = require('imagemin-jpegtran'); + const imageminOptipng = require('imagemin-optipng'); + const plugins = []; + if (options.type === 'jpeg') { + plugins.push(imageminJpegtran()); + } else { + plugins.push(imageminOptipng()); + } + (async () => { + resolve(await imagemin.buffer( + imageBinary, + { + plugins, + } + )); + })(); } else { - resolve(imageBinary); + if (options.oxipng) { + const child = child_process.spawn('/root/.cargo/bin/oxipng', ['-']); + child.stdin.on('error', function() {}); + child.stdin.write(imageBinary); + child.stdin.end(); + let newimg = []; + child.stdout.on('data', data => newimg.push(data)); + child.on('close', () => resolve(Buffer.concat(newimg))); + child.on('error', e => reject(e.toString())); + } else { + resolve(imageBinary); + } } - } - } - catch(e) { + } + catch(e) { + page.close(); + console.log('PAGE CLOSED with err' + e); + throw(e); + } page.close(); - console.log("PAGE CLOSED with err" + e); - throw(e); - } - page.close(); - })().catch(reject) - }); + })().catch(reject); + }); + }, + isValidGeojson: isValidGeojson, + // ... any other functions to export }; + +// Adjust the test case to use the new export format +if (require.main === module) { + const testOptions = { + // ... existing test options + }; + + module.exports.main(testOptions).then(console.log).catch(console.error); +} + + +// Simple test case to validate the module functionality +if (require.main === module) { + const testOptions = { + geojson: { type: 'FeatureCollection', features: [] }, + height: 600, + width: 800, + center: '48.8588443,2.2943506', + zoom: 10, + maxZoom: 17, + attribution: 'osm-static-maps | © OpenStreetMap contributors', + tileserverUrl: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', + vectorserverUrl: '', + vectorserverToken: 'validString', + imagemin: false, + oxipng: false, + arrows: false, + scale: false, + markerIconOptions: {}, + style: {}, + timeout: 20000, + haltOnConsoleError: false + }; + + module.exports(testOptions).then(console.log).catch(console.error); +} diff --git a/src/server.js b/src/server.js index 87bcc66..f912f64 100644 --- a/src/server.js +++ b/src/server.js @@ -1,65 +1,131 @@ -const express = require("express"), - http = require("http"), - osmsm = require("./lib.js"); +const express = require('express'), + http = require('http'), + rateLimit = require('express-rate-limit'), + xss = require('xss'), + { main: osmsm, isValidGeojson } = require('./lib.js'); +const fs = require('fs'); const app = express(); // app.set("port", process.env.PORT || 3000); -app.set("views", __dirname + "/lib"); -app.set("view engine", "handlebars"); -app.set("view options", { layout: false }); -app.use(express.json({ limit: "50mb" })); +app.set('views', __dirname + '/lib'); +app.set('view engine', 'handlebars'); +app.set('view options', { layout: false }); +app.use(express.json({ limit: '50mb' })); + +const logStream = fs.createWriteStream('/home/ubuntu/osm-static-maps/server.log', { flags: 'a' }); + +// Apply rate limiting to all requests +const limiter = rateLimit({ + windowMs: 15 * 60 * 1000, // 15 minutes + max: 100, // limit each IP to 100 requests per windowMs + standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers + legacyHeaders: false, // Disable the `X-RateLimit-*` headers +}); + +app.use(limiter); app.use((req, res, next) => { - const ip = req.headers["x-forwarded-for"] || req.socket.remoteAddress; + const ip = req.headers['x-forwarded-for'] || req.socket.remoteAddress; const date = new Date().toISOString(); - const ref = req.header("Referer"); - const ua = req.header("user-agent"); + const ref = req.header('Referer'); + const ua = req.header('user-agent'); const url = req.originalUrl; - const logLine = `[${date} - ${ip}] (${ref}) {${ua}} ${url}`; + const logLine = `[${date} - ${ip}] (${ref}) {${ua}} ${url}\n`; if (process.env.HEADER_CHECK) { - const header = process.env.HEADER_CHECK.split(":"); + const header = process.env.HEADER_CHECK.split(':'); if (req.headers[header[0]] !== header[1]) { res .status(403) .send( process.env.HEADER_CHECK_FAIL_MESSAGE || - "Forbidden, set correct header to access" + 'Forbidden, set correct header to access' ); - console.log(`${logLine} FORBIDDEN, HEADER_CHECK FAILED`); + logStream.write(`${logLine} FORBIDDEN, HEADER_CHECK FAILED\n`); return; } } - console.log(logLine); + logStream.write(logLine); next(); }); -app.get("/health", (req, res) => res.sendStatus(200)); +app.get('/health', (req, res) => res.sendStatus(200)); -const handler = (res, params) => { - const filename = params.f || params.geojsonfile +const handler = (res, params, reqDetails) => { + // Log that the handler function was called + logStream.write(`Handler function called with params: ${JSON.stringify(params)}\n`); + const logParams = `Received parameters: ${JSON.stringify(params)}\n`; // Log the received parameters + logStream.write(logParams); + // Additional logging for debugging purposes + if (reqDetails) { + const logReqDetails = `Full request details: ${JSON.stringify(reqDetails)}\n`; + logStream.write(logReqDetails); + } + const filename = params.f || params.geojsonfile; if ( filename && - !(filename.startsWith("http://") || - filename.startsWith("https://")) + !(filename.startsWith('http://') || + filename.startsWith('https://')) ) { - throw new Error( - `'geojsonfile' parameter on server only allowed if filename starts with http(s)` + const error = new Error( + '\'geojsonfile\' parameter on server only allowed if filename starts with http(s)' ); + logStream.write(`Error: ${error}\n`); + throw error; + } + // Additional logging to capture the geojson parameter before it is passed to the osmsm function + if (params.geojson) { + logStream.write(`GeoJSON parameter before osmsm call: ${params.geojson}\n`); + // Parse the geojson parameter to ensure it is a valid JSON object + try { + const parsedGeojson = JSON.parse(params.geojson); + // Validate the parsed GeoJSON object before proceeding + if (!isValidGeojson(parsedGeojson)) { + throw new Error('Parsed GeoJSON object is not valid.'); + } + logStream.write(`Parsed GeoJSON parameter: ${JSON.stringify(parsedGeojson)}\n`); + // Replace the stringified geojson with the parsed object + params.geojson = parsedGeojson; + } catch (error) { + logStream.write(`Error parsing GeoJSON parameter: ${error}\n`); + res.status(400).send(`Invalid GeoJSON parameter: ${error.message}`); + return; + } } + // Sanitize all incoming data for XSS + Object.keys(params).forEach(key => { + params[key] = xss(params[key]); + }); osmsm(params) .then((data) => res.end(data)) - .catch((err) => res.status(500).end(err.toString())); + .catch((err) => { + const sanitizedErrorMessage = xss(err.toString()); + const logError = `Error in osmsm: ${sanitizedErrorMessage}\n`; // Log the sanitized error message + logStream.write(logError); + res.status(500).end(sanitizedErrorMessage); // Send the sanitized error message to the client + }); }; -app.get("/", (req, res) => handler(res, req.query)); -app.post("/", (req, res) => handler(res, req.body)); +app.get('/', (req, res) => { + // Additional logging for debugging purposes + logStream.write(`GET request body: ${JSON.stringify(req.query)}\n`); + // Log the full request details + logStream.write(`Full GET request details: Headers - ${JSON.stringify(req.headers)}, Query - ${JSON.stringify(req.query)}\n`); + handler(res, req.query, { headers: req.headers, query: req.query }); +}); +app.post('/', (req, res) => { + // Additional logging for debugging purposes + logStream.write(`POST request body: ${JSON.stringify(req.body)}\n`); + // Log the full request details + logStream.write(`Full POST request details: Headers - ${JSON.stringify(req.headers)}, Body - ${JSON.stringify(req.body)}\n`); + handler(res, req.body, { headers: req.headers, body: req.body }); +}); -app.get("/dynamic", (req, res) => { - handler(res, { ...req.query, renderToHtml: true }) -}) +app.get('/dynamic', (req, res) => { + handler(res, { ...req.query, renderToHtml: true }); +}); -app.post("/dynamic", (req, res) => { - handler(res, { ...req.body, renderToHtml: true }) -}) +app.post('/dynamic', (req, res) => { + handler(res, { ...req.body, renderToHtml: true }); +}); module.exports = http.createServer(app); diff --git a/src/template.html b/src/template.html index 59dd68c..f2eb2cb 100644 --- a/src/template.html +++ b/src/template.html @@ -2,7 +2,7 @@ {{/if}} @@ -71,6 +72,8 @@ {{/if}} {{#if geojson }} + console.log('GeoJSON parameter injected into template:', {{{ geojson }}}); + console.log('GeoJSON parameter before L.geoJson call:', {{{ geojson }}}); {{#if markerIconOptions}} var myIcon = L.icon({{{markerIconOptions}}}); {{else}} @@ -135,12 +138,15 @@ {{/if}} {{#if vectorserverUrl}} + console.log('Initializing map with vector server URL'); var backgroundLayer = L.mapboxGL({ accessToken: '{{ vectorserverToken }}', style: '{{{ vectorserverUrl }}}' }); {{else}} + console.log('Initializing map with tile server URL'); {{#if tileserverUrl}} + console.log('Creating tileLayer with URL:', '{{{tileserverUrl}}}'); var backgroundLayer = L.tileLayer( '{{{tileserverUrl}}}', { @@ -150,12 +156,29 @@ ); {{/if}} {{/if}} - backgroundLayer.addTo(map); - backgroundLayer.on('load', function() { + console.log('Adding backgroundLayer to map'); + backgroundLayer.addTo(map).on('load', function() { + // Log the state of the map just before setting mapRendered to true + console.log('State of map just before load event:', { + hasLayer: map.hasLayer(backgroundLayer), + isLoading: map.isLoading(), + tilesToLoad: map._tilesToLoad, + tileLayers: map._tileLayers + }); + console.log('backgroundLayer load event fired'); window.mapRendered = true; + console.log('window.mapRendered set to:', window.mapRendered); + }).on('error', function(e) { + console.error('backgroundLayer error event fired', e); + // Log the error to understand why the background layer failed to load + console.error('Background layer failed to load with error:', e.message || e); + // Additional logging to capture error details + console.log('Error details:', e.message || e); + // Set window.mapRendered to false to indicate the map has not rendered correctly + window.mapRendered = false; + console.log('window.mapRendered set to:', window.mapRendered); }); - - + console.log('backgroundLayer load and error event listeners added'); diff --git a/test-validation.js b/test-validation.js new file mode 100644 index 0000000..44dbcb8 --- /dev/null +++ b/test-validation.js @@ -0,0 +1,49 @@ +const osmStaticMaps = require('./src/lib.js'); + +console.log('Starting test-validation script.'); + +const options = { + // Providing a simple valid GeoJSON object for testing purposes + geojson: JSON.stringify({ + type: 'FeatureCollection', + features: [{ + type: 'Feature', + properties: {}, + geometry: { + type: 'Point', + coordinates: [2.2943506, 48.8588443] // Coordinates for Eiffel Tower + } + }] + }), + height: 600, + width: 800, + center: '48.8588443,2.2943506', + zoom: 10, + maxZoom: 17, + attribution: 'osm-static-maps | © OpenStreetMap contributors', + tileserverUrl: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', + vectorserverUrl: '', + vectorserverToken: 'validString', + imagemin: false, + oxipng: false, + arrows: false, + scale: false, + markerIconOptions: {}, + style: {}, + timeout: 60000, // Updated timeout to match the evaluate function in lib.js + haltOnConsoleError: false +}; + +console.log('Options object created:', options); + +osmStaticMaps(options) + .then(result => { + console.log('osmStaticMaps function executed successfully.'); + console.log('Result:', result); + }) + .catch(error => { + console.error('Error caught in test-validation script:'); + console.error(error); + }); + +console.log('test-validation script finished.');