From 47396438708148b850b5d949e67ef3fba54e898e Mon Sep 17 00:00:00 2001 From: PaulDalek Date: Wed, 9 Oct 2024 13:58:45 +0200 Subject: [PATCH] Added missing envs, validation middleware, validation enhancements, overall options, scenarios and test corrections. --- .env.sample | 18 + README.md | 142 ++-- bin/cli.js | 6 +- lib/browser.js | 2 + lib/chart.js | 20 +- lib/logger.js | 5 - lib/schemas/config.js | 157 ++-- lib/server/error.js | 2 +- lib/server/routes/export.js | 143 +--- lib/server/routes/health.js | 2 +- lib/server/routes/versionChange.js | 2 +- lib/server/server.js | 37 +- lib/server/validate.js | 160 ++++ lib/server/webSocket.js | 6 +- lib/validate.js | 464 +++++----- package-lock.json | 435 +++++----- package.json | 12 +- public/index.html | 4 +- samples/module/svg.js | 6 +- tests/node/scenarios/svgBasic.json | 4 +- tests/node/scenarios/svgBasicWithScale.json | 4 +- .../scenarios/svgBasicWithScaleToPdf.json | 4 +- tests/node/scenarios/svgForeignObject.json | 4 +- tests/unit/validation/cli.test.js | 175 +++- tests/unit/validation/config.test.js | 173 +++- tests/unit/validation/envs.test.js | 56 +- tests/unit/validation/shared.js | 803 ++++++++++++------ tests/utils/testsUtils.js | 4 +- 28 files changed, 1767 insertions(+), 1083 deletions(-) create mode 100644 lib/server/validate.js diff --git a/.env.sample b/.env.sample index 3b69c39b..c51989d6 100644 --- a/.env.sample +++ b/.env.sample @@ -13,16 +13,34 @@ HIGHCHARTS_INDICATOR_SCRIPTS = HIGHCHARTS_CUSTOM_SCRIPTS = # EXPORT CONFIG +EXPORT_INFILE = +EXPORT_INSTR = +EXPORT_OPTIONS = +EXPORT_SVG = EXPORT_TYPE = png EXPORT_CONSTR = chart +EXPORT_OUTFILE = +EXPORT_B64 = false +EXPORT_NO_DOWNLOAD = false +EXPORT_HEIGHT = +EXPORT_WIDTH = +EXPORT_SCALE = EXPORT_DEFAULT_HEIGHT = 400 EXPORT_DEFAULT_WIDTH = 600 EXPORT_DEFAULT_SCALE = 1 +EXPORT_GLOBAL_OPTIONS = +EXPORT_THEME_OPTIONS = +EXPORT_BATCH = EXPORT_RASTERIZATION_TIMEOUT = 1500 # CUSTOM LOGIC CONFIG CUSTOM_LOGIC_ALLOW_CODE_EXECUTION = false CUSTOM_LOGIC_ALLOW_FILE_RESOURCES = false +CUSTOM_LOGIC_CUSTOM_CODE = +CUSTOM_LOGIC_CALLBACK = +CUSTOM_LOGIC_RESOURCES = +CUSTOM_LOGIC_LOAD_CONFIG = +CUSTOM_LOGIC_CREATE_CONFIG = # SERVER CONFIG SERVER_ENABLE = false diff --git a/README.md b/README.md index 610c4401..c60236bf 100644 --- a/README.md +++ b/README.md @@ -185,28 +185,34 @@ The format, along with its default values, is as follows (using the recommended ] }, "export": { - "infile": false, - "instr": false, - "options": false, - "outfile": "chart.png", + "infile": null, + "instr": null, + "options": null, + "svg": null, + "outfile": null, "type": "png", "constr": "chart", - "height": 400, - "width": 600, - "scale": 1, - "globalOptions": false, - "themeOptions": false, - "batch": false, + "b64": false, + "noDownload": false, + "defaultHeight": 400, + "defaultWidth": 600, + "defaultScale": 1, + "height": null, + "width": null, + "scale": null, + "globalOptions": null, + "themeOptions": null, + "batch": null, "rasterizationTimeout": 1500 }, "customLogic": { "allowCodeExecution": false, "allowFileResources": false, - "customCode": false, - "callback": false, - "resources": false, - "loadConfig": false, - "createConfig": false + "customCode": null, + "callback": null, + "resources": null, + "loadConfig": null, + "createConfig": null }, "server": { "enable": false, @@ -214,8 +220,8 @@ The format, along with its default values, is as follows (using the recommended "port": 7801, "benchmarking": false, "proxy": { - "host": false, - "port": false, + "host": null, + "port": null, "timeout": 5000 }, "rateLimiting": { @@ -224,14 +230,14 @@ The format, along with its default values, is as follows (using the recommended "window": 1, "delay": 0, "trustProxy": false, - "skipKey": false, - "skipToken": false + "skipKey": null, + "skipToken": null }, "ssl": { "enable": false, "force": false, "port": 443, - "certPath": false + "certPath": null } }, "pool": { @@ -282,8 +288,7 @@ The format, along with its default values, is as follows (using the recommended "reconnectAttempts": 3, "messageInterval": 3600000, "gatherAllOptions": false, - "url": false, - "secret": false + "url": null } } ``` @@ -296,9 +301,11 @@ To load an additional JSON configuration file, use the `--loadConfig ` These variables are set in your environment and take precedence over options from the `lib/schemas/config.js` file. They can be set in the `.env` file (refer to the `.env.sample` file). If you prefer setting these variables through the `package.json`, use `export` command on Linux/Mac OS X and `set` command on Windows. +_Available variables:_ + ### Puppeteer Config -- `PUPPETEER_ARGS`: An array of an additional Puppeteer arguments sent to the browser init (defaults to ``). +- `PUPPETEER_ARGS`: A stringified version of additional Puppeteer arguments sent during browser initialization. The string can be enclosed in _[_ and _]_, and the arguments must be separated by the _;_ character (defaults to ``). ### Highcharts Config @@ -314,11 +321,16 @@ These variables are set in your environment and take precedence over options fro ### Export Config -- `EXPORT_TYPE`: The format of the file to export to. Can be **jpeg**, **png**, **pdf** or **svg** (defaults to `png`). -- `EXPORT_CONSTR`: The constructor to use. Can be **chart**, **stockChart**, **mapChart** or **ganttChart** (defaults to `chart`). -- `EXPORT_DEFAULT_HEIGHT`: The default height of the exported chart. Used when not found any value set (defaults to `400`). -- `EXPORT_DEFAULT_WIDTH`: The default width of the exported chart. Used when not found any value set (defaults to `600`). -- `EXPORT_DEFAULT_SCALE`: The default scale of the exported chart. Ranges between **0.1** and **5.0** (defaults to `1`). +- `EXPORT_TYPE`: The format of the file to export to. Can be **jpeg**, **png**, **pdf**, or **svg** (defaults to `png`). +- `EXPORT_CONSTR`: The constructor to use. Can be **chart**, **stockChart**, **mapChart**, or **ganttChart** (defaults to `chart`). +- `EXPORT_B64`: Boolean flag, set to **true** to receive the chart in the _base64_ format instead of the _binary_ (defaults to `false`). +- `EXPORT_NO_DOWNLOAD`: Boolean flag, set to **true** to exclude attachment headers from the response (defaults to `false`). +- `EXPORT_HEIGHT`: The height of the exported chart. Overrides the option in the chart settings (defaults to ``). +- `EXPORT_WIDTH`: The width of the exported chart. Overrides the option in the chart settings (defaults to ``). +- `EXPORT_SCALE`: The scale of the exported chart. Ranges between **0.1** and **5.0** (defaults to ``). +- `EXPORT_DEFAULT_HEIGHT`: The default height for exported charts if not set explicitly (defaults to `400`). +- `EXPORT_DEFAULT_WIDTH`: The default width for exported charts if not set explicitly (defaults to `600`). +- `EXPORT_DEFAULT_SCALE`: The default scale for exported charts if not set explicitly. Ranges between **0.1** and **5.0** (defaults to `1`). - `EXPORT_RASTERIZATION_TIMEOUT`: The specified duration, in milliseconds, to wait for rendering a webpage (defaults to `1500`). ### Custom Logic Config @@ -371,7 +383,7 @@ These variables are set in your environment and take precedence over options fro ### Logging Config -- `LOGGING_LEVEL`: The logging level to be used. Can be **0** - silent, **1** - error, **2** - warning, **3** - notice, **4** - verbose or **5** benchmark (defaults to `4`). +- `LOGGING_LEVEL`: The logging level to be used. Can be **0** - silent, **1** - error, **2** - warning, **3** - notice, **4** - verbose or **5** - benchmark (defaults to `4`). - `LOGGING_FILE`: The name of a log file. The `logToFile` and `logDest` options also need to be set to enable file logging (defaults to `highcharts-export-server.log`). - `LOGGING_DEST`: The path to store log files. The `logToFile` option also needs to be set to enable file logging (defaults to `log`). - `LOGGING_TO_CONSOLE`: Enables or disables showing logs in the console (defaults to `true`). @@ -422,31 +434,46 @@ For readability reasons, many options passed as CLI arguments have slightly diff _Available options:_ -- `--infile`: The input file should include a name and a type (**.json** or **.svg**) and must be a correctly formatted JSON or SVG file (defaults to `false`). -- `--instr`: An input in a form of a stringified JSON or SVG file. Overrides the `--infile` option (defaults to `false`). -- `--options`: An alias for the `--instr` option (defaults to `false`). -- `--outfile`: The output filename, accompanied by a type (**jpeg**, **png**, **pdf**, or **svg**). Ignores the `--type` flag (defaults to `false`). +- `--puppeteerArgs`: A stringified version of additional Puppeteer arguments sent during browser initialization. The string can be enclosed in _[_ and _]_, and the arguments must be separated by the _;_ character (defaults to [Default JSON Config](#default-json-config)). +- `--version`: Highcharts version to use (defaults to `latest`). +- `--cdnUrl`: Highcharts CDN URL of scripts to be used (defaults to `https://code.highcharts.com/`). +- `--forceFetch`: The flag that determines whether to refetch all scripts after each server rerun (defaults to `false`). +- `--cachePath`: A directory path where the fetched Highcharts scripts should be placed (defaults to `.cache`). +- `--coreScripts`: Highcharts core scripts to fetch (defaults to [Default JSON Config](#default-json-config)). +- `--moduleScripts`: Highcharts module scripts to fetch (defaults to [Default JSON Config](#default-json-config)). +- `--indicatorScripts`: Highcharts indicator scripts to fetch (defaults to [Default JSON Config](#default-json-config)). +- `--customScripts`: Additional custom scripts or dependencies to fetch (defaults to [Default JSON Config](#default-json-config)). +- `--infile`: The input file should include a name and a type (**.json** or **.svg**) and must be a correctly formatted JSON or SVG file (defaults to `null`). +- `--instr`: An input in a form of a stringified JSON or SVG file. Overrides the `--infile` option (defaults to `null`). +- `--options`: An alias for the `--instr` option (defaults to `null`). +- `--svg`: A string containing SVG representation to render as a chart (defaults to `null`). +- `--outfile`: The output filename, accompanied by a type (**jpeg**, **png**, **pdf**, or **svg**). Ignores the `--type` flag (defaults to `null`). - `--type`: The format of the file to export to. Can be **jpeg**, **png**, **pdf**, or **svg** (defaults to `png`). -- `--constr`: The constructor to use. Can be **chart**, **stockChart**, **mapChart** or **ganttChart** (defaults to `chart`). -- `--height`: The height of the exported chart. Overrides the option in the chart settings (defaults to `400`). -- `--width`: The width of the exported chart. Overrides the option in the chart settings (defaults to `600`). -- `--scale`: The scale of the exported chart. Ranges between **0.1** and **5.0** (defaults to `1`). -- `--globalOptions`: Either a stringified JSON or a filename containing global options to be passed into the `Highcharts.setOptions` (defaults to `false`). -- `--themeOptions`: Either a stringified JSON or a filename containing theme options to be passed into the `Highcharts.setOptions` (defaults to `false`). -- `--batch`: Initiates a batch job with a string containing input/output pairs: **"in=out;in=out;.."** (defaults to `false`). +- `--constr`: The constructor to use. Can be **chart**, **stockChart**, **mapChart**, or **ganttChart** (defaults to `chart`). +- `--b64`: Boolean flag, set to **true** to receive the chart in the _base64_ format instead of the _binary_ (defaults to `false`). +- `--noDownload`: Boolean flag, set to **true** to exclude attachment headers from the response (defaults to `false`). +- `--height`: The height of the exported chart. Overrides the option in the chart settings (defaults to `null`). +- `--width`: The width of the exported chart. Overrides the option in the chart settings (defaults to `null`). +- `--scale`: The scale of the exported chart. Ranges between **0.1** and **5.0** (defaults to `null`). +- `--defaultHeight`: The default height for exported charts if not set explicitly (defaults to `400`). +- `--defaultWidth`: The default width for exported charts if not set explicitly (defaults to `600`). +- `--defaultScale`: The default scale for exported charts if not set explicitly. Ranges between **0.1** and **5.0** (defaults to `1`). +- `--globalOptions`: Either a stringified JSON or a filename containing global options to be passed into the `Highcharts.setOptions` (defaults to `null`). +- `--themeOptions`: Either a stringified JSON or a filename containing theme options to be passed into the `Highcharts.setOptions` (defaults to `null`). +- `--batch`: Initiates a batch job with a string containing input/output pairs: **"in=out;in=out;.."** (defaults to `null`). - `--rasterizationTimeout`: The specified duration, in milliseconds, to wait for rendering a webpage (defaults to `1500`). - `--allowCodeExecution`: Controls whether the execution of arbitrary code is allowed during the exporting process (defaults to `false`). - `--allowFileResources`: Controls the ability to inject resources from the filesystem. This setting has no effect when running as a server (defaults to `false`). -- `--customCode`: Custom code to execute before chart initialization. It can be a function, code wrapped within a function, or a filename with the _.js_ extension (defaults to `false`). -- `--callback`: JavaScript code to run during construction. It can be a function or a filename with the _.js_ extension (defaults to `false`). -- `--resources`: Additional resources in the form of a stringified JSON. It may contain `files` (array of JS filenames), `js` (stringified JS), and `css` (stringified CSS) sections (defaults to `false`). -- `--loadConfig`: A file containing a pre-defined configuration to use (defaults to `false`). -- `--createConfig`: Enables setting options through a prompt and saving them in a provided config file (defaults to `false`). +- `--customCode`: Custom code to execute before chart initialization. It can be a function, code wrapped within a function, or a filename with the _.js_ extension (defaults to `null`). +- `--callback`: JavaScript code to run during construction. It can be a function or a filename with the _.js_ extension (defaults to `null`). +- `--resources`: Additional resources in the form of a stringified JSON. It may contain `files` (array of JS filenames), `js` (stringified JS), and `css` (stringified CSS) sections (defaults to `null`). +- `--loadConfig`: A file containing a pre-defined configuration to use (defaults to `null`). +- `--createConfig`: Enables setting options through a prompt and saving them in a provided config file (defaults to `null`). - `--enableServer`: If set to **true**, the server starts on 0.0.0.0 (defaults to `false`). - `--host`: The hostname of the server. Additionally, it starts a server listening on the provided hostname (defaults to `0.0.0.0`). - `--port`: The port to be used for the server when enabled (defaults to `7801`). -- `--serverBenchmarking`: Indicates whether to display the duration, in milliseconds, of specific actions that occur on the server while serving a request (defaults to `false`). -- `--proxyHost`: The host of the proxy server to use, if it exists (defaults to `false`). +- `--serverBenchmarking`: Indicates whether to display a message with the duration, in milliseconds, of specific actions that occur on the server while serving a request (defaults to `false`). +- `--proxyHost`: The host of the proxy server to use, if it exists (defaults to `null`). - `--proxyPort`: The port of the proxy server to use, if it exists (defaults to `false`). - `--proxyTimeout`: The timeout, in milliseconds, for the proxy server to use, if it exists (defaults to `5000`). - `--enableRateLimiting`: Enables rate limiting for the server (defaults to `false`). @@ -454,12 +481,12 @@ _Available options:_ - `--window`: The time window, in minutes, for the rate limiting (defaults to `1`). - `--delay`: The delay duration for each successive request before reaching the maximum limit (defaults to `0`). - `--trustProxy`: Set this to **true** if the server is behind a load balancer (defaults to `false`). -- `--skipKey`: Allows bypassing the rate limiter and should be provided with the `--skipToken` argument (defaults to `false`). -- `--skipToken`: Allows bypassing the rate limiter and should be provided with the `--skipKey` argument (defaults to `false`). +- `--skipKey`: Allows bypassing the rate limiter and should be provided with the `skipToken` argument (defaults to `null`). +- `--skipToken`: Allows bypassing the rate limiter and should be provided with the `skipKey` argument (defaults to `null`). - `--enableSsl`: Enables or disables the SSL protocol (defaults to `false`). - `--sslForce`: If set to **true**, the server is forced to serve only over HTTPS (defaults to `false`). - `--sslPort`: The port on which to run the SSL server (defaults to `443`). -- `--certPath`: The path to the SSL certificate/key file (defaults to `false`). +- `--sslCertPath`: The path to the SSL certificate/key file (defaults to `null`). - `--minWorkers`: The number of minimum and initial pool workers to spawn (defaults to `4`). - `--maxWorkers`: The number of maximum pool workers to spawn (defaults to `8`). - `--workLimit`: The number of work pieces that can be performed before restarting the worker process (defaults to `40`). @@ -469,7 +496,7 @@ _Available options:_ - `--idleTimeout`: The duration, in milliseconds, after which an idle resource is destroyed (defaults to `30000`). - `--createRetryInterval`: The duration, in milliseconds, to wait before retrying the create process in case of a failure (defaults to `200`). - `--reaperInterval`: The duration, in milliseconds, after which the check for idle resources to destroy is triggered (defaults to `1000`). -- `--poolBenchmarking`: Indicate whether to show statistics for the pool of resources or not (defaults to `false`). +- `--poolBenchmarking`: Indicates whether to show statistics for the pool of resources or not (defaults to `false`). - `--logLevel`: The logging level to be used. Can be **0** - silent, **1** - error, **2** - warning, **3** - notice, **4** - verbose or **5** - benchmark (defaults to `4`). - `--logFile`: The name of a log file. The `logToFile` and `logDest` options also need to be set to enable file logging (defaults to `highcharts-export-server.log`). - `--logDest`: The path to store log files. The `logToFile` option also needs to be set to enable file logging (defaults to `log`). @@ -477,7 +504,7 @@ _Available options:_ - `--logToFile`: Enables or disables creation of the log directory and saving the log into a .log file (defaults to `true`). - `--enableUi`: Enables or disables the user interface (UI) for the Export Server (defaults to `false`). - `--uiRoute`: The endpoint route to which the user interface (UI) should be attached (defaults to `/`). -- `--nodeEnv`: The type of Node.js environment (defaults to `production`). +- `--nodeEnv`: The type of Node.js environment. The value controls whether to include the error's stack in a response or not. Can be development or production (defaults to `production`). - `--listenToProcessExits`: Decides whether or not to attach _process.exit_ handlers (defaults to `true`). - `--noLogo`: Skip printing the logo on a startup. Will be replaced by a simple text (defaults to `false`). - `--hardResetPage`: Determines whether the page's content should be reset from scratch, including Highcharts scripts (defaults to `false`). @@ -497,14 +524,13 @@ _Available options:_ - `--wsReconnectAttempts`: The number of reconnect attempts before returning a connection error (defaults to `3`). - `--wsMessageInterval`: The interval, in milliseconds, for auto sending the data through a WebSocket connection (defaults to `3600000`). - `--wsGatherAllOptions`: Decides whether or not to gather all chart's options or only ones defined in the **telemetry.json** file (defaults to `false`). -- `--wsUrl`: The URL of the WebSocket server (defaults to `false`). -- `--wsSecret`: The secret used to create a JSON Web Token sent to the WebSocket server (defaults to `false`). +- `--wsUrl`: The URL of the WebSocket server (defaults to `null`). # HTTP Server Apart from using as a CLI tool, which allows you to run one command at a time, it is also possible to configure the server to accept POST requests. The simplest way to enable the server is to run the command below: -`highcharts-export-server --enableServer 1` +`highcharts-export-server --enableServer true` ## Server Test @@ -525,6 +551,8 @@ To enable SSL support, add `--certPath ` when running the serve ## HTTP Server POST Arguments +_Available request arguments:_ + The server accepts the following arguments in a POST request body: - `infile`: Chart options in the form of JSON or stringified JSON. @@ -784,7 +812,7 @@ Please refer to the [Configuration](https://github.com/highcharts/node-export-se ## Note About Version And Help Information -Typing `highcharts-export-server --version` will display information about the current version, and `highcharts-export-server --help` will display information about available CLI options. +Typing `highcharts-export-server --v` will display information about the current version of the Export Server, and `highcharts-export-server --h` will display information about available CLI options. ## Note About Deprecated Options @@ -800,7 +828,7 @@ Additionally, some options are now named differently due to the new structure an - `fromFile` -> `loadConfig` - `sslOnly` -> `force` or `sslForce` -- `sslPath` -> `certPath` +- `sslPath` -> `sslCertPath` - `rateLimit` -> `maxRequests` - `workers` -> `maxWorkers` diff --git a/bin/cli.js b/bin/cli.js index df7576b4..45d4fadf 100644 --- a/bin/cli.js +++ b/bin/cli.js @@ -38,15 +38,13 @@ async function start() { const args = process.argv; // Display version information if requested - if ( - ['-v', '--v', '-version', '--version'].includes(args[args.length - 1]) - ) { + if (['-v', '--v'].includes(args[args.length - 1])) { // Print logo with the version information return printVersion(); } // Display help information if requested - if (['-h', '--h', '-help', '--help'].includes(args[args.length - 1])) { + if (['-h', '--h'].includes(args[args.length - 1])) { // Print logo with the version information return printUsage(); } diff --git a/lib/browser.js b/lib/browser.js index 7c45fa21..cc9518ec 100644 --- a/lib/browser.js +++ b/lib/browser.js @@ -70,6 +70,8 @@ export async function createBrowser(puppeteerArgs = []) { headless: other.browserShellMode ? 'shell' : true, userDataDir: 'tmp', args: puppeteerArgs, + // Must be disabled for debugging to work + pipe: !enabledDebug, handleSIGINT: false, handleSIGTERM: false, handleSIGHUP: false, diff --git a/lib/chart.js b/lib/chart.js index 061910c7..b0f4c06a 100644 --- a/lib/chart.js +++ b/lib/chart.js @@ -25,7 +25,7 @@ import { roundNumber, wrapAround } from './utils.js'; -import { validateOption } from './validate.js'; +import { strictValidate, validateOption } from './validate.js'; import ExportError from './errors/ExportError.js'; @@ -53,13 +53,13 @@ export async function startExport(options = getOptions(), endCallback) { const exportOptions = options.export; // Export using SVG as an input - if (options.payload.svg !== null) { + if (exportOptions.svg !== null) { try { log(4, '[chart] Attempting to export from an SVG input.'); // Export from an SVG string const result = _exportAsString( - sanitize(options.payload.svg), // #209 + sanitize(exportOptions.svg), // #209 options, endCallback ); @@ -308,7 +308,10 @@ function _doStraightInject(options, endCallback) { } catch (error) { return endCallback( new ExportError( - `[chart] Malformed input detected for ${options.export?.requestId || '?'}. Please make sure that your JSON/JavaScript options are sent using the "options" attribute, and that if you're using SVG, it is unescaped.` + (options.export?.requestId + ? `[chart] Malformed input detected for the request: ${options.export?.requestId}. ` + : '[chart] Malformed input detected. ') + + 'Please make sure that your JSON/JavaScript options are sent using the "options" attribute, and that if you are using SVG, it is unescaped.' ).setError(error) ); } @@ -414,6 +417,15 @@ async function _prepareExport(options, endCallback, json, svg) { ..._findChartSize(exportOptions) }; + /// TO DO: Check if it is needed, the height error occurs here + // The last strict validation of options right before exporting process + // try { + // // Validate final options + // options = strictValidate(options); + // } catch (error) { + // logZodIssues(1, error.issues, '[config] Final options validation error'); + // } + // Post the work to the pool try { const result = await postWork(exportOptions.strInj || json || svg, options); diff --git a/lib/logger.js b/lib/logger.js index ff4b92bb..08495033 100644 --- a/lib/logger.js +++ b/lib/logger.js @@ -211,11 +211,6 @@ export function enableFileLogging(dest, file) { file, toFile: true }; - - // Need a correct logs destination - if (logging.dest.length === 0) { - return log(1, '[logger] File logging initialization: no path supplied.'); - } } /** diff --git a/lib/schemas/config.js b/lib/schemas/config.js index b2622b88..c6a1f8e4 100644 --- a/lib/schemas/config.js +++ b/lib/schemas/config.js @@ -79,6 +79,7 @@ export const defaultConfig = { ], type: ['string[]'], envLink: 'PUPPETEER_ARGS', + cliName: 'puppeteerArgs', description: 'Array of Puppeteer arguments', promptOptions: { type: 'list', @@ -226,6 +227,7 @@ export const defaultConfig = { 'https://cdnjs.cloudflare.com/ajax/libs/moment-timezone/0.5.45/moment-timezone-with-data.min.js' ], type: ['string[]'], + envLink: 'HIGHCHARTS_CUSTOM_SCRIPTS', description: 'Additional custom scripts or dependencies to fetch', promptOptions: { type: 'list', @@ -237,23 +239,45 @@ export const defaultConfig = { infile: { value: null, type: ['string', 'null'], + envLink: 'EXPORT_INFILE', description: - 'Input filename with type, formatted correctly as JSON or SVG' + 'Input filename with type, formatted correctly as JSON or SVG', + promptOptions: { + type: 'text' + } }, instr: { value: null, type: ['object', 'string', 'null'], + envLink: 'EXPORT_INSTR', description: - 'Overrides the `infile` with JSON, stringified JSON, or SVG input' + 'Overrides the `infile` with JSON, stringified JSON, or SVG input', + promptOptions: { + type: 'text' + } }, options: { value: null, type: ['object', 'string', 'null'], - description: 'Alias for the `instr` option' + envLink: 'EXPORT_OPTIONS', + description: 'Alias for the `instr` option', + promptOptions: { + type: 'text' + } + }, + svg: { + value: null, + type: ['string', 'null'], + envLink: 'EXPORT_SVG', + description: 'SVG string representation of the chart to render', + promptOptions: { + type: 'text' + } }, outfile: { value: null, type: ['string', 'null'], + envLink: 'EXPORT_OUTFILE', description: 'Output filename with type. Can be jpeg, png, pdf, or svg and ignores `type` option', promptOptions: { @@ -283,6 +307,26 @@ export const defaultConfig = { choices: ['chart', 'stockChart', 'mapChart', 'ganttChart'] } }, + b64: { + value: false, + type: ['boolean'], + envLink: 'EXPORT_B64', + description: + 'Whether or not to the chart should be received in base64 format instead of binary', + promptOptions: { + type: 'toggle' + } + }, + noDownload: { + value: false, + type: ['boolean'], + envLink: 'EXPORT_NO_DOWNLOAD', + description: + 'Whether or not to include or exclude attachment headers in the response', + promptOptions: { + type: 'toggle' + } + }, defaultHeight: { value: 400, type: ['number'], @@ -316,36 +360,60 @@ export const defaultConfig = { height: { value: null, type: ['number', 'null'], - description: 'Height of the exported chart, overrides chart settings' + envLink: 'EXPORT_HEIGHT', + description: 'Height of the exported chart, overrides chart settings', + promptOptions: { + type: 'number' + } }, width: { value: null, type: ['number', 'null'], - description: 'Width of the exported chart, overrides chart settings' + envLink: 'EXPORT_WIDTH', + description: 'Width of the exported chart, overrides chart settings', + promptOptions: { + type: 'number' + } }, scale: { value: null, type: ['number', 'null'], + envLink: 'EXPORT_SCALE', description: - 'Scale of the exported chart, overrides chart settings. Ranges from 0.1 to 5.0' + 'Scale of the exported chart, overrides chart settings. Ranges from 0.1 to 5.0', + promptOptions: { + type: 'number' + } }, globalOptions: { value: null, type: ['object', 'string', 'null'], + envLink: 'EXPORT_GLOBAL_OPTIONS', description: - 'JSON, stringified JSON or filename with global options for Highcharts.setOptions' + 'JSON, stringified JSON or filename with global options for Highcharts.setOptions', + promptOptions: { + type: 'text' + } }, themeOptions: { value: null, type: ['object', 'string', 'null'], + envLink: 'EXPORT_THEME_OPTIONS', description: - 'JSON, stringified JSON or filename with theme options for Highcharts.setOptions' + 'JSON, stringified JSON or filename with theme options for Highcharts.setOptions', + promptOptions: { + type: 'text' + } }, batch: { value: null, type: ['string', 'null'], + envLink: 'EXPORT_BATCH', description: - 'Batch job string with input/output pairs: "in=out;in=out;..."' + 'Batch job string with input/output pairs: "in=out;in=out;..."', + promptOptions: { + type: 'text' + } }, rasterizationTimeout: { value: 1500, @@ -381,32 +449,52 @@ export const defaultConfig = { customCode: { value: null, type: ['string', 'null'], + envLink: 'CUSTOM_LOGIC_CUSTOM_CODE', description: - 'Custom code to execute before chart initialization. Can be a function, code wrapped in a function, or a .js filename' + 'Custom code to execute before chart initialization. Can be a function, code wrapped in a function, or a .js filename', + promptOptions: { + type: 'text' + } }, callback: { value: null, type: ['string', 'null'], + envLink: 'CUSTOM_LOGIC_CALLBACK', description: - 'JavaScript code to run during construction. Can be a function or a .js filename' + 'JavaScript code to run during construction. Can be a function or a .js filename', + promptOptions: { + type: 'text' + } }, resources: { value: null, type: ['object', 'string', 'null'], + envLink: 'CUSTOM_LOGIC_RESOURCES', description: - 'Additional resources as JSON, stringified JSON, or filename, containing files, js, and css sections' + 'Additional resources as JSON, stringified JSON, or filename, containing files, js, and css sections', + promptOptions: { + type: 'text' + } }, loadConfig: { value: null, type: ['string', 'null'], + envLink: 'CUSTOM_LOGIC_LOAD_CONFIG', legacyName: 'fromFile', - description: 'File with a pre-defined configuration to use' + description: 'File with a pre-defined configuration to use', + promptOptions: { + type: 'text' + } }, createConfig: { value: null, type: ['string', 'null'], + envLink: 'CUSTOM_LOGIC_CREATE_CONFIG', description: - 'Prompt-based option setting, saved to a provided config file' + 'Prompt-based option setting, saved to a provided config file', + promptOptions: { + type: 'text' + } } }, server: { @@ -980,47 +1068,6 @@ export const defaultConfig = { promptOptions: { type: 'text' } - }, - secret: { - value: null, - type: ['string', 'null'], - envLink: 'WEB_SOCKET_SECRET', - cliName: 'wsSecret', - description: - 'Secret used to create a JSON Web Token for the WebSocket server', - promptOptions: { - type: 'text' - } - } - }, - payload: { - svg: { - value: null, - type: ['string', 'null'], - description: 'SVG string representation of the chart to render' - }, - b64: { - value: false, - type: ['boolean'], - description: - 'Whether or not to the chart should be received in base64 format instead of binary', - promptOptions: { - type: 'toggle' - } - }, - noDownload: { - value: false, - type: ['boolean'], - description: - 'Whether or not to include or exclude attachment headers in the response', - promptOptions: { - type: 'toggle' - } - }, - requestId: { - value: null, - type: ['string', 'null'], - description: 'Unique identifier for each request served by the server' } } }; diff --git a/lib/server/error.js b/lib/server/error.js index 065cc91d..d2493e2f 100644 --- a/lib/server/error.js +++ b/lib/server/error.js @@ -33,7 +33,7 @@ function logErrorMiddleware(error, request, response, next) { } // Call the returnErrorMiddleware - next(error); + return next(error); } /** diff --git a/lib/server/routes/export.js b/lib/server/routes/export.js index a5ea9b74..7ef6d6b4 100644 --- a/lib/server/routes/export.js +++ b/lib/server/routes/export.js @@ -12,28 +12,13 @@ See LICENSE file in root for details. *******************************************************************************/ -import { v4 as uuid } from 'uuid'; - -import { getAllowCodeExecution, startExport } from '../../chart.js'; +import { startExport } from '../../chart.js'; import { getOptions } from '../../config.js'; -import { log, logZodIssues } from '../../logger.js'; +import { log } from '../../logger.js'; import { prepareTelemetry } from '../../telemetry.js'; -import { - fixConstr, - fixType, - isCorrectJSON, - isObjectEmpty, - isPrivateRangeUrlFound, - measureTime, - mergeConfigOptions -} from '../../utils.js'; -import { looseValidate } from '../../validate.js'; +import { measureTime, mergeConfigOptions } from '../../utils.js'; -import NoCorrectBodyError from '../../errors/NoCorrectBodyError.js'; -import NoCorrectChartDataError from '../../errors/NoCorrectChartDataError.js'; import NoCorrectResultError from '../../errors/NoCorrectResultError.js'; -import PrivateRangeUrlError from '../../errors/PrivateRangeUrlError.js'; -import ValidationError from '../../errors/ValidationError.js'; // Reversed MIME types const reversedMime = { @@ -59,101 +44,20 @@ async function requestExport(request, response, next) { // Start counting time for a request const requestCounter = measureTime(); - // Create a unique ID for a request - const uniqueId = uuid().replace(/-/g, ''); - - // Get the request body - const body = request.body; - - // Throw 'NoCorrectBodyError' if there is no correct body - if (!body || isObjectEmpty(body)) { - log( - 2, - `The request with ID ${uniqueId} from ${ - request.headers['x-forwarded-for'] || request.connection.remoteAddress - } was incorrect. Received payload is empty.` - ); - throw new NoCorrectBodyError(); - } - - // Get the allowCodeExecution option for the server - const allowCodeExecution = getAllowCodeExecution(); - - // Find a correct chart options - const instr = isCorrectJSON( - // Use one of the below - body.infile || body.options || body.data, - // Stringify options - true, - // Allow or disallow functions - allowCodeExecution - ); - - // Throw 'NoCorrectChartDataError' if there is no correct options or SVG - if (!instr && !body.svg) { - log( - 2, - `The request with ID ${uniqueId} from ${ - request.headers['x-forwarded-for'] || request.connection.remoteAddress - } was incorrect. Received payload is ${JSON.stringify(body)}.` - ); - throw new NoCorrectChartDataError(); - } - // In case the connection is closed, force to abort further actions let connectionAborted = false; request.socket.on('close', () => { connectionAborted = true; }); - // Gather and organize options from the payload - let requestOptions = null; - try { - // Validate options from the request body - requestOptions = looseValidate({ - export: { - instr, - type: fixType(body.type), - constr: fixConstr(body.constr), - height: body.height, - width: body.width, - scale: body.scale, - globalOptions: isCorrectJSON( - body.globalOptions, - true, - allowCodeExecution - ), - themeOptions: isCorrectJSON( - body.themeOptions, - true, - allowCodeExecution - ) - }, - customLogic: { - allowCodeExecution, - allowFileResources: false, - resources: isCorrectJSON(body.resources, true, allowCodeExecution), - callback: body.callback, - customCode: body.customCode - }, - payload: { - svg: body.svg, - b64: body.b64, - noDownload: body.noDownload, - requestId: uniqueId - } - }); - } catch (error) { - logZodIssues( - 1, - error.issues, - '[config] Request options validation error' - ); - throw new ValidationError(); - } + // Get the options previously validated in the middleware + const requestOptions = request.validatedOptions; + + // Get the request id + const requestId = requestOptions.payload.requestId; // Log info about an incoming request with correct data - log(4, `[export] Got an incoming HTTP request with ID ${uniqueId}.`); + log(4, `[export] Got an incoming HTTP request with ID ${requestId}.`); // Get the current server's global options const defaultOptions = getOptions(); @@ -162,12 +66,7 @@ async function requestExport(request, response, next) { const options = mergeConfigOptions(defaultOptions, requestOptions); // Save the instr in the options - options.export.options = instr; - - // Test xlink:href elements from payload's SVG - if (options.payload.svg && isPrivateRangeUrlFound(options.payload.svg)) { - throw PrivateRangeUrlError(); - } + options.export.options = requestOptions.export.instr; // Start the export process await startExport(options, (error, data) => { @@ -178,7 +77,7 @@ async function requestExport(request, response, next) { if (defaultOptions.server.benchmarking) { log( 5, - `[benchmark] Request: ${uniqueId} - After the whole exporting process: ${requestCounter()}ms.` + `[benchmark] Request: ${requestId} - After the whole exporting process: ${requestCounter()}ms.` ); } @@ -199,7 +98,7 @@ async function requestExport(request, response, next) { if (!data || !data.result) { log( 2, - `The request with ID ${uniqueId} from ${ + `The request with ID ${requestId} from ${ request.headers['x-forwarded-for'] || request.connection.remoteAddress } was incorrect. Received result is ${data.result}.` @@ -208,7 +107,7 @@ async function requestExport(request, response, next) { } // Telemetry only for the options based request - if (!options.payload.svg) { + if (!options.export.svg) { // Prepare and send the options through the WebSocket prepareTelemetry(options.export.options, options.payload.requestId); } @@ -219,7 +118,7 @@ async function requestExport(request, response, next) { const type = data.options.export.type; // If only base64 is required, return it - if (body.b64) { + if (options.export.b64) { // SVG Exception for the Highcharts 11.3.0 version if (type === 'pdf' || type == 'svg') { return response.send( @@ -235,12 +134,8 @@ async function requestExport(request, response, next) { response.header('Content-Type', reversedMime[type] || 'image/png'); // Decide whether to download or not chart file - if (!body.noDownload) { - response.attachment( - `${request.params.filename || request.body.filename || 'chart'}.${ - type || 'png' - }` - ); + if (!options.export.noDownload) { + response.attachment(options.export.outfile); } // If SVG, return plain content @@ -250,18 +145,18 @@ async function requestExport(request, response, next) { } }); } catch (error) { - next(error); + return next(error); } } export default (app) => { /** - * Adds the POST / a route for handling POST requests at the root endpoint. + * Adds the POST / - a route for handling POST requests at the root endpoint. */ app.post('/', requestExport); /** - * Adds the POST /:filename a route for handling POST requests with + * Adds the POST /:filename - a route for handling POST requests with * a specified filename parameter. */ app.post('/:filename', requestExport); diff --git a/lib/server/routes/health.js b/lib/server/routes/health.js index 46c5be6a..0ca48a8e 100644 --- a/lib/server/routes/health.js +++ b/lib/server/routes/health.js @@ -77,7 +77,7 @@ export default (app) => { const period = successRates.length; const movingAverage = _calculateMovingAverage(); - log(4, '[health.js] GET /health [200] - returning server health.'); + log(4, '[health] Returning server health.'); response.send({ status: 'OK', diff --git a/lib/server/routes/versionChange.js b/lib/server/routes/versionChange.js index 343c7a24..d5ca3cfa 100644 --- a/lib/server/routes/versionChange.js +++ b/lib/server/routes/versionChange.js @@ -73,7 +73,7 @@ export default (app) => throw new HttpError('No new version supplied.', 400); } } catch (error) { - next(error); + return next(error); } } ); diff --git a/lib/server/server.js b/lib/server/server.js index 3893ec1e..26b7fe8d 100644 --- a/lib/server/server.js +++ b/lib/server/server.js @@ -32,13 +32,21 @@ import versionChangeRoute from './routes/versionChange.js'; import exportRoute from './routes/export.js'; import healthRoute from './routes/health.js'; import uiRoute from './routes/ui.js'; +import validateHandler from './validate.js'; import ExportError from '../errors/ExportError.js'; -import HttpError from '../errors/HttpError.js'; // Array of an active servers const activeServers = new Map(); +// Enable parsing of form data (files) with Multer package +const upload = multer({ + storage: multer.memoryStorage(), + limits: { + fieldSize: 50 * 1024 * 1024 + } +}); + // Create express app const app = express(); @@ -48,14 +56,6 @@ app.disable('x-powered-by'); // Enable CORS support app.use(cors()); -// Enable parsing of form data (files) with Multer package -const upload = multer({ - storage: multer.memoryStorage(), - limits: { - fieldSize: 50 * 1024 * 1024 - } -}); - // Enable body parser for JSON data app.use( express.json({ @@ -74,22 +74,6 @@ app.use( // Use only non-file multipart form fields app.use(upload.none()); -// Allow only JSON, URL-encoded and form data without files types of data -app.use((request, response, next) => { - const contentType = request.headers['content-type'] || ''; - if ( - !contentType.includes('application/json') && - !contentType.includes('application/x-www-form-urlencoded') && - !contentType.includes('multipart/form-data') - ) { - throw new HttpError( - 'Content-Type must be application/json, application/x-www-form-urlencoded, or multipart/form-data.', - 415 - ); - } - next(); -}); - /** * Starts HTTP or/and HTTPS server based on the provided configuration. The * `serverOptions` object contains all server related properties (see the @@ -192,6 +176,9 @@ export async function startServer(serverOptions = getOptions().server) { // Set up static folder's route app.use(express.static(join(__dirname, 'public'))); + // Set up validation handler + validateHandler(app); + // Set up routes healthRoute(app); exportRoute(app); diff --git a/lib/server/validate.js b/lib/server/validate.js new file mode 100644 index 00000000..cc033aa2 --- /dev/null +++ b/lib/server/validate.js @@ -0,0 +1,160 @@ +/******************************************************************************* + +Highcharts Export Server + +Copyright (c) 2016-2024, Highsoft + +Licenced under the MIT licence. + +Additionally a valid Highcharts license is required for use. + +See LICENSE file in root for details. + +*******************************************************************************/ + +import { v4 as uuid } from 'uuid'; + +import { getAllowCodeExecution } from '../chart.js'; +import { log, logZodIssues } from '../logger.js'; +import { + fixConstr, + fixType, + isCorrectJSON, + isObjectEmpty, + isPrivateRangeUrlFound +} from '../utils.js'; +import { looseValidate } from '../validate.js'; + +import HttpError from '../errors/HttpError.js'; +import NoCorrectBodyError from '../errors/NoCorrectBodyError.js'; +import NoCorrectChartDataError from '../errors/NoCorrectChartDataError.js'; +import PrivateRangeUrlError from '../errors/PrivateRangeUrlError.js'; +import ValidationError from '../errors/ValidationError.js'; + +/** + * Middleware for validating the content-type header. + * + * @param {Express.Request} request - The Express request object. + * @param {Express.Response} response - The Express response object. + * @param {Function} next - The next middleware function. + */ +function contentTypeMiddleware(request, response, next) { + // Get the content type header + const contentType = request.headers['content-type'] || ''; + + // Allow only JSON, URL-encoded and form data without files types of data + if ( + !contentType.includes('application/json') && + !contentType.includes('application/x-www-form-urlencoded') && + !contentType.includes('multipart/form-data') + ) { + throw new HttpError( + 'Content-Type must be application/json, application/x-www-form-urlencoded, or multipart/form-data.', + 415 + ); + } + return next(); +} + +/** + * Middleware for validating the request body. + * + * @param {Express.Request} request - The Express request object. + * @param {Express.Response} response - The Express response object. + * @param {Function} next - The next middleware function. + */ +function requestBodyMiddleware(request, response, next) { + // Get the request body + const body = request.body; + + // Create a unique ID for a request + const requestId = uuid().replace(/-/g, ''); + + // Throw 'NoCorrectBodyError' if there is no correct body + if (!body || isObjectEmpty(body)) { + log( + 2, + `The request with ID ${requestId} from ${ + request.headers['x-forwarded-for'] || request.connection.remoteAddress + } was incorrect. Received payload is empty.` + ); + throw new NoCorrectBodyError(); + } + + // Get the allowCodeExecution option for the server + const allowCodeExecution = getAllowCodeExecution(); + + // Find a correct chart options + const instr = isCorrectJSON( + // Use one of the below + body.infile || body.options || body.data, + // Stringify options + true, + // Allow or disallow functions + allowCodeExecution + ); + + // Throw 'NoCorrectChartDataError' if there is no correct chart data + if (instr === null && !body.svg) { + log( + 2, + `The request with ID ${requestId} from ${ + request.headers['x-forwarded-for'] || request.connection.remoteAddress + } was incorrect. Received payload is missing correct chart data for export: ${JSON.stringify(body)}.` + ); + throw new NoCorrectChartDataError(); + } + + // Test xlink:href elements from payload's SVG + if (body.svg && isPrivateRangeUrlFound(body.svg)) { + throw PrivateRangeUrlError(); + } + + try { + // Validate options from the body and store parsed structure in the request + request.validatedOptions = looseValidate({ + export: { + instr, + svg: body.svg, + outfile: + body.outfile || + `${request.params.filename || 'chart'}.${fixType(body.type)}`, + type: fixType(body.type), + constr: fixConstr(body.constr), + b64: body.b64, + noDownload: body.noDownload, + height: body.height, + width: body.width, + scale: body.scale, + globalOptions: isCorrectJSON( + body.globalOptions, + true, + allowCodeExecution + ), + themeOptions: isCorrectJSON(body.themeOptions, true, allowCodeExecution) + }, + customLogic: { + allowCodeExecution, + allowFileResources: false, + customCode: body.customCode, + callback: body.callback, + resources: isCorrectJSON(body.resources, true, allowCodeExecution) + }, + payload: { + requestId + } + }); + } catch (error) { + logZodIssues(1, error.issues, '[config] Request options validation error'); + throw new ValidationError(); + } + return next(); +} + +export default (app) => { + // Add content type validation middleware + app.post(['/', '/:filename'], contentTypeMiddleware); + + // Add request body request validation middleware + app.post(['/', '/:filename'], requestBodyMiddleware); +}; diff --git a/lib/server/webSocket.js b/lib/server/webSocket.js index e8c29b33..7a1f6274 100644 --- a/lib/server/webSocket.js +++ b/lib/server/webSocket.js @@ -21,6 +21,7 @@ import { log, logWithStack } from '../logger.js'; import { telemetryData } from '../telemetry.js'; import { addTimer } from '../timers.js'; import { getNewDate } from '../utils.js'; +import { envs } from '../validate.js'; // WebSocket clients map const webSocketClients = new Map(); @@ -40,12 +41,15 @@ let messageInterval = null; export function webSocketInit(address) { webSocketOptions = getOptions().webSocket; if (webSocketOptions.enable === true) { + // Get the secret directly from envs + const webSocketSecret = envs.WEB_SOCKET_SECRET; + // Options for the WebSocket connection const connectionOptions = { rejectUnauthorized: webSocketOptions.rejectUnauthorized, headers: { // Set an access token - auth: jwt.sign({ success: 'success' }, webSocketOptions.secret, { + auth: jwt.sign({ success: 'success' }, webSocketSecret, { algorithm: 'HS256' }), // Send the server address in a custom header diff --git a/lib/validate.js b/lib/validate.js index f61666cf..7d9fecf4 100644 --- a/lib/validate.js +++ b/lib/validate.js @@ -94,10 +94,10 @@ const v = { * The validation schema ensures that: * * - When `strictCheck` is true, the schema will accept trimmed strings except - * the forbidden values: 'false', 'NaN', 'undefined', 'null', and ''. + * the forbidden values: 'false', 'undefined', 'null', and ''. * * - When `strictCheck` is false, the schema will accept trimmed strings and - * null. The forbidden values: 'false', 'NaN', 'undefined', 'null', and '' + * null. The forbidden values: 'false', 'undefined', 'null', and '' * will be transformed to null. * * @param {boolean} strictCheck - Determines if stricter validation should be @@ -111,8 +111,7 @@ const v = { .string() .trim() .refine( - (value) => - !['false', 'NaN', 'undefined', 'null', ''].includes(value), + (value) => !['false', 'undefined', 'null', ''].includes(value), { params: { errorMessage: `The string contains a forbidden value` @@ -123,9 +122,7 @@ const v = { .string() .trim() .transform((value) => - !['false', 'NaN', 'undefined', 'null', ''].includes(value) - ? value - : null + !['false', 'undefined', 'null', ''].includes(value) ? value : null ) .nullable(); }, @@ -162,51 +159,6 @@ const v = { .nullable(); }, - /** - * The `object` validator that returns a Zod schema with an optional stricter - * check based on the `strictCheck` parameter. - * - * The validation schema ensures that: - * - * - When `strictCheck` is true, the schema will accept any key-value pairs - * objects and null. - * - * - When `strictCheck` is false, the schema will accept any key-value pairs - * objects, null and trimmed string values which start with the '{' and end - * with the '}' and will be transformed to null if the provided value is - * 'undefined', 'null', or ''. - * - * @param {boolean} strictCheck - Determines if stricter validation should be - * applied. - * - * @returns {z.ZodSchema} A Zod schema object for validating object values. - */ - object(strictCheck) { - return strictCheck - ? z.object({}).passthrough().nullable() - : z - .union([ - z - .string() - .trim() - .refine( - (value) => - (value.startsWith('{') && value.endsWith('}')) || - ['undefined', 'null', ''].includes(value), - { - params: { - errorMessage: `The value must be a string that starts with '{' and ends with '}'` - } - } - ) - .transform((value) => - !['undefined', 'null', ''].includes(value) ? value : null - ), - z.object({}).passthrough() - ]) - .nullable(); - }, - /** * The `stringArray` validator that returns a Zod schema with an optional * stricter check based on the `strictCheck` parameter. @@ -221,13 +173,15 @@ const v = { * from the '[' and ']' characters and by the logic provided through the * `filterCallback`. If the array is empty, it will be transformed to null. * + * @param {function} filterCallback - The filter callback. + * @param {string} separator - The separator for spliting a string. * @param {boolean} strictCheck - Determines if stricter validation should be * applied. * * @returns {z.ZodSchema} A Zod schema object for validating array of string * values. */ - stringArray(filterCallback, strictCheck) { + stringArray(filterCallback, separator, strictCheck) { const arraySchema = z.string().trim().array(); const stringSchema = z .string() @@ -239,7 +193,7 @@ const v = { if (value.endsWith(']')) { value = value.slice(0, -1); } - return value.split(','); + return value.split(separator); }); const transformCallback = (value) => @@ -406,6 +360,79 @@ const v = { !['undefined', 'null', ''].includes(value) ? value : null ) .nullable(); + }, + + /** + * The `chartConfig` validator that returns a Zod schema. + * + * The validation schema ensures that the schema will accept object values or + * trimmed string values that contain ' + value.indexOf('= 0 || + value.indexOf('= 0 || + (value.startsWith('{') && value.endsWith('}')) || + ['undefined', 'null', ''].includes(value), + { + params: { + errorMessage: `The value must be a string that contains ' + !['undefined', 'null', ''].includes(value) ? value : null + ), + z.object({}).passthrough() + ]) + .nullable(); + }, + + /** + * The `additionalOptions` validator that returns a Zod schema. + * + * The validation schema ensures that the schema will accept object values or + * trimmed string values that end with '.json' and are at least one character + * long excluding the extension, start with the '{' and end with the '}', and + * null. The 'undefined', 'null', and '' values will be transformed to null. + * + * @returns {z.ZodSchema} A Zod schema object for validating additional chart + * options value. + */ + additionalOptions() { + return z + .union([ + z + .string() + .trim() + .refine( + (value) => + (value.length >= 6 && value.endsWith('.json')) || + (value.startsWith('{') && value.endsWith('}')) || + ['undefined', 'null', ''].includes(value), + { + params: { + errorMessage: `The value must be a string that ends with '.json' or starts with '{' and ends with '}'` + } + } + ) + .transform((value) => + !['undefined', 'null', ''].includes(value) ? value : null + ), + z.object({}).passthrough() + ]) + .nullable(); } }; @@ -433,7 +460,8 @@ const config = { */ args(strictCheck) { return v.stringArray( - (value) => !['false', 'undefined', 'NaN', 'null', ''].includes(value), + (value) => !['false', 'undefined', 'null', ''].includes(value), + ';', strictCheck ); }, @@ -569,6 +597,7 @@ const config = { coreScripts(strictCheck) { return v.stringArray( (value) => coreScripts.value.includes(value), + ',', strictCheck ); }, @@ -588,6 +617,7 @@ const config = { moduleScripts(strictCheck) { return v.stringArray( (value) => moduleScripts.value.includes(value), + ',', strictCheck ); }, @@ -607,6 +637,7 @@ const config = { indicatorScripts(strictCheck) { return v.stringArray( (value) => indicatorScripts.value.includes(value), + ',', strictCheck ); }, @@ -626,6 +657,7 @@ const config = { customScripts(strictCheck) { return v.stringArray( (value) => value.startsWith('https://') || value.startsWith('http://'), + ',', strictCheck ); }, @@ -688,85 +720,60 @@ const config = { }, /** - * The `instr` validator that returns a Zod schema with an optional stricter - * check based on the `strictCheck` parameter. + * The `instr` validator that returns a Zod schema. * - * The validation schema ensures the same work as the `object` validator. - * - * @param {boolean} strictCheck - Determines if stricter validation should be - * applied. + * The validation schema ensures the same work as the `options` validator. * * @returns {z.ZodSchema} A Zod schema object for validating the `instr` * option. */ - instr(strictCheck) { - //// return v.object(strictCheck); - return strictCheck - ? z.object({}).passthrough().nullable() - : z - .union([ - z - .string() - .trim() - .refine( - (value) => - value.indexOf('= 0 || - value.indexOf('= 0 || - (value.startsWith('{') && value.endsWith('}')) || - ['undefined', 'null', ''].includes(value), - { - params: { - errorMessage: `The value must be a string that contains ' - !['undefined', 'null', ''].includes(value) ? value : null - ), - z.object({}).passthrough() - ]) - .nullable(); + instr() { + return v.chartConfig(); }, /** - * The `options` validator that returns a Zod schema with an optional stricter - * check based on the `strictCheck` parameter. + * The `options` validator that returns a Zod schema. + * + * The validation schema ensures the same work as the `options` validator. * - * The validation schema ensures the same work as the `object` validator. + * @returns {z.ZodSchema} A Zod schema object for validating the `options` + * option. + */ + options() { + return v.chartConfig(); + }, + + /** + * The `svg` validator that returns a Zod schema. + * + * The validation schema ensures that the schema will accept object values or + * trimmed string values that contain ' - value.indexOf('= 0 || - value.indexOf('= 0 || - (value.startsWith('{') && value.endsWith('}')) || - ['undefined', 'null', ''].includes(value), - { - params: { - errorMessage: `The value must be a string that contains ' - !['undefined', 'null', ''].includes(value) ? value : null - ), - z.object({}).passthrough() - ]) - .nullable(); + svg() { + return z + .string() + .trim() + .refine( + (value) => + value.indexOf('= 0 || + value.indexOf('= 0 || + ['false', 'undefined', 'null', ''].includes(value), + { + params: { + errorMessage: `The value must be a string that contains ' + !['false', 'undefined', 'null', ''].includes(value) ? value : null + ) + .nullable(); }, /** @@ -956,7 +963,7 @@ const config = { * The `height` validator that returns a Zod schema with an optional stricter * check based on the `strictCheck` parameter. * - * The validation schema ensures the same work as a nullable `positiveNum` + * The validation schema ensures the same work as a nullable `defaultHeight` * validator. * * @param {boolean} strictCheck - Determines if stricter validation should be @@ -966,14 +973,14 @@ const config = { * option. */ height(strictCheck) { - return v.positiveNum(strictCheck).nullable(); + return this.defaultHeight(strictCheck).nullable(); }, /** * The `width` validator that returns a Zod schema with an optional stricter * check based on the `strictCheck` parameter. * - * The validation schema ensures the same work as a nullable `positiveNum` + * The validation schema ensures the same work as a nullable `defaultWidth` * validator. * * @param {boolean} strictCheck - Determines if stricter validation should be @@ -983,7 +990,7 @@ const config = { * option. */ width(strictCheck) { - return v.positiveNum(strictCheck).nullable(); + return this.defaultWidth(strictCheck).nullable(); }, /** @@ -1004,83 +1011,29 @@ const config = { }, /** - * The `globalOptions` validator that returns a Zod schema with an optional - * stricter check based on the `strictCheck` parameter. + * The `globalOptions` validator that returns a Zod schema. * - * The validation schema ensures the same work as the `object` validator. - * - * @param {boolean} strictCheck - Determines if stricter validation should be - * applied. + * The validation schema ensures the same work as the `additionalOptions` + * validator. * * @returns {z.ZodSchema} A Zod schema object for validating the * `globalOptions` option. */ - globalOptions(strictCheck) { - //// return v.object(strictCheck); - return strictCheck - ? z.object({}).passthrough().nullable() - : z - .union([ - z - .string() - .trim() - .refine( - (value) => - (value.length >= 6 && value.endsWith('.json')) || - (value.startsWith('{') && value.endsWith('}')) || - ['undefined', 'null', ''].includes(value), - { - params: { - errorMessage: `The value must be a string that ends with '.json' or starts with '{' and ends with '}'` - } - } - ) - .transform((value) => - !['undefined', 'null', ''].includes(value) ? value : null - ), - z.object({}).passthrough() - ]) - .nullable(); + globalOptions() { + return v.additionalOptions(); }, /** - * The `themeOptions` validator that returns a Zod schema with an optional - * stricter check based on the `strictCheck` parameter. + * The `themeOptions` validator that returns a Zod schema. * - * The validation schema ensures the same work as the `object` validator. - * - * @param {boolean} strictCheck - Determines if stricter validation should be - * applied. + * The validation schema ensures the same work as the `additionalOptions` + * validator. * * @returns {z.ZodSchema} A Zod schema object for validating the * `themeOptions` option. */ - themeOptions(strictCheck) { - //// return v.object(strictCheck); - return strictCheck - ? z.object({}).passthrough().nullable() - : z - .union([ - z - .string() - .trim() - .refine( - (value) => - (value.length >= 6 && value.endsWith('.json')) || - (value.startsWith('{') && value.endsWith('}')) || - ['undefined', 'null', ''].includes(value), - { - params: { - errorMessage: `The value must be a string that ends with '.json' or starts with '{' and ends with '}'` - } - } - ) - .transform((value) => - !['undefined', 'null', ''].includes(value) ? value : null - ), - z.object({}).passthrough() - ]) - .nullable(); + themeOptions() { + return v.additionalOptions(); }, /** @@ -1210,8 +1163,8 @@ const config = { css: v.string(false), files: v .stringArray( - (value) => - !['false', 'undefined', 'NaN', 'null', ''].includes(value), + (value) => !['undefined', 'null', ''].includes(value), + ',', true ) .nullable() @@ -1260,6 +1213,7 @@ const config = { * stricter check based on the `strictCheck` parameter. * * The validation schema ensures the same work as the `string` validator. + * Additionally, it must be a string that ends with '.json'. * * @param {boolean} strictCheck - Determines if stricter validation should be * applied. @@ -1268,14 +1222,24 @@ const config = { * option. */ loadConfig(strictCheck) { - return v.string(strictCheck); + return v + .string(strictCheck) + .refine( + (value) => + value === null || (value.length >= 6 && value.endsWith('.json')), + { + params: { + errorMessage: `The value must be a string that ends with .json ` + } + } + ); }, /** * The `createConfig` validator that returns a Zod schema with an optional * stricter check based on the `strictCheck` parameter. * - * The validation schema ensures the same work as the `string` validator. + * The validation schema ensures the same work as the `loadConfig` validator. * * @param {boolean} strictCheck - Determines if stricter validation should be * applied. @@ -1284,7 +1248,7 @@ const config = { * `createConfig` option. */ createConfig(strictCheck) { - return v.string(strictCheck); + return this.loadConfig(strictCheck); }, /** @@ -1805,6 +1769,7 @@ const config = { * check based on the `strictCheck` parameter. * * The validation schema ensures the same work as the `string` validator. + * Additionally, it must be a string that ends with '.log'. * * @param {boolean} strictCheck - Determines if stricter validation should be * applied. @@ -1813,7 +1778,17 @@ const config = { * option. */ logFile(strictCheck) { - return v.string(strictCheck); + return v + .string(strictCheck) + .refine( + (value) => + value === null || (value.length >= 5 && value.endsWith('.log')), + { + params: { + errorMessage: `The value must be a string that ends with '.log'` + } + } + ); }, /** @@ -2254,56 +2229,6 @@ const config = { return v.string(strictCheck); }, - /** - * The `svg` validator that returns a Zod schema with an optional stricter - * check based on the `strictCheck` parameter. - * - * The validation schema ensures the same work as the `string` validator. - * - * @param {boolean} strictCheck - Determines if stricter validation should be - * applied. - * - * @returns {z.ZodSchema} A Zod schema object for validating the `svg` option. - */ - svg(strictCheck) { - //// return v.string(strictCheck); - return strictCheck - ? z - .string() - .trim() - .refine( - (value) => - value.indexOf('= 0 || - value.indexOf('= 0 || - !['false', 'NaN', 'undefined', 'null', ''].includes(value), - { - params: { - errorMessage: `The value must be a string that contains ' - value.indexOf('= 0 || - value.indexOf('= 0 || - ['false', 'NaN', 'undefined', 'null', ''].includes(value), - { - params: { - errorMessage: `The value must be a string that contains ' - !['false', 'NaN', 'undefined', 'null', ''].includes(value) - ? value - : null - ) - .nullable(); - }, - /** * The `b64` validator that returns a Zod schema with an optional stricter * check based on the `strictCheck` parameter. @@ -2340,6 +2265,7 @@ const config = { * stricter check based on the `strictCheck` parameter. * * The validation schema ensures the same work as the `string` validator. + * Additionally, it must be a stringified UUID or can be null. * * @param {boolean} strictCheck - Determines if stricter validation should be * applied. @@ -2347,8 +2273,11 @@ const config = { * @returns {z.ZodSchema} A Zod schema object for validating the `requestId` * option. */ - requestId(strictCheck) { - return v.string(strictCheck); + requestId() { + return z + .string() + .uuid({ message: 'The value must be a stringified UUID' }) + .nullable(); } }; @@ -2380,19 +2309,22 @@ const ExportSchema = (strictCheck) => z .object({ infile: config.infile(strictCheck), - instr: config.instr(false), - options: config.options(false), + instr: config.instr(), + options: config.options(), + svg: config.svg(), outfile: config.outfile(strictCheck), type: config.type(strictCheck), constr: config.constr(strictCheck), + b64: config.b64(strictCheck), + noDownload: config.noDownload(strictCheck), defaultHeight: config.defaultHeight(strictCheck), defaultWidth: config.defaultWidth(strictCheck), defaultScale: config.defaultScale(strictCheck), height: config.height(strictCheck), width: config.width(strictCheck), scale: config.scale(strictCheck), - globalOptions: config.globalOptions(false), - themeOptions: config.themeOptions(false), + globalOptions: config.globalOptions(), + themeOptions: config.themeOptions(), batch: config.batch(false), rasterizationTimeout: config.rasterizationTimeout(strictCheck) }) @@ -2535,19 +2467,15 @@ const WebSocketSchema = (strictCheck) => reconnectAttempts: config.wsReconnectAttempts(strictCheck), messageInterval: config.wsMessageInterval(strictCheck), gatherAllOptions: config.wsGatherAllOptions(strictCheck), - url: config.wsUrl(strictCheck), - secret: config.wsSecret(false) + url: config.wsUrl(strictCheck) }) .partial(); // Schema for the payload section of options -const PayloadSchema = (strictCheck) => +const PayloadSchema = () => z .object({ - svg: config.svg(false), - b64: config.b64(strictCheck), - noDownload: config.noDownload(strictCheck), - requestId: config.requestId(false) + requestId: config.requestId() }) .partial(); @@ -2564,7 +2492,7 @@ export const StrictConfigSchema = z.object({ other: OtherSchema(true), debug: DebugSchema(true), webSocket: WebSocketSchema(true), - payload: PayloadSchema(true) + payload: PayloadSchema() }); // Loose schema for the config @@ -2580,7 +2508,7 @@ export const LooseConfigSchema = z.object({ other: OtherSchema(false), debug: DebugSchema(false), webSocket: WebSocketSchema(false), - payload: PayloadSchema(false) + payload: PayloadSchema() }); // Schema for the environment variables config @@ -2600,16 +2528,34 @@ export const EnvSchema = z.object({ HIGHCHARTS_CUSTOM_SCRIPTS: config.customScripts(false), // export + EXPORT_INFILE: config.infile(false), + EXPORT_INSTR: config.instr(), + EXPORT_OPTIONS: config.options(), + EXPORT_SVG: config.svg(), EXPORT_TYPE: config.type(false), EXPORT_CONSTR: config.constr(false), + EXPORT_OUTFILE: config.outfile(false), + EXPORT_B64: config.b64(false), + EXPORT_NO_DOWNLOAD: config.noDownload(false), + EXPORT_HEIGHT: config.height(false), + EXPORT_WIDTH: config.width(false), + EXPORT_SCALE: config.scale(false), EXPORT_DEFAULT_HEIGHT: config.defaultHeight(false), EXPORT_DEFAULT_WIDTH: config.defaultWidth(false), EXPORT_DEFAULT_SCALE: config.defaultScale(false), + EXPORT_GLOBAL_OPTIONS: config.globalOptions(), + EXPORT_THEME_OPTIONS: config.themeOptions(), + EXPORT_BATCH: config.batch(false), EXPORT_RASTERIZATION_TIMEOUT: config.rasterizationTimeout(false), // custom CUSTOM_LOGIC_ALLOW_CODE_EXECUTION: config.allowCodeExecution(false), CUSTOM_LOGIC_ALLOW_FILE_RESOURCES: config.allowFileResources(false), + CUSTOM_LOGIC_CUSTOM_CODE: config.customCode(false), + CUSTOM_LOGIC_CALLBACK: config.callback(false), + CUSTOM_LOGIC_RESOURCES: config.resources(false), + CUSTOM_LOGIC_LOAD_CONFIG: config.loadConfig(false), + CUSTOM_LOGIC_CREATE_CONFIG: config.createConfig(false), // server SERVER_ENABLE: config.enableServer(false), diff --git a/package-lock.json b/package-lock.json index aff42bcc..3a2e0b62 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,16 +11,16 @@ "dependencies": { "colors": "1.4.0", "cors": "^2.8.5", - "dompurify": "^3.1.6", + "dompurify": "3.1.6", "dotenv": "^16.4.5", "express": "^4.21.0", - "express-rate-limit": "^7.4.0", + "express-rate-limit": "^7.4.1", "https-proxy-agent": "^7.0.5", "jsdom": "^25.0.1", "jsonwebtoken": "^9.0.2", "multer": "^1.4.5-lts.1", "prompts": "^2.4.2", - "puppeteer": "^23.4.1", + "puppeteer": "^23.5.1", "tarn": "^3.0.2", "uuid": "^10.0.0", "ws": "^8.18.0", @@ -34,14 +34,14 @@ "@rollup/plugin-terser": "^0.4.4", "eslint": "^8.57.0", "eslint-config-prettier": "^9.1.0", - "eslint-plugin-import": "^2.30.0", + "eslint-plugin-import": "^2.31.0", "eslint-plugin-prettier": "^5.2.1", "husky": "^9.1.6", "jest": "^29.7.0", "lint-staged": "^15.2.10", "nodemon": "^3.1.7", "prettier": "^3.3.3", - "rollup": "^4.22.5" + "rollup": "^4.24.0" }, "engines": { "node": ">=18.12.0" @@ -62,12 +62,12 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", - "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.25.7.tgz", + "integrity": "sha512-0xZJFNE5XMpENsgfHYTw8FbX4kv53mFLn2i3XPoq69LyhYSCBJtitaHx9QnsVTrsogI4Z3+HtEfZ2/GFPOtf5g==", "license": "MIT", "dependencies": { - "@babel/highlight": "^7.24.7", + "@babel/highlight": "^7.25.7", "picocolors": "^1.0.0" }, "engines": { @@ -75,9 +75,9 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.25.4", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.4.tgz", - "integrity": "sha512-+LGRog6RAsCJrrrg/IO6LGmpphNe5DiK30dGjCoxxeGv49B10/3XYGxPsAwrDlMFcFEvdAUavDT8r9k/hSyQqQ==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.7.tgz", + "integrity": "sha512-9ickoLz+hcXCeh7jrcin+/SLWm+GkxE2kTvoYyp38p4WkdFXfQJxDFGWp/YHjiKLPx06z2A7W8XKuqbReXDzsw==", "dev": true, "license": "MIT", "engines": { @@ -85,22 +85,22 @@ } }, "node_modules/@babel/core": { - "version": "7.25.2", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.25.2.tgz", - "integrity": "sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.25.7.tgz", + "integrity": "sha512-yJ474Zv3cwiSOO9nXJuqzvwEeM+chDuQ8GJirw+pZ91sCGCyOZ3dJkVE09fTV0VEVzXyLWhh3G/AolYTPX7Mow==", "dev": true, "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.24.7", - "@babel/generator": "^7.25.0", - "@babel/helper-compilation-targets": "^7.25.2", - "@babel/helper-module-transforms": "^7.25.2", - "@babel/helpers": "^7.25.0", - "@babel/parser": "^7.25.0", - "@babel/template": "^7.25.0", - "@babel/traverse": "^7.25.2", - "@babel/types": "^7.25.2", + "@babel/code-frame": "^7.25.7", + "@babel/generator": "^7.25.7", + "@babel/helper-compilation-targets": "^7.25.7", + "@babel/helper-module-transforms": "^7.25.7", + "@babel/helpers": "^7.25.7", + "@babel/parser": "^7.25.7", + "@babel/template": "^7.25.7", + "@babel/traverse": "^7.25.7", + "@babel/types": "^7.25.7", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -116,31 +116,31 @@ } }, "node_modules/@babel/generator": { - "version": "7.25.6", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.6.tgz", - "integrity": "sha512-VPC82gr1seXOpkjAAKoLhP50vx4vGNlF4msF64dSFq1P8RfB+QAuJWGHPXXPc8QyfVWwwB/TNNU4+ayZmHNbZw==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.7.tgz", + "integrity": "sha512-5Dqpl5fyV9pIAD62yK9P7fcA768uVPUyrQmqpqstHWgMma4feF1x/oFysBCVZLY5wJ2GkMUCdsNDnGZrPoR6rA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.25.6", + "@babel/types": "^7.25.7", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", - "jsesc": "^2.5.1" + "jsesc": "^3.0.2" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.25.2", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.2.tgz", - "integrity": "sha512-U2U5LsSaZ7TAt3cfaymQ8WHh0pxvdHoEk6HVpaexxixjyEquMh0L0YNJNM6CTGKMXV1iksi0iZkGw4AcFkPaaw==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.7.tgz", + "integrity": "sha512-DniTEax0sv6isaw6qSQSfV4gVRNtw2rte8HHM45t9ZR0xILaufBRNkpMifCRiAPyvL4ACD6v0gfCwCmtOQaV4A==", "dev": true, "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.25.2", - "@babel/helper-validator-option": "^7.24.8", - "browserslist": "^4.23.1", + "@babel/compat-data": "^7.25.7", + "@babel/helper-validator-option": "^7.25.7", + "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" }, @@ -149,30 +149,30 @@ } }, "node_modules/@babel/helper-module-imports": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz", - "integrity": "sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.7.tgz", + "integrity": "sha512-o0xCgpNmRohmnoWKQ0Ij8IdddjyBFE4T2kagL/x6M3+4zUgc+4qTOUBoNe4XxDskt1HPKO007ZPiMgLDq2s7Kw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/traverse": "^7.24.7", - "@babel/types": "^7.24.7" + "@babel/traverse": "^7.25.7", + "@babel/types": "^7.25.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.25.2", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.25.2.tgz", - "integrity": "sha512-BjyRAbix6j/wv83ftcVJmBt72QtHI56C7JXZoG2xATiLpmoC7dpd8WnkikExHDVPpi/3qCmO6WY1EaXOluiecQ==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.25.7.tgz", + "integrity": "sha512-k/6f8dKG3yDz/qCwSM+RKovjMix563SLxQFo0UhRNo239SP6n9u5/eLtKD6EAjwta2JHJ49CsD8pms2HdNiMMQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.24.7", - "@babel/helper-simple-access": "^7.24.7", - "@babel/helper-validator-identifier": "^7.24.7", - "@babel/traverse": "^7.25.2" + "@babel/helper-module-imports": "^7.25.7", + "@babel/helper-simple-access": "^7.25.7", + "@babel/helper-validator-identifier": "^7.25.7", + "@babel/traverse": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -182,9 +182,9 @@ } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz", - "integrity": "sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.25.7.tgz", + "integrity": "sha512-eaPZai0PiqCi09pPs3pAFfl/zYgGaE6IdXtYvmf0qlcDTd3WCtO7JWCcRd64e0EQrcYgiHibEZnOGsSY4QSgaw==", "dev": true, "license": "MIT", "engines": { @@ -192,23 +192,23 @@ } }, "node_modules/@babel/helper-simple-access": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.7.tgz", - "integrity": "sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.25.7.tgz", + "integrity": "sha512-FPGAkJmyoChQeM+ruBGIDyrT2tKfZJO8NcxdC+CWNJi7N8/rZpSxK7yvBJ5O/nF1gfu5KzN7VKG3YVSLFfRSxQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/traverse": "^7.24.7", - "@babel/types": "^7.24.7" + "@babel/traverse": "^7.25.7", + "@babel/types": "^7.25.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-string-parser": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz", - "integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.7.tgz", + "integrity": "sha512-CbkjYdsJNHFk8uqpEkpCvRs3YRp9tY6FmFY7wLMSYuGYkrdUi7r2lc4/wqsvlHoMznX3WJ9IP8giGPq68T/Y6g==", "dev": true, "license": "MIT", "engines": { @@ -216,18 +216,18 @@ } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", - "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.7.tgz", + "integrity": "sha512-AM6TzwYqGChO45oiuPqwL2t20/HdMC1rTPAesnBCgPCSF1x3oN9MVUwQV2iyz4xqWrctwK5RNC8LV22kaQCNYg==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-option": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz", - "integrity": "sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.7.tgz", + "integrity": "sha512-ytbPLsm+GjArDYXJ8Ydr1c/KJuutjF2besPNbIZnZ6MKUxi/uTA22t2ymmA4WFjZFpjiAMO0xuuJPqK2nvDVfQ==", "dev": true, "license": "MIT", "engines": { @@ -235,26 +235,26 @@ } }, "node_modules/@babel/helpers": { - "version": "7.25.6", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.25.6.tgz", - "integrity": "sha512-Xg0tn4HcfTijTwfDwYlvVCl43V6h4KyVVX2aEm4qdO/PC6L2YvzLHFdmxhoeSA3eslcE6+ZVXHgWwopXYLNq4Q==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.25.7.tgz", + "integrity": "sha512-Sv6pASx7Esm38KQpF/U/OXLwPPrdGHNKoeblRxgZRLXnAtnkEe4ptJPDtAZM7fBLadbc1Q07kQpSiGQ0Jg6tRA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/template": "^7.25.0", - "@babel/types": "^7.25.6" + "@babel/template": "^7.25.7", + "@babel/types": "^7.25.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/highlight": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", - "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.25.7.tgz", + "integrity": "sha512-iYyACpW3iW8Fw+ZybQK+drQre+ns/tKpXbNESfrhNnPLIklLbXr7MYJ6gPEd0iETGLOK+SxMjVvKb/ffmk+FEw==", "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.24.7", + "@babel/helper-validator-identifier": "^7.25.7", "chalk": "^2.4.2", "js-tokens": "^4.0.0", "picocolors": "^1.0.0" @@ -335,13 +335,13 @@ } }, "node_modules/@babel/parser": { - "version": "7.25.6", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.6.tgz", - "integrity": "sha512-trGdfBdbD0l1ZPmcJ83eNxB9rbEax4ALFTF7fN386TMYbeCQbyme5cOEXQhbGXKebwGaB/J52w1mrklMcbgy6Q==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.7.tgz", + "integrity": "sha512-aZn7ETtQsjjGG5HruveUK06cU3Hljuhd9Iojm4M8WWv3wLE6OkE5PWbDUkItmMgegmccaITudyuW5RPYrYlgWw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.25.6" + "@babel/types": "^7.25.7" }, "bin": { "parser": "bin/babel-parser.js" @@ -406,13 +406,13 @@ } }, "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.25.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.25.6.tgz", - "integrity": "sha512-sXaDXaJN9SNLymBdlWFA+bjzBhFD617ZaFiY13dGt7TVslVvVgA6fkZOP7Ki3IGElC45lwHdOTrCtKZGVAWeLQ==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.25.7.tgz", + "integrity": "sha512-AqVo+dguCgmpi/3mYBdu9lkngOBlQ2w2vnNpa6gfiCxQZLzV4ZbhsXitJ2Yblkoe1VQwtHSaNmIaGll/26YWRw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.8" + "@babel/helper-plugin-utils": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -448,13 +448,13 @@ } }, "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.7.tgz", - "integrity": "sha512-6ddciUPe/mpMnOKv/U+RSd2vvVy+Yw/JfBB0ZHYjEZt9NLHmCUylNYlsbqCCS1Bffjlb0fCwC9Vqz+sBz6PsiQ==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.25.7.tgz", + "integrity": "sha512-ruZOnKO+ajVL/MVx+PwNBPOkrnXTXoWMtte1MBpegfCArhqOe3Bj52avVj1huLLxNKYKXYaSxZ2F+woK1ekXfw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -574,13 +574,13 @@ } }, "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.25.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.4.tgz", - "integrity": "sha512-uMOCoHVU52BsSWxPOMVv5qKRdeSlPuImUCB2dlPuBSU+W2/ROE7/Zg8F2Kepbk+8yBa68LlRKxO+xgEVWorsDg==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.7.tgz", + "integrity": "sha512-rR+5FDjpCHqqZN2bzZm18bVYGaejGq5ZkpVCJLXor/+zlSrSoc4KWcHI0URVWjl/68Dyr1uwZUz/1njycEAv9g==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.8" + "@babel/helper-plugin-utils": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -590,32 +590,32 @@ } }, "node_modules/@babel/template": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.0.tgz", - "integrity": "sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.7.tgz", + "integrity": "sha512-wRwtAgI3bAS+JGU2upWNL9lSlDcRCqD05BZ1n3X2ONLH1WilFP6O1otQjeMK/1g0pvYcXC7b/qVUB1keofjtZA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.24.7", - "@babel/parser": "^7.25.0", - "@babel/types": "^7.25.0" + "@babel/code-frame": "^7.25.7", + "@babel/parser": "^7.25.7", + "@babel/types": "^7.25.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.25.6", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.6.tgz", - "integrity": "sha512-9Vrcx5ZW6UwK5tvqsj0nGpp/XzqthkT0dqIc9g1AdtygFToNtTF67XzYS//dm+SAK9cp3B9R4ZO/46p63SCjlQ==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.7.tgz", + "integrity": "sha512-jatJPT1Zjqvh/1FyJs6qAHL+Dzb7sTb+xr7Q+gM1b+1oBsMsQQ4FkVKb6dFlJvLlVssqkRzV05Jzervt9yhnzg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.24.7", - "@babel/generator": "^7.25.6", - "@babel/parser": "^7.25.6", - "@babel/template": "^7.25.0", - "@babel/types": "^7.25.6", + "@babel/code-frame": "^7.25.7", + "@babel/generator": "^7.25.7", + "@babel/parser": "^7.25.7", + "@babel/template": "^7.25.7", + "@babel/types": "^7.25.7", "debug": "^4.3.1", "globals": "^11.1.0" }, @@ -634,14 +634,14 @@ } }, "node_modules/@babel/types": { - "version": "7.25.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.6.tgz", - "integrity": "sha512-/l42B1qxpG6RdfYf343Uw1vmDjeNhneUXtzhojE7pDgfpEypmRhI6j1kr17XCVv4Cgl9HdAiQY2x0GwKm7rWCw==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.7.tgz", + "integrity": "sha512-vwIVdXG+j+FOpkwqHRcBgHLYNL7XMkufrlaFvL9o6Ai9sJn9+PdyIL5qa0XzTZw084c+u9LOls53eoZWP/W5WQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.24.8", - "@babel/helper-validator-identifier": "^7.24.7", + "@babel/helper-string-parser": "^7.25.7", + "@babel/helper-validator-identifier": "^7.25.7", "to-fast-properties": "^2.0.0" }, "engines": { @@ -1342,9 +1342,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.22.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.22.5.tgz", - "integrity": "sha512-SU5cvamg0Eyu/F+kLeMXS7GoahL+OoizlclVFX3l5Ql6yNlywJJ0OuqTzUx0v+aHhPHEB/56CT06GQrRrGNYww==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.24.0.tgz", + "integrity": "sha512-Q6HJd7Y6xdB48x8ZNVDOqsbh2uByBhgK8PiQgPhwkIw/HC/YX5Ghq2mQY5sRMZWHb3VsFkWooUVOZHKr7DmDIA==", "cpu": [ "arm" ], @@ -1356,9 +1356,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.22.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.22.5.tgz", - "integrity": "sha512-S4pit5BP6E5R5C8S6tgU/drvgjtYW76FBuG6+ibG3tMvlD1h9LHVF9KmlmaUBQ8Obou7hEyS+0w+IR/VtxwNMQ==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.24.0.tgz", + "integrity": "sha512-ijLnS1qFId8xhKjT81uBHuuJp2lU4x2yxa4ctFPtG+MqEE6+C5f/+X/bStmxapgmwLwiL3ih122xv8kVARNAZA==", "cpu": [ "arm64" ], @@ -1370,9 +1370,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.22.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.22.5.tgz", - "integrity": "sha512-250ZGg4ipTL0TGvLlfACkIxS9+KLtIbn7BCZjsZj88zSg2Lvu3Xdw6dhAhfe/FjjXPVNCtcSp+WZjVsD3a/Zlw==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.24.0.tgz", + "integrity": "sha512-bIv+X9xeSs1XCk6DVvkO+S/z8/2AMt/2lMqdQbMrmVpgFvXlmde9mLcbQpztXm1tajC3raFDqegsH18HQPMYtA==", "cpu": [ "arm64" ], @@ -1384,9 +1384,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.22.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.22.5.tgz", - "integrity": "sha512-D8brJEFg5D+QxFcW6jYANu+Rr9SlKtTenmsX5hOSzNYVrK5oLAEMTUgKWYJP+wdKyCdeSwnapLsn+OVRFycuQg==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.24.0.tgz", + "integrity": "sha512-X6/nOwoFN7RT2svEQWUsW/5C/fYMBe4fnLK9DQk4SX4mgVBiTA9h64kjUYPvGQ0F/9xwJ5U5UfTbl6BEjaQdBQ==", "cpu": [ "x64" ], @@ -1398,9 +1398,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.22.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.22.5.tgz", - "integrity": "sha512-PNqXYmdNFyWNg0ma5LdY8wP+eQfdvyaBAojAXgO7/gs0Q/6TQJVXAXe8gwW9URjbS0YAammur0fynYGiWsKlXw==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.24.0.tgz", + "integrity": "sha512-0KXvIJQMOImLCVCz9uvvdPgfyWo93aHHp8ui3FrtOP57svqrF/roSSR5pjqL2hcMp0ljeGlU4q9o/rQaAQ3AYA==", "cpu": [ "arm" ], @@ -1412,9 +1412,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.22.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.22.5.tgz", - "integrity": "sha512-kSSCZOKz3HqlrEuwKd9TYv7vxPYD77vHSUvM2y0YaTGnFc8AdI5TTQRrM1yIp3tXCKrSL9A7JLoILjtad5t8pQ==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.24.0.tgz", + "integrity": "sha512-it2BW6kKFVh8xk/BnHfakEeoLPv8STIISekpoF+nBgWM4d55CZKc7T4Dx1pEbTnYm/xEKMgy1MNtYuoA8RFIWw==", "cpu": [ "arm" ], @@ -1426,9 +1426,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.22.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.22.5.tgz", - "integrity": "sha512-oTXQeJHRbOnwRnRffb6bmqmUugz0glXaPyspp4gbQOPVApdpRrY/j7KP3lr7M8kTfQTyrBUzFjj5EuHAhqH4/w==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.24.0.tgz", + "integrity": "sha512-i0xTLXjqap2eRfulFVlSnM5dEbTVque/3Pi4g2y7cxrs7+a9De42z4XxKLYJ7+OhE3IgxvfQM7vQc43bwTgPwA==", "cpu": [ "arm64" ], @@ -1440,9 +1440,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.22.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.22.5.tgz", - "integrity": "sha512-qnOTIIs6tIGFKCHdhYitgC2XQ2X25InIbZFor5wh+mALH84qnFHvc+vmWUpyX97B0hNvwNUL4B+MB8vJvH65Fw==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.24.0.tgz", + "integrity": "sha512-9E6MKUJhDuDh604Qco5yP/3qn3y7SLXYuiC0Rpr89aMScS2UAmK1wHP2b7KAa1nSjWJc/f/Lc0Wl1L47qjiyQw==", "cpu": [ "arm64" ], @@ -1454,9 +1454,9 @@ ] }, "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.22.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.22.5.tgz", - "integrity": "sha512-TMYu+DUdNlgBXING13rHSfUc3Ky5nLPbWs4bFnT+R6Vu3OvXkTkixvvBKk8uO4MT5Ab6lC3U7x8S8El2q5o56w==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.24.0.tgz", + "integrity": "sha512-2XFFPJ2XMEiF5Zi2EBf4h73oR1V/lycirxZxHZNc93SqDN/IWhYYSYj8I9381ikUFXZrz2v7r2tOVk2NBwxrWw==", "cpu": [ "ppc64" ], @@ -1468,9 +1468,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.22.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.22.5.tgz", - "integrity": "sha512-PTQq1Kz22ZRvuhr3uURH+U/Q/a0pbxJoICGSprNLAoBEkyD3Sh9qP5I0Asn0y0wejXQBbsVMRZRxlbGFD9OK4A==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.24.0.tgz", + "integrity": "sha512-M3Dg4hlwuntUCdzU7KjYqbbd+BLq3JMAOhCKdBE3TcMGMZbKkDdJ5ivNdehOssMCIokNHFOsv7DO4rlEOfyKpg==", "cpu": [ "riscv64" ], @@ -1482,9 +1482,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.22.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.22.5.tgz", - "integrity": "sha512-bR5nCojtpuMss6TDEmf/jnBnzlo+6n1UhgwqUvRoe4VIotC7FG1IKkyJbwsT7JDsF2jxR+NTnuOwiGv0hLyDoQ==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.24.0.tgz", + "integrity": "sha512-mjBaoo4ocxJppTorZVKWFpy1bfFj9FeCMJqzlMQGjpNPY9JwQi7OuS1axzNIk0nMX6jSgy6ZURDZ2w0QW6D56g==", "cpu": [ "s390x" ], @@ -1496,9 +1496,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.22.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.22.5.tgz", - "integrity": "sha512-N0jPPhHjGShcB9/XXZQWuWBKZQnC1F36Ce3sDqWpujsGjDz/CQtOL9LgTrJ+rJC8MJeesMWrMWVLKKNR/tMOCA==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.24.0.tgz", + "integrity": "sha512-ZXFk7M72R0YYFN5q13niV0B7G8/5dcQ9JDp8keJSfr3GoZeXEoMHP/HlvqROA3OMbMdfr19IjCeNAnPUG93b6A==", "cpu": [ "x64" ], @@ -1510,9 +1510,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.22.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.22.5.tgz", - "integrity": "sha512-uBa2e28ohzNNwjr6Uxm4XyaA1M/8aTgfF2T7UIlElLaeXkgpmIJ2EitVNQxjO9xLLLy60YqAgKn/AqSpCUkE9g==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.24.0.tgz", + "integrity": "sha512-w1i+L7kAXZNdYl+vFvzSZy8Y1arS7vMgIy8wusXJzRrPyof5LAb02KGr1PD2EkRcl73kHulIID0M501lN+vobQ==", "cpu": [ "x64" ], @@ -1524,9 +1524,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.22.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.22.5.tgz", - "integrity": "sha512-RXT8S1HP8AFN/Kr3tg4fuYrNxZ/pZf1HemC5Tsddc6HzgGnJm0+Lh5rAHJkDuW3StI0ynNXukidROMXYl6ew8w==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.24.0.tgz", + "integrity": "sha512-VXBrnPWgBpVDCVY6XF3LEW0pOU51KbaHhccHw6AS6vBWIC60eqsH19DAeeObl+g8nKAz04QFdl/Cefta0xQtUQ==", "cpu": [ "arm64" ], @@ -1538,9 +1538,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.22.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.22.5.tgz", - "integrity": "sha512-ElTYOh50InL8kzyUD6XsnPit7jYCKrphmddKAe1/Ytt74apOxDq5YEcbsiKs0fR3vff3jEneMM+3I7jbqaMyBg==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.24.0.tgz", + "integrity": "sha512-xrNcGDU0OxVcPTH/8n/ShH4UevZxKIO6HJFK0e15XItZP2UcaiLFd5kiX7hJnqCbSztUF8Qot+JWBC/QXRPYWQ==", "cpu": [ "ia32" ], @@ -1552,9 +1552,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.22.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.22.5.tgz", - "integrity": "sha512-+lvL/4mQxSV8MukpkKyyvfwhH266COcWlXE/1qxwN08ajovta3459zrjLghYMgDerlzNwLAcFpvU+WWE5y6nAQ==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.24.0.tgz", + "integrity": "sha512-fbMkAF7fufku0N2dE5TBXcNlg0pt0cJue4xBRE2Qc5Vqikxr4VCgKj/ht6SMdFcOacVA9rqF70APJ8RN/4vMJw==", "cpu": [ "x64" ], @@ -2484,9 +2484,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001664", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001664.tgz", - "integrity": "sha512-AmE7k4dXiNKQipgn7a2xg558IRqPN3jMQY/rOsbxDhrd0tyChwbITBfiwtnqz8bi2M5mIWbxAYBvk7W7QBUS2g==", + "version": "1.0.30001666", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001666.tgz", + "integrity": "sha512-gD14ICmoV5ZZM1OdzPWmpx+q4GyefaK06zi8hmfHV5xe4/2nOQX3+Dw5o+fSqOws2xVwL9j+anOPFwHzdEdV4g==", "dev": true, "funding": [ { @@ -2570,9 +2570,9 @@ } }, "node_modules/chromium-bidi": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.6.5.tgz", - "integrity": "sha512-RuLrmzYrxSb0s9SgpB+QN5jJucPduZQ/9SIe76MDxYJuecPW5mxMdacJ1f4EtgiV+R0p3sCkznTMvH0MPGFqjA==", + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.8.0.tgz", + "integrity": "sha512-uJydbGdTw0DEUjhoogGveneJVWX/9YuqkWePzMmkBYwtdAqo5d3J/ovNKFr+/2hWXYmYCr6it8mSSTIj6SS6Ug==", "license": "Apache-2.0", "dependencies": { "mitt": "3.0.1", @@ -3213,9 +3213,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.29", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.29.tgz", - "integrity": "sha512-PF8n2AlIhCKXQ+gTpiJi0VhcHDb69kYX4MtCiivctc2QD3XuNZ/XIOlbGzt7WAjjEev0TtaH6Cu3arZExm5DOw==", + "version": "1.5.31", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.31.tgz", + "integrity": "sha512-QcDoBbQeYt0+3CWcK/rEbuHvwpbT/8SV9T3OSgs6cX1FlcUAkgrkqbg9zLnDrMM/rLamzQwal4LYFCiWk861Tg==", "dev": true, "license": "ISC" }, @@ -3607,9 +3607,9 @@ } }, "node_modules/eslint-plugin-import": { - "version": "2.30.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.30.0.tgz", - "integrity": "sha512-/mHNE9jINJfiD2EKkg1BKyPyUk4zdnT54YgbOgfjSakWT5oyX/qQLVNTkehyfpcMxZXMy1zyonZ2v7hZTX43Yw==", + "version": "2.31.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.31.0.tgz", + "integrity": "sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==", "dev": true, "license": "MIT", "dependencies": { @@ -3621,7 +3621,7 @@ "debug": "^3.2.7", "doctrine": "^2.1.0", "eslint-import-resolver-node": "^0.3.9", - "eslint-module-utils": "^2.9.0", + "eslint-module-utils": "^2.12.0", "hasown": "^2.0.2", "is-core-module": "^2.15.1", "is-glob": "^4.0.3", @@ -3630,13 +3630,14 @@ "object.groupby": "^1.0.3", "object.values": "^1.2.0", "semver": "^6.3.1", + "string.prototype.trimend": "^1.0.8", "tsconfig-paths": "^3.15.0" }, "engines": { "node": ">=4" }, "peerDependencies": { - "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" } }, "node_modules/eslint-plugin-import/node_modules/debug": { @@ -3907,9 +3908,9 @@ } }, "node_modules/express-rate-limit": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.4.0.tgz", - "integrity": "sha512-v1204w3cXu5gCDmAvgvzI6qjzZzoMWKnyVDk3ACgfswTQLYiGen+r8w0VnXnGMmzEN/g8fwIQ4JrFFd4ZP6ssg==", + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.4.1.tgz", + "integrity": "sha512-KS3efpnpIDVIXopMc65EMbWbUht7qvTCdtCR2dD/IZmi9MIkopYESwyRqLgv8Pfu589+KqDqOdzJWW7AHoACeg==", "license": "MIT", "engines": { "node": ">= 16" @@ -5862,16 +5863,16 @@ } }, "node_modules/jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", + "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", "dev": true, "license": "MIT", "bin": { "jsesc": "bin/jsesc" }, "engines": { - "node": ">=4" + "node": ">=6" } }, "node_modules/json-buffer": { @@ -7393,17 +7394,17 @@ } }, "node_modules/puppeteer": { - "version": "23.4.1", - "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-23.4.1.tgz", - "integrity": "sha512-+wWfWTkQ8L9IB/3OVGSUp37c0eQ5za/85KdX+LAq2wTZkMdocgYGMCs+/91e2f/RXIYzve4x/uGxN8zG2sj8+w==", + "version": "23.5.1", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-23.5.1.tgz", + "integrity": "sha512-9WUnrCx7nB/GEMbJdKiSSU6G4t6Nzn39BBochWYyhK2wWxKwibZDwEF71AUP9D17Byn6OoPifsXhyvnqN/D4mQ==", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { "@puppeteer/browsers": "2.4.0", - "chromium-bidi": "0.6.5", + "chromium-bidi": "0.8.0", "cosmiconfig": "^9.0.0", "devtools-protocol": "0.0.1342118", - "puppeteer-core": "23.4.1", + "puppeteer-core": "23.5.1", "typed-query-selector": "^2.12.0" }, "bin": { @@ -7414,13 +7415,13 @@ } }, "node_modules/puppeteer-core": { - "version": "23.4.1", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-23.4.1.tgz", - "integrity": "sha512-uCxGtn8VE9PlKhdFJX/zZySi9K3Ufr3qUZe28jxJoZUqiMJOi+SFh2zhiFDSjWqZIDkc0FtnaCC+rewW3MYXmg==", + "version": "23.5.1", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-23.5.1.tgz", + "integrity": "sha512-We6xKCSZaZ23+GAYckeNfeDeJIVuhxOBsh/gZkbULu/XLFJ3umSiiQ8Ey927h3g/XrCCr8CnSZ5fvP5v2vB5Yw==", "license": "Apache-2.0", "dependencies": { "@puppeteer/browsers": "2.4.0", - "chromium-bidi": "0.6.5", + "chromium-bidi": "0.8.0", "debug": "^4.3.7", "devtools-protocol": "0.0.1342118", "typed-query-selector": "^2.12.0", @@ -7565,16 +7566,16 @@ } }, "node_modules/regexp.prototype.flags": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", - "integrity": "sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==", + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.3.tgz", + "integrity": "sha512-vqlC04+RQoFalODCbCumG2xIOvapzVMHwsyIGM/SIE8fRhFFsXeH8/QQ+s0T0kDAhKc4k30s73/0ydkHQz6HlQ==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.6", + "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-errors": "^1.3.0", - "set-function-name": "^2.0.1" + "set-function-name": "^2.0.2" }, "engines": { "node": ">= 0.4" @@ -7734,9 +7735,9 @@ } }, "node_modules/rollup": { - "version": "4.22.5", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.22.5.tgz", - "integrity": "sha512-WoinX7GeQOFMGznEcWA1WrTQCd/tpEbMkc3nuMs9BT0CPjMdSjPMTVClwWd4pgSQwJdP65SK9mTCNvItlr5o7w==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.24.0.tgz", + "integrity": "sha512-DOmrlGSXNk1DM0ljiQA+i+o0rSLhtii1je5wgk60j49d1jHT5YYttBv1iWOnYSTG+fZZESUOSNiAl89SIet+Cg==", "dev": true, "license": "MIT", "dependencies": { @@ -7750,22 +7751,22 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.22.5", - "@rollup/rollup-android-arm64": "4.22.5", - "@rollup/rollup-darwin-arm64": "4.22.5", - "@rollup/rollup-darwin-x64": "4.22.5", - "@rollup/rollup-linux-arm-gnueabihf": "4.22.5", - "@rollup/rollup-linux-arm-musleabihf": "4.22.5", - "@rollup/rollup-linux-arm64-gnu": "4.22.5", - "@rollup/rollup-linux-arm64-musl": "4.22.5", - "@rollup/rollup-linux-powerpc64le-gnu": "4.22.5", - "@rollup/rollup-linux-riscv64-gnu": "4.22.5", - "@rollup/rollup-linux-s390x-gnu": "4.22.5", - "@rollup/rollup-linux-x64-gnu": "4.22.5", - "@rollup/rollup-linux-x64-musl": "4.22.5", - "@rollup/rollup-win32-arm64-msvc": "4.22.5", - "@rollup/rollup-win32-ia32-msvc": "4.22.5", - "@rollup/rollup-win32-x64-msvc": "4.22.5", + "@rollup/rollup-android-arm-eabi": "4.24.0", + "@rollup/rollup-android-arm64": "4.24.0", + "@rollup/rollup-darwin-arm64": "4.24.0", + "@rollup/rollup-darwin-x64": "4.24.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.24.0", + "@rollup/rollup-linux-arm-musleabihf": "4.24.0", + "@rollup/rollup-linux-arm64-gnu": "4.24.0", + "@rollup/rollup-linux-arm64-musl": "4.24.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.24.0", + "@rollup/rollup-linux-riscv64-gnu": "4.24.0", + "@rollup/rollup-linux-s390x-gnu": "4.24.0", + "@rollup/rollup-linux-x64-gnu": "4.24.0", + "@rollup/rollup-linux-x64-musl": "4.24.0", + "@rollup/rollup-win32-arm64-msvc": "4.24.0", + "@rollup/rollup-win32-ia32-msvc": "4.24.0", + "@rollup/rollup-win32-x64-msvc": "4.24.0", "fsevents": "~2.3.2" } }, @@ -8590,21 +8591,21 @@ "license": "MIT" }, "node_modules/tldts": { - "version": "6.1.48", - "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.48.tgz", - "integrity": "sha512-SPbnh1zaSzi/OsmHb1vrPNnYuwJbdWjwo5TbBYYMlTtH3/1DSb41t8bcSxkwDmmbG2q6VLPVvQc7Yf23T+1EEw==", + "version": "6.1.50", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.50.tgz", + "integrity": "sha512-q9GOap6q3KCsLMdOjXhWU5jVZ8/1dIib898JBRLsN+tBhENpBDcAVQbE0epADOjw11FhQQy9AcbqKGBQPUfTQA==", "license": "MIT", "dependencies": { - "tldts-core": "^6.1.48" + "tldts-core": "^6.1.50" }, "bin": { "tldts": "bin/cli.js" } }, "node_modules/tldts-core": { - "version": "6.1.48", - "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.48.tgz", - "integrity": "sha512-3gD9iKn/n2UuFH1uilBviK9gvTNT6iYwdqrj1Vr5mh8FuelvpRNaYVH4pNYqUgOGU4aAdL9X35eLuuj0gRsx+A==", + "version": "6.1.50", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.50.tgz", + "integrity": "sha512-na2EcZqmdA2iV9zHV7OHQDxxdciEpxrjbkp+aHmZgnZKHzoElLajP59np5/4+sare9fQBfixgvXKx8ev1d7ytw==", "license": "MIT" }, "node_modules/tmpl": { diff --git a/package.json b/package.json index 2d982056..200c01a3 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ }, "scripts": { "prestart": "rm -rf tmp || npx -y rimraf tmp && node ./node_modules/puppeteer/install.mjs", - "start": "node ./bin/cli.js --enableServer true --logLevel 2", + "start": "node ./bin/cli.js --enableServer true --logLevel 4", "start:dev": "npx nodemon ./bin/cli.js --enableServer true --logLevel 4", "start:debug": "node --inspect-brk=9229 ./bin/cli.js --enableDebug 1 --enableServer true --logLevel 4", "complete": "npm run lint && npm run format && npm run build", @@ -50,16 +50,16 @@ "dependencies": { "colors": "1.4.0", "cors": "^2.8.5", - "dompurify": "^3.1.6", + "dompurify": "3.1.6", "dotenv": "^16.4.5", "express": "^4.21.0", - "express-rate-limit": "^7.4.0", + "express-rate-limit": "^7.4.1", "https-proxy-agent": "^7.0.5", "jsdom": "^25.0.1", "jsonwebtoken": "^9.0.2", "multer": "^1.4.5-lts.1", "prompts": "^2.4.2", - "puppeteer": "^23.4.1", + "puppeteer": "^23.5.1", "tarn": "^3.0.2", "uuid": "^10.0.0", "ws": "^8.18.0", @@ -70,13 +70,13 @@ "@rollup/plugin-terser": "^0.4.4", "eslint": "^8.57.0", "eslint-config-prettier": "^9.1.0", - "eslint-plugin-import": "^2.30.0", + "eslint-plugin-import": "^2.31.0", "eslint-plugin-prettier": "^5.2.1", "husky": "^9.1.6", "jest": "^29.7.0", "lint-staged": "^15.2.10", "nodemon": "^3.1.7", "prettier": "^3.3.3", - "rollup": "^4.22.5" + "rollup": "^4.24.0" } } diff --git a/public/index.html b/public/index.html index bd3fc62d..9fae0329 100644 --- a/public/index.html +++ b/public/index.html @@ -90,7 +90,7 @@

Highcharts Export Server

The exact pixel width of the exported image. Defaults to chart.width or 600px. Maximum width is 2000px. - +
@@ -98,7 +98,7 @@

Highcharts Export Server

set to 4x. Remember that the width parameter has a higher precedence over scaling.
- +
diff --git a/samples/module/svg.js b/samples/module/svg.js index 75b72fec..2bd9f1b0 100644 --- a/samples/module/svg.js +++ b/samples/module/svg.js @@ -7,14 +7,12 @@ const exportSettings = { export: { type: 'png', outfile: './samples/module/svg.png', - scale: 2 + scale: 2, + svg: 'Highcharts.com' }, pool: { minWorkers: 1, maxWorkers: 1 - }, - payload: { - svg: 'Highcharts.com' } }; diff --git a/tests/node/scenarios/svgBasic.json b/tests/node/scenarios/svgBasic.json index ef5fe984..91167edc 100644 --- a/tests/node/scenarios/svgBasic.json +++ b/tests/node/scenarios/svgBasic.json @@ -1,9 +1,7 @@ { "export": { "scale": 2, - "outfile": "svgBasic.png" - }, - "payload": { + "outfile": "svgBasic.png", "svg": "Created with Highcharts 10.2.1ValuesBasic SVGSeries 1Series 2JanFebMarApr0246810Highcharts.com" } } diff --git a/tests/node/scenarios/svgBasicWithScale.json b/tests/node/scenarios/svgBasicWithScale.json index 2fd41f50..dd2838e3 100644 --- a/tests/node/scenarios/svgBasicWithScale.json +++ b/tests/node/scenarios/svgBasicWithScale.json @@ -1,9 +1,7 @@ { "export": { "scale": 3, - "outfile": "svgBasicWithScale.png" - }, - "payload": { + "outfile": "svgBasicWithScale.png", "svg": "Created with Highcharts 10.2.1ValuesBasic SVGSeries 1Series 2JanFebMarApr0246810Highcharts.com" } } diff --git a/tests/node/scenarios/svgBasicWithScaleToPdf.json b/tests/node/scenarios/svgBasicWithScaleToPdf.json index aa6fffef..7089ad6a 100644 --- a/tests/node/scenarios/svgBasicWithScaleToPdf.json +++ b/tests/node/scenarios/svgBasicWithScaleToPdf.json @@ -2,9 +2,7 @@ "export": { "scale": 2, "type": "pdf", - "outfile": "svgBasicWithScaleToPdf.pdf" - }, - "payload": { + "outfile": "svgBasicWithScaleToPdf.pdf", "svg": "Created with Highcharts 10.2.1ValuesBasic SVGSeries 1Series 2JanFebMarApr0246810Highcharts.com" } } diff --git a/tests/node/scenarios/svgForeignObject.json b/tests/node/scenarios/svgForeignObject.json index b49b0c82..b7302766 100644 --- a/tests/node/scenarios/svgForeignObject.json +++ b/tests/node/scenarios/svgForeignObject.json @@ -1,9 +1,7 @@ { "export": { "scale": 2, - "outfile": "svgForeignObject.png" - }, - "payload": { + "outfile": "svgForeignObject.png", "svg": "Created with Highcharts 10.2.1ValuesSVG with a foreign objectSeries 1Series 2JanFebMarAprMayJunJulAugSepOctNovDec-1001020304050Highcharts.comThis subtitle is HTML" } } diff --git a/tests/unit/validation/cli.test.js b/tests/unit/validation/cli.test.js index b66ddd5b..5e423157 100644 --- a/tests/unit/validation/cli.test.js +++ b/tests/unit/validation/cli.test.js @@ -10,11 +10,53 @@ describe('CLI options should be correctly parsed and validated', () => { // puppeteer tests.puppeteer('puppeteer', { args: [ + '--allow-running-insecure-content', + '--ash-no-nudges', + '--autoplay-policy=user-gesture-required', + '--block-new-web-contents', + '--disable-accelerated-2d-canvas', + '--disable-background-networking', + '--disable-background-timer-throttling', + '--disable-backgrounding-occluded-windows', + '--disable-breakpad', + '--disable-checker-imaging', + '--disable-client-side-phishing-detection', + '--disable-component-extensions-with-background-pages', + '--disable-component-update', + '--disable-default-apps', + '--disable-dev-shm-usage', + '--disable-domain-reliability', + '--disable-extensions', + '--disable-features=CalculateNativeWinOcclusion,InterestFeedContentSuggestions,WebOTP', + '--disable-hang-monitor', + '--disable-ipc-flooding-protection', + '--disable-logging', + '--disable-notifications', + '--disable-offer-store-unmasked-wallet-cards', + '--disable-popup-blocking', + '--disable-print-preview', + '--disable-prompt-on-repost', + '--disable-renderer-backgrounding', + '--disable-search-engine-choice-screen', + '--disable-session-crashed-bubble', + '--disable-setuid-sandbox', + '--disable-site-isolation-trials', + '--disable-speech-api', '--disable-sync', '--enable-unsafe-webgpu', '--hide-crash-restore-bubble', '--hide-scrollbars', - '--metrics-recording-only' + '--metrics-recording-only', + '--mute-audio', + '--no-default-browser-check', + '--no-first-run', + '--no-pings', + '--no-sandbox', + '--no-startup-window', + '--no-zygote', + '--password-store=basic', + '--process-per-tab', + '--use-mock-keychain' ] }); @@ -24,10 +66,80 @@ describe('CLI options should be correctly parsed and validated', () => { cdnUrl: 'https://code.highcharts.com', forceFetch: false, cachePath: '.cache', - coreScripts: ['highcharts'], - moduleScripts: ['stock'], + coreScripts: ['highcharts', 'highcharts-more', 'highcharts-3d'], + moduleScripts: [ + 'stock', + 'map', + 'gantt', + 'exporting', + 'parallel-coordinates', + 'accessibility', + // 'annotations-advanced', + 'boost-canvas', + 'boost', + 'data', + 'data-tools', + 'draggable-points', + 'static-scale', + 'broken-axis', + 'heatmap', + 'tilemap', + 'tiledwebmap', + 'timeline', + 'treemap', + 'treegraph', + 'item-series', + 'drilldown', + 'histogram-bellcurve', + 'bullet', + 'funnel', + 'funnel3d', + 'geoheatmap', + 'pyramid3d', + 'networkgraph', + 'overlapping-datalabels', + 'pareto', + 'pattern-fill', + 'pictorial', + 'price-indicator', + 'sankey', + 'arc-diagram', + 'dependency-wheel', + 'series-label', + 'series-on-point', + 'solid-gauge', + 'sonification', + // 'stock-tools', + 'streamgraph', + 'sunburst', + 'variable-pie', + 'variwide', + 'vector', + 'venn', + 'windbarb', + 'wordcloud', + 'xrange', + 'no-data-to-display', + 'drag-panes', + 'debugger', + 'dumbbell', + 'lollipop', + 'cylinder', + 'organization', + 'dotplot', + 'marker-clusters', + 'hollowcandlestick', + 'heikinashi', + 'flowmap', + 'export-data', + 'navigator', + 'textpath' + ], indicatorScripts: ['indicators-all'], - customScripts: ['https://example.js'] + customScripts: [ + 'https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.30.1/moment.min.js', + 'https://cdnjs.cloudflare.com/ajax/libs/moment-timezone/0.5.45/moment-timezone-with-data.min.js' + ] }); // export @@ -35,9 +147,12 @@ describe('CLI options should be correctly parsed and validated', () => { infile: null, instr: null, options: null, + svg: null, outfile: null, type: 'png', constr: 'chart', + b64: false, + noDownload: false, defaultHeight: 400, defaultWidth: 600, defaultScale: 1, @@ -67,9 +182,26 @@ describe('CLI options should be correctly parsed and validated', () => { host: '0.0.0.0', port: 7801, benchmarking: false, - proxy: {}, - rateLimiting: {}, - ssl: {} + proxy: { + host: null, + port: null, + timeout: 5000 + }, + rateLimiting: { + enable: false, + maxRequests: 10, + window: 1, + delay: 0, + trustProxy: false, + skipKey: null, + skipToken: null + }, + ssl: { + enable: false, + force: false, + port: 443, + certPath: null + } }); // pool @@ -131,15 +263,11 @@ describe('CLI options should be correctly parsed and validated', () => { reconnectAttempts: 3, messageInterval: 3600000, gatherAllOptions: false, - url: null, - secret: null + url: null }); // payload tests.payload('payload', { - svg: "", - b64: false, - noDownload: false, requestId: 'd4faa416-0e85-433a-9f84-e735567d8fa5' }); }); @@ -151,7 +279,7 @@ describe('Puppeteer configuration options should be correctly parsed and validat // puppeteer.args tests.puppeteerArgs( 'args', - '--disable-sync, --enable-unsafe-webgpu, --hide-crash-restore-bubble, --hide-scrollbars, --metrics-recording-only', + '--disable-sync; --enable-unsafe-webgpu; --hide-crash-restore-bubble; --hide-scrollbars; --metrics-recording-only', [ '--disable-sync', '--enable-unsafe-webgpu', @@ -227,6 +355,9 @@ describe('Export configuration options should be correctly parsed and validated' // export.options tests.exportOptions('options'); + // export.svg + tests.exportSvg('svg'); + // export.outfile tests.exportOutfile('outfile'); @@ -244,6 +375,12 @@ describe('Export configuration options should be correctly parsed and validated' ['stock', 'map', 'gantt'] ); + // export.b64 + tests.exportB64('b64'); + + // export.noDownload + tests.exportNoDownload('noDownload'); + // export.defaultHeight tests.exportDefaultHeight('defaultHeight'); @@ -554,24 +691,12 @@ describe('WebSocket configuration options should be correctly parsed and validat ['ws://example.com', 'wss://example.com'], ['ws:a.com', 'ws:/b.com', 'wss:c.com', 'wss:/d.com'] ); - - // webSocket.secret - tests.webSocketSecret('secret'); }); describe('Payload configuration options should be correctly parsed and validated', () => { // Return config tests with a specific schema and strictCheck flag injected const tests = configTests(LooseConfigSchema.shape.payload, false); - // payload.svg - tests.payloadSvg('svg'); - - // payload.b64 - tests.payloadB64('b64'); - - // payload.noDownload - tests.payloadNoDownload('noDownload'); - // payload.requestId tests.payloadRequestId('requestId'); }); diff --git a/tests/unit/validation/config.test.js b/tests/unit/validation/config.test.js index 5b32775f..192232a1 100644 --- a/tests/unit/validation/config.test.js +++ b/tests/unit/validation/config.test.js @@ -10,11 +10,53 @@ describe('Configuration options should be correctly parsed and validated', () => // puppeteer tests.puppeteer('puppeteer', { args: [ + '--allow-running-insecure-content', + '--ash-no-nudges', + '--autoplay-policy=user-gesture-required', + '--block-new-web-contents', + '--disable-accelerated-2d-canvas', + '--disable-background-networking', + '--disable-background-timer-throttling', + '--disable-backgrounding-occluded-windows', + '--disable-breakpad', + '--disable-checker-imaging', + '--disable-client-side-phishing-detection', + '--disable-component-extensions-with-background-pages', + '--disable-component-update', + '--disable-default-apps', + '--disable-dev-shm-usage', + '--disable-domain-reliability', + '--disable-extensions', + '--disable-features=CalculateNativeWinOcclusion,InterestFeedContentSuggestions,WebOTP', + '--disable-hang-monitor', + '--disable-ipc-flooding-protection', + '--disable-logging', + '--disable-notifications', + '--disable-offer-store-unmasked-wallet-cards', + '--disable-popup-blocking', + '--disable-print-preview', + '--disable-prompt-on-repost', + '--disable-renderer-backgrounding', + '--disable-search-engine-choice-screen', + '--disable-session-crashed-bubble', + '--disable-setuid-sandbox', + '--disable-site-isolation-trials', + '--disable-speech-api', '--disable-sync', '--enable-unsafe-webgpu', '--hide-crash-restore-bubble', '--hide-scrollbars', - '--metrics-recording-only' + '--metrics-recording-only', + '--mute-audio', + '--no-default-browser-check', + '--no-first-run', + '--no-pings', + '--no-sandbox', + '--no-startup-window', + '--no-zygote', + '--password-store=basic', + '--process-per-tab', + '--use-mock-keychain' ] }); @@ -24,10 +66,80 @@ describe('Configuration options should be correctly parsed and validated', () => cdnUrl: 'https://code.highcharts.com', forceFetch: false, cachePath: '.cache', - coreScripts: ['highcharts'], - moduleScripts: ['stock'], + coreScripts: ['highcharts', 'highcharts-more', 'highcharts-3d'], + moduleScripts: [ + 'stock', + 'map', + 'gantt', + 'exporting', + 'parallel-coordinates', + 'accessibility', + // 'annotations-advanced', + 'boost-canvas', + 'boost', + 'data', + 'data-tools', + 'draggable-points', + 'static-scale', + 'broken-axis', + 'heatmap', + 'tilemap', + 'tiledwebmap', + 'timeline', + 'treemap', + 'treegraph', + 'item-series', + 'drilldown', + 'histogram-bellcurve', + 'bullet', + 'funnel', + 'funnel3d', + 'geoheatmap', + 'pyramid3d', + 'networkgraph', + 'overlapping-datalabels', + 'pareto', + 'pattern-fill', + 'pictorial', + 'price-indicator', + 'sankey', + 'arc-diagram', + 'dependency-wheel', + 'series-label', + 'series-on-point', + 'solid-gauge', + 'sonification', + // 'stock-tools', + 'streamgraph', + 'sunburst', + 'variable-pie', + 'variwide', + 'vector', + 'venn', + 'windbarb', + 'wordcloud', + 'xrange', + 'no-data-to-display', + 'drag-panes', + 'debugger', + 'dumbbell', + 'lollipop', + 'cylinder', + 'organization', + 'dotplot', + 'marker-clusters', + 'hollowcandlestick', + 'heikinashi', + 'flowmap', + 'export-data', + 'navigator', + 'textpath' + ], indicatorScripts: ['indicators-all'], - customScripts: ['https://example.js'] + customScripts: [ + 'https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.30.1/moment.min.js', + 'https://cdnjs.cloudflare.com/ajax/libs/moment-timezone/0.5.45/moment-timezone-with-data.min.js' + ] }); // export @@ -35,9 +147,12 @@ describe('Configuration options should be correctly parsed and validated', () => infile: null, instr: null, options: null, + svg: null, outfile: null, type: 'png', constr: 'chart', + b64: false, + noDownload: false, defaultHeight: 400, defaultWidth: 600, defaultScale: 1, @@ -67,9 +182,26 @@ describe('Configuration options should be correctly parsed and validated', () => host: '0.0.0.0', port: 7801, benchmarking: false, - proxy: {}, - rateLimiting: {}, - ssl: {} + proxy: { + host: null, + port: null, + timeout: 5000 + }, + rateLimiting: { + enable: false, + maxRequests: 10, + window: 1, + delay: 0, + trustProxy: false, + skipKey: null, + skipToken: null + }, + ssl: { + enable: false, + force: false, + port: 443, + certPath: null + } }); // pool @@ -131,15 +263,11 @@ describe('Configuration options should be correctly parsed and validated', () => reconnectAttempts: 3, messageInterval: 3600000, gatherAllOptions: false, - url: null, - secret: null + url: null }); // payload tests.payload('payload', { - svg: "", - b64: false, - noDownload: false, requestId: 'd4faa416-0e85-433a-9f84-e735567d8fa5' }); }); @@ -238,6 +366,9 @@ describe('Export configuration options should be correctly parsed and validated' // export.options tests.exportOptions('options'); + // export.svg + tests.exportSvg('svg'); + // export.outfile tests.exportOutfile('outfile'); @@ -255,6 +386,12 @@ describe('Export configuration options should be correctly parsed and validated' ['stock', 'map', 'gantt'] ); + // export.b64 + tests.exportB64('b64'); + + // export.noDownload + tests.exportNoDownload('noDownload'); + // export.defaultHeight tests.exportDefaultHeight('defaultHeight'); @@ -565,24 +702,12 @@ describe('WebSocket configuration options should be correctly parsed and validat ['ws://example.com', 'wss://example.com'], ['ws:a.com', 'ws:/b.com', 'wss:c.com', 'wss:/d.com'] ); - - // webSocket.secret - tests.webSocketSecret('secret'); }); describe('Payload configuration options should be correctly parsed and validated', () => { // Return config tests with a specific schema and strictCheck flag injected const tests = configTests(StrictConfigSchema.shape.payload, true); - // payload.svg - tests.payloadSvg('svg'); - - // payload.b64 - tests.payloadB64('b64'); - - // payload.noDownload - tests.payloadNoDownload('noDownload'); - // payload.requestId tests.payloadRequestId('requestId'); }); diff --git a/tests/unit/validation/envs.test.js b/tests/unit/validation/envs.test.js index 7b9083f8..5dce794f 100644 --- a/tests/unit/validation/envs.test.js +++ b/tests/unit/validation/envs.test.js @@ -10,7 +10,7 @@ describe('PUPPETEER environment variables should be correctly parsed and validat // PUPPETEER_ARGS tests.puppeteerArgs( 'PUPPETEER_ARGS', - '--disable-sync, --enable-unsafe-webgpu, --hide-crash-restore-bubble, --hide-scrollbars, --metrics-recording-only', + '--disable-sync; --enable-unsafe-webgpu; --hide-crash-restore-bubble; --hide-scrollbars; --metrics-recording-only', [ '--disable-sync', '--enable-unsafe-webgpu', @@ -74,6 +74,21 @@ describe('HIGHCHARTS environment variables should be correctly parsed and valida }); describe('EXPORT environment variables should be correctly parsed and validated', () => { + // EXPORT_INFILE + tests.exportInfile('EXPORT_INFILE'); + + // EXPORT_INSTR + tests.exportInstr('EXPORT_INSTR'); + + // EXPORT_OPTIONS + tests.exportOptions('EXPORT_OPTIONS'); + + // EXPORT_SVG + tests.exportSvg('EXPORT_SVG'); + + // EXPORT_OUTFILE + tests.exportOutfile('EXPORT_OUTFILE'); + // EXPORT_TYPE tests.exportType( 'EXPORT_TYPE', @@ -88,6 +103,12 @@ describe('EXPORT environment variables should be correctly parsed and validated' ['stock', 'map', 'gantt'] ); + // EXPORT_B64 + tests.exportB64('EXPORT_B64'); + + // EXPORT_NO_DOWNLOAD + tests.exportNoDownload('EXPORT_NO_DOWNLOAD'); + // EXPORT_DEFAULT_HEIGHT tests.exportDefaultHeight('EXPORT_DEFAULT_HEIGHT'); @@ -97,6 +118,24 @@ describe('EXPORT environment variables should be correctly parsed and validated' // EXPORT_DEFAULT_SCALE tests.exportDefaultScale('EXPORT_DEFAULT_SCALE'); + // EXPORT_HEIGHT + tests.exportDefaultHeight('EXPORT_HEIGHT'); + + // EXPORT_WIDTH + tests.exportDefaultWidth('EXPORT_WIDTH'); + + // EXPORT_SCALE + tests.exportDefaultScale('EXPORT_SCALE'); + + // EXPORT_GLOBAL_OPTIONS + tests.exportGlobalOptions('EXPORT_GLOBAL_OPTIONS'); + + // EXPORT_THEME_OPTIONS + tests.exportThemeOptions('EXPORT_THEME_OPTIONS'); + + // EXPORT_BATCH + tests.exportBatch('EXPORT_BATCH'); + // EXPORT_RASTERIZATION_TIMEOUT tests.exportRasterizationTimeout('EXPORT_RASTERIZATION_TIMEOUT'); }); @@ -107,6 +146,21 @@ describe('CUSTOM_LOGIC environment variables should be correctly parsed and vali // CUSTOM_LOGIC_ALLOW_FILE_RESOURCES tests.customLogicAllowFileResources('CUSTOM_LOGIC_ALLOW_FILE_RESOURCES'); + + // CUSTOM_LOGIC_CUSTOM_CODE + tests.customLogicCustomCode('CUSTOM_LOGIC_CUSTOM_CODE'); + + // CUSTOM_LOGIC_CALLBACK + tests.customLogicCallback('CUSTOM_LOGIC_CALLBACK'); + + // CUSTOM_LOGIC_RESOURCES + tests.customLogicResources('CUSTOM_LOGIC_RESOURCES'); + + // CUSTOM_LOGIC_LOAD_CONFIG + tests.customLogicLoadConfig('CUSTOM_LOGIC_LOAD_CONFIG'); + + // CUSTOM_LOGIC_CREATE_CONFIG + tests.customLogicCreateConfig('CUSTOM_LOGIC_CREATE_CONFIG'); }); describe('SERVER environment variables should be correctly parsed and validated', () => { diff --git a/tests/unit/validation/shared.js b/tests/unit/validation/shared.js index c7bae3a3..37546753 100644 --- a/tests/unit/validation/shared.js +++ b/tests/unit/validation/shared.js @@ -11,19 +11,6 @@ import { validatePropOfSchema } from '../../utils/testsUtils.js'; * validation. */ export function configTests(schema, strictCheck) { - /** - * Verifies that a property that is not present in the object results in - * undefined. - * - * @param {string} property - The property to check for undefined. - * - * @throws {Error} Throws an error if the schema validation fails. - */ - const noPropertyToUndefined = (property) => { - const obj = {}; - expect(schema.parse(obj)[property]).toBe(undefined); - }; - /** * Verifies that a property with the value undefined is accepted. * @@ -240,13 +227,11 @@ export function configTests(schema, strictCheck) { }); if (strictCheck) { - it("should not accept 'false', 'undefined', 'NaN', 'null', '' values", () => { + it("should not accept 'false', 'undefined', 'null', '' values", () => { const obj = { [property]: 'false' }; expect(() => schema.parse(obj)).toThrow(); obj[property] = 'undefined'; expect(() => schema.parse(obj)).toThrow(); - obj[property] = 'NaN'; - expect(() => schema.parse(obj)).toThrow(); obj[property] = 'null'; expect(() => schema.parse(obj)).toThrow(); obj[property] = ''; @@ -283,13 +268,11 @@ export function configTests(schema, strictCheck) { 'undefined' ])); } else { - it("should accept 'false', 'undefined', 'NaN', 'null', '' values and trasform to null", () => { + it("should accept 'false', 'undefined', 'null', '' values and trasform to null", () => { const obj = { [property]: 'false' }; expect(schema.parse(obj)[property]).toBe(null); obj[property] = 'undefined'; expect(schema.parse(obj)[property]).toBe(null); - obj[property] = 'NaN'; - expect(schema.parse(obj)[property]).toBe(null); obj[property] = 'null'; expect(schema.parse(obj)[property]).toBe(null); obj[property] = ''; @@ -462,107 +445,10 @@ export function configTests(schema, strictCheck) { } }, - /** - * The object validator. - */ - object(property, strictCheck) { - it('should accept any object values', () => { - const obj = { [property]: {} }; - expect(schema.parse(obj)[property]).toEqual({}); - obj[property] = { a: 1 }; - expect(schema.parse(obj)[property]).toEqual({ a: 1 }); - obj[property] = { a: '1', b: { c: 3 } }; - expect(schema.parse(obj)[property]).toEqual({ a: '1', b: { c: 3 } }); - }); - - it('should not accept any array values', () => { - const obj = { [property]: [] }; - expect(() => schema.parse(obj)).toThrow(); - obj[property] = [1]; - expect(() => schema.parse(obj)).toThrow(); - obj[property] = ['a']; - expect(() => schema.parse(obj)).toThrow(); - obj[property] = [{ a: 1 }]; - expect(() => schema.parse(obj)).toThrow(); - }); - - it('should not accept any other object based values', () => { - const obj = { [property]: function () {} }; - expect(() => schema.parse(obj)).toThrow(); - obj[property] = () => {}; - expect(() => schema.parse(obj)).toThrow(); - obj[property] = new Date(); - expect(() => schema.parse(obj)).toThrow(); - }); - - it('should accept undefined', () => { - acceptUndefined(property); - }); - - it('should accept null', () => { - acceptNull(property); - }); - - if (strictCheck) { - it('should not accept a stringified undefined', () => { - stringUndefinedThrow(property); - }); - - it('should not accept a stringified null', () => { - stringNullThrow(property); - }); - - it('should not accept an empty string', () => { - emptyStringThrow(property); - }); - - it('should not accept values of other types', () => - validatePropOfSchema(schema, property, [ - 'undefined', - 'null', - 'object', - 'other' - ])); - } else { - it("should accept a string value that starts with the '{' and ends with the '}'", () => { - const obj = { [property]: '{}' }; - expect(schema.parse(obj)[property]).toBe('{}'); - obj[property] = '{ a: 1 }'; - expect(schema.parse(obj)[property]).toBe('{ a: 1 }'); - obj[property] = '{ a: "1", b: { c: 3 } }'; - expect(schema.parse(obj)[property]).toBe('{ a: "1", b: { c: 3 } }'); - }); - - it('should accept a stringified undefined and transform it to null', () => { - stringUndefinedToNull(property); - }); - - it('should accept a stringified null and transform it to null', () => { - stringNullToNull(property); - }); - - it('should accept an empty string and transform it to null', () => { - emptyStringToNull(property); - }); - - it('should not accept values of other types', () => - validatePropOfSchema(schema, property, [ - 'undefined', - 'null', - 'emptyString', - 'stringUndefined', - 'stringNull', - 'stringObject', - 'object', - 'other' - ])); - } - }, - /** * The array of strings validator. */ - stringArray(property, value, correctValue) { + stringArray(property, value, correctValue, separator = ',') { it('should accept a string value or an array of strings and correctly parse it to an array of strings', () => { const obj = { [property]: value }; expect(schema.parse(obj)[property]).toEqual(correctValue); @@ -580,7 +466,7 @@ export function configTests(schema, strictCheck) { it('should accept an array of strings and filter it from the forbidden values', () => { const obj = { - [property]: [...value, 'false', 'undefined', 'NaN', 'null', ''] + [property]: [...value, 'false', 'undefined', 'null', ''] }; expect(schema.parse(obj)[property]).toEqual(correctValue); }); @@ -611,7 +497,7 @@ export function configTests(schema, strictCheck) { it('should filter a stringified array of a values string from forbidden values and correctly parse it to an array of strings', () => { const obj = { - [property]: `[${value}, false, undefined, NaN, null,]` + [property]: `[${value}${separator} false${separator} undefined${separator} null${separator}]` }; expect(schema.parse(obj)[property]).toEqual(correctValue); }); @@ -1050,81 +936,95 @@ export function configTests(schema, strictCheck) { }, /** - * The infile option validator. + * The svg validator. */ - infile(property) { - it('should accept string values that end with .json or .svg', () => { - const obj = { [property]: 'chart.json' }; - expect(schema.parse(obj)[property]).toBe('chart.json'); - obj[property] = 'chart.svg'; - expect(schema.parse(obj)[property]).toBe('chart.svg'); - }); - - it('should not accept string values that do not end with .json or .svg', () => { - const obj = { [property]: 'chart.pdf' }; - expect(() => schema.parse(obj)).toThrow(); - obj[property] = 'chart.png'; - expect(() => schema.parse(obj)).toThrow(); - }); - - it('should not accept string values that are not at least one character long without the extensions', () => { - const obj = { [property]: '.json' }; - expect(() => schema.parse(obj)).toThrow(); - obj[property] = '.svg'; - expect(() => schema.parse(obj)).toThrow(); + svg(property) { + it('should accept a string value that starts with the { + const obj = { + [property]: "..." + }; + expect(schema.parse(obj)[property]).toBe( + "..." + ); + obj[property] = + '...'; + expect(schema.parse(obj)[property]).toBe( + '...' + ); }); it('should accept undefined', () => { acceptUndefined(property); }); + it("should accept 'false', 'undefined', 'null', '' values and trasform to null", () => { + const obj = { [property]: 'false' }; + expect(schema.parse(obj)[property]).toBe(null); + obj[property] = 'undefined'; + expect(schema.parse(obj)[property]).toBe(null); + obj[property] = 'null'; + expect(schema.parse(obj)[property]).toBe(null); + obj[property] = ''; + expect(schema.parse(obj)[property]).toBe(null); + }); + it('should accept null', () => { acceptNull(property); }); - if (strictCheck) { - it('should not accept a stringified undefined', () => { - stringUndefinedThrow(property); - }); - - it('should not accept a stringified null', () => { - stringNullThrow(property); - }); - - it('should not accept an empty string', () => { - emptyStringThrow(property); - }); - - it('should not accept values of other types', () => - validatePropOfSchema(schema, property, ['undefined', 'null'])); - } else { - it('should accept a stringified undefined and transform it to null', () => { - stringUndefinedToNull(property); - }); + it('should accept a stringified undefined and transform it to null', () => { + stringUndefinedToNull(property); + }); - it('should accept a stringified null and transform it to null', () => { - stringNullToNull(property); - }); + it('should accept a stringified null and transform it to null', () => { + stringNullToNull(property); + }); - it('should accept an empty string and transform it to null', () => { - emptyStringToNull(property); - }); + it('should accept an empty string and transform it to null', () => { + emptyStringToNull(property); + }); - it('should not accept values of other types', () => - validatePropOfSchema(schema, property, [ - 'emptyString', - 'stringUndefined', - 'stringNull', - 'undefined', - 'null' - ])); - } + it('should not accept values of other types', () => + validatePropOfSchema(schema, property, [ + 'emptyString', + 'string', + 'stringBoolean', + 'stringNumber', + 'stringBigInt', + 'stringUndefined', + 'stringNull', + 'stringSymbol', + 'stringObject', + 'stringArray', + 'stringFunction', + 'stringOther', + 'undefined', + 'null' + ])); }, /** - * The svg validator. + * The chartConfig validator. */ - svg(property, strictCheck) { + chartConfig(property) { + it('should accept any object values', () => { + const obj = { [property]: {} }; + expect(schema.parse(obj)[property]).toEqual({}); + obj[property] = { a: 1 }; + expect(schema.parse(obj)[property]).toEqual({ a: 1 }); + obj[property] = { a: '1', b: { c: 3 } }; + expect(schema.parse(obj)[property]).toEqual({ a: '1', b: { c: 3 } }); + }); + + it("should accept a string value that starts with the '{' and ends with the '}'", () => { + const obj = { [property]: '{}' }; + expect(schema.parse(obj)[property]).toBe('{}'); + obj[property] = '{ a: 1 }'; + expect(schema.parse(obj)[property]).toBe('{ a: 1 }'); + obj[property] = '{ a: "1", b: { c: 3 } }'; + expect(schema.parse(obj)[property]).toBe('{ a: "1", b: { c: 3 } }'); + }); + it('should accept a string value that starts with the { const obj = { [property]: "..." @@ -1139,28 +1039,185 @@ export function configTests(schema, strictCheck) { ); }); + it('should not accept any array values', () => { + const obj = { [property]: [] }; + expect(() => schema.parse(obj)).toThrow(); + obj[property] = [1]; + expect(() => schema.parse(obj)).toThrow(); + obj[property] = ['a']; + expect(() => schema.parse(obj)).toThrow(); + obj[property] = [{ a: 1 }]; + expect(() => schema.parse(obj)).toThrow(); + }); + + it('should not accept any other object based values', () => { + const obj = { [property]: function () {} }; + expect(() => schema.parse(obj)).toThrow(); + obj[property] = () => {}; + expect(() => schema.parse(obj)).toThrow(); + obj[property] = new Date(); + expect(() => schema.parse(obj)).toThrow(); + }); + it('should accept undefined', () => { acceptUndefined(property); }); - if (strictCheck) { - it("should not accept 'false', 'undefined', 'NaN', 'null', '' values", () => { - const obj = { [property]: 'false' }; - expect(() => schema.parse(obj)).toThrow(); - obj[property] = 'undefined'; - expect(() => schema.parse(obj)).toThrow(); - obj[property] = 'NaN'; - expect(() => schema.parse(obj)).toThrow(); - obj[property] = 'null'; - expect(() => schema.parse(obj)).toThrow(); - obj[property] = ''; - expect(() => schema.parse(obj)).toThrow(); - }); + it('should accept null', () => { + acceptNull(property); + }); - it('should not accept null', () => { - nullThrow(property); - }); + it('should accept a stringified undefined and transform it to null', () => { + stringUndefinedToNull(property); + }); + + it('should accept a stringified null and transform it to null', () => { + stringNullToNull(property); + }); + + it('should accept an empty string and transform it to null', () => { + emptyStringToNull(property); + }); + + it('should not accept values of other types', () => + validatePropOfSchema(schema, property, [ + 'undefined', + 'null', + 'emptyString', + 'stringUndefined', + 'stringNull', + 'stringObject', + 'object', + 'other' + ])); + }, + + /** + * The additionalOptions validator. + */ + additionalOptions(property) { + it('should accept any object values', () => { + const obj = { [property]: {} }; + expect(schema.parse(obj)[property]).toEqual({}); + obj[property] = { a: 1 }; + expect(schema.parse(obj)[property]).toEqual({ a: 1 }); + obj[property] = { a: '1', b: { c: 3 } }; + expect(schema.parse(obj)[property]).toEqual({ a: '1', b: { c: 3 } }); + }); + + it("should accept a string value that starts with the '{' and ends with the '}'", () => { + const obj = { [property]: '{}' }; + expect(schema.parse(obj)[property]).toBe('{}'); + obj[property] = '{ a: 1 }'; + expect(schema.parse(obj)[property]).toBe('{ a: 1 }'); + obj[property] = '{ a: "1", b: { c: 3 } }'; + expect(schema.parse(obj)[property]).toBe('{ a: "1", b: { c: 3 } }'); + }); + + it('should accept string values that end with .json', () => { + const obj = { [property]: 'options.json' }; + expect(schema.parse(obj)[property]).toBe('options.json'); + }); + + it('should not accept string values that do not end with .json', () => { + const obj = { [property]: 'options.pdf' }; + expect(() => schema.parse(obj)).toThrow(); + obj[property] = 'options.png'; + expect(() => schema.parse(obj)).toThrow(); + }); + + it('should not accept string values that are not at least one character long without the extensions', () => { + const obj = { [property]: '.json' }; + expect(() => schema.parse(obj)).toThrow(); + }); + + it('should not accept any array values', () => { + const obj = { [property]: [] }; + expect(() => schema.parse(obj)).toThrow(); + obj[property] = [1]; + expect(() => schema.parse(obj)).toThrow(); + obj[property] = ['a']; + expect(() => schema.parse(obj)).toThrow(); + obj[property] = [{ a: 1 }]; + expect(() => schema.parse(obj)).toThrow(); + }); + + it('should not accept any other object based values', () => { + const obj = { [property]: function () {} }; + expect(() => schema.parse(obj)).toThrow(); + obj[property] = () => {}; + expect(() => schema.parse(obj)).toThrow(); + obj[property] = new Date(); + expect(() => schema.parse(obj)).toThrow(); + }); + it('should accept undefined', () => { + acceptUndefined(property); + }); + + it('should accept null', () => { + acceptNull(property); + }); + + it('should accept a stringified undefined and transform it to null', () => { + stringUndefinedToNull(property); + }); + + it('should accept a stringified null and transform it to null', () => { + stringNullToNull(property); + }); + + it('should accept an empty string and transform it to null', () => { + emptyStringToNull(property); + }); + + it('should not accept values of other types', () => + validatePropOfSchema(schema, property, [ + 'undefined', + 'null', + 'emptyString', + 'stringUndefined', + 'stringNull', + 'stringObject', + 'object', + 'other' + ])); + }, + + /** + * The infile option validator. + */ + infile(property) { + it('should accept string values that end with .json or .svg', () => { + const obj = { [property]: 'chart.json' }; + expect(schema.parse(obj)[property]).toBe('chart.json'); + obj[property] = 'chart.svg'; + expect(schema.parse(obj)[property]).toBe('chart.svg'); + }); + + it('should not accept string values that do not end with .json or .svg', () => { + const obj = { [property]: 'chart.pdf' }; + expect(() => schema.parse(obj)).toThrow(); + obj[property] = 'chart.png'; + expect(() => schema.parse(obj)).toThrow(); + }); + + it('should not accept string values that are not at least one character long without the extensions', () => { + const obj = { [property]: '.json' }; + expect(() => schema.parse(obj)).toThrow(); + obj[property] = '.svg'; + expect(() => schema.parse(obj)).toThrow(); + }); + + it('should accept undefined', () => { + acceptUndefined(property); + }); + + it('should accept null', () => { + acceptNull(property); + }); + + if (strictCheck) { it('should not accept a stringified undefined', () => { stringUndefinedThrow(property); }); @@ -1174,36 +1231,8 @@ export function configTests(schema, strictCheck) { }); it('should not accept values of other types', () => - validatePropOfSchema(schema, property, [ - 'string', - 'stringBoolean', - 'stringNumber', - 'stringBigInt', - 'stringSymbol', - 'stringObject', - 'stringArray', - 'stringFunction', - 'stringOther', - 'undefined' - ])); + validatePropOfSchema(schema, property, ['undefined', 'null'])); } else { - it("should accept 'false', 'undefined', 'NaN', 'null', '' values and trasform to null", () => { - const obj = { [property]: 'false' }; - expect(schema.parse(obj)[property]).toBe(null); - obj[property] = 'undefined'; - expect(schema.parse(obj)[property]).toBe(null); - obj[property] = 'NaN'; - expect(schema.parse(obj)[property]).toBe(null); - obj[property] = 'null'; - expect(schema.parse(obj)[property]).toBe(null); - obj[property] = ''; - expect(schema.parse(obj)[property]).toBe(null); - }); - - it('should accept null', () => { - acceptNull(property); - }); - it('should accept a stringified undefined and transform it to null', () => { stringUndefinedToNull(property); }); @@ -1219,17 +1248,8 @@ export function configTests(schema, strictCheck) { it('should not accept values of other types', () => validatePropOfSchema(schema, property, [ 'emptyString', - 'string', - 'stringBoolean', - 'stringNumber', - 'stringBigInt', 'stringUndefined', 'stringNull', - 'stringSymbol', - 'stringObject', - 'stringArray', - 'stringFunction', - 'stringOther', 'undefined', 'null' ])); @@ -1755,6 +1775,117 @@ export function configTests(schema, strictCheck) { } }, + /** + * The logFile option validator. + */ + logFile(property, strictCheck) { + it('should accept a string value that ends with the .log extension and is at least one character long without the extension', () => { + const obj = { [property]: 'text.log' }; + expect(schema.parse(obj)[property]).toBe('text.log'); + obj[property] = 't.log'; + expect(schema.parse(obj)[property]).toBe('t.log'); + }); + + it('should not accept a string value that does not end with the .log extension or is not at least one character long without the extension', () => { + const obj = { [property]: 'text' }; + expect(() => schema.parse(obj)).toThrow(); + obj[property] = '.log'; + expect(() => schema.parse(obj)).toThrow(); + }); + + it('should accept undefined', () => { + acceptUndefined(property); + }); + + if (strictCheck) { + it("should not accept 'false', 'undefined', 'null', '' values", () => { + const obj = { [property]: 'false' }; + expect(() => schema.parse(obj)).toThrow(); + obj[property] = 'undefined'; + expect(() => schema.parse(obj)).toThrow(); + obj[property] = 'null'; + expect(() => schema.parse(obj)).toThrow(); + obj[property] = ''; + expect(() => schema.parse(obj)).toThrow(); + }); + + it('should not accept null', () => { + nullThrow(property); + }); + + it('should not accept a stringified undefined', () => { + stringUndefinedThrow(property); + }); + + it('should not accept a stringified null', () => { + stringNullThrow(property); + }); + + it('should not accept an empty string', () => { + emptyStringThrow(property); + }); + + it('should not accept values of other types', () => + validatePropOfSchema(schema, property, [ + 'string', + 'stringBoolean', + 'stringNumber', + 'stringBigInt', + 'stringSymbol', + 'stringObject', + 'stringArray', + 'stringFunction', + 'stringOther', + 'undefined' + ])); + } else { + it("should accept 'false', 'undefined', 'null', '' values and trasform to null", () => { + const obj = { [property]: 'false' }; + expect(schema.parse(obj)[property]).toBe(null); + obj[property] = 'undefined'; + expect(schema.parse(obj)[property]).toBe(null); + obj[property] = 'null'; + expect(schema.parse(obj)[property]).toBe(null); + obj[property] = ''; + expect(schema.parse(obj)[property]).toBe(null); + }); + + it('should accept null', () => { + acceptNull(property); + }); + + it('should accept a stringified undefined and transform it to null', () => { + stringUndefinedToNull(property); + }); + + it('should accept a stringified null and transform it to null', () => { + stringNullToNull(property); + }); + + it('should accept an empty string and transform it to null', () => { + emptyStringToNull(property); + }); + + it('should not accept values of other types', () => + validatePropOfSchema(schema, property, [ + 'emptyString', + 'string', + 'stringBoolean', + 'stringNumber', + 'stringBigInt', + 'stringUndefined', + 'stringNull', + 'stringSymbol', + 'stringObject', + 'stringArray', + 'stringFunction', + 'stringOther', + 'undefined', + 'null' + ])); + } + }, + /** * The resources option validator. */ @@ -1873,7 +2004,118 @@ export function configTests(schema, strictCheck) { }, /** - * The config object section validator. + * The createConfig/loadConfig options validator. + */ + customConfig(property, strictCheck) { + it('should accept a string value that ends with the .json extension and is at least one character long without the extension', () => { + const obj = { [property]: 'text.json' }; + expect(schema.parse(obj)[property]).toBe('text.json'); + obj[property] = 't.json'; + expect(schema.parse(obj)[property]).toBe('t.json'); + }); + + it('should not accept a string value that does not end with the .json extension or is not at least one character long without the extension', () => { + const obj = { [property]: 'text' }; + expect(() => schema.parse(obj)).toThrow(); + obj[property] = '.json'; + expect(() => schema.parse(obj)).toThrow(); + }); + + it('should accept undefined', () => { + acceptUndefined(property); + }); + + if (strictCheck) { + it("should not accept 'false', 'undefined', 'null', '' values", () => { + const obj = { [property]: 'false' }; + expect(() => schema.parse(obj)).toThrow(); + obj[property] = 'undefined'; + expect(() => schema.parse(obj)).toThrow(); + obj[property] = 'null'; + expect(() => schema.parse(obj)).toThrow(); + obj[property] = ''; + expect(() => schema.parse(obj)).toThrow(); + }); + + it('should not accept null', () => { + nullThrow(property); + }); + + it('should not accept a stringified undefined', () => { + stringUndefinedThrow(property); + }); + + it('should not accept a stringified null', () => { + stringNullThrow(property); + }); + + it('should not accept an empty string', () => { + emptyStringThrow(property); + }); + + it('should not accept values of other types', () => + validatePropOfSchema(schema, property, [ + 'string', + 'stringBoolean', + 'stringNumber', + 'stringBigInt', + 'stringSymbol', + 'stringObject', + 'stringArray', + 'stringFunction', + 'stringOther', + 'undefined' + ])); + } else { + it("should accept 'false', 'undefined', 'null', '' values and trasform to null", () => { + const obj = { [property]: 'false' }; + expect(schema.parse(obj)[property]).toBe(null); + obj[property] = 'undefined'; + expect(schema.parse(obj)[property]).toBe(null); + obj[property] = 'null'; + expect(schema.parse(obj)[property]).toBe(null); + obj[property] = ''; + expect(schema.parse(obj)[property]).toBe(null); + }); + + it('should accept null', () => { + acceptNull(property); + }); + + it('should accept a stringified undefined and transform it to null', () => { + stringUndefinedToNull(property); + }); + + it('should accept a stringified null and transform it to null', () => { + stringNullToNull(property); + }); + + it('should accept an empty string and transform it to null', () => { + emptyStringToNull(property); + }); + + it('should not accept values of other types', () => + validatePropOfSchema(schema, property, [ + 'emptyString', + 'string', + 'stringBoolean', + 'stringNumber', + 'stringBigInt', + 'stringUndefined', + 'stringNull', + 'stringSymbol', + 'stringObject', + 'stringArray', + 'stringFunction', + 'stringOther', + 'undefined', + 'null' + ])); + } + }, + + /** + * The config object validator. */ configObject(property, value) { it(`should accept an object with the ${property} properties`, () => { @@ -1897,12 +2139,13 @@ export function configTests(schema, strictCheck) { ).toEqual({}); }); - it('should accept undefined', () => { - acceptUndefined(property); + it('should accept object with no properties and transform it to undefined', () => { + const obj = {}; + expect(schema.parse(obj)[property]).toBe(undefined); }); - it('should accept object with no properties and transform it to undefined', () => { - noPropertyToUndefined(property); + it('should accept undefined', () => { + acceptUndefined(property); }); it('should not accept values of other types', () => @@ -1911,6 +2154,56 @@ export function configTests(schema, strictCheck) { 'object', 'other' ])); + }, + + /** + * The requestId validator. + */ + requestId(property) { + it('should accept a correct UUID string value', () => { + const obj = { [property]: 'b012bde4-8b91-4d68-8b48-cd099358a17f' }; + expect(schema.parse(obj)[property]).toBe( + 'b012bde4-8b91-4d68-8b48-cd099358a17f' + ); + obj[property] = '0694de13-ac56-44f9-813c-1c91674e6a19'; + expect(schema.parse(obj)[property]).toBe( + '0694de13-ac56-44f9-813c-1c91674e6a19' + ); + }); + + it('should accept undefined', () => { + acceptUndefined(property); + }); + + it('should accept null', () => { + acceptNull(property); + }); + + it("should not accept 'false', 'undefined', 'null', '' values", () => { + const obj = { [property]: 'false' }; + expect(() => schema.parse(obj)).toThrow(); + obj[property] = 'undefined'; + expect(() => schema.parse(obj)).toThrow(); + obj[property] = 'null'; + expect(() => schema.parse(obj)).toThrow(); + obj[property] = ''; + expect(() => schema.parse(obj)).toThrow(); + }); + + it('should not accept a stringified undefined', () => { + stringUndefinedThrow(property); + }); + + it('should not accept a stringified null', () => { + stringNullThrow(property); + }); + + it('should not accept an empty string', () => { + emptyStringThrow(property); + }); + + it('should not accept values of other types', () => + validatePropOfSchema(schema, property, ['null', 'undefined'])); } }; @@ -1921,7 +2214,7 @@ export function configTests(schema, strictCheck) { }, puppeteerArgs: (property, value, filteredValue) => { describe(property, () => - validationTests.stringArray(property, value, filteredValue) + validationTests.stringArray(property, value, filteredValue, ';') ); }, highcharts: (property, value) => { @@ -1971,10 +2264,13 @@ export function configTests(schema, strictCheck) { describe(property, () => validationTests.infile(property)); }, exportInstr: (property) => { - describe(property, () => validationTests.object(property, false)); + describe(property, () => validationTests.chartConfig(property, false)); }, exportOptions: (property) => { - describe(property, () => validationTests.object(property, false)); + describe(property, () => validationTests.chartConfig(property, false)); + }, + exportSvg: (property) => { + describe(property, () => validationTests.svg(property)); }, exportOutfile: (property) => { describe(property, () => validationTests.outfile(property)); @@ -1989,6 +2285,12 @@ export function configTests(schema, strictCheck) { validationTests.acceptValues(property, correctValue, incorrectValue) ); }, + exportB64: (property) => { + describe(property, () => validationTests.boolean(property)); + }, + exportNoDownload: (property) => { + describe(property, () => validationTests.boolean(property)); + }, exportDefaultHeight: (property) => { describe(property, () => validationTests.positiveNum(property)); }, @@ -2008,10 +2310,14 @@ export function configTests(schema, strictCheck) { describe(property, () => validationTests.nullableScale(property)); }, exportGlobalOptions: (property) => { - describe(property, () => validationTests.object(property, false)); + describe(property, () => + validationTests.additionalOptions(property, false) + ); }, exportThemeOptions: (property) => { - describe(property, () => validationTests.object(property, false)); + describe(property, () => + validationTests.additionalOptions(property, false) + ); }, exportBatch: (property) => { describe(property, () => validationTests.string(property, false)); @@ -2038,10 +2344,10 @@ export function configTests(schema, strictCheck) { describe(property, () => validationTests.resources(property)); }, customLogicLoadConfig: (property) => { - describe(property, () => validationTests.string(property, false)); + describe(property, () => validationTests.customConfig(property, false)); }, customLogicCreateConfig: (property) => { - describe(property, () => validationTests.string(property, false)); + describe(property, () => validationTests.customConfig(property, false)); }, server: (property, value) => { describe(property, () => validationTests.configObject(property, value)); @@ -2151,7 +2457,7 @@ export function configTests(schema, strictCheck) { describe(property, () => validationTests.logLevel(property, strictCheck)); }, loggingFile: (property) => { - describe(property, () => validationTests.string(property, strictCheck)); + describe(property, () => validationTests.logFile(property, strictCheck)); }, loggingDest: (property) => { describe(property, () => validationTests.string(property, strictCheck)); @@ -2259,17 +2565,8 @@ export function configTests(schema, strictCheck) { payload: (property, value) => { describe(property, () => validationTests.configObject(property, value)); }, - payloadSvg: (property) => { - describe(property, () => validationTests.svg(property, false)); - }, - payloadB64: (property) => { - describe(property, () => validationTests.boolean(property)); - }, - payloadNoDownload: (property) => { - describe(property, () => validationTests.boolean(property)); - }, payloadRequestId: (property) => { - describe(property, () => validationTests.string(property, false)); + describe(property, () => validationTests.requestId(property)); } }; } diff --git a/tests/utils/testsUtils.js b/tests/utils/testsUtils.js index 80387423..a5deeb0f 100644 --- a/tests/utils/testsUtils.js +++ b/tests/utils/testsUtils.js @@ -46,8 +46,8 @@ export const possibleValues = { stringFunction: ['function () {}', '() => {}'], // Other objects - other: [new Date(), new RegExp('abc'), new Error('')], - stringOther: ['new Date()', 'new RegExp("abc")', 'new Error("")'] + other: [new Date(), new Error(''), new RegExp('abc')], + stringOther: ['new Date()', 'new Error("")', 'new RegExp("abc")'] }; /**