running-tools

A collection of tools for runners and their coaches
git clone https://git.ashermorgan.net/running-tools/
Log | Files | Refs | README

commit d2776a3d0e96ab2f4236e7207fc23dc88798f73c
parent a5d8a40f7441b55f79d00a901c6014f9780e309e
Author: Asher Morgan <59518073+ashermorgan@users.noreply.github.com>
Date:   Sat, 30 Aug 2025 11:42:18 -0700

Merge branch 'dev'

Diffstat:
D.env | 2--
D.eslintrc.cjs | 14--------------
M.github/workflows/node.js.yml | 4++--
M.gitignore | 2+-
M404.html | 2+-
MCHANGELOG.md | 140++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------
MREADME.md | 19+++++++++++++++----
Aeslint.config.js | 37+++++++++++++++++++++++++++++++++++++
Mindex.html | 8++++----
Mpackage-lock.json | 9769+++++++++++++++++++++++++++++++++++++++++++++++++------------------------------
Mpackage.json | 47++++++++++++++++++++++++++++++++---------------
Mplaywright.config.js | 11++++++++++-
Msrc/App.vue | 9+++++----
Msrc/assets/global.css | 23+++++++++++++++++++++++
Asrc/components/AdvancedOptionsInput.vue | 111+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/components/DecimalInput.vue | 26++++++++++----------------
Msrc/components/DoubleOutputTable.vue | 69++++++++++++++++++++++++++++++++-------------------------------------
Msrc/components/IntegerInput.vue | 22++++++++--------------
Msrc/components/PaceInput.vue | 44+++++++++++++++++++++-----------------------
Dsrc/components/RaceOptions.vue | 30------------------------------
Msrc/components/SingleOutputTable.vue | 30+++++++++++++-----------------
Msrc/components/SplitOutputTable.vue | 90+++++++++++++++++++++++++++++++++++++++++++------------------------------------
Msrc/components/TargetEditor.vue | 136++++++++++++++++++++++++++++++++++++-------------------------------------------
Msrc/components/TargetSetSelector.vue | 74+++++++++++++++++++++++++++++++++++++++++++++-----------------------------
Msrc/components/TimeInput.vue | 31+++++++++++++++----------------
Asrc/composables/useObjectModel.ts | 35+++++++++++++++++++++++++++++++++++
Dsrc/composables/useStorage.js | 36------------------------------------
Asrc/composables/useStorage.ts | 32++++++++++++++++++++++++++++++++
Asrc/core/calculators.ts | 263+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/core/migrations.ts | 182+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/core/racePrediction.ts | 505+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/core/targets.ts | 211+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/core/units.ts | 396+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/core/utils.ts | 55+++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dsrc/main.js | 11-----------
Asrc/main.ts | 13+++++++++++++
Dsrc/router/index.js | 110-------------------------------------------------------------------------------
Asrc/router/index.ts | 120+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dsrc/utils/calculators.js | 173-------------------------------------------------------------------------------
Dsrc/utils/format.js | 99-------------------------------------------------------------------------------
Dsrc/utils/paces.js | 21---------------------
Dsrc/utils/races.js | 461-------------------------------------------------------------------------------
Dsrc/utils/targets.js | 111-------------------------------------------------------------------------------
Dsrc/utils/units.js | 199-------------------------------------------------------------------------------
Msrc/views/AboutPage.vue | 77+++++++++++++++++++++++++++++++++--------------------------------------------
Msrc/views/BatchCalculator.vue | 272+++++++++++++++++++++++++++++++++++++++++--------------------------------------
Asrc/views/ChangeLog.vue | 10++++++++++
Msrc/views/NotFoundPage.vue | 2+-
Msrc/views/PaceCalculator.vue | 61++++++++++++++++++++++---------------------------------------
Msrc/views/RaceCalculator.vue | 84+++++++++++++++++++++++++++----------------------------------------------------
Msrc/views/SplitCalculator.vue | 59+++++++++++++++++++++++++----------------------------------
Msrc/views/UnitCalculator.vue | 193++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------------
Msrc/views/WorkoutCalculator.vue | 72+++++++++++++++++++++++-------------------------------------------------
Mtests/e2e/batch-calculator.spec.js | 329+++++++++++++++++++++++++++++++++++++++++--------------------------------------
Mtests/e2e/cross-calculator.spec.js | 1285+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------
Mtests/e2e/pace-calculator.spec.js | 236++++++++++++++++++++++++++++++++++++++++---------------------------------------
Mtests/e2e/race-calculator.spec.js | 274+++++++++++++++++++++++++++++++++++++++++--------------------------------------
Mtests/e2e/split-calculator.spec.js | 365++++++++++++++++++++++++++++++++++++++++---------------------------------------
Mtests/e2e/unit-calculator.spec.js | 102+++++++++++++++++++++++++++++++++++++++++--------------------------------------
Mtests/e2e/workout-calculator.spec.js | 282+++++++++++++++++++++++++++++++++++++++++++------------------------------------
Atests/unit/components/AdvancedOptionsInput.spec.js | 556+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtests/unit/components/DoubleOutputTable.spec.js | 6++++--
Mtests/unit/components/PaceInput.spec.js | 84++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------
Dtests/unit/components/RaceOptions.spec.js | 38--------------------------------------
Mtests/unit/components/SplitOutputTable.spec.js | 13++++++++++---
Mtests/unit/components/TargetEditor.spec.js | 106++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------
Mtests/unit/components/TargetSetSelector.spec.js | 149++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------
Mtests/unit/components/TimeInput.spec.js | 17++++++++++++++++-
Atests/unit/core/calculators.spec.js | 257+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atests/unit/core/migration.spec.js | 150+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atests/unit/core/racePrediction.spec.js | 250+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atests/unit/core/targets.spec.js | 59+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atests/unit/core/units.spec.js | 439+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atests/unit/core/utils.spec.js | 206+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dtests/unit/utils/calculators.spec.js | 201-------------------------------------------------------------------------------
Dtests/unit/utils/format.spec.js | 193-------------------------------------------------------------------------------
Dtests/unit/utils/paces.spec.js | 14--------------
Dtests/unit/utils/races.spec.js | 166-------------------------------------------------------------------------------
Dtests/unit/utils/targets.spec.js | 21---------------------
Dtests/unit/utils/units.spec.js | 62--------------------------------------------------------------
Mtests/unit/views/BatchCalculator.spec.js | 664++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------
Mtests/unit/views/PaceCalculator.spec.js | 309+++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------------
Mtests/unit/views/RaceCalculator.spec.js | 437++++++++++++++++++++++++++++++++++++++++++++++---------------------------------
Mtests/unit/views/SplitCalculator.spec.js | 261++++++++++++++++++++++++++++++++++++++++++++-----------------------------------
Mtests/unit/views/WorkoutCalculator.spec.js | 470+++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------
Atsconfig.app.json | 14++++++++++++++
Atsconfig.json | 11+++++++++++
Atsconfig.node.json | 12++++++++++++
Avite-env.d.ts | 1+
Dvite.config.js | 64----------------------------------------------------------------
Avite.config.ts | 80+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
91 files changed, 14532 insertions(+), 8264 deletions(-)

diff --git a/.env b/.env @@ -1,2 +0,0 @@ -# No specific domain by default -VITE_API_DOMAIN= diff --git a/.eslintrc.cjs b/.eslintrc.cjs @@ -1,14 +0,0 @@ -/* eslint-env node */ -module.exports = { - root: true, - 'extends': [ - 'plugin:vue/vue3-essential', - 'eslint:recommended' - ], - parserOptions: { - ecmaVersion: 'latest' - }, - env: { - node: true, - }, -} diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml @@ -19,11 +19,11 @@ jobs: - name: Set up Node.js uses: actions/setup-node@v3 with: - node-version: '18.x' + node-version: '20.x' cache: 'npm' - run: npm ci - - run: npx playwright install --with-deps + - run: npx playwright install - run: npm run build --if-present - run: npm run test:unit - run: npm run test:e2e diff --git a/.gitignore b/.gitignore @@ -21,7 +21,7 @@ pnpm-debug.log* *.njsproj *.sln *.sw? -/test-results/ +/tests/e2e/results/ /playwright-report/ /blob-report/ /playwright/.cache/ diff --git a/404.html b/404.html @@ -8,7 +8,7 @@ <link rel="icon" type="image/png" sizes="16x16" href="/img/icons/favicon-16x16.png"> <title>404 Not Found - Running Tools</title> - <meta name="description" content="A collection of tools for runners and their coaches that calculate splits, predict race times, convert units, and more"> + <meta name="description" content="%VITE_DESCRIPTION%"> </head> <body> diff --git a/CHANGELOG.md b/CHANGELOG.md @@ -1,90 +1,134 @@ # Changelog -## 1.4.1 - 2024-09-07 +## 1.5.0 -- 2025-08-30 + +### Added + +- Support custom workout names (when enabled under advanced options) +- Add changelog page + +### Changed + +- Synchronize prediction model and Riegel exponent options across all applicable + calculators +- Round durations in the Batch Calculator to make the results more compact +- Abbreviate default workout names where the split and total portions are equal + (e.g. "5 km @ 5 km" becomes just "5 km") +- Hide Riegel exponent advanced option when not in use + +### Fixed + +- Prevent decimal input fields from being reformatted while the user is still + typing +- Correct inaccuracies with unit conversions to and from miles + +## 1.4.1 -- 2024-09-07 ### Fixed -- Bug that prevented workout split distances from being edited through the Batch - Calculator -- Improved accuracy of the VO2 Max model -- Miscellaneous dark mode issues -## 1.4.0 - 2024-07-11 +- Allow workout split distances to be edited from the Batch Calculator +- Improve accuracy of the VO2 Max race prediction model +- Fix miscellaneous dark mode issues + +## 1.4.0 -- 2024-07-11 ### Added -- Batch Calculator -- Workout Calculator + +- Add Batch Calculator for performing pace, race, and workout calculations over + a range of input times +- Add Workout Calculator for estimating target workout splits ### Changed -- The edit target set dialog is opened automatically after a new target set is + +- Open the edit target set dialog automatically after a new target set is created -- Target sets can only be used by the calculator they were created in (and by - the Batch Calculator for pace, race, and workout target sets) + +### Removed + +- Remove support for using target sets across multiple calculators (excluding + the Batch Calculator, where applicable) ### Fixed -- Bug that prevented Split Calculator splits from being saved after a new target - set was created -## 1.3.0 - 2024-03-25 +- Ensure Split Calculator splits are saved after a new target set is created + +## 1.3.0 -- 2024-03-25 ### Added -- Support for custom cross-calculator target sets -- About page + +- Add about page ### Changed -- Reorganized Race Calculator options and output -- Default units can now be customized + +- Allow target sets to be used across multiple calculators +- Reorganize Race Calculator options and output +- Allow default units to be customized ### Fixed -- Improved input element behavior and validation -- Improved accuracy of the Purdy Points model for time-based targets -- Improved accessibility -## 1.2.0 - 2021-11-20 +- Improve input element behavior and validation +- Improve accuracy of the Purdy Points race prediction model for time-based + targets +- Improve accessibility + +## 1.2.0 -- 2021-11-20 ### Added -- Split calculator + +- Add Split Calculator for calculating split paces and cumulative times of + races ### Changed -- Calculator state is saved between sessions -- Improved arrow key behavior in time input fields -- Improved formatting of distances and durations -## 1.1.1 - 2021-09-19 +- Save calculator state between sessions +- Improve arrow key behavior in time input fields +- Improve formatting of distances and durations + +## 1.1.1 -- 2021-09-19 ### Fixed -- Bug in how default units were chosen -- Issue that caused all pace and race calculator results to be NaN -## 1.1.0 - 2021-09-16 +- Fix bug in how default units are chosen +- Fix issue that caused all Pace and Race Calculator results to be NaN after + updating to version 1.1.0 + +## 1.1.0 -- 2021-09-16 ### Added -- Time based targets in pace and race calculators -- Advanced race calculator options and output + +- Support time-based targets in Pace and Race Calculators +- Add advanced options and output in Race Calculator ### Changed -- Race calculator shows pace of each result -- Improved calculator interfaces -- Default units are chosen automatically -## 1.0.0 - 2021-08-30 +- Show the pace of each result in Race Calculator +- Improve calculator interfaces +- Choose default units automatically according to the user's language region + +## 1.0.0 -- 2021-08-30 ### Added -- Dark mode -- Race calculator + +- Add Race Calculator for estimating equivalent race results +- Implement dark mode ### Changed -- The list of distance targets can be edited -- Improved appearance -- Inactive pages are cached -- Minute and second input fields wrap around -## 0.2.0 - 2021-08-18 +- Allow the list of Pace Calculator targets to be edited +- Improve appearance on mobile devices +- Cache inactive pages +- Allow minute and second input fields to wrap around + +## 0.2.0 -- 2021-08-18 ### Added -- Pace and Unit Calculators -- Progressive Web App -## 0.1.0 - 2021-07-29 +- Add Pace Calculator for calculating distances and times that are at equivalent + paces +- Add Unit Calculator for converting between distance, time, speed, and pace + units +- Implement Progressive Web App functionality -### Added -- Basic app structure +## 0.1.0 -- 2021-07-29 + +*Initial release with basic app structure.* diff --git a/README.md b/README.md @@ -1,8 +1,12 @@ # Running Tools -A collection of tools for runners and their coaches. -Try it out [here](https://apps.ashermorgan.net/running-tools/). + +A collection of tools for runners and their coaches. Try it out +[here](https://apps.ashermorgan.net/running-tools/). ## Features + +Running Tools contains six calculators: + - [Batch Calculator](https://apps.ashermorgan.net/running-tools/#/calculate/batch): Create tables of the results of the other calculators over a range of inputs - [Pace Calculator](https://apps.ashermorgan.net/running-tools/#/calculate/paces): @@ -16,7 +20,13 @@ Try it out [here](https://apps.ashermorgan.net/running-tools/). - [Workout Calculator](https://apps.ashermorgan.net/running-tools/#/calculate/workouts): Estimate target workout splits using previous race results +The Pace, Race, and Unit Calculators are the simplest to use, while the Batch, +Split, and Workout Calculators are designed for more advanced use cases. More +information about each calculator can be found +[here](https://apps.ashermorgan.net/running-tools/#/about). + ## Setup + Install dependencies ``` @@ -29,9 +39,10 @@ Run development server npm run dev ``` -Run linter and tests +Run type checker, linter, unit tests, and end-to-end tests ``` +npm run type-check npm run lint npm run test:unit npm run test:e2e @@ -40,5 +51,5 @@ npm run test:e2e Build for production ``` -VITE_API_DOMAIN=https://example.com BASE_URL=/running-tools/ npm run build +DOMAIN=example.com BASE_URL=/running-tools/ npm run build ``` diff --git a/eslint.config.js b/eslint.config.js @@ -0,0 +1,37 @@ +import { globalIgnores } from 'eslint/config' +import { configureVueProject, defineConfigWithVueTs, vueTsConfigs } from '@vue/eslint-config-typescript' +import globals from 'globals' +import js from '@eslint/js' +import ts from 'typescript-eslint' +import pluginVue from 'eslint-plugin-vue' +import pluginPlaywright from 'eslint-plugin-playwright' + +configureVueProject({ scriptLangs: ['ts', 'js'] }) + +export default defineConfigWithVueTs([ + { + name: 'app/files-to-lint', + files: ['**/*.{js,mjs,jsx,vue}'], + }, + + globalIgnores(['**/dist/**', '**/dist-ssr/**', '**/coverage/**']), + + { + languageOptions: { + globals: { + ...globals.browser, + ...globals.node, + }, + }, + }, + + js.configs.recommended, + ts.configs.recommended, + ...pluginVue.configs['flat/essential'], + vueTsConfigs.recommended, + + { + ...pluginPlaywright.configs['flat/recommended'], + files: ['tests/e2e/*.{test,spec}.{js,ts,jsx,tsx}'], + }, +]) diff --git a/index.html b/index.html @@ -8,12 +8,12 @@ <link rel="icon" type="image/png" sizes="16x16" href="/img/icons/favicon-16x16.png"> <title>Running Tools</title> - <meta name="description" content="A collection of tools for runners and their coaches that calculate splits, predict race times, convert units, and more"> + <meta name="description" content="%VITE_DESCRIPTION%"> <meta property="og:title" content="Running Tools"/> - <meta property="og:description" content="A collection of tools for runners and their coaches that calculate splits, predict race times, convert units, and more"/> + <meta property="og:description" content="%VITE_DESCRIPTION%"/> <meta property="og:type" content="website"/> - <meta property="og:url" content="%VITE_API_DOMAIN%%BASE_URL%"/> - <meta property="og:image" content="%VITE_API_DOMAIN%%BASE_URL%img/icons/open-graph-1280x640.png"/> + <meta property="og:url" content="%VITE_DOMAIN%%BASE_URL%"/> + <meta property="og:image" content="%VITE_DOMAIN%%BASE_URL%img/icons/open-graph-1280x640.png"/> <!-- Microsoft tags --> <meta name="msapplication-square70x70logo" content="%BASE_URL%img/icons/mstile-icon-128x128.png"> diff --git a/package-lock.json b/package-lock.json @@ -1,167 +1,124 @@ { "name": "running-tools", - "version": "1.4.1", + "version": "1.5.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "running-tools", - "version": "1.4.1", + "version": "1.5.0", "dependencies": { "feather-icons": "^4.29.2", - "vue": "^3.4.27", + "vue": "^3.5.17", "vue-feather": "^2.0.0", "vue-router": "^4.3.2" }, "devDependencies": { - "@playwright/test": "^1.44.0", - "@types/node": "^20.12.12", - "@vitejs/plugin-vue": "^4.6.2", + "@eslint/js": "^9.22.0", + "@playwright/test": "^1.53.1", + "@tsconfig/node22": "^22.0.2", + "@types/markdown-it": "^14.1.2", + "@types/node": "^24.0.7", + "@vitejs/plugin-vue": "^5.2.4", + "@vue/eslint-config-typescript": "^14.5.1", "@vue/test-utils": "^2.4.6", - "eslint": "^8.57.0", - "eslint-plugin-vue": "^9.26.0", - "jsdom": "^22.1.0", - "pwa-asset-generator": "^6.3.1", - "vite": "^4.5.3", - "vite-plugin-pwa": "^0.16.7", - "vitest": "^0.32.4" - } - }, - "node_modules/@aashutoshrathi/word-wrap": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", - "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", - "dev": true, - "engines": { - "node": ">=0.10.0" + "@vue/tsconfig": "^0.7.0", + "eslint": "^9.22.0", + "eslint-plugin-playwright": "^2.2.0", + "eslint-plugin-vue": "^10.2.0", + "jsdom": "^26.1.0", + "markdown-it": "^14.1.0", + "npm-run-all2": "^8.0.4", + "pwa-asset-generator": "^8.0.5", + "typescript": "^5.8.3", + "typescript-eslint": "^8.34.1", + "vite": "^6.3.5", + "vite-plugin-pwa": "^1.0.0", + "vitest": "^3.2.4", + "vue-tsc": "^2.2.10" } }, "node_modules/@ampproject/remapping": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", - "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" }, "engines": { "node": ">=6.0.0" } }, - "node_modules/@babel/code-frame": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", - "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", - "dev": true, - "dependencies": { - "@babel/highlight": "^7.23.4", - "chalk": "^2.4.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/code-frame/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "node_modules/@asamuzakjp/css-color": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz", + "integrity": "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==", "dev": true, + "license": "MIT", "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" + "@csstools/css-calc": "^2.1.3", + "@csstools/css-color-parser": "^3.0.9", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "lru-cache": "^10.4.3" } }, - "node_modules/@babel/code-frame/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", "dev": true, + "license": "MIT", "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" }, "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/code-frame/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/@babel/code-frame/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "node_modules/@babel/code-frame/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/@babel/code-frame/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "engines": { - "node": ">=4" + "node": ">=6.9.0" } }, - "node_modules/@babel/code-frame/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "node_modules/@babel/code-frame/node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } + "license": "MIT" }, "node_modules/@babel/compat-data": { - "version": "7.22.9", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.22.9.tgz", - "integrity": "sha512-5UamI7xkUcJ3i9qVDS+KFDEK8/7oJ55/sJMB1Ge7IEapr7KfdfV/HErR+koZwOfd+SgtFKOKRhRakdg++DcJpQ==", + "version": "7.27.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.5.tgz", + "integrity": "sha512-KiRAp/VoJaWkkte84TvUd9qjdbZAdiqyvMxrGl1N6vzFogKmaLgoM3L1kgtLicp2HP5fBJS8JrZKLVIZGVJAVg==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.22.9", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.22.9.tgz", - "integrity": "sha512-G2EgeufBcYw27U4hhoIwFcgc1XU7TlXJ3mv04oOv1WCuo900U/anZSPzEqNjwdjgffkk2Gs0AN0dW1CKVLcG7w==", + "version": "7.27.4", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.4.tgz", + "integrity": "sha512-bXYxrXFubeYdvB0NhD/NBB3Qi6aZeV20GOWVI47t2dkecCEoneR4NPVcb7abpXDEvejgrUfFtG6vG/zxAKmg+g==", "dev": true, + "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.22.5", - "@babel/generator": "^7.22.9", - "@babel/helper-compilation-targets": "^7.22.9", - "@babel/helper-module-transforms": "^7.22.9", - "@babel/helpers": "^7.22.6", - "@babel/parser": "^7.22.7", - "@babel/template": "^7.22.5", - "@babel/traverse": "^7.22.8", - "@babel/types": "^7.22.5", - "convert-source-map": "^1.7.0", + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.27.3", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.27.3", + "@babel/helpers": "^7.27.4", + "@babel/parser": "^7.27.4", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.27.4", + "@babel/types": "^7.27.3", + "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", - "json5": "^2.2.2", + "json5": "^2.2.3", "semver": "^6.3.1" }, "engines": { @@ -177,66 +134,56 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, + "license": "ISC", "bin": { "semver": "bin/semver.js" } }, "node_modules/@babel/generator": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.6.tgz", - "integrity": "sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==", + "version": "7.27.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.5.tgz", + "integrity": "sha512-ZGhA37l0e/g2s1Cnzdix0O3aLYm66eF8aufiVteOgnwxgnRP8GoyMj7VWsgWnQbVKXyge7hqrFh2K2TQM6t1Hw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/types": "^7.23.6", - "@jridgewell/gen-mapping": "^0.3.2", - "@jridgewell/trace-mapping": "^0.3.17", - "jsesc": "^2.5.1" + "@babel/parser": "^7.27.5", + "@babel/types": "^7.27.3", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^3.0.2" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz", - "integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==", - "dev": true, - "dependencies": { - "@babel/types": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.5.tgz", - "integrity": "sha512-m1EP3lVOPptR+2DwD125gziZNcmoNSHGmJROKoy87loWUQyJaVXDgpmruWqDARZSmtYQ+Dl25okU8+qhVzuykw==", + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", + "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.27.3" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.22.9", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.9.tgz", - "integrity": "sha512-7qYrNM6HjpnPHJbopxmb8hSPoZ0gsX8IvUS32JGVoy+pU9e5N0nLr1VjJoR6kA4d9dmGLxNYOjeB8sUDal2WMw==", + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.22.9", - "@babel/helper-validator-option": "^7.22.5", - "browserslist": "^4.21.9", + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" } }, "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { @@ -244,6 +191,7 @@ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", "dev": true, + "license": "ISC", "dependencies": { "yallist": "^3.0.2" } @@ -253,30 +201,24 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, + "license": "ISC", "bin": { "semver": "bin/semver.js" } }, - "node_modules/@babel/helper-compilation-targets/node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true - }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.22.9", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.9.tgz", - "integrity": "sha512-Pwyi89uO4YrGKxL/eNJ8lfEH55DnRloGPOseaA8NFNL6jAUnn+KccaISiFazCj5IolPPDjGSdzQzXVzODVRqUQ==", - "dev": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-function-name": "^7.22.5", - "@babel/helper-member-expression-to-functions": "^7.22.5", - "@babel/helper-optimise-call-expression": "^7.22.5", - "@babel/helper-replace-supers": "^7.22.9", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.27.1.tgz", + "integrity": "sha512-QwGAmuvM17btKU5VqXfb+Giw4JcN0hjuufz3DYnpeVDvZLAObloM77bhMXiqry3Iio+Ai4phVRDwl6WU10+r5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-member-expression-to-functions": "^7.27.1", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/traverse": "^7.27.1", "semver": "^6.3.1" }, "engines": { @@ -291,18 +233,20 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, + "license": "ISC", "bin": { "semver": "bin/semver.js" } }, "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.22.9", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.9.tgz", - "integrity": "sha512-+svjVa/tFwsNSG4NEy1h85+HQ5imbT92Q5/bgtS7P0GTQlP8WuFdqsiABmQouhiFGyV66oGxZFpeYHza1rNsKw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.27.1.tgz", + "integrity": "sha512-uVDC72XVf8UbrH5qQTc18Agb8emwjTiZrQE11Nv3CuBEZmVvTwwE9CBUEvHku06gQCAyYf8Nv6ja1IN+6LMbxQ==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "regexpu-core": "^5.3.1", + "@babel/helper-annotate-as-pure": "^7.27.1", + "regexpu-core": "^6.2.0", "semver": "^6.3.1" }, "engines": { @@ -317,15 +261,17 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, + "license": "ISC", "bin": { "semver": "bin/semver.js" } }, "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.1.tgz", - "integrity": "sha512-kX4oXixDxG197yhX+J3Wp+NpL2wuCFjWQAr6yX2jtCnflK9ulMI51ULFGIrWiX1jGfvAxdHp+XQCcP2bZGPs9A==", + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.4.tgz", + "integrity": "sha512-jljfR1rGnXXNWnmQg2K3+bvhkxB51Rl32QRaOTuwwjviGrHzIbSc8+x9CpraDtbT7mfyjXObULP4w/adunNwAw==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-compilation-targets": "^7.22.6", "@babel/helper-plugin-utils": "^7.22.5", @@ -334,78 +280,47 @@ "resolve": "^1.14.2" }, "peerDependencies": { - "@babel/core": "^7.4.0-0" - } - }, - "node_modules/@babel/helper-environment-visitor": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", - "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-function-name": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", - "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", - "dev": true, - "dependencies": { - "@babel/template": "^7.22.15", - "@babel/types": "^7.23.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-hoist-variables": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", - "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", - "dev": true, - "dependencies": { - "@babel/types": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.22.5.tgz", - "integrity": "sha512-aBiH1NKMG0H2cGZqspNvsaBe6wNGjbJjuLy29aU+eDZjSbbN53BaxlpB02xm9v34pLTZ1nIQPFYn2qMZoa5BQQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.27.1.tgz", + "integrity": "sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/types": "^7.22.5" + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-imports": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.5.tgz", - "integrity": "sha512-8Dl6+HD/cKifutF5qGd/8ZJi84QeAKh+CEe1sBzz8UayBBGg1dAIJrdHOcOM5b2MpzWL2yuotJTtGjETq0qjXg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/types": "^7.22.5" + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.22.9", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.22.9.tgz", - "integrity": "sha512-t+WA2Xn5K+rTeGtC8jCsdAH52bjggG5TKRuRrAGNM/mjIbO4GxvlLMFOEz9wXY5I2XQ60PMFsAG2WIcG82dQMQ==", + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz", + "integrity": "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-module-imports": "^7.22.5", - "@babel/helper-simple-access": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/helper-validator-identifier": "^7.22.5" + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.27.3" }, "engines": { "node": ">=6.9.0" @@ -415,35 +330,38 @@ } }, "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz", - "integrity": "sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz", + "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", - "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-remap-async-to-generator": { - "version": "7.22.9", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.9.tgz", - "integrity": "sha512-8WWC4oR4Px+tr+Fp0X3RHDVfINGpF3ad1HIbrc8A77epiR6eMMc6jsgozkzT2uDiOOdoS9cLIQ+XD2XvI2WSmQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.27.1.tgz", + "integrity": "sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-wrap-function": "^7.22.9" + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-wrap-function": "^7.27.1", + "@babel/traverse": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -453,14 +371,15 @@ } }, "node_modules/@babel/helper-replace-supers": { - "version": "7.22.9", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.22.9.tgz", - "integrity": "sha512-LJIKvvpgPOPUThdYqcX6IXRuIcTkcAub0IaDRGCZH0p5GPUp7PhRU9QVgFcDDd51BaPkk77ZjqFwh6DZTAEmGg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.27.1.tgz", + "integrity": "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-member-expression-to-functions": "^7.22.5", - "@babel/helper-optimise-call-expression": "^7.22.5" + "@babel/helper-member-expression-to-functions": "^7.27.1", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/traverse": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -469,223 +388,174 @@ "@babel/core": "^7.0.0" } }, - "node_modules/@babel/helper-simple-access": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", - "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", - "dev": true, - "dependencies": { - "@babel/types": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz", - "integrity": "sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==", - "dev": true, - "dependencies": { - "@babel/types": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-split-export-declaration": { - "version": "7.22.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", - "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz", + "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/types": "^7.22.5" + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-string-parser": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz", - "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==", - "dev": true, + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", - "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", - "dev": true, + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-option": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.5.tgz", - "integrity": "sha512-R3oB6xlIVKUnxNUxbmgq7pKjxpru24zlimpE8WK47fACIlM0II/Hm1RS8IaOI7NgCr6LNS+jl5l75m20npAziw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-wrap-function": { - "version": "7.22.9", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.22.9.tgz", - "integrity": "sha512-sZ+QzfauuUEfxSEjKFmi3qDSHgLsTPK/pEpoD/qonZKOtTPTLbf59oabPQ4rKekt9lFcj/hTZaOhWwFYrgjk+Q==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.27.1.tgz", + "integrity": "sha512-NFJK2sHUvrjo8wAU/nQTWU890/zB2jj0qBcCbZbbf+005cAsv6tMjXz31fBign6M5ov1o0Bllu+9nbqkfsjjJQ==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-function-name": "^7.22.5", - "@babel/template": "^7.22.5", - "@babel/types": "^7.22.5" + "@babel/template": "^7.27.1", + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helpers": { - "version": "7.22.6", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.22.6.tgz", - "integrity": "sha512-YjDs6y/fVOYFV8hAf1rxd1QvR9wJe1pDBZ2AREKq/SDayfPzgk0PBnVuTCE5X1acEpMMNOVUqoe+OwiZGJ+OaA==", + "version": "7.27.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.6.tgz", + "integrity": "sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/template": "^7.22.5", - "@babel/traverse": "^7.22.6", - "@babel/types": "^7.22.5" + "@babel/template": "^7.27.2", + "@babel/types": "^7.27.6" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/highlight": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", - "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", - "dev": true, + "node_modules/@babel/parser": { + "version": "7.27.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.5.tgz", + "integrity": "sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg==", + "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.22.20", - "chalk": "^2.4.2", - "js-tokens": "^4.0.0" + "@babel/types": "^7.27.3" }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" + "bin": { + "parser": "bin/babel-parser.js" }, "engines": { - "node": ">=4" + "node": ">=6.0.0" } }, - "node_modules/@babel/highlight/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.27.1.tgz", + "integrity": "sha512-QPG3C9cCVRQLxAVwmefEmwdTanECuUBMQZ/ym5kiw3XKCGA7qkuQLcjWWHcrD/GKbn/WmJwaezfuuAOcyKlRPA==", "dev": true, + "license": "MIT", "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.27.1" }, "engines": { - "node": ">=4" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "node_modules/@babel/highlight/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.27.1.tgz", + "integrity": "sha512-qNeq3bCKnGgLkEXUuFry6dPlGfCdQNZbn7yUAPCInwAJHMU7THJfrBSozkcWq5sNM6RcF3S8XyQL2A52KNR9IA==", "dev": true, + "license": "MIT", "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/@babel/highlight/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "node_modules/@babel/highlight/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/@babel/highlight/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, + "@babel/helper-plugin-utils": "^7.27.1" + }, "engines": { - "node": ">=4" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "node_modules/@babel/highlight/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.27.1.tgz", + "integrity": "sha512-g4L7OYun04N1WyqMNjldFwlfPCLVkgB54A/YCXICZYBsvJJE3kByKv9c9+R/nAfmIfjl2rKYLNyMHboYbZaWaA==", "dev": true, + "license": "MIT", "dependencies": { - "has-flag": "^3.0.0" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/parser": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.5.tgz", - "integrity": "sha512-EOv5IK8arwh3LI47dz1b0tKUb/1uhHAnHJOrjgtQMIpu1uXd9mlFrJg9IUgGUgZ41Ch0K8REPTYpO7B76b4vJg==", - "bin": { - "parser": "bin/babel-parser.js" + "node": ">=6.9.0" }, - "engines": { - "node": ">=6.0.0" + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.22.5.tgz", - "integrity": "sha512-NP1M5Rf+u2Gw9qfSO4ihjcTGW5zXTi36ITLd4/EoAcEhIZ0yjMqmftDNl3QC19CX7olhrjpyU454g/2W7X0jvQ==", + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.27.1.tgz", + "integrity": "sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/plugin-transform-optional-chaining": "^7.27.1" }, "engines": { "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.0.0" + "@babel/core": "^7.13.0" } }, - "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.22.5.tgz", - "integrity": "sha512-31Bb65aZaUwqCbWMnZPduIZxCBngHFlzyN6Dq6KAJjtx+lx6ohKHubc61OomYi7XwVD4Ol0XCVz4h+pYFR048g==", + "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.27.1.tgz", + "integrity": "sha512-6BpaYGDavZqkI6yT+KSPdpZFfpnd68UKXbcjI9pJ13pvHhPrCKWOOLp+ysvMeA+DxnhuPpgIaRpxRxo5A9t5jw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", - "@babel/plugin-transform-optional-chaining": "^7.22.5" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.27.1" }, "engines": { "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.13.0" + "@babel/core": "^7.0.0" } }, "node_modules/@babel/plugin-proposal-private-property-in-object": { @@ -693,6 +563,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" }, @@ -700,53 +571,63 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-proposal-unicode-property-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz", - "integrity": "sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==", + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.27.1.tgz", + "integrity": "sha512-UT/Jrhw57xg4ILHLFnzFpPDlMbcdEicaAtjPQpbj9wa8T4r5KVWCimHcL/460g8Ht0DMxDyjsLgiWSkVjnwPFg==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { - "node": ">=4" + "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz", + "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "node_modules/@babel/plugin-syntax-unicode-sets-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", + "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@babel/core": "^7.0.0" } }, - "node_modules/@babel/plugin-syntax-class-static-block": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", - "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.27.1.tgz", + "integrity": "sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -755,37 +636,50 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-dynamic-import": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", - "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", + "node_modules/@babel/plugin-transform-async-generator-functions": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.27.1.tgz", + "integrity": "sha512-eST9RrwlpaoJBDHShc+DS2SG4ATTi2MYNb4OxYkf3n+7eb49LWpnS+HSpVfW4x927qQwgk8A2hGNVaajAEw0EA==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-remap-async-to-generator": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-export-namespace-from": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", - "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.27.1.tgz", + "integrity": "sha512-NREkZsZVJS4xmTr8qzE5y8AfIPqsdQfRuUiLRTEzb7Qii8iFWCyDKaUV2c0rCuh4ljDZ98ALHP/PetiBV2nddA==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-remap-async-to-generator": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-import-assertions": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.22.5.tgz", - "integrity": "sha512-rdV97N7KqsRzeNGoWUOK6yUsWarLjE5Su/Snk9IYPU9CwkWHs4t+rTGOvffTR8XGkJMTAdLfO0xVnXm8wugIJg==", + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.27.1.tgz", + "integrity": "sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -794,13 +688,14 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.22.5.tgz", - "integrity": "sha512-KwvoWDeNKPETmozyFE0P2rOLqh39EoQHNjqizrI5B8Vt0ZNS7M56s7dAiAqbYfiAYOuIzIh96z3iR2ktgu3tEg==", + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.27.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.27.5.tgz", + "integrity": "sha512-JF6uE2s67f0y2RZcm2kpAUEbD50vH62TyWVebxwHAlbSdM49VqPz8t4a1uIjp4NIOIZ4xzLfjY5emt/RCyC7TQ==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -809,124 +704,162 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "node_modules/@babel/plugin-transform-class-properties": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.27.1.tgz", + "integrity": "sha512-D0VcalChDMtuRvJIu3U/fwWjf8ZMykz5iZsg77Nuj821vCKI3zCyRLwRdWbsuJ/uRwZhZ002QtCqIkwC/ZkvbA==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "node_modules/@babel/plugin-transform-class-static-block": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.27.1.tgz", + "integrity": "sha512-s734HmYU78MVzZ++joYM+NkJusItbdRcbm+AGRgJCt3iA+yux0QpD9cBVdz3tKyrjVYWRl7j0mHSmv4lhV0aoA==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@babel/core": "^7.12.0" } }, - "node_modules/@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "node_modules/@babel/plugin-transform-classes": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.27.1.tgz", + "integrity": "sha512-7iLhfFAubmpeJe/Wo2TVuDrykh/zlWXLzPNdL0Jqn/Xu8R3QQ8h9ff8FQoISZOsw74/HFqFI7NX63HN7QFIHKA==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-compilation-targets": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1", + "@babel/traverse": "^7.27.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "node_modules/@babel/plugin-transform-classes/node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "license": "MIT", + "engines": { + "node": ">=4" } }, - "node_modules/@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.27.1.tgz", + "integrity": "sha512-lj9PGWvMTVksbWiDT2tW68zGS/cyo4AkZ/QTp0sQT0mjPopCmrSkzxeXkznjqBxzDI6TclZhOJbBmbBLjuOZUw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/template": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.27.3.tgz", + "integrity": "sha512-s4Jrok82JpiaIprtY2nHsYmrThKvvwgHwjgd7UMiYhZaN0asdXNLr0y+NjTfkA7SyQE5i2Fb7eawUOZmLvyqOA==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.27.1.tgz", + "integrity": "sha512-gEbkDVGRvjj7+T1ivxrfgygpT7GUd4vmODtYpbs0gZATdkX8/iSnOtZSxiZnsgm1YjTgjI6VKBGSJJevkrclzw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.27.1.tgz", + "integrity": "sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-private-property-in-object": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", - "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.27.1.tgz", + "integrity": "sha512-hkGcueTEzuhB30B3eJCbCYeCaaEQOmQR0AdvzpD4LoN0GXMWzzGSuRrxR2xTnCrvNbVwK9N6/jQ92GSLfiZWoQ==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@babel/core": "^7.0.0" } }, - "node_modules/@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", - "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "node_modules/@babel/plugin-transform-dynamic-import": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.27.1.tgz", + "integrity": "sha512-MHzkWQcEmjzzVW9j2q8LGjwGWpG2mjwaaB0BNQwst3FIjqsg8Ct/mIZlvSPJvfi9y2AC8mi/ktxbFVL9pZ1I4A==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -935,29 +868,30 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-unicode-sets-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", - "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.27.1.tgz", + "integrity": "sha512-uspvXnhHvGKf2r4VVtBpeFnuDWsJLQ6MF6lGJLC89jBR1uoVeqM416AZtTuhTezOfgHicpJQmoD5YUakO/YmXQ==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.0.0" + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-arrow-functions": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.22.5.tgz", - "integrity": "sha512-26lTNXoVRdAnsaDXPpvCNUq+OVWEVC6bx7Vvz9rC53F2bagUWW4u4ii2+h8Fejfh7RYqPxn+libeFBBck9muEw==", + "node_modules/@babel/plugin-transform-export-namespace-from": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.27.1.tgz", + "integrity": "sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -966,16 +900,15 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-async-generator-functions": { - "version": "7.22.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.22.7.tgz", - "integrity": "sha512-7HmE7pk/Fmke45TODvxvkxRMV9RazV+ZZzhOL9AG8G29TLrr3jkjwF7uJfxZ30EoXpO+LJkq4oA8NjO2DTnEDg==", + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.27.1.tgz", + "integrity": "sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-remap-async-to-generator": "^7.22.5", - "@babel/plugin-syntax-async-generators": "^7.8.4" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -984,15 +917,16 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.22.5.tgz", - "integrity": "sha512-b1A8D8ZzE/VhNDoV1MSJTnpKkCG5bJo+19R4o4oy03zM7ws8yEMK755j61Dc3EyvdysbqH5BOOTquJ7ZX9C6vQ==", + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.27.1.tgz", + "integrity": "sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-remap-async-to-generator": "^7.22.5" + "@babel/helper-compilation-targets": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1001,13 +935,14 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-block-scoped-functions": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.22.5.tgz", - "integrity": "sha512-tdXZ2UdknEKQWKJP1KMNmuF5Lx3MymtMN/pvA+p/VEkhK8jVcQ1fzSy8KM9qRYhAf2/lV33hoMPKI/xaI9sADA==", + "node_modules/@babel/plugin-transform-json-strings": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.27.1.tgz", + "integrity": "sha512-6WVLVJiTjqcQauBhn1LkICsR2H+zm62I3h9faTDKt1qP4jn2o72tSvqMwtGFKGTpojce0gJs+76eZ2uCHRZh0Q==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1016,13 +951,14 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.22.5.tgz", - "integrity": "sha512-EcACl1i5fSQ6bt+YGuU/XGCeZKStLmyVGytWkpyhCLeQVA0eu6Wtiw92V+I1T/hnezUv7j74dA/Ro69gWcU+hg==", + "node_modules/@babel/plugin-transform-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.27.1.tgz", + "integrity": "sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1031,14 +967,14 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-class-properties": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.22.5.tgz", - "integrity": "sha512-nDkQ0NfkOhPTq8YCLiWNxp1+f9fCobEjCb0n8WdbNUBc4IB5V7P1QnX9IjpSoquKrXF5SKojHleVNs2vGeHCHQ==", + "node_modules/@babel/plugin-transform-logical-assignment-operators": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.27.1.tgz", + "integrity": "sha512-SJvDs5dXxiae4FbSL1aBJlG4wvl594N6YEVVn9e3JGulwioy6z3oPjx/sQBO3Y4NwUu5HNix6KJ3wBZoewcdbw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1047,251 +983,14 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-class-static-block": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.22.5.tgz", - "integrity": "sha512-SPToJ5eYZLxlnp1UzdARpOGeC2GbHvr9d/UV0EukuVx8atktg194oe+C5BqQ8jRTkgLRVOPYeXRSBg1IlMoVRA==", + "node_modules/@babel/plugin-transform-member-expression-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.27.1.tgz", + "integrity": "sha512-hqoBX4dcZ1I33jCSWcXrP+1Ku7kdqXf1oeah7ooKOIiAdKQ+uqftgCFNOSzA5AMS2XIHEYeGFg4cKRCdpxzVOQ==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-class-static-block": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.12.0" - } - }, - "node_modules/@babel/plugin-transform-classes": { - "version": "7.22.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.22.6.tgz", - "integrity": "sha512-58EgM6nuPNG6Py4Z3zSuu0xWu2VfodiMi72Jt5Kj2FECmaYk1RrTXA45z6KBFsu9tRgwQDwIiY4FXTt+YsSFAQ==", - "dev": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-compilation-targets": "^7.22.6", - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-function-name": "^7.22.5", - "@babel/helper-optimise-call-expression": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-replace-supers": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "globals": "^11.1.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-classes/node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.22.5.tgz", - "integrity": "sha512-4GHWBgRf0krxPX+AaPtgBAlTgTeZmqDynokHOX7aqqAB4tHs3U2Y02zH6ETFdLZGcg9UQSD1WCmkVrE9ErHeOg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/template": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.22.5.tgz", - "integrity": "sha512-GfqcFuGW8vnEqTUBM7UtPd5A4q797LTvvwKxXTgRsFjoqaJiEg9deBG6kWeQYkVEL569NpnmpC0Pkr/8BLKGnQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-dotall-regex": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.22.5.tgz", - "integrity": "sha512-5/Yk9QxCQCl+sOIB1WelKnVRxTJDSAIxtJLL2/pqL14ZVlbH0fUQUZa/T5/UnQtBNgghR7mfB8ERBKyKPCi7Vw==", - "dev": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-duplicate-keys": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.22.5.tgz", - "integrity": "sha512-dEnYD+9BBgld5VBXHnF/DbYGp3fqGMsyxKbtD1mDyIA7AkTSpKXFhCVuj/oQVOoALfBs77DudA0BE4d5mcpmqw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-dynamic-import": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.22.5.tgz", - "integrity": "sha512-0MC3ppTB1AMxd8fXjSrbPa7LT9hrImt+/fcj+Pg5YMD7UQyWp/02+JWpdnCymmsXwIx5Z+sYn1bwCn4ZJNvhqQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-dynamic-import": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.22.5.tgz", - "integrity": "sha512-vIpJFNM/FjZ4rh1myqIya9jXwrwwgFRHPjT3DkUA9ZLHuzox8jiXkOLvwm1H+PQIP3CqfC++WPKeuDi0Sjdj1g==", - "dev": true, - "dependencies": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-export-namespace-from": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.22.5.tgz", - "integrity": "sha512-X4hhm7FRnPgd4nDA4b/5V280xCx6oL7Oob5+9qVS5C13Zq4bh1qq7LU0GgRU6b5dBWBvhGaXYVB4AcN6+ol6vg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-for-of": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.22.5.tgz", - "integrity": "sha512-3kxQjX1dU9uudwSshyLeEipvrLjBCVthCgeTp6CzE/9JYrlAIaeekVxRpCWsDDfYTfRZRoCeZatCQvwo+wvK8A==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-function-name": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.22.5.tgz", - "integrity": "sha512-UIzQNMS0p0HHiQm3oelztj+ECwFnj+ZRV4KnguvlsD2of1whUeM6o7wGNj6oLwcDoAXQ8gEqfgC24D+VdIcevg==", - "dev": true, - "dependencies": { - "@babel/helper-compilation-targets": "^7.22.5", - "@babel/helper-function-name": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-json-strings": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.22.5.tgz", - "integrity": "sha512-DuCRB7fu8MyTLbEQd1ew3R85nx/88yMoqo2uPSjevMj3yoN7CDM8jkgrY0wmVxfJZyJ/B9fE1iq7EQppWQmR5A==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-json-strings": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-literals": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.22.5.tgz", - "integrity": "sha512-fTLj4D79M+mepcw3dgFBTIDYpbcB9Sm0bpm4ppXPaO+U+PKFFyV9MGRvS0gvGw62sd10kT5lRMKXAADb9pWy8g==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-logical-assignment-operators": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.22.5.tgz", - "integrity": "sha512-MQQOUW1KL8X0cDWfbwYP+TbVbZm16QmQXJQ+vndPtH/BoO0lOKpVoEDMI7+PskYxH+IiE0tS8xZye0qr1lGzSA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-member-expression-literals": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.22.5.tgz", - "integrity": "sha512-RZEdkNtzzYCFl9SE9ATaUMTj2hqMb4StarOJLrZRbqqU4HSBE7UlBw9WBWQiDzrJZJdUWiMTVDI6Gv/8DPvfew==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1301,13 +1000,14 @@ } }, "node_modules/@babel/plugin-transform-modules-amd": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.22.5.tgz", - "integrity": "sha512-R+PTfLTcYEmb1+kK7FNkhQ1gP4KgjpSO6HfH9+f8/yfp2Nt3ggBjiVpRwmwTlfqZLafYKJACy36yDXlEmI9HjQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.27.1.tgz", + "integrity": "sha512-iCsytMg/N9/oFq6n+gFTvUYDZQOMK5kEdeYxmxt91fcJGycfxVP9CnrxoliM0oumFERba2i8ZtwRUCMhvP1LnA==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1317,14 +1017,14 @@ } }, "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.22.5.tgz", - "integrity": "sha512-B4pzOXj+ONRmuaQTg05b3y/4DuFz3WcCNAXPLb2Q0GT0TrGKGxNKV4jwsXts+StaM0LQczZbOpj8o1DLPDJIiA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.27.1.tgz", + "integrity": "sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-simple-access": "^7.22.5" + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1334,15 +1034,16 @@ } }, "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.22.5.tgz", - "integrity": "sha512-emtEpoaTMsOs6Tzz+nbmcePl6AKVtS1yC4YNAeMun9U8YCsgadPNxnOPQ8GhHFB2qdx+LZu9LgoC0Lthuu05DQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.27.1.tgz", + "integrity": "sha512-w5N1XzsRbc0PQStASMksmUeqECuzKuTJer7kFagK8AXgpCMkeDMO5S+aaFb7A51ZYDF7XI34qsTX+fkHiIm5yA==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-module-transforms": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.5" + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1352,13 +1053,14 @@ } }, "node_modules/@babel/plugin-transform-modules-umd": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.22.5.tgz", - "integrity": "sha512-+S6kzefN/E1vkSsKx8kmQuqeQsvCKCd1fraCM7zXm4SFoggI099Tr4G8U81+5gtMdUeMQ4ipdQffbKLX0/7dBQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.27.1.tgz", + "integrity": "sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1368,13 +1070,14 @@ } }, "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.22.5.tgz", - "integrity": "sha512-YgLLKmS3aUBhHaxp5hi1WJTgOUb/NCuDHzGT9z9WTt3YG+CPRhJs6nprbStx6DnWM4dh6gt7SU3sZodbZ08adQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.27.1.tgz", + "integrity": "sha512-SstR5JYy8ddZvD6MhV0tM/j16Qds4mIpJTOd1Yu9J9pJjH93bxHECF7pgtc28XvkzTD6Pxcm/0Z73Hvk7kb3Ng==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1384,12 +1087,13 @@ } }, "node_modules/@babel/plugin-transform-new-target": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.22.5.tgz", - "integrity": "sha512-AsF7K0Fx/cNKVyk3a+DW0JLo+Ua598/NxMRvxDnkpCIGFh43+h/v2xyhRUYf6oD8gE4QtL83C7zZVghMjHd+iw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.27.1.tgz", + "integrity": "sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1399,13 +1103,13 @@ } }, "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.22.5.tgz", - "integrity": "sha512-6CF8g6z1dNYZ/VXok5uYkkBBICHZPiGEl7oDnAx2Mt1hlHVHOSIKWJaXHjQJA5VB43KZnXZDIexMchY4y2PGdA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.27.1.tgz", + "integrity": "sha512-aGZh6xMo6q9vq1JGcw58lZ1Z0+i0xB2x0XaauNIUXd6O1xXc3RwoWEBlsTQrY4KQ9Jf0s5rgD6SiNkaUdJegTA==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1415,13 +1119,13 @@ } }, "node_modules/@babel/plugin-transform-numeric-separator": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.22.5.tgz", - "integrity": "sha512-NbslED1/6M+sXiwwtcAB/nieypGw02Ejf4KtDeMkCEpP6gWFMX1wI9WKYua+4oBneCCEmulOkRpwywypVZzs/g==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.27.1.tgz", + "integrity": "sha512-fdPKAcujuvEChxDBJ5c+0BTaS6revLV7CJL08e4m3de8qJfNIuCc2nc7XJYOjBoTMJeqSmwXJ0ypE14RCjLwaw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-numeric-separator": "^7.10.4" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1431,16 +1135,16 @@ } }, "node_modules/@babel/plugin-transform-object-rest-spread": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.22.5.tgz", - "integrity": "sha512-Kk3lyDmEslH9DnvCDA1s1kkd3YWQITiBOHngOtDL9Pt6BZjzqb6hiOlb8VfjiiQJ2unmegBqZu0rx5RxJb5vmQ==", + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.27.3.tgz", + "integrity": "sha512-7ZZtznF9g4l2JCImCo5LNKFHB5eXnN39lLtLY5Tg+VkR0jwOt7TBciMckuiQIOIW7L5tkQOCh3bVGYeXgMx52Q==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.22.5", - "@babel/helper-compilation-targets": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.22.5" + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/plugin-transform-destructuring": "^7.27.3", + "@babel/plugin-transform-parameters": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1450,13 +1154,14 @@ } }, "node_modules/@babel/plugin-transform-object-super": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.22.5.tgz", - "integrity": "sha512-klXqyaT9trSjIUrcsYIfETAzmOEZL3cBYqOYLJxBHfMFFggmXOv+NYSX/Jbs9mzMVESw/WycLFPRx8ba/b2Ipw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.27.1.tgz", + "integrity": "sha512-SFy8S9plRPbIcxlJ8A6mT/CxFdJx/c04JEctz4jf8YZaVS2px34j7NXRrlGlHkN/M2gnpL37ZpGRGVFLd3l8Ng==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-replace-supers": "^7.22.5" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1466,13 +1171,13 @@ } }, "node_modules/@babel/plugin-transform-optional-catch-binding": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.22.5.tgz", - "integrity": "sha512-pH8orJahy+hzZje5b8e2QIlBWQvGpelS76C63Z+jhZKsmzfNaPQ+LaW6dcJ9bxTpo1mtXbgHwy765Ro3jftmUg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.27.1.tgz", + "integrity": "sha512-txEAEKzYrHEX4xSZN4kJ+OfKXFVSWKB2ZxM9dpcE3wT7smwkNmXo5ORRlVzMVdJbD+Q8ILTgSD7959uj+3Dm3Q==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1482,14 +1187,14 @@ } }, "node_modules/@babel/plugin-transform-optional-chaining": { - "version": "7.22.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.22.6.tgz", - "integrity": "sha512-Vd5HiWml0mDVtcLHIoEU5sw6HOUW/Zk0acLs/SAeuLzkGNOPc9DB4nkUajemhCmTIz3eiaKREZn2hQQqF79YTg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.27.1.tgz", + "integrity": "sha512-BQmKPPIuc8EkZgNKsv0X4bPmOoayeu4F1YCwx2/CfmDSXDbp7GnzlUH+/ul5VGfRg1AoFPsrIThlEBj2xb4CAg==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", - "@babel/plugin-syntax-optional-chaining": "^7.8.3" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1499,12 +1204,13 @@ } }, "node_modules/@babel/plugin-transform-parameters": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.22.5.tgz", - "integrity": "sha512-AVkFUBurORBREOmHRKo06FjHYgjrabpdqRSwq6+C7R5iTCZOsM4QbcB27St0a4U6fffyAOqh3s/qEfybAhfivg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.27.1.tgz", + "integrity": "sha512-018KRk76HWKeZ5l4oTj2zPpSh+NbGdt0st5S6x0pga6HgrjBOJb24mMDHorFopOOd6YHkLgOZ+zaCjZGPO4aKg==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1514,13 +1220,14 @@ } }, "node_modules/@babel/plugin-transform-private-methods": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.22.5.tgz", - "integrity": "sha512-PPjh4gyrQnGe97JTalgRGMuU4icsZFnWkzicB/fUtzlKUqvsWBKEpPPfr5a2JiyirZkHxnAqkQMO5Z5B2kK3fA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.27.1.tgz", + "integrity": "sha512-10FVt+X55AjRAYI9BrdISN9/AQWHqldOeZDUoLyif1Kn05a56xVBXb8ZouL8pZ9jem8QpXaOt8TS7RHUIS+GPA==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1530,15 +1237,15 @@ } }, "node_modules/@babel/plugin-transform-private-property-in-object": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.22.5.tgz", - "integrity": "sha512-/9xnaTTJcVoBtSSmrVyhtSvO3kbqS2ODoh2juEU72c3aYonNF0OMGiaz2gjukyKM2wBBYJP38S4JiE0Wfb5VMQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.27.1.tgz", + "integrity": "sha512-5J+IhqTi1XPa0DXF83jYOaARrX+41gOewWbkPyjMNRDqgOCqdffGh8L3f/Ek5utaEBZExjSAzcyjmV9SSAWObQ==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-create-class-features-plugin": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1548,12 +1255,13 @@ } }, "node_modules/@babel/plugin-transform-property-literals": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.22.5.tgz", - "integrity": "sha512-TiOArgddK3mK/x1Qwf5hay2pxI6wCZnvQqrFSqbtg1GLl2JcNMitVH/YnqjP+M31pLUeTfzY1HAXFDnUBV30rQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.27.1.tgz", + "integrity": "sha512-oThy3BCuCha8kDZ8ZkgOg2exvPYUlprMukKQXI1r1pJ47NCvxfkEy8vK+r/hT9nF0Aa4H1WUPZZjHTFtAhGfmQ==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1563,13 +1271,13 @@ } }, "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.22.5.tgz", - "integrity": "sha512-rR7KePOE7gfEtNTh9Qw+iO3Q/e4DEsoQ+hdvM6QUDH7JRJ5qxq5AA52ZzBWbI5i9lfNuvySgOGP8ZN7LAmaiPw==", + "version": "7.27.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.27.5.tgz", + "integrity": "sha512-uhB8yHerfe3MWnuLAhEbeQ4afVoqv8BQsPqrTv7e/jZ9y00kJL6l9a/f4OWaKxotmjzewfEyXE1vgDJenkQ2/Q==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "regenerator-transform": "^0.15.1" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1578,13 +1286,31 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-transform-regexp-modifiers": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.27.1.tgz", + "integrity": "sha512-TtEciroaiODtXvLZv4rmfMhkCv8jx3wgKpL68PuiPh2M4fvz5jhsA7697N1gMvkvr/JTF13DrFYyEbY9U7cVPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, "node_modules/@babel/plugin-transform-reserved-words": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.22.5.tgz", - "integrity": "sha512-DTtGKFRQUDm8svigJzZHzb/2xatPc6TzNvAIJ5GqOKDsGFYgAskjRulbR/vGsPKq3OPqtexnz327qYpP57RFyA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.27.1.tgz", + "integrity": "sha512-V2ABPHIJX4kC7HegLkYoDpfg9PVmuWy/i6vUM5eGK22bx4YVFD3M5F0QQnWQoDs6AGsUWTVOopBiMFQgHaSkVw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1594,12 +1320,13 @@ } }, "node_modules/@babel/plugin-transform-shorthand-properties": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.22.5.tgz", - "integrity": "sha512-vM4fq9IXHscXVKzDv5itkO1X52SmdFBFcMIBZ2FRn2nqVYqw6dBexUgMvAjHW+KXpPPViD/Yo3GrDEBaRC0QYA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.27.1.tgz", + "integrity": "sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1609,13 +1336,14 @@ } }, "node_modules/@babel/plugin-transform-spread": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.22.5.tgz", - "integrity": "sha512-5ZzDQIGyvN4w8+dMmpohL6MBo+l2G7tfC/O2Dg7/hjpgeWvUx8FzfeOKxGog9IimPa4YekaQ9PlDqTLOljkcxg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.27.1.tgz", + "integrity": "sha512-kpb3HUqaILBJcRFVhFUs6Trdd4mkrzcGXss+6/mxUd273PfbWqSDHRzMT2234gIg2QYfAjvXLSquP1xECSg09Q==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1625,12 +1353,13 @@ } }, "node_modules/@babel/plugin-transform-sticky-regex": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.22.5.tgz", - "integrity": "sha512-zf7LuNpHG0iEeiyCNwX4j3gDg1jgt1k3ZdXBKbZSoA3BbGQGvMiSvfbZRR3Dr3aeJe3ooWFZxOOG3IRStYp2Bw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.27.1.tgz", + "integrity": "sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1640,12 +1369,13 @@ } }, "node_modules/@babel/plugin-transform-template-literals": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.22.5.tgz", - "integrity": "sha512-5ciOehRNf+EyUeewo8NkbQiUs4d6ZxiHo6BcBcnFlgiJfu16q0bQUw9Jvo0b0gBKFG1SMhDSjeKXSYuJLeFSMA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.27.1.tgz", + "integrity": "sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1655,12 +1385,13 @@ } }, "node_modules/@babel/plugin-transform-typeof-symbol": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.22.5.tgz", - "integrity": "sha512-bYkI5lMzL4kPii4HHEEChkD0rkc+nvnlR6+o/qdqR6zrm0Sv/nodmyLhlq2DO0YKLUNd2VePmPRjJXSBh9OIdA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.27.1.tgz", + "integrity": "sha512-RiSILC+nRJM7FY5srIyc4/fGIwUhyDuuBSdWn4y6yT6gm652DpCHZjIipgn6B7MQ1ITOUnAKWixEUjQRIBIcLw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1670,12 +1401,13 @@ } }, "node_modules/@babel/plugin-transform-unicode-escapes": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.22.5.tgz", - "integrity": "sha512-biEmVg1IYB/raUO5wT1tgfacCef15Fbzhkx493D3urBI++6hpJ+RFG4SrWMn0NEZLfvilqKf3QDrRVZHo08FYg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.27.1.tgz", + "integrity": "sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1685,13 +1417,14 @@ } }, "node_modules/@babel/plugin-transform-unicode-property-regex": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.22.5.tgz", - "integrity": "sha512-HCCIb+CbJIAE6sXn5CjFQXMwkCClcOfPCzTlilJ8cUatfzwHlWQkbtV0zD338u9dZskwvuOYTuuaMaA8J5EI5A==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.27.1.tgz", + "integrity": "sha512-uW20S39PnaTImxp39O5qFlHLS9LJEmANjMG7SxIhap8rCHqu0Ik+tLEPX5DKmHn6CsWQ7j3lix2tFOa5YtL12Q==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1701,13 +1434,14 @@ } }, "node_modules/@babel/plugin-transform-unicode-regex": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.22.5.tgz", - "integrity": "sha512-028laaOKptN5vHJf9/Arr/HiJekMd41hOEZYvNsrsXqJ7YPYuX2bQxh31fkZzGmq3YqHRJzYFFAVYvKfMPKqyg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.27.1.tgz", + "integrity": "sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1717,13 +1451,14 @@ } }, "node_modules/@babel/plugin-transform-unicode-sets-regex": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.22.5.tgz", - "integrity": "sha512-lhMfi4FC15j13eKrh3DnYHjpGj6UKQHtNKTbtc1igvAhRy4+kLhV07OpLcsN0VgDEw/MjAvJO4BdMJsHwMhzCg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.27.1.tgz", + "integrity": "sha512-EtkOujbc4cgvb0mlpQefi4NTPBzhSIevblFevACNLUspmrALgmEBdL/XfnyyITfd8fKBZrZys92zOWcik7j9Tw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1733,90 +1468,80 @@ } }, "node_modules/@babel/preset-env": { - "version": "7.22.9", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.22.9.tgz", - "integrity": "sha512-wNi5H/Emkhll/bqPjsjQorSykrlfY5OWakd6AulLvMEytpKasMVUpVy8RL4qBIBs5Ac6/5i0/Rv0b/Fg6Eag/g==", - "dev": true, - "dependencies": { - "@babel/compat-data": "^7.22.9", - "@babel/helper-compilation-targets": "^7.22.9", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-validator-option": "^7.22.5", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.22.5", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.22.5", + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.27.2.tgz", + "integrity": "sha512-Ma4zSuYSlGNRlCLO+EAzLnCmJK2vdstgv+n7aUP+/IKZrOfWHOJVdSJtuub8RzHTj3ahD37k5OKJWvzf16TQyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.27.2", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.27.1", + "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.27.1", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.27.1", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.27.1", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.27.1", "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-class-properties": "^7.12.13", - "@babel/plugin-syntax-class-static-block": "^7.14.5", - "@babel/plugin-syntax-dynamic-import": "^7.8.3", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3", - "@babel/plugin-syntax-import-assertions": "^7.22.5", - "@babel/plugin-syntax-import-attributes": "^7.22.5", - "@babel/plugin-syntax-import-meta": "^7.10.4", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5", - "@babel/plugin-syntax-top-level-await": "^7.14.5", + "@babel/plugin-syntax-import-assertions": "^7.27.1", + "@babel/plugin-syntax-import-attributes": "^7.27.1", "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", - "@babel/plugin-transform-arrow-functions": "^7.22.5", - "@babel/plugin-transform-async-generator-functions": "^7.22.7", - "@babel/plugin-transform-async-to-generator": "^7.22.5", - "@babel/plugin-transform-block-scoped-functions": "^7.22.5", - "@babel/plugin-transform-block-scoping": "^7.22.5", - "@babel/plugin-transform-class-properties": "^7.22.5", - "@babel/plugin-transform-class-static-block": "^7.22.5", - "@babel/plugin-transform-classes": "^7.22.6", - "@babel/plugin-transform-computed-properties": "^7.22.5", - "@babel/plugin-transform-destructuring": "^7.22.5", - "@babel/plugin-transform-dotall-regex": "^7.22.5", - "@babel/plugin-transform-duplicate-keys": "^7.22.5", - "@babel/plugin-transform-dynamic-import": "^7.22.5", - "@babel/plugin-transform-exponentiation-operator": "^7.22.5", - "@babel/plugin-transform-export-namespace-from": "^7.22.5", - "@babel/plugin-transform-for-of": "^7.22.5", - "@babel/plugin-transform-function-name": "^7.22.5", - "@babel/plugin-transform-json-strings": "^7.22.5", - "@babel/plugin-transform-literals": "^7.22.5", - "@babel/plugin-transform-logical-assignment-operators": "^7.22.5", - "@babel/plugin-transform-member-expression-literals": "^7.22.5", - "@babel/plugin-transform-modules-amd": "^7.22.5", - "@babel/plugin-transform-modules-commonjs": "^7.22.5", - "@babel/plugin-transform-modules-systemjs": "^7.22.5", - "@babel/plugin-transform-modules-umd": "^7.22.5", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.22.5", - "@babel/plugin-transform-new-target": "^7.22.5", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.22.5", - "@babel/plugin-transform-numeric-separator": "^7.22.5", - "@babel/plugin-transform-object-rest-spread": "^7.22.5", - "@babel/plugin-transform-object-super": "^7.22.5", - "@babel/plugin-transform-optional-catch-binding": "^7.22.5", - "@babel/plugin-transform-optional-chaining": "^7.22.6", - "@babel/plugin-transform-parameters": "^7.22.5", - "@babel/plugin-transform-private-methods": "^7.22.5", - "@babel/plugin-transform-private-property-in-object": "^7.22.5", - "@babel/plugin-transform-property-literals": "^7.22.5", - "@babel/plugin-transform-regenerator": "^7.22.5", - "@babel/plugin-transform-reserved-words": "^7.22.5", - "@babel/plugin-transform-shorthand-properties": "^7.22.5", - "@babel/plugin-transform-spread": "^7.22.5", - "@babel/plugin-transform-sticky-regex": "^7.22.5", - "@babel/plugin-transform-template-literals": "^7.22.5", - "@babel/plugin-transform-typeof-symbol": "^7.22.5", - "@babel/plugin-transform-unicode-escapes": "^7.22.5", - "@babel/plugin-transform-unicode-property-regex": "^7.22.5", - "@babel/plugin-transform-unicode-regex": "^7.22.5", - "@babel/plugin-transform-unicode-sets-regex": "^7.22.5", - "@babel/preset-modules": "^0.1.5", - "@babel/types": "^7.22.5", - "babel-plugin-polyfill-corejs2": "^0.4.4", - "babel-plugin-polyfill-corejs3": "^0.8.2", - "babel-plugin-polyfill-regenerator": "^0.5.1", - "core-js-compat": "^3.31.0", + "@babel/plugin-transform-arrow-functions": "^7.27.1", + "@babel/plugin-transform-async-generator-functions": "^7.27.1", + "@babel/plugin-transform-async-to-generator": "^7.27.1", + "@babel/plugin-transform-block-scoped-functions": "^7.27.1", + "@babel/plugin-transform-block-scoping": "^7.27.1", + "@babel/plugin-transform-class-properties": "^7.27.1", + "@babel/plugin-transform-class-static-block": "^7.27.1", + "@babel/plugin-transform-classes": "^7.27.1", + "@babel/plugin-transform-computed-properties": "^7.27.1", + "@babel/plugin-transform-destructuring": "^7.27.1", + "@babel/plugin-transform-dotall-regex": "^7.27.1", + "@babel/plugin-transform-duplicate-keys": "^7.27.1", + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.27.1", + "@babel/plugin-transform-dynamic-import": "^7.27.1", + "@babel/plugin-transform-exponentiation-operator": "^7.27.1", + "@babel/plugin-transform-export-namespace-from": "^7.27.1", + "@babel/plugin-transform-for-of": "^7.27.1", + "@babel/plugin-transform-function-name": "^7.27.1", + "@babel/plugin-transform-json-strings": "^7.27.1", + "@babel/plugin-transform-literals": "^7.27.1", + "@babel/plugin-transform-logical-assignment-operators": "^7.27.1", + "@babel/plugin-transform-member-expression-literals": "^7.27.1", + "@babel/plugin-transform-modules-amd": "^7.27.1", + "@babel/plugin-transform-modules-commonjs": "^7.27.1", + "@babel/plugin-transform-modules-systemjs": "^7.27.1", + "@babel/plugin-transform-modules-umd": "^7.27.1", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.27.1", + "@babel/plugin-transform-new-target": "^7.27.1", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.27.1", + "@babel/plugin-transform-numeric-separator": "^7.27.1", + "@babel/plugin-transform-object-rest-spread": "^7.27.2", + "@babel/plugin-transform-object-super": "^7.27.1", + "@babel/plugin-transform-optional-catch-binding": "^7.27.1", + "@babel/plugin-transform-optional-chaining": "^7.27.1", + "@babel/plugin-transform-parameters": "^7.27.1", + "@babel/plugin-transform-private-methods": "^7.27.1", + "@babel/plugin-transform-private-property-in-object": "^7.27.1", + "@babel/plugin-transform-property-literals": "^7.27.1", + "@babel/plugin-transform-regenerator": "^7.27.1", + "@babel/plugin-transform-regexp-modifiers": "^7.27.1", + "@babel/plugin-transform-reserved-words": "^7.27.1", + "@babel/plugin-transform-shorthand-properties": "^7.27.1", + "@babel/plugin-transform-spread": "^7.27.1", + "@babel/plugin-transform-sticky-regex": "^7.27.1", + "@babel/plugin-transform-template-literals": "^7.27.1", + "@babel/plugin-transform-typeof-symbol": "^7.27.1", + "@babel/plugin-transform-unicode-escapes": "^7.27.1", + "@babel/plugin-transform-unicode-property-regex": "^7.27.1", + "@babel/plugin-transform-unicode-regex": "^7.27.1", + "@babel/plugin-transform-unicode-sets-regex": "^7.27.1", + "@babel/preset-modules": "0.1.6-no-external-plugins", + "babel-plugin-polyfill-corejs2": "^0.4.10", + "babel-plugin-polyfill-corejs3": "^0.11.0", + "babel-plugin-polyfill-regenerator": "^0.6.1", + "core-js-compat": "^3.40.0", "semver": "^6.3.1" }, "engines": { @@ -1831,72 +1556,63 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, + "license": "ISC", "bin": { "semver": "bin/semver.js" } }, "node_modules/@babel/preset-modules": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.5.tgz", - "integrity": "sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==", + "version": "0.1.6-no-external-plugins", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", + "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", - "@babel/plugin-transform-dotall-regex": "^7.4.4", "@babel/types": "^7.4.4", "esutils": "^2.0.2" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" } }, - "node_modules/@babel/regjsgen": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz", - "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==", - "dev": true - }, "node_modules/@babel/runtime": { - "version": "7.22.6", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.6.tgz", - "integrity": "sha512-wDb5pWm4WDdF6LFUde3Jl8WzPA+3ZbxYqkC6xAXuD3irdEHN1k0NfTRrJD8ZD378SJ61miMLCqIOXYhd8x+AJQ==", + "version": "7.27.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.6.tgz", + "integrity": "sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q==", "dev": true, - "dependencies": { - "regenerator-runtime": "^0.13.11" - }, + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/template": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", - "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.22.13", - "@babel/parser": "^7.22.15", - "@babel/types": "^7.22.15" + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.6.tgz", - "integrity": "sha512-czastdK1e8YByZqezMPFiZ8ahwVMh/ESl9vPgvgdB9AmFMGP5jfpFax74AQgl5zj4XHzqeYAg2l8PuUeRS1MgQ==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.23.5", - "@babel/generator": "^7.23.6", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.23.6", - "@babel/types": "^7.23.6", + "version": "7.27.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.4.tgz", + "integrity": "sha512-oNcu2QbHqts9BtOWJosOVJapWjBDSxGCpFvikNR5TGDYDQf3JwpIoMzIKrvfoti93cLfPJEG4tH9SPVeyCGgdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.27.3", + "@babel/parser": "^7.27.4", + "@babel/template": "^7.27.2", + "@babel/types": "^7.27.3", "debug": "^4.3.1", "globals": "^11.1.0" }, @@ -1909,410 +1625,666 @@ "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", "dev": true, + "license": "MIT", "engines": { "node": ">=4" } }, "node_modules/@babel/types": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.6.tgz", - "integrity": "sha512-+uarb83brBzPKN38NX1MkB6vb6+mwvR6amUulqAE7ccQw1pEl+bCia9TbdG1lsnFP7lZySvUn37CHyXQdfTwzg==", - "dev": true, + "version": "7.27.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.6.tgz", + "integrity": "sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q==", + "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.23.4", - "@babel/helper-validator-identifier": "^7.22.20", - "to-fast-properties": "^2.0.0" + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@esbuild/android-arm": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", - "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==", - "cpu": [ - "arm" - ], + "node_modules/@csstools/color-helpers": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.0.2.tgz", + "integrity": "sha512-JqWH1vsgdGcw2RR6VliXXdA0/59LttzlU8UlRT/iUUsEeWfYq8I+K0yhihEUTTHLRm1EXvpsCx3083EU15ecsA==", "dev": true, - "optional": true, - "os": [ - "android" + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } ], + "license": "MIT-0", "engines": { - "node": ">=12" + "node": ">=18" } }, - "node_modules/@esbuild/android-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz", - "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==", - "cpu": [ - "arm64" - ], + "node_modules/@csstools/css-calc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz", + "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==", "dev": true, - "optional": true, - "os": [ - "android" + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } ], + "license": "MIT", "engines": { - "node": ">=12" + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" } }, - "node_modules/@esbuild/android-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz", - "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==", - "cpu": [ - "x64" - ], + "node_modules/@csstools/css-color-parser": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.0.10.tgz", + "integrity": "sha512-TiJ5Ajr6WRd1r8HSiwJvZBiJOqtH86aHpUjq5aEKWHiII2Qfjqd/HCWKPOW8EP4vcspXbHnXrwIDlu5savQipg==", "dev": true, - "optional": true, - "os": [ - "android" + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } ], + "license": "MIT", + "dependencies": { + "@csstools/color-helpers": "^5.0.2", + "@csstools/css-calc": "^2.1.4" + }, "engines": { - "node": ">=12" + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", + "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", + "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.5.tgz", + "integrity": "sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.5.tgz", + "integrity": "sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.5.tgz", + "integrity": "sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.5.tgz", + "integrity": "sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz", - "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==", + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.5.tgz", + "integrity": "sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz", - "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==", + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.5.tgz", + "integrity": "sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", - "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.5.tgz", + "integrity": "sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "freebsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz", - "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==", + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.5.tgz", + "integrity": "sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "freebsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-arm": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz", - "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==", + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.5.tgz", + "integrity": "sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==", "cpu": [ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", - "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.5.tgz", + "integrity": "sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz", - "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==", + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.5.tgz", + "integrity": "sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==", "cpu": [ "ia32" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", - "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.5.tgz", + "integrity": "sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==", "cpu": [ "loong64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", - "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.5.tgz", + "integrity": "sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==", "cpu": [ "mips64el" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", - "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.5.tgz", + "integrity": "sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==", "cpu": [ "ppc64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz", - "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==", + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.5.tgz", + "integrity": "sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==", "cpu": [ "riscv64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz", - "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==", + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.5.tgz", + "integrity": "sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==", "cpu": [ "s390x" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz", - "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==", + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.5.tgz", + "integrity": "sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.5.tgz", + "integrity": "sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz", - "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==", + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.5.tgz", + "integrity": "sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "netbsd" ], "engines": { - "node": ">=12" + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.5.tgz", + "integrity": "sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz", - "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==", + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.5.tgz", + "integrity": "sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "openbsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", - "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.5.tgz", + "integrity": "sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "sunos" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", - "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.5.tgz", + "integrity": "sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz", - "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==", + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.5.tgz", + "integrity": "sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==", "cpu": [ "ia32" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", - "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.5.tgz", + "integrity": "sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", - "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", + "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", "dev": true, + "license": "MIT", "dependencies": { - "eslint-visitor-keys": "^3.3.0" + "eslint-visitor-keys": "^3.4.3" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, + "funding": { + "url": "https://opencollective.com/eslint" + }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "node_modules/@eslint-community/regexpp": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", - "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", "dev": true, + "license": "MIT", "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, + "node_modules/@eslint/config-array": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.1.tgz", + "integrity": "sha512-OL0RJzC/CBzli0DrrR31qzj6d6i6Mm3HByuhflhl4LOBiWxN+3i6/t/ZQQNii4tjksXi8r2CRW1wMpWA2ULUEw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.6", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-array/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.3.tgz", + "integrity": "sha512-u180qk2Um1le4yf0ruXH3PYFeEZeYC3p/4wCTKrr2U1CmGdzGi3KtY0nuPDH48UJxlKCC5RDzbcbh4X0XlqgHg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.14.0.tgz", + "integrity": "sha512-qIbV0/JZr7iSDjqAc60IqbLdsj9GDt16xQtWD+B78d/HAlvysGdZZ6rpJHGAc2T0FQx1X6thsSPdnoiGKdNtdg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, "node_modules/@eslint/eslintrc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", - "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", "dev": true, + "license": "MIT", "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", + "espree": "^10.0.1", + "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", @@ -2320,33 +2292,122 @@ "strip-json-comments": "^3.1.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" } }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/@eslint/js": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", - "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", + "version": "9.29.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.29.0.tgz", + "integrity": "sha512-3PIF4cBw/y+1u2EazflInpV+lYsSG0aByVIQzAgb1m1MhHFSbqTyNqtBKHgWf/9Ykud+DhILS9EGkmekVhbKoQ==", "dev": true, + "license": "MIT", "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" } }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.11.14", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", - "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", + "node_modules/@eslint/object-schema": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", + "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.5.tgz", + "integrity": "sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==", + "dev": true, + "license": "Apache-2.0", "dependencies": { - "@humanwhocodes/object-schema": "^2.0.2", - "debug": "^4.3.1", - "minimatch": "^3.0.5" + "@eslint/core": "^0.15.2", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit/node_modules/@eslint/core": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.2.tgz", + "integrity": "sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.6", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", + "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.3.0" }, "engines": { - "node": ">=10.10.0" + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", + "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" } }, "node_modules/@humanwhocodes/module-importer": { @@ -2354,6 +2415,7 @@ "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", "dev": true, + "license": "Apache-2.0", "engines": { "node": ">=12.22" }, @@ -2362,17 +2424,26 @@ "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", - "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", - "dev": true + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", "dev": true, + "license": "ISC", "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", @@ -2385,115 +2456,67 @@ "node": ">=12" } }, - "node_modules/@isaacs/cliui/node_modules/ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/@jest/schemas": { - "version": "29.6.0", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.0.tgz", - "integrity": "sha512-rxLjXyJBTL4LQeJW3aKo0M/+GkCOXsO+8i9Iu7eDb6KwtP65ayoDsitrdPBtujxQ88k4wI2FNYfa6TOGwSn6cQ==", - "dev": true, - "dependencies": { - "@sinclair/typebox": "^0.27.8" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", - "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", "dev": true, + "license": "MIT", "dependencies": { - "@jridgewell/set-array": "^1.0.1", + "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/trace-mapping": "^0.3.24" }, "engines": { "node": ">=6.0.0" } }, "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", - "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.0.0" } }, "node_modules/@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.0.0" } }, "node_modules/@jridgewell/source-map": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz", - "integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==", + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", + "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", "dev": true, + "license": "MIT", "dependencies": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.18", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz", - "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==", + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", "dev": true, + "license": "MIT", "dependencies": { - "@jridgewell/resolve-uri": "3.1.0", - "@jridgewell/sourcemap-codec": "1.4.14" - } - }, - "node_modules/@jridgewell/trace-mapping/node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", - "dev": true - }, - "node_modules/@nicolo-ribaudo/semver-v6": { - "version": "6.3.3", - "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/semver-v6/-/semver-v6-6.3.3.tgz", - "integrity": "sha512-3Yc1fUTs69MG/uZbJlLSI3JISMn2UV2rg+1D/vROUqZyh3l6iYHCs7GMp+M40ZD7yOdDbYjJcU1oTJhrc+dGKg==", - "dev": true, - "bin": { - "semver": "bin/semver.js" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, "node_modules/@nodelib/fs.scandir": { @@ -2501,6 +2524,7 @@ "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", "dev": true, + "license": "MIT", "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" @@ -2514,6 +2538,7 @@ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", "dev": true, + "license": "MIT", "engines": { "node": ">= 8" } @@ -2523,6 +2548,7 @@ "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", "dev": true, + "license": "MIT", "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" @@ -2535,44 +2561,435 @@ "version": "0.1.1", "resolved": "https://registry.npmjs.org/@one-ini/wasm/-/wasm-0.1.1.tgz", "integrity": "sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", "dev": true, + "license": "MIT", "optional": true, "engines": { "node": ">=14" } }, "node_modules/@playwright/test": { - "version": "1.44.0", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.44.0.tgz", - "integrity": "sha512-rNX5lbNidamSUorBhB4XZ9SQTjAqfe5M+p37Z8ic0jPFBMo5iCtQz1kRWkEMg+rYOKSlVycpQmpqjSFq7LXOfg==", + "version": "1.53.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.53.1.tgz", + "integrity": "sha512-Z4c23LHV0muZ8hfv4jw6HngPJkbbtZxTkxPNIg7cJcTc9C28N/p2q7g3JZS2SiKBBHJ3uM1dgDye66bB7LEk5w==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "playwright": "1.44.0" + "playwright": "1.53.1" }, "bin": { "playwright": "cli.js" }, "engines": { - "node": ">=16" + "node": ">=18" + } + }, + "node_modules/@puppeteer/browsers": { + "version": "2.10.5", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.10.5.tgz", + "integrity": "sha512-eifa0o+i8dERnngJwKrfp3dEq7ia5XFyoqB17S4gK8GhsQE4/P8nxOfQSE0zQHxzzLo/cmF+7+ywEQ7wK7Fb+w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "debug": "^4.4.1", + "extract-zip": "^2.0.1", + "progress": "^2.0.3", + "proxy-agent": "^6.5.0", + "semver": "^7.7.2", + "tar-fs": "^3.0.8", + "yargs": "^17.7.2" + }, + "bin": { + "browsers": "lib/cjs/main-cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@rollup/plugin-node-resolve": { + "version": "15.3.1", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.3.1.tgz", + "integrity": "sha512-tgg6b91pAybXHJQMAAwW9VuWBO6Thi+q7BCNARLwSqlmsHz0XYURtGvh/AuwSADXSI4h/2uHbs7s4FzlZDGSGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "@types/resolve": "1.20.2", + "deepmerge": "^4.2.2", + "is-module": "^1.0.0", + "resolve": "^1.22.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.78.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-terser": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/@rollup/plugin-terser/-/plugin-terser-0.4.4.tgz", + "integrity": "sha512-XHeJC5Bgvs8LfukDwWZp7yeqin6ns8RTl2B9avbejt6tZqsqvVoWI7ZTQrcNsfKEDWBTnTxM8nMDkO2IFFbd0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "serialize-javascript": "^6.0.1", + "smob": "^1.0.0", + "terser": "^5.17.4" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.2.0.tgz", + "integrity": "sha512-qWJ2ZTbmumwiLFomfzTyt5Kng4hwPi9rwCYN4SHb6eaRU1KNO4ccxINHr/VhH4GgPlt1XfSTLX2LBTme8ne4Zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } } }, - "node_modules/@sinclair/typebox": { - "version": "0.27.8", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", - "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", - "dev": true + "node_modules/@rollup/pluginutils/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/pluginutils/node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.44.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.44.0.tgz", + "integrity": "sha512-xEiEE5oDW6tK4jXCAyliuntGR+amEMO7HLtdSshVuhFnKTYoeYMyXQK7pLouAJJj5KHdwdn87bfHAR2nSdNAUA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.44.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.44.0.tgz", + "integrity": "sha512-uNSk/TgvMbskcHxXYHzqwiyBlJ/lGcv8DaUfcnNwict8ba9GTTNxfn3/FAoFZYgkaXXAdrAA+SLyKplyi349Jw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.44.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.44.0.tgz", + "integrity": "sha512-VGF3wy0Eq1gcEIkSCr8Ke03CWT+Pm2yveKLaDvq51pPpZza3JX/ClxXOCmTYYq3us5MvEuNRTaeyFThCKRQhOA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.44.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.44.0.tgz", + "integrity": "sha512-fBkyrDhwquRvrTxSGH/qqt3/T0w5Rg0L7ZIDypvBPc1/gzjJle6acCpZ36blwuwcKD/u6oCE/sRWlUAcxLWQbQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.44.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.44.0.tgz", + "integrity": "sha512-u5AZzdQJYJXByB8giQ+r4VyfZP+walV+xHWdaFx/1VxsOn6eWJhK2Vl2eElvDJFKQBo/hcYIBg/jaKS8ZmKeNQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.44.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.44.0.tgz", + "integrity": "sha512-qC0kS48c/s3EtdArkimctY7h3nHicQeEUdjJzYVJYR3ct3kWSafmn6jkNCA8InbUdge6PVx6keqjk5lVGJf99g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.44.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.44.0.tgz", + "integrity": "sha512-x+e/Z9H0RAWckn4V2OZZl6EmV0L2diuX3QB0uM1r6BvhUIv6xBPL5mrAX2E3e8N8rEHVPwFfz/ETUbV4oW9+lQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.44.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.44.0.tgz", + "integrity": "sha512-1exwiBFf4PU/8HvI8s80icyCcnAIB86MCBdst51fwFmH5dyeoWVPVgmQPcKrMtBQ0W5pAs7jBCWuRXgEpRzSCg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.44.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.44.0.tgz", + "integrity": "sha512-ZTR2mxBHb4tK4wGf9b8SYg0Y6KQPjGpR4UWwTFdnmjB4qRtoATZ5dWn3KsDwGa5Z2ZBOE7K52L36J9LueKBdOQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.44.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.44.0.tgz", + "integrity": "sha512-GFWfAhVhWGd4r6UxmnKRTBwP1qmModHtd5gkraeW2G490BpFOZkFtem8yuX2NyafIP/mGpRJgTJ2PwohQkUY/Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.44.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.44.0.tgz", + "integrity": "sha512-xw+FTGcov/ejdusVOqKgMGW3c4+AgqrfvzWEVXcNP6zq2ue+lsYUgJ+5Rtn/OTJf7e2CbgTFvzLW2j0YAtj0Gg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.44.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.44.0.tgz", + "integrity": "sha512-bKGibTr9IdF0zr21kMvkZT4K6NV+jjRnBoVMt2uNMG0BYWm3qOVmYnXKzx7UhwrviKnmK46IKMByMgvpdQlyJQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.44.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.44.0.tgz", + "integrity": "sha512-vV3cL48U5kDaKZtXrti12YRa7TyxgKAIDoYdqSIOMOFBXqFj2XbChHAtXquEn2+n78ciFgr4KIqEbydEGPxXgA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.44.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.44.0.tgz", + "integrity": "sha512-TDKO8KlHJuvTEdfw5YYFBjhFts2TR0VpZsnLLSYmB7AaohJhM8ctDSdDnUGq77hUh4m/djRafw+9zQpkOanE2Q==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.44.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.44.0.tgz", + "integrity": "sha512-8541GEyktXaw4lvnGp9m84KENcxInhAt6vPWJ9RodsB/iGjHoMB2Pp5MVBCiKIRxrxzJhGCxmNzdu+oDQ7kwRA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.44.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.44.0.tgz", + "integrity": "sha512-iUVJc3c0o8l9Sa/qlDL2Z9UP92UZZW1+EmQ4xfjTc1akr0iUFZNfxrXJ/R1T90h/ILm9iXEY6+iPrmYB3pXKjw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.44.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.44.0.tgz", + "integrity": "sha512-PQUobbhLTQT5yz/SPg116VJBgz+XOtXt8D1ck+sfJJhuEsMj2jSej5yTdp8CvWBSceu+WW+ibVL6dm0ptG5fcA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.44.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.44.0.tgz", + "integrity": "sha512-M0CpcHf8TWn+4oTxJfh7LQuTuaYeXGbk0eageVjQCKzYLsajWS/lFC94qlRqOlyC2KvRT90ZrfXULYmukeIy7w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.44.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.44.0.tgz", + "integrity": "sha512-3XJ0NQtMAXTWFW8FqZKcw3gOQwBtVWP/u8TpHP3CRPXD7Pd6s8lLdH3sHWh8vqKCyyiI8xW5ltJScQmBU9j7WA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.44.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.44.0.tgz", + "integrity": "sha512-Q2Mgwt+D8hd5FIPUuPDsvPR7Bguza6yTkJxspDGkZj7tBRn2y4KSWYuIXpftFSjBra76TbKerCV7rgFPQrn+wQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] }, "node_modules/@surma/rollup-plugin-off-main-thread": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.3.tgz", "integrity": "sha512-lR8q/9W7hZpMWweNiAKU7NQerBnzQQLvi8qnTDU/fxItPhtZVMbPV3lbCwjhIlNBe9Bbr5V+KHshvWmVSG9cxQ==", "dev": true, + "license": "Apache-2.0", "dependencies": { "ejs": "^3.1.6", "json5": "^2.2.0", @@ -2585,325 +3002,756 @@ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", "dev": true, + "license": "MIT", "dependencies": { "sourcemap-codec": "^1.4.8" } }, - "node_modules/@tootallnate/once": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", - "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "node_modules/@tootallnate/quickjs-emscripten": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", + "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", "dev": true, - "engines": { - "node": ">= 10" - } + "license": "MIT" }, - "node_modules/@types/chai": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.5.tgz", - "integrity": "sha512-mEo1sAde+UCE6b2hxn332f1g1E8WfYRu6p5SvTKr2ZKC1f7gFJXk4h5PyGP9Dt6gCaG8y8XhwnXWC6Iy2cmBng==", - "dev": true + "node_modules/@tsconfig/node22": { + "version": "22.0.2", + "resolved": "https://registry.npmjs.org/@tsconfig/node22/-/node22-22.0.2.tgz", + "integrity": "sha512-Kmwj4u8sDRDrMYRoN9FDEcXD8UpBSaPQQ24Gz+Gamqfm7xxn+GBR7ge/Z7pK8OXNGyUzbSwJj+TH6B+DS/epyA==", + "dev": true, + "license": "MIT" }, - "node_modules/@types/chai-subset": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@types/chai-subset/-/chai-subset-1.3.3.tgz", - "integrity": "sha512-frBecisrNGz+F4T6bcc+NLeolfiojh5FxW2klu669+8BARtyQv2C/GkNW6FUodVe4BroGMP/wER/YDGc7rEllw==", + "node_modules/@types/chai": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.2.tgz", + "integrity": "sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==", "dev": true, + "license": "MIT", "dependencies": { - "@types/chai": "*" + "@types/deep-eql": "*" } }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/estree": { - "version": "0.0.39", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", - "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==", - "dev": true + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" }, - "node_modules/@types/minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==", - "dev": true + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" }, - "node_modules/@types/node": { - "version": "20.12.12", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.12.tgz", - "integrity": "sha512-eWLDGF/FOSPtAvEqeRAQ4C8LSA7M1I7i0ky1I8U7kD1J5ITyW3AsRhQrKVoWf5pFKZ2kILsEGJhsI9r93PYnOw==", + "node_modules/@types/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/markdown-it": { + "version": "14.1.2", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz", + "integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==", "dev": true, + "license": "MIT", "dependencies": { - "undici-types": "~5.26.4" + "@types/linkify-it": "^5", + "@types/mdurl": "^2" } }, - "node_modules/@types/normalize-package-data": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz", - "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==", - "dev": true + "node_modules/@types/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==", + "dev": true, + "license": "MIT" }, - "node_modules/@types/resolve": { - "version": "1.17.1", - "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz", - "integrity": "sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==", + "node_modules/@types/node": { + "version": "24.0.7", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.7.tgz", + "integrity": "sha512-YIEUUr4yf8q8oQoXPpSlnvKNVKDQlPMWrmOcgzoduo7kvA2UF0/BwJ/eMKFTiTtkNL17I0M6Xe2tvwFU7be6iw==", "dev": true, + "license": "MIT", "dependencies": { - "@types/node": "*" + "undici-types": "~7.8.0" } }, + "node_modules/@types/resolve": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", + "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/trusted-types": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.3.tgz", - "integrity": "sha512-NfQ4gyz38SL8sDNrSixxU2Os1a5xcdFxipAFxYEuLUlvU2uDwS4NUpsImcf1//SlWItCVMMLiylsxbmNMToV/g==", - "dev": true + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "dev": true, + "license": "MIT" }, "node_modules/@types/yauzl": { "version": "2.10.3", "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", "dev": true, + "license": "MIT", "optional": true, "dependencies": { "@types/node": "*" } }, - "node_modules/@ungap/structured-clone": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", - "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", - "dev": true + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.34.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.34.1.tgz", + "integrity": "sha512-STXcN6ebF6li4PxwNeFnqF8/2BNDvBupf2OPx2yWNzr6mKNGF7q49VM00Pz5FaomJyqvbXpY6PhO+T9w139YEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.34.1", + "@typescript-eslint/type-utils": "8.34.1", + "@typescript-eslint/utils": "8.34.1", + "@typescript-eslint/visitor-keys": "8.34.1", + "graphemer": "^1.4.0", + "ignore": "^7.0.0", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.34.1", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.34.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.34.1.tgz", + "integrity": "sha512-4O3idHxhyzjClSMJ0a29AcoK0+YwnEqzI6oz3vlRf3xw0zbzt15MzXwItOlnr5nIth6zlY2RENLsOPvhyrKAQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.34.1", + "@typescript-eslint/types": "8.34.1", + "@typescript-eslint/typescript-estree": "8.34.1", + "@typescript-eslint/visitor-keys": "8.34.1", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.34.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.34.1.tgz", + "integrity": "sha512-nuHlOmFZfuRwLJKDGQOVc0xnQrAmuq1Mj/ISou5044y1ajGNp2BNliIqp7F2LPQ5sForz8lempMFCovfeS1XoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.34.1", + "@typescript-eslint/types": "^8.34.1", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.34.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.34.1.tgz", + "integrity": "sha512-beu6o6QY4hJAgL1E8RaXNC071G4Kso2MGmJskCFQhRhg8VOH/FDbC8soP8NHN7e/Hdphwp8G8cE6OBzC8o41ZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.34.1", + "@typescript-eslint/visitor-keys": "8.34.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.34.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.34.1.tgz", + "integrity": "sha512-K4Sjdo4/xF9NEeA2khOb7Y5nY6NSXBnod87uniVYW9kHP+hNlDV8trUSFeynA2uxWam4gIWgWoygPrv9VMWrYg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.34.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.34.1.tgz", + "integrity": "sha512-Tv7tCCr6e5m8hP4+xFugcrwTOucB8lshffJ6zf1mF1TbU67R+ntCc6DzLNKM+s/uzDyv8gLq7tufaAhIBYeV8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/typescript-estree": "8.34.1", + "@typescript-eslint/utils": "8.34.1", + "debug": "^4.3.4", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.34.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.34.1.tgz", + "integrity": "sha512-rjLVbmE7HR18kDsjNIZQHxmv9RZwlgzavryL5Lnj2ujIRTeXlKtILHgRNmQ3j4daw7zd+mQgy+uyt6Zo6I0IGA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.34.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.34.1.tgz", + "integrity": "sha512-rjCNqqYPuMUF5ODD+hWBNmOitjBWghkGKJg6hiCHzUvXRy6rK22Jd3rwbP2Xi+R7oYVvIKhokHVhH41BxPV5mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.34.1", + "@typescript-eslint/tsconfig-utils": "8.34.1", + "@typescript-eslint/types": "8.34.1", + "@typescript-eslint/visitor-keys": "8.34.1", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.34.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.34.1.tgz", + "integrity": "sha512-mqOwUdZ3KjtGk7xJJnLbHxTuWVn3GO2WZZuM+Slhkun4+qthLdXx32C8xIXbO1kfCECb3jIs3eoxK3eryk7aoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.7.0", + "@typescript-eslint/scope-manager": "8.34.1", + "@typescript-eslint/types": "8.34.1", + "@typescript-eslint/typescript-estree": "8.34.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.34.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.34.1.tgz", + "integrity": "sha512-xoh5rJ+tgsRKoXnkBPFRLZ7rjKM0AfVbC68UZ/ECXoDbfggb9RbEySN359acY1vS3qZ0jVTVWzbtfapwm5ztxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.34.1", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } }, "node_modules/@vitejs/plugin-vue": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-4.6.2.tgz", - "integrity": "sha512-kqf7SGFoG+80aZG6Pf+gsZIVvGSCKE98JbiWqcCV9cThtg91Jav0yvYFC9Zb+jKetNGF6ZKeoaxgZfND21fWKw==", + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz", + "integrity": "sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==", "dev": true, + "license": "MIT", "engines": { - "node": "^14.18.0 || >=16.0.0" + "node": "^18.0.0 || >=20.0.0" }, "peerDependencies": { - "vite": "^4.0.0 || ^5.0.0", + "vite": "^5.0.0 || ^6.0.0", "vue": "^3.2.25" } }, "node_modules/@vitest/expect": { - "version": "0.32.4", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-0.32.4.tgz", - "integrity": "sha512-m7EPUqmGIwIeoU763N+ivkFjTzbaBn0n9evsTOcde03ugy2avPs3kZbYmw3DkcH1j5mxhMhdamJkLQ6dM1bk/A==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz", + "integrity": "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==", "dev": true, + "license": "MIT", "dependencies": { - "@vitest/spy": "0.32.4", - "@vitest/utils": "0.32.4", - "chai": "^4.3.7" + "@types/chai": "^5.2.2", + "@vitest/spy": "3.2.4", + "@vitest/utils": "3.2.4", + "chai": "^5.2.0", + "tinyrainbow": "^2.0.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, - "node_modules/@vitest/runner": { - "version": "0.32.4", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-0.32.4.tgz", - "integrity": "sha512-cHOVCkiRazobgdKLnczmz2oaKK9GJOw6ZyRcaPdssO1ej+wzHVIkWiCiNacb3TTYPdzMddYkCgMjZ4r8C0JFCw==", + "node_modules/@vitest/mocker": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.4.tgz", + "integrity": "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==", "dev": true, + "license": "MIT", "dependencies": { - "@vitest/utils": "0.32.4", - "p-limit": "^4.0.0", - "pathe": "^1.1.1" + "@vitest/spy": "3.2.4", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.17" }, "funding": { "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } } }, - "node_modules/@vitest/runner/node_modules/p-limit": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", - "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", + "node_modules/@vitest/pretty-format": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz", + "integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==", "dev": true, + "license": "MIT", "dependencies": { - "yocto-queue": "^1.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "tinyrainbow": "^2.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://opencollective.com/vitest" } }, - "node_modules/@vitest/runner/node_modules/yocto-queue": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", - "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", + "node_modules/@vitest/runner": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.4.tgz", + "integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==", "dev": true, - "engines": { - "node": ">=12.20" + "license": "MIT", + "dependencies": { + "@vitest/utils": "3.2.4", + "pathe": "^2.0.3", + "strip-literal": "^3.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/snapshot": { - "version": "0.32.4", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-0.32.4.tgz", - "integrity": "sha512-IRpyqn9t14uqsFlVI2d7DFMImGMs1Q9218of40bdQQgMePwVdmix33yMNnebXcTzDU5eiV3eUsoxxH5v0x/IQA==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.4.tgz", + "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==", "dev": true, + "license": "MIT", "dependencies": { - "magic-string": "^0.30.0", - "pathe": "^1.1.1", - "pretty-format": "^29.5.0" + "@vitest/pretty-format": "3.2.4", + "magic-string": "^0.30.17", + "pathe": "^2.0.3" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/spy": { - "version": "0.32.4", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-0.32.4.tgz", - "integrity": "sha512-oA7rCOqVOOpE6rEoXuCOADX7Lla1LIa4hljI2MSccbpec54q+oifhziZIJXxlE/CvI2E+ElhBHzVu0VEvJGQKQ==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz", + "integrity": "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==", "dev": true, + "license": "MIT", "dependencies": { - "tinyspy": "^2.1.1" + "tinyspy": "^4.0.3" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/utils": { - "version": "0.32.4", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-0.32.4.tgz", - "integrity": "sha512-Gwnl8dhd1uJ+HXrYyV0eRqfmk9ek1ASE/LWfTCuWMw+d07ogHqp4hEAV28NiecimK6UY9DpSEPh+pXBA5gtTBg==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.4.tgz", + "integrity": "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==", "dev": true, + "license": "MIT", "dependencies": { - "diff-sequences": "^29.4.3", - "loupe": "^2.3.6", - "pretty-format": "^29.5.0" + "@vitest/pretty-format": "3.2.4", + "loupe": "^3.1.4", + "tinyrainbow": "^2.0.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, + "node_modules/@volar/language-core": { + "version": "2.4.14", + "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.14.tgz", + "integrity": "sha512-X6beusV0DvuVseaOEy7GoagS4rYHgDHnTrdOj5jeUb49fW5ceQyP9Ej5rBhqgz2wJggl+2fDbbojq1XKaxDi6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/source-map": "2.4.14" + } + }, + "node_modules/@volar/source-map": { + "version": "2.4.14", + "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.14.tgz", + "integrity": "sha512-5TeKKMh7Sfxo8021cJfmBzcjfY1SsXsPMMjMvjY7ivesdnybqqS+GxGAoXHAOUawQTwtdUxgP65Im+dEmvWtYQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@volar/typescript": { + "version": "2.4.14", + "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.14.tgz", + "integrity": "sha512-p8Z6f/bZM3/HyCdRNFZOEEzts51uV8WHeN8Tnfnm2EBv6FDB2TQLzfVx7aJvnl8ofKAOnS64B2O8bImBFaauRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "2.4.14", + "path-browserify": "^1.0.1", + "vscode-uri": "^3.0.8" + } + }, "node_modules/@vue/compiler-core": { - "version": "3.4.27", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.27.tgz", - "integrity": "sha512-E+RyqY24KnyDXsCuQrI+mlcdW3ALND6U7Gqa/+bVwbcpcR3BRRIckFoz7Qyd4TTlnugtwuI7YgjbvsLmxb+yvg==", + "version": "3.5.17", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.17.tgz", + "integrity": "sha512-Xe+AittLbAyV0pabcN7cP7/BenRBNcteM4aSDCtRvGw0d9OL+HG1u/XHLY/kt1q4fyMeZYXyIYrsHuPSiDPosA==", + "license": "MIT", "dependencies": { - "@babel/parser": "^7.24.4", - "@vue/shared": "3.4.27", + "@babel/parser": "^7.27.5", + "@vue/shared": "3.5.17", "entities": "^4.5.0", "estree-walker": "^2.0.2", - "source-map-js": "^1.2.0" + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-core/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/@vue/compiler-core/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, "node_modules/@vue/compiler-dom": { - "version": "3.4.27", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.27.tgz", - "integrity": "sha512-kUTvochG/oVgE1w5ViSr3KUBh9X7CWirebA3bezTbB5ZKBQZwR2Mwj9uoSKRMFcz4gSMzzLXBPD6KpCLb9nvWw==", + "version": "3.5.17", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.17.tgz", + "integrity": "sha512-+2UgfLKoaNLhgfhV5Ihnk6wB4ljyW1/7wUIog2puUqajiC29Lp5R/IKDdkebh9jTbTogTbsgB+OY9cEWzG95JQ==", + "license": "MIT", "dependencies": { - "@vue/compiler-core": "3.4.27", - "@vue/shared": "3.4.27" + "@vue/compiler-core": "3.5.17", + "@vue/shared": "3.5.17" } }, "node_modules/@vue/compiler-sfc": { - "version": "3.4.27", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.27.tgz", - "integrity": "sha512-nDwntUEADssW8e0rrmE0+OrONwmRlegDA1pD6QhVeXxjIytV03yDqTey9SBDiALsvAd5U4ZrEKbMyVXhX6mCGA==", - "dependencies": { - "@babel/parser": "^7.24.4", - "@vue/compiler-core": "3.4.27", - "@vue/compiler-dom": "3.4.27", - "@vue/compiler-ssr": "3.4.27", - "@vue/shared": "3.4.27", + "version": "3.5.17", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.17.tgz", + "integrity": "sha512-rQQxbRJMgTqwRugtjw0cnyQv9cP4/4BxWfTdRBkqsTfLOHWykLzbOc3C4GGzAmdMDxhzU/1Ija5bTjMVrddqww==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.27.5", + "@vue/compiler-core": "3.5.17", + "@vue/compiler-dom": "3.5.17", + "@vue/compiler-ssr": "3.5.17", + "@vue/shared": "3.5.17", "estree-walker": "^2.0.2", - "magic-string": "^0.30.10", - "postcss": "^8.4.38", - "source-map-js": "^1.2.0" + "magic-string": "^0.30.17", + "postcss": "^8.5.6", + "source-map-js": "^1.2.1" } }, + "node_modules/@vue/compiler-sfc/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, "node_modules/@vue/compiler-ssr": { - "version": "3.4.27", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.27.tgz", - "integrity": "sha512-CVRzSJIltzMG5FcidsW0jKNQnNRYC8bT21VegyMMtHmhW3UOI7knmUehzswXLrExDLE6lQCZdrhD4ogI7c+vuw==", + "version": "3.5.17", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.17.tgz", + "integrity": "sha512-hkDbA0Q20ZzGgpj5uZjb9rBzQtIHLS78mMilwrlpWk2Ep37DYntUz0PonQ6kr113vfOEdM+zTBuJDaceNIW0tQ==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.17", + "@vue/shared": "3.5.17" + } + }, + "node_modules/@vue/compiler-vue2": { + "version": "2.7.16", + "resolved": "https://registry.npmjs.org/@vue/compiler-vue2/-/compiler-vue2-2.7.16.tgz", + "integrity": "sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==", + "dev": true, + "license": "MIT", "dependencies": { - "@vue/compiler-dom": "3.4.27", - "@vue/shared": "3.4.27" + "de-indent": "^1.0.2", + "he": "^1.2.0" } }, "node_modules/@vue/devtools-api": { - "version": "6.6.1", - "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.1.tgz", - "integrity": "sha512-LgPscpE3Vs0x96PzSSB4IGVSZXZBZHpfxs+ZA1d+VEPwHdOXowy/Y2CsvCAIFrf+ssVU1pD1jidj505EpUnfbA==" + "version": "6.6.4", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.4.tgz", + "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==", + "license": "MIT" + }, + "node_modules/@vue/eslint-config-typescript": { + "version": "14.5.1", + "resolved": "https://registry.npmjs.org/@vue/eslint-config-typescript/-/eslint-config-typescript-14.5.1.tgz", + "integrity": "sha512-ys6qdYHGXS/WLt0r5vUcTiG163F4NbNpx3ABTsGITw8k5uCFiv4g9E1N9Jydlw62KzJMVKGcpXbg6LCA3fV+eA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/utils": "^8.34.1", + "fast-glob": "^3.3.3", + "typescript-eslint": "^8.34.1", + "vue-eslint-parser": "^10.1.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "peerDependencies": { + "eslint": "^9.10.0", + "eslint-plugin-vue": "^9.28.0 || ^10.0.0", + "typescript": ">=4.8.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@vue/language-core": { + "version": "2.2.10", + "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-2.2.10.tgz", + "integrity": "sha512-+yNoYx6XIKuAO8Mqh1vGytu8jkFEOH5C8iOv3i8Z/65A7x9iAOXA97Q+PqZ3nlm2lxf5rOJuIGI/wDtx/riNYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "~2.4.11", + "@vue/compiler-dom": "^3.5.0", + "@vue/compiler-vue2": "^2.7.16", + "@vue/shared": "^3.5.0", + "alien-signals": "^1.0.3", + "minimatch": "^9.0.3", + "muggle-string": "^0.4.1", + "path-browserify": "^1.0.1" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } }, "node_modules/@vue/reactivity": { - "version": "3.4.27", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.27.tgz", - "integrity": "sha512-kK0g4NknW6JX2yySLpsm2jlunZJl2/RJGZ0H9ddHdfBVHcNzxmQ0sS0b09ipmBoQpY8JM2KmUw+a6sO8Zo+zIA==", + "version": "3.5.17", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.17.tgz", + "integrity": "sha512-l/rmw2STIscWi7SNJp708FK4Kofs97zc/5aEPQh4bOsReD/8ICuBcEmS7KGwDj5ODQLYWVN2lNibKJL1z5b+Lw==", + "license": "MIT", "dependencies": { - "@vue/shared": "3.4.27" + "@vue/shared": "3.5.17" } }, "node_modules/@vue/runtime-core": { - "version": "3.4.27", - "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.27.tgz", - "integrity": "sha512-7aYA9GEbOOdviqVvcuweTLe5Za4qBZkUY7SvET6vE8kyypxVgaT1ixHLg4urtOlrApdgcdgHoTZCUuTGap/5WA==", + "version": "3.5.17", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.17.tgz", + "integrity": "sha512-QQLXa20dHg1R0ri4bjKeGFKEkJA7MMBxrKo2G+gJikmumRS7PTD4BOU9FKrDQWMKowz7frJJGqBffYMgQYS96Q==", + "license": "MIT", "dependencies": { - "@vue/reactivity": "3.4.27", - "@vue/shared": "3.4.27" + "@vue/reactivity": "3.5.17", + "@vue/shared": "3.5.17" } }, "node_modules/@vue/runtime-dom": { - "version": "3.4.27", - "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.27.tgz", - "integrity": "sha512-ScOmP70/3NPM+TW9hvVAz6VWWtZJqkbdf7w6ySsws+EsqtHvkhxaWLecrTorFxsawelM5Ys9FnDEMt6BPBDS0Q==", - "dependencies": { - "@vue/runtime-core": "3.4.27", - "@vue/shared": "3.4.27", + "version": "3.5.17", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.17.tgz", + "integrity": "sha512-8El0M60TcwZ1QMz4/os2MdlQECgGoVHPuLnQBU3m9h3gdNRW9xRmI8iLS4t/22OQlOE6aJvNNlBiCzPHur4H9g==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.17", + "@vue/runtime-core": "3.5.17", + "@vue/shared": "3.5.17", "csstype": "^3.1.3" } }, "node_modules/@vue/server-renderer": { - "version": "3.4.27", - "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.27.tgz", - "integrity": "sha512-dlAMEuvmeA3rJsOMJ2J1kXU7o7pOxgsNHVr9K8hB3ImIkSuBrIdy0vF66h8gf8Tuinf1TK3mPAz2+2sqyf3KzA==", + "version": "3.5.17", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.17.tgz", + "integrity": "sha512-BOHhm8HalujY6lmC3DbqF6uXN/K00uWiEeF22LfEsm9Q93XeJ/plHTepGwf6tqFcF7GA5oGSSAAUock3VvzaCA==", + "license": "MIT", "dependencies": { - "@vue/compiler-ssr": "3.4.27", - "@vue/shared": "3.4.27" + "@vue/compiler-ssr": "3.5.17", + "@vue/shared": "3.5.17" }, "peerDependencies": { - "vue": "3.4.27" + "vue": "3.5.17" } }, "node_modules/@vue/shared": { - "version": "3.4.27", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.27.tgz", - "integrity": "sha512-DL3NmY2OFlqmYYrzp39yi3LDkKxa5vZVwxWdQ3rG0ekuWscHraeIbnI8t+aZK7qhYqEqWKTUdijadunb9pnrgA==" + "version": "3.5.17", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.17.tgz", + "integrity": "sha512-CabR+UN630VnsJO/jHWYBC1YVXyMq94KKp6iF5MQgZJs5I8cmjw6oVMO1oDbtBkENSHSSn/UadWlW/OAgdmKrg==", + "license": "MIT" }, "node_modules/@vue/test-utils": { "version": "2.4.6", "resolved": "https://registry.npmjs.org/@vue/test-utils/-/test-utils-2.4.6.tgz", "integrity": "sha512-FMxEjOpYNYiFe0GkaHsnJPXFHxQ6m4t8vI/ElPGpMWxZKpmRvQ33OIrvRXemy6yha03RxhOlQuy+gZMC3CQSow==", "dev": true, + "license": "MIT", "dependencies": { "js-beautify": "^1.14.9", "vue-component-type-helpers": "^2.0.0" } }, - "node_modules/abab": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", - "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", - "dev": true + "node_modules/@vue/tsconfig": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@vue/tsconfig/-/tsconfig-0.7.0.tgz", + "integrity": "sha512-ku2uNz5MaZ9IerPPUyOHzyjhXoX2kVJaVf7hL315DC17vS6IiZRmmCPfggNbU16QTvM80+uYYy3eYJB59WCtvg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "typescript": "5.x", + "vue": "^3.4.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + }, + "vue": { + "optional": true + } + } }, "node_modules/abbrev": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz", "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==", "dev": true, + "license": "ISC", "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, "node_modules/acorn": { - "version": "8.9.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.9.0.tgz", - "integrity": "sha512-jaVNAFBHNLXspO543WnNNPZFRtavh3skAkITqD0/2aeMkKZTN+254PyhwxFYrk3vQ1xfY+2wbesJMs/JC8/PwQ==", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, + "license": "MIT", "bin": { "acorn": "bin/acorn" }, @@ -2916,29 +3764,19 @@ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "dev": true, + "license": "MIT", "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, - "node_modules/acorn-walk": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", - "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", + "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", "dev": true, - "dependencies": { - "debug": "4" - }, + "license": "MIT", "engines": { - "node": ">= 6.0.0" + "node": ">= 14" } }, "node_modules/ajv": { @@ -2946,6 +3784,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, + "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -2957,13 +3796,24 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/alien-signals": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-1.0.13.tgz", + "integrity": "sha512-OGj9yyTnJEttvzhTUWuscOvtqxq5vrhF7vL9oS0xJ2mK0ItPYP1/y+vCFebfxoEyAz0++1AIwJ5CMr+Fk3nDmg==", + "dev": true, + "license": "MIT" + }, "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", "dev": true, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, "node_modules/ansi-styles": { @@ -2971,6 +3821,7 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, + "license": "MIT", "dependencies": { "color-convert": "^2.0.1" }, @@ -2985,65 +3836,107 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true + "dev": true, + "license": "Python-2.0" }, "node_modules/array-buffer-byte-length": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", - "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "is-array-buffer": "^3.0.1" + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/arrify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==", + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" + }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/assertion-error": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", - "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", "dev": true, + "license": "MIT", "engines": { - "node": "*" + "node": ">=12" + } + }, + "node_modules/ast-types": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", + "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" } }, "node_modules/async": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", - "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==", - "dev": true + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "dev": true, + "license": "MIT" }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true + "node_modules/async-function": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } }, "node_modules/at-least-node": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", "dev": true, + "license": "ISC", "engines": { "node": ">= 4.0.0" } }, "node_modules/available-typed-arrays": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", - "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", "dev": true, + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, "engines": { "node": ">= 0.4" }, @@ -3051,96 +3944,174 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/b4a": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.7.tgz", + "integrity": "sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.4.tgz", - "integrity": "sha512-9WeK9snM1BfxB38goUEv2FLnA6ja07UMfazFHzCXUb3NyDZAwfXvQiURQ6guTTMeHcOsdknULm1PDhs4uWtKyA==", + "version": "0.4.13", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.13.tgz", + "integrity": "sha512-3sX/eOms8kd3q2KZ6DAhKPc0dgm525Gqq5NtWKZ7QYYZEv57OQ54KtblzJzH1lQF/eQxO8KjWGIK9IPUJNus5g==", "dev": true, + "license": "MIT", "dependencies": { "@babel/compat-data": "^7.22.6", - "@babel/helper-define-polyfill-provider": "^0.4.1", - "@nicolo-ribaudo/semver-v6": "^6.3.3" + "@babel/helper-define-polyfill-provider": "^0.6.4", + "semver": "^6.3.1" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs2/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" } }, "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.2.tgz", - "integrity": "sha512-Cid+Jv1BrY9ReW9lIfNlNpsI53N+FN7gE+f73zLAUbr9C52W4gKLWSByx47pfDJsEysojKArqOtOKZSVIIUTuQ==", + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.11.1.tgz", + "integrity": "sha512-yGCqvBT4rwMczo28xkH/noxJ6MZ4nJfkVYdoDaC/utLtWrXxv27HVrzAeSbqR8SxDsp46n0YF47EbHoixy6rXQ==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.4.1", - "core-js-compat": "^3.31.0" + "@babel/helper-define-polyfill-provider": "^0.6.3", + "core-js-compat": "^3.40.0" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.1.tgz", - "integrity": "sha512-L8OyySuI6OSQ5hFy9O+7zFjyr4WhAfRjLIOkhQGYl+emwJkd/S4XXT1JpfrgR1jrQ1NcGiOh+yAdGlF8pnC3Jw==", + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.4.tgz", + "integrity": "sha512-7gD3pRadPrbjhjLyxebmx/WrFYcuSjZ0XbdUujQMZ/fcE9oeewk2U/7PCvez84UeuK3oSjmPZ0Ch0dlupQvGzw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.4.1" + "@babel/helper-define-polyfill-provider": "^0.6.4" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true + "dev": true, + "license": "MIT" }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "node_modules/bare-events": { + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.5.4.tgz", + "integrity": "sha512-+gFfDkR8pj4/TrWCGUGWmJIkBwuxPS5F+a5yWjOHQt2hHvNZd5YLzadjmDUtFmMM4y429bnKLa8bYBMHcYdnQA==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" + "license": "Apache-2.0", + "optional": true + }, + "node_modules/bare-fs": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.1.5.tgz", + "integrity": "sha512-1zccWBMypln0jEE05LzZt+V/8y8AQsQQqxtklqaIyg5nu6OAYFhZxPXinJTSG+kU5qyNmeLgcn9AW7eHiCHVLA==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "bare-events": "^2.5.4", + "bare-path": "^3.0.0", + "bare-stream": "^2.6.4" + }, + "engines": { + "bare": ">=1.16.0" + }, + "peerDependencies": { + "bare-buffer": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true } - ] + } }, - "node_modules/bl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "node_modules/bare-os": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.6.1.tgz", + "integrity": "sha512-uaIjxokhFidJP+bmmvKSgiMzj2sV5GPHaZVAIktcxcpCyBFFWO+YlikVAdhmUo2vYFvFhOXIAlldqV29L8126g==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "engines": { + "bare": ">=1.14.0" + } + }, + "node_modules/bare-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz", + "integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==", "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "bare-os": "^3.0.1" + } + }, + "node_modules/bare-stream": { + "version": "2.6.5", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.6.5.tgz", + "integrity": "sha512-jSmxKJNJmHySi6hC42zlZnq00rga4jjxcgNZjY9N5WlOe/iOoGRtdwGsHzQv2RlH2KOYMwGUXhf2zXd32BA9RA==", + "dev": true, + "license": "Apache-2.0", + "optional": true, "dependencies": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" + "streamx": "^2.21.0" + }, + "peerDependencies": { + "bare-buffer": "*", + "bare-events": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + }, + "bare-events": { + "optional": true + } + } + }, + "node_modules/basic-ftp": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.5.tgz", + "integrity": "sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" } }, "node_modules/boolbase": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, + "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "balanced-match": "^1.0.0" } }, "node_modules/braces": { @@ -3148,6 +4119,7 @@ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, + "license": "MIT", "dependencies": { "fill-range": "^7.1.1" }, @@ -3156,9 +4128,9 @@ } }, "node_modules/browserslist": { - "version": "4.21.9", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.9.tgz", - "integrity": "sha512-M0MFoZzbUrRU4KNfCrDLnvyE7gub+peetoTid3TBIqtunaDJyXlwhakT+/VkvSXcfIzFfK/nkCs4nmyTmxdNSg==", + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.0.tgz", + "integrity": "sha512-PJ8gYKeS5e/whHBh8xrwYK+dAvEj7JXtz6uTucnMRB8OiGTsKccFekoRrjajPBHV8oOY+2tI4uxeceSimKwMFA==", "dev": true, "funding": [ { @@ -3174,11 +4146,12 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001503", - "electron-to-chromium": "^1.4.431", - "node-releases": "^2.0.12", - "update-browserslist-db": "^1.0.11" + "caniuse-lite": "^1.0.30001718", + "electron-to-chromium": "^1.5.160", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.3" }, "bin": { "browserslist": "cli.js" @@ -3187,35 +4160,12 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, - "node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, "node_modules/buffer-crc32": { "version": "0.2.13", "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", "dev": true, + "license": "MIT", "engines": { "node": "*" } @@ -3224,81 +4174,83 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true - }, - "node_modules/builtin-modules": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", - "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", "dev": true, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } + "license": "MIT" }, "node_modules/cac": { "version": "6.7.14", "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", "dev": true, + "license": "MIT", "dependencies": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, "engines": { - "node": ">=6" + "node": ">= 0.4" } }, - "node_modules/camelcase-keys": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", - "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==", + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", "dev": true, + "license": "MIT", "dependencies": { - "camelcase": "^5.3.1", - "map-obj": "^4.0.0", - "quick-lru": "^4.0.1" + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" }, "engines": { - "node": ">=8" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" } }, "node_modules/caniuse-lite": { - "version": "1.0.30001658", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001658.tgz", - "integrity": "sha512-N2YVqWbJELVdrnsW5p+apoQyYt51aBMSsBZki1XZEfeBCexcM/sf4xiAHcXQBkuOwJBXtWF7aW1sYX6tKebPHw==", + "version": "1.0.30001737", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001737.tgz", + "integrity": "sha512-BiloLiXtQNrY5UyF0+1nSJLXUENuhka2pzy2Fx5pGxqavdrxSCW4U6Pn/PoG3Efspi2frRbHpBV2XsrPE6EDlw==", "dev": true, "funding": [ { @@ -3313,24 +4265,24 @@ "type": "github", "url": "https://github.com/sponsors/ai" } - ] + ], + "license": "CC-BY-4.0" }, "node_modules/chai": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.7.tgz", - "integrity": "sha512-HLnAzZ2iupm25PlN0xFreAlBA5zaBSv3og0DdeGA4Ar6h6rJ3A0rolRUKJhSF2V10GZKDgWF/VmAEsNWjCRB+A==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.2.0.tgz", + "integrity": "sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==", "dev": true, + "license": "MIT", "dependencies": { - "assertion-error": "^1.1.0", - "check-error": "^1.0.2", - "deep-eql": "^4.1.2", - "get-func-name": "^2.0.0", - "loupe": "^2.3.1", - "pathval": "^1.1.1", - "type-detect": "^4.0.5" + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" }, "engines": { - "node": ">=4" + "node": ">=12" } }, "node_modules/chalk": { @@ -3338,6 +4290,7 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -3350,86 +4303,182 @@ } }, "node_modules/check-error": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", - "integrity": "sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", + "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", "dev": true, + "license": "MIT", "engines": { - "node": "*" + "node": ">= 16" } }, "node_modules/cheerio": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.12.tgz", - "integrity": "sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.1.0.tgz", + "integrity": "sha512-+0hMx9eYhJvWbgpKV9hN7jg0JcwydpopZE4hgi+KvQtByZXPp04NiCWU0LzcAbP63abZckIHkTQaXVF52mX3xQ==", "dev": true, + "license": "MIT", "dependencies": { "cheerio-select": "^2.1.0", "dom-serializer": "^2.0.0", "domhandler": "^5.0.3", - "domutils": "^3.0.1", - "htmlparser2": "^8.0.1", - "parse5": "^7.0.0", - "parse5-htmlparser2-tree-adapter": "^7.0.0" + "domutils": "^3.2.2", + "encoding-sniffer": "^0.2.0", + "htmlparser2": "^10.0.0", + "parse5": "^7.3.0", + "parse5-htmlparser2-tree-adapter": "^7.1.0", + "parse5-parser-stream": "^7.1.2", + "undici": "^7.10.0", + "whatwg-mimetype": "^4.0.0" + }, + "engines": { + "node": ">=18.17" + }, + "funding": { + "url": "https://github.com/cheeriojs/cheerio?sponsor=1" + } + }, + "node_modules/cheerio-select": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", + "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-select": "^5.1.0", + "css-what": "^6.1.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/chrome-launcher": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-1.2.0.tgz", + "integrity": "sha512-JbuGuBNss258bvGil7FT4HKdC3SC2K7UAEUqiPy3ACS3Yxo3hAW6bvFpCu2HsIJLgTqxgEX6BkujvzZfLpUD0Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/node": "*", + "escape-string-regexp": "^4.0.0", + "is-wsl": "^2.2.0", + "lighthouse-logger": "^2.0.1" + }, + "bin": { + "print-chrome-path": "bin/print-chrome-path.cjs" + }, + "engines": { + "node": ">=12.13.0" + } + }, + "node_modules/chromium-bidi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-5.1.0.tgz", + "integrity": "sha512-9MSRhWRVoRPDG0TgzkHrshFSJJNZzfY5UFqUMuksg7zL1yoZIZ3jLB0YAgHclbiAxPI86pBnwDX1tbzoiV8aFw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "mitt": "^3.0.1", + "zod": "^3.24.1" + }, + "peerDependencies": { + "devtools-protocol": "*" + } + }, + "node_modules/classnames": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==", + "license": "MIT" + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/cliui/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" }, "engines": { - "node": ">= 6" - }, - "funding": { - "url": "https://github.com/cheeriojs/cheerio?sponsor=1" + "node": ">=8" } }, - "node_modules/cheerio-select": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", - "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, + "license": "MIT", "dependencies": { - "boolbase": "^1.0.0", - "css-select": "^5.1.0", - "css-what": "^6.1.0", - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3", - "domutils": "^3.0.1" + "ansi-regex": "^5.0.1" }, - "funding": { - "url": "https://github.com/sponsors/fb55" + "engines": { + "node": ">=8" } }, - "node_modules/chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "dev": true - }, - "node_modules/chrome-launcher": { - "version": "0.15.2", - "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-0.15.2.tgz", - "integrity": "sha512-zdLEwNo3aUVzIhKhTtXfxhdvZhUghrnmkvcAq2NoDd+LeOHKf03H5jwZ8T/STsAlzyALkBVK552iaG1fGf1xVQ==", + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, + "license": "MIT", "dependencies": { - "@types/node": "*", - "escape-string-regexp": "^4.0.0", - "is-wsl": "^2.2.0", - "lighthouse-logger": "^1.0.0" - }, - "bin": { - "print-chrome-path": "bin/print-chrome-path.js" + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" }, "engines": { - "node": ">=12.13.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/classnames": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.2.tgz", - "integrity": "sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==" - }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, + "license": "MIT", "dependencies": { "color-name": "~1.1.4" }, @@ -3441,31 +4490,25 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "dev": true, + "license": "MIT" }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", "dev": true, - "dependencies": { - "delayed-stream": "~1.0.0" - }, + "license": "MIT", "engines": { - "node": ">= 0.8" + "node": ">=14" } }, - "node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - }, "node_modules/common-tags": { "version": "1.8.2", "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.2.tgz", "integrity": "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==", "dev": true, + "license": "MIT", "engines": { "node": ">=4.0.0" } @@ -3474,13 +4517,15 @@ "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/condense-newlines": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/condense-newlines/-/condense-newlines-0.2.1.tgz", "integrity": "sha512-P7X+QL9Hb9B/c8HI5BFFKmjgBu2XpQuF98WZ9XkO+dBGgk5XgwiQz7o1SmpglNWId3581UcS0SFAWfoIhMHPfg==", "dev": true, + "license": "MIT", "dependencies": { "extend-shallow": "^2.0.1", "is-whitespace": "^0.3.0", @@ -3490,71 +4535,55 @@ "node": ">=0.10.0" } }, - "node_modules/condense-newlines/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/config-chain": { "version": "1.1.13", "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", "dev": true, + "license": "MIT", "dependencies": { "ini": "^1.3.4", "proto-list": "~1.2.1" } }, "node_modules/convert-source-map": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", - "dev": true + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" }, "node_modules/core-js": { - "version": "3.31.0", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.31.0.tgz", - "integrity": "sha512-NIp2TQSGfR6ba5aalZD+ZQ1fSxGhDo/s1w0nx3RYzf2pnJxt7YynxFlFScP6eV7+GZsKO95NSjGxyJsU3DZgeQ==", + "version": "3.43.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.43.0.tgz", + "integrity": "sha512-N6wEbTTZSYOY2rYAn85CuvWWkCK6QweMn7/4Nr3w+gDBeBhk/x4EJeY6FPo4QzDoJZxVTv8U7CMvgWk6pOHHqA==", "hasInstallScript": true, + "license": "MIT", "funding": { "type": "opencollective", "url": "https://opencollective.com/core-js" } }, "node_modules/core-js-compat": { - "version": "3.31.1", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.31.1.tgz", - "integrity": "sha512-wIDWd2s5/5aJSdpOJHfSibxNODxoGoWOBHt8JSPB41NOE94M7kuTPZCYLOlTtuoXTsBPKobpJ6T+y0SSy5L9SA==", + "version": "3.43.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.43.0.tgz", + "integrity": "sha512-2GML2ZsCc5LR7hZYz4AXmjQw8zuy2T//2QntwdnpuYI7jteT6GVYJL7F6C2C57R7gSYrcqVW3lAALefdbhBLDA==", "dev": true, + "license": "MIT", "dependencies": { - "browserslist": "^4.21.9" + "browserslist": "^4.25.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/core-js" } }, - "node_modules/cross-fetch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz", - "integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==", - "dev": true, - "dependencies": { - "node-fetch": "2.6.7" - } - }, "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, + "license": "MIT", "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -3569,6 +4598,7 @@ "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -3578,6 +4608,7 @@ "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "boolbase": "^1.0.0", "css-what": "^6.1.0", @@ -3594,6 +4625,7 @@ "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", "dev": true, + "license": "BSD-2-Clause", "engines": { "node": ">= 6" }, @@ -3606,6 +4638,7 @@ "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", "dev": true, + "license": "MIT", "bin": { "cssesc": "bin/cssesc" }, @@ -3614,101 +4647,141 @@ } }, "node_modules/cssstyle": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-3.0.0.tgz", - "integrity": "sha512-N4u2ABATi3Qplzf0hWbVCdjenim8F3ojEXpBDF5hBpjzW182MjNGLqfmQ0SkSPeQ+V86ZXgeH8aXj6kayd4jgg==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.5.0.tgz", + "integrity": "sha512-/7gw8TGrvH/0g564EnhgFZogTMVe+lifpB7LWU+PEsiq5o83TUXR3fDbzTRXOJhoJwck5IS9ez3Em5LNMMO2aw==", "dev": true, + "license": "MIT", "dependencies": { - "rrweb-cssom": "^0.6.0" + "@asamuzakjp/css-color": "^3.2.0", + "rrweb-cssom": "^0.8.0" }, "engines": { - "node": ">=14" + "node": ">=18" } }, "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "license": "MIT" + }, + "node_modules/data-uri-to-buffer": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", + "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } }, "node_modules/data-urls": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-4.0.0.tgz", - "integrity": "sha512-/mMTei/JXPqvFqQtfyTowxmJVwr2PVAeCcDxyFf6LhoOu/09TX2OX3kb2wzi4DMXcfj4OItwDOnhl5oziPnT6g==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", + "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", "dev": true, + "license": "MIT", "dependencies": { - "abab": "^2.0.6", - "whatwg-mimetype": "^3.0.0", - "whatwg-url": "^12.0.0" + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0" }, "engines": { - "node": ">=14" + "node": ">=18" } }, - "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "node_modules/data-view-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", "dev": true, + "license": "MIT", "dependencies": { - "ms": "2.1.2" + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" }, "engines": { - "node": ">=6.0" + "node": ">= 0.4" }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "node_modules/data-view-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/inspect-js" } }, - "node_modules/decamelize-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.1.tgz", - "integrity": "sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==", + "node_modules/data-view-byte-offset": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", "dev": true, + "license": "MIT", "dependencies": { - "decamelize": "^1.1.0", - "map-obj": "^1.0.0" + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/decamelize-keys/node_modules/map-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", - "integrity": "sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==", + "node_modules/de-indent": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz", + "integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, "engines": { - "node": ">=0.10.0" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, "node_modules/decimal.js": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", - "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==", - "dev": true + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.5.0.tgz", + "integrity": "sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw==", + "dev": true, + "license": "MIT" }, "node_modules/deep-eql": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", - "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", "dev": true, - "dependencies": { - "type-detect": "^4.0.0" - }, + "license": "MIT", "engines": { "node": ">=6" } @@ -3717,25 +4790,29 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/deepmerge": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } }, - "node_modules/define-properties": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz", - "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==", + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", "dev": true, + "license": "MIT", "dependencies": { - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" }, "engines": { "node": ">= 0.4" @@ -3744,47 +4821,52 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/devtools-protocol": { - "version": "0.0.981744", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.981744.tgz", - "integrity": "sha512-0cuGS8+jhR67Fy7qG3i3Pc7Aw494sb9yG9QgpG97SFVWwolgYjlhJg7n+UaHxOQT30d1TYu/EYe9k01ivLErIg==", - "dev": true - }, - "node_modules/diff-sequences": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.4.3.tgz", - "integrity": "sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA==", + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "node_modules/degenerator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", + "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", "dev": true, + "license": "MIT", "dependencies": { - "esutils": "^2.0.2" + "ast-types": "^0.13.4", + "escodegen": "^2.1.0", + "esprima": "^4.0.1" }, "engines": { - "node": ">=6.0.0" + "node": ">= 14" } }, + "node_modules/devtools-protocol": { + "version": "0.0.1452169", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1452169.tgz", + "integrity": "sha512-FOFDVMGrAUNp0dDKsAU1TorWJUx2JOU1k9xdgBKKJF3IBh/Uhl2yswG5r3TEAOrCiGY2QRp1e6LVDQrCsTKO4g==", + "dev": true, + "license": "BSD-3-Clause" + }, "node_modules/dom-serializer": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", "dev": true, + "license": "MIT", "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", @@ -3794,6 +4876,19 @@ "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" } }, + "node_modules/dom-serializer/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/domelementtype": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", @@ -3804,25 +4899,15 @@ "type": "github", "url": "https://github.com/sponsors/fb55" } - ] - }, - "node_modules/domexception": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz", - "integrity": "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==", - "dev": true, - "dependencies": { - "webidl-conversions": "^7.0.0" - }, - "engines": { - "node": ">=12" - } + ], + "license": "BSD-2-Clause" }, "node_modules/domhandler": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "domelementtype": "^2.3.0" }, @@ -3834,10 +4919,11 @@ } }, "node_modules/domutils": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", - "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", @@ -3847,17 +4933,34 @@ "url": "https://github.com/fb55/domutils?sponsor=1" } }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/editorconfig": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-1.0.4.tgz", "integrity": "sha512-L9Qe08KWTlqYMVvMcTIvMAdl1cDUubzRNYL+WfA4bLDMHe4nemKkpmYzkznE1FwLKu0EEmy6obgQKzMJrg4x9Q==", "dev": true, + "license": "MIT", "dependencies": { "@one-ini/wasm": "0.1.1", "commander": "^10.0.0", @@ -3871,29 +4974,12 @@ "node": ">=14" } }, - "node_modules/editorconfig/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/editorconfig/node_modules/commander": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", - "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", - "dev": true, - "engines": { - "node": ">=14" - } - }, "node_modules/editorconfig/node_modules/minimatch": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.1.tgz", "integrity": "sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==", "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" }, @@ -3909,6 +4995,7 @@ "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", "dev": true, + "license": "Apache-2.0", "dependencies": { "jake": "^10.8.5" }, @@ -3920,30 +5007,49 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.4.460", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.460.tgz", - "integrity": "sha512-kKiHnbrHME7z8E6AYaw0ehyxY5+hdaRmeUbjBO22LZMdqTYCO29EvF0T1cQ3pJ1RN5fyMcHl1Lmcsdt9WWJpJQ==", - "dev": true + "version": "1.5.171", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.171.tgz", + "integrity": "sha512-scWpzXEJEMrGJa4Y6m/tVotb0WuvNmasv3wWVzUAeCgKU0ToFOhUW6Z+xWnRQANMYGxN4ngJXIThgBJOqzVPCQ==", + "dev": true, + "license": "ISC" }, "node_modules/emoji-regex": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true + "dev": true, + "license": "MIT" + }, + "node_modules/encoding-sniffer": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.1.tgz", + "integrity": "sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw==", + "dev": true, + "license": "MIT", + "dependencies": { + "iconv-lite": "^0.6.3", + "whatwg-encoding": "^3.1.1" + }, + "funding": { + "url": "https://github.com/fb55/encoding-sniffer?sponsor=1" + } }, "node_modules/end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", "dev": true, + "license": "MIT", "dependencies": { "once": "^1.4.0" } }, "node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "license": "BSD-2-Clause", "engines": { "node": ">=0.12" }, @@ -3951,87 +5057,141 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, - "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, "node_modules/es-abstract": { - "version": "1.21.3", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.3.tgz", - "integrity": "sha512-ZU4miiY1j3sGPFLJ34VJXEqhpmL+HGByCinGHv4HC+Fxl2fI2Z4yR6tl0mORnDr6PA8eihWo4LmSWDbvhALckg==", - "dev": true, - "dependencies": { - "array-buffer-byte-length": "^1.0.0", - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "es-set-tostringtag": "^2.0.1", - "es-to-primitive": "^1.2.1", - "function.prototype.name": "^1.1.5", - "get-intrinsic": "^1.2.1", - "get-symbol-description": "^1.0.0", - "globalthis": "^1.0.3", - "gopd": "^1.0.1", - "has": "^1.0.3", - "has-property-descriptors": "^1.0.0", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "internal-slot": "^1.0.5", - "is-array-buffer": "^3.0.2", + "version": "1.24.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz", + "integrity": "sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.3.0", + "get-proto": "^1.0.1", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", "is-callable": "^1.2.7", - "is-negative-zero": "^2.0.2", - "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.2", - "is-string": "^1.0.7", - "is-typed-array": "^1.1.10", - "is-weakref": "^1.0.2", - "object-inspect": "^1.12.3", + "is-data-view": "^1.0.2", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.2.1", + "is-set": "^2.0.3", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.1", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.4", "object-keys": "^1.1.1", - "object.assign": "^4.1.4", - "regexp.prototype.flags": "^1.5.0", - "safe-regex-test": "^1.0.0", - "string.prototype.trim": "^1.2.7", - "string.prototype.trimend": "^1.0.6", - "string.prototype.trimstart": "^1.0.6", - "typed-array-byte-offset": "^1.0.0", - "typed-array-length": "^1.0.4", - "unbox-primitive": "^1.0.2", - "which-typed-array": "^1.1.10" + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.4", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "stop-iteration-iterator": "^1.1.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.19" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" }, "engines": { "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" } }, "node_modules/es-set-tostringtag": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz", - "integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", "dev": true, + "license": "MIT", "dependencies": { - "get-intrinsic": "^1.1.3", - "has": "^1.0.3", - "has-tostringtag": "^1.0.0" + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" }, "engines": { "node": ">= 0.4" } }, "node_modules/es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", + "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", "dev": true, + "license": "MIT", "dependencies": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" }, "engines": { "node": ">= 0.4" @@ -4041,47 +5201,52 @@ } }, "node_modules/esbuild": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", - "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.5.tgz", + "integrity": "sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==", "dev": true, "hasInstallScript": true, + "license": "MIT", "bin": { "esbuild": "bin/esbuild" }, "engines": { - "node": ">=12" + "node": ">=18" }, "optionalDependencies": { - "@esbuild/android-arm": "0.18.20", - "@esbuild/android-arm64": "0.18.20", - "@esbuild/android-x64": "0.18.20", - "@esbuild/darwin-arm64": "0.18.20", - "@esbuild/darwin-x64": "0.18.20", - "@esbuild/freebsd-arm64": "0.18.20", - "@esbuild/freebsd-x64": "0.18.20", - "@esbuild/linux-arm": "0.18.20", - "@esbuild/linux-arm64": "0.18.20", - "@esbuild/linux-ia32": "0.18.20", - "@esbuild/linux-loong64": "0.18.20", - "@esbuild/linux-mips64el": "0.18.20", - "@esbuild/linux-ppc64": "0.18.20", - "@esbuild/linux-riscv64": "0.18.20", - "@esbuild/linux-s390x": "0.18.20", - "@esbuild/linux-x64": "0.18.20", - "@esbuild/netbsd-x64": "0.18.20", - "@esbuild/openbsd-x64": "0.18.20", - "@esbuild/sunos-x64": "0.18.20", - "@esbuild/win32-arm64": "0.18.20", - "@esbuild/win32-ia32": "0.18.20", - "@esbuild/win32-x64": "0.18.20" + "@esbuild/aix-ppc64": "0.25.5", + "@esbuild/android-arm": "0.25.5", + "@esbuild/android-arm64": "0.25.5", + "@esbuild/android-x64": "0.25.5", + "@esbuild/darwin-arm64": "0.25.5", + "@esbuild/darwin-x64": "0.25.5", + "@esbuild/freebsd-arm64": "0.25.5", + "@esbuild/freebsd-x64": "0.25.5", + "@esbuild/linux-arm": "0.25.5", + "@esbuild/linux-arm64": "0.25.5", + "@esbuild/linux-ia32": "0.25.5", + "@esbuild/linux-loong64": "0.25.5", + "@esbuild/linux-mips64el": "0.25.5", + "@esbuild/linux-ppc64": "0.25.5", + "@esbuild/linux-riscv64": "0.25.5", + "@esbuild/linux-s390x": "0.25.5", + "@esbuild/linux-x64": "0.25.5", + "@esbuild/netbsd-arm64": "0.25.5", + "@esbuild/netbsd-x64": "0.25.5", + "@esbuild/openbsd-arm64": "0.25.5", + "@esbuild/openbsd-x64": "0.25.5", + "@esbuild/sunos-x64": "0.25.5", + "@esbuild/win32-arm64": "0.25.5", + "@esbuild/win32-ia32": "0.25.5", + "@esbuild/win32-x64": "0.25.5" } }, "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -4091,6 +5256,7 @@ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" }, @@ -4098,94 +5264,182 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/escodegen/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/eslint": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", - "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", + "version": "9.29.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.29.0.tgz", + "integrity": "sha512-GsGizj2Y1rCWDu6XoEekL3RLilp0voSePurjZIkxL3wlm5o5EC9VpgaP7lrCvjnkuLvzFBQWB3vWB3K5KQTveQ==", "dev": true, + "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.57.0", - "@humanwhocodes/config-array": "^0.11.14", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.20.1", + "@eslint/config-helpers": "^0.2.1", + "@eslint/core": "^0.14.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.29.0", + "@eslint/plugin-kit": "^0.3.1", + "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "@ungap/structured-clone": "^1.2.0", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", + "cross-spawn": "^7.0.6", "debug": "^4.3.2", - "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.2", - "eslint-visitor-keys": "^3.4.3", - "espree": "^9.6.1", - "esquery": "^1.4.2", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", + "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "graphemer": "^1.4.0", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "strip-ansi": "^6.0.1", - "text-table": "^0.2.0" + "optionator": "^0.9.3" }, "bin": { "eslint": "bin/eslint.js" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-playwright": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-playwright/-/eslint-plugin-playwright-2.2.0.tgz", + "integrity": "sha512-qSQpAw7RcSzE3zPp8FMGkthaCWovHZ/BsXtpmnGax9vQLIovlh1bsZHEa2+j2lv9DWhnyeLM/qZmp7ffQZfQvg==", + "dev": true, + "license": "MIT", + "workspaces": [ + "examples" + ], + "dependencies": { + "globals": "^13.23.0" + }, + "engines": { + "node": ">=16.6.0" + }, + "peerDependencies": { + "eslint": ">=8.40.0" + } + }, + "node_modules/eslint-plugin-playwright/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint-plugin-playwright/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/eslint-plugin-vue": { - "version": "9.26.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.26.0.tgz", - "integrity": "sha512-eTvlxXgd4ijE1cdur850G6KalZqk65k1JKoOI2d1kT3hr8sPD07j1q98FRFdNnpxBELGPWxZmInxeHGF/GxtqQ==", + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-10.2.0.tgz", + "integrity": "sha512-tl9s+KN3z0hN2b8fV2xSs5ytGl7Esk1oSCxULLwFcdaElhZ8btYYZFrWxvh4En+czrSDtuLCeCOGa8HhEZuBdQ==", "dev": true, + "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "globals": "^13.24.0", "natural-compare": "^1.4.0", "nth-check": "^2.1.1", "postcss-selector-parser": "^6.0.15", - "semver": "^7.6.0", - "vue-eslint-parser": "^9.4.2", + "semver": "^7.6.3", "xml-name-validator": "^4.0.0" }, "engines": { - "node": "^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "peerDependencies": { - "eslint": "^6.2.0 || ^7.0.0 || ^8.0.0 || ^9.0.0" + "eslint": "^8.57.0 || ^9.0.0", + "vue-eslint-parser": "^10.0.0" } }, "node_modules/eslint-scope": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", - "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" @@ -4196,6 +5450,7 @@ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true, + "license": "Apache-2.0", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, @@ -4203,28 +5458,94 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/espree": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "acorn": "^8.9.0", + "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" + "eslint-visitor-keys": "^4.2.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" } }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/esquery": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", - "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "estraverse": "^5.1.0" }, @@ -4237,6 +5558,7 @@ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "estraverse": "^5.2.0" }, @@ -4249,29 +5571,47 @@ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, + "license": "BSD-2-Clause", "engines": { "node": ">=4.0" } }, "node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true, + "license": "BSD-2-Clause", "engines": { "node": ">=0.10.0" } }, + "node_modules/expect-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.1.tgz", + "integrity": "sha512-/kP8CAwxzLVEeFrMm4kMmy4CCDlpipyA7MYLVrdJIkV0fYF0UaigQHRsxHiuY/GEea+bh4KSv3TIlgr+2UL6bw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/extend-shallow": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", "dev": true, + "license": "MIT", "dependencies": { "is-extendable": "^0.1.0" }, @@ -4284,6 +5624,7 @@ "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "debug": "^4.1.1", "get-stream": "^5.1.0", @@ -4303,19 +5644,28 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true + "dev": true, + "license": "MIT" + }, + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", + "dev": true, + "license": "MIT" }, "node_modules/fast-glob": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", - "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", "dev": true, + "license": "MIT", "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", - "micromatch": "^4.0.4" + "micromatch": "^4.0.8" }, "engines": { "node": ">=8.6.0" @@ -4326,6 +5676,7 @@ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, + "license": "ISC", "dependencies": { "is-glob": "^4.0.1" }, @@ -4337,19 +5688,39 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true + "dev": true, + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz", + "integrity": "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" }, "node_modules/fastq": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", - "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", "dev": true, + "license": "ISC", "dependencies": { "reusify": "^1.0.4" } @@ -4359,6 +5730,7 @@ "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", "dev": true, + "license": "MIT", "dependencies": { "pend": "~1.2.0" } @@ -4367,21 +5739,23 @@ "version": "4.29.2", "resolved": "https://registry.npmjs.org/feather-icons/-/feather-icons-4.29.2.tgz", "integrity": "sha512-0TaCFTnBTVCz6U+baY2UJNKne5ifGh7sMG4ZC2LoBWCZdIyPa+y6UiR4lEYGws1JOFWdee8KAsAIvu0VcXqiqA==", + "license": "MIT", "dependencies": { "classnames": "^2.2.5", "core-js": "^3.1.3" } }, "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", "dev": true, + "license": "MIT", "dependencies": { - "flat-cache": "^3.0.4" + "flat-cache": "^4.0.0" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">=16.0.0" } }, "node_modules/filelist": { @@ -4389,24 +5763,17 @@ "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", "dev": true, + "license": "Apache-2.0", "dependencies": { "minimatch": "^5.0.1" } }, - "node_modules/filelist/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, "node_modules/filelist/node_modules/minimatch": { "version": "5.1.6", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" }, @@ -4419,6 +5786,7 @@ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, + "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" }, @@ -4427,26 +5795,28 @@ } }, "node_modules/find-process": { - "version": "1.4.7", - "resolved": "https://registry.npmjs.org/find-process/-/find-process-1.4.7.tgz", - "integrity": "sha512-/U4CYp1214Xrp3u3Fqr9yNynUrr5Le4y0SsJh2lMDDSbpwYSz3M2SMWQC+wqcx79cN8PQtHQIL8KnuY9M66fdg==", + "version": "1.4.10", + "resolved": "https://registry.npmjs.org/find-process/-/find-process-1.4.10.tgz", + "integrity": "sha512-ncYFnWEIwL7PzmrK1yZtaccN8GhethD37RzBHG6iOZoFYB4vSmLLXfeWJjeN5nMvCJMjOtBvBBF8OgxEcikiZg==", "dev": true, + "license": "MIT", "dependencies": { - "chalk": "^4.0.0", - "commander": "^5.1.0", - "debug": "^4.1.1" + "chalk": "~4.1.2", + "commander": "^12.1.0", + "loglevel": "^1.9.2" }, "bin": { "find-process": "bin/find-process.js" } }, "node_modules/find-process/node_modules/commander": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", - "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", "dev": true, + "license": "MIT", "engines": { - "node": ">= 6" + "node": ">=18" } }, "node_modules/find-up": { @@ -4454,6 +5824,7 @@ "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, + "license": "MIT", "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" @@ -4466,40 +5837,50 @@ } }, "node_modules/flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", "dev": true, + "license": "MIT", "dependencies": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" + "flatted": "^3.2.9", + "keyv": "^4.5.4" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">=16" } }, "node_modules/flatted": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", - "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", - "dev": true + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" }, "node_modules/for-each": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", - "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", "dev": true, + "license": "MIT", "dependencies": { - "is-callable": "^1.1.3" + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/foreground-child": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", - "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", "dev": true, + "license": "ISC", "dependencies": { - "cross-spawn": "^7.0.0", + "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" }, "engines": { @@ -4509,31 +5890,12 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "dev": true, - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/fs-constants": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", - "dev": true - }, "node_modules/fs-extra": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", "dev": true, + "license": "MIT", "dependencies": { "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", @@ -4544,20 +5906,12 @@ "node": ">=10" } }, - "node_modules/fs-extra/node_modules/universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", - "dev": true, - "engines": { - "node": ">= 10.0.0" - } - }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/fsevents": { "version": "2.3.2", @@ -4565,6 +5919,7 @@ "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", "dev": true, "hasInstallScript": true, + "license": "MIT", "optional": true, "os": [ "darwin" @@ -4574,21 +5929,28 @@ } }, "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/function.prototype.name": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", - "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", + "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.0", - "functions-have-names": "^1.2.2" + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "functions-have-names": "^1.2.3", + "hasown": "^2.0.2", + "is-callable": "^1.2.7" }, "engines": { "node": ">= 0.4" @@ -4602,6 +5964,7 @@ "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", "dev": true, + "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -4611,29 +5974,41 @@ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } }, - "node_modules/get-func-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", - "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true, + "license": "ISC", "engines": { - "node": "*" + "node": "6.* || 8.* || >= 10.*" } }, "node_modules/get-intrinsic": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", - "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "dev": true, + "license": "MIT", "dependencies": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3" + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -4643,13 +6018,29 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz", "integrity": "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==", - "dev": true + "dev": true, + "license": "ISC" + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } }, "node_modules/get-stream": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", "dev": true, + "license": "MIT", "dependencies": { "pump": "^3.0.0" }, @@ -4661,13 +6052,15 @@ } }, "node_modules/get-symbol-description": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", - "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" }, "engines": { "node": ">= 0.4" @@ -4676,21 +6069,37 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "node_modules/get-uri": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.4.tgz", + "integrity": "sha512-E1b1lFFLvLgak2whF2xDBcOy6NLVGZBqqjJjsIhvopKfWWEi64pLVTWWehV8KlLerZkfNTA95sTe2OdJKm1OzQ==", "dev": true, + "license": "MIT", "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "basic-ftp": "^5.0.2", + "data-uri-to-buffer": "^6.0.2", + "debug": "^4.3.4" }, "engines": { - "node": "*" + "node": ">= 14" + } + }, + "node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -4701,6 +6110,7 @@ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dev": true, + "license": "ISC", "dependencies": { "is-glob": "^4.0.3" }, @@ -4709,27 +6119,27 @@ } }, "node_modules/globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", "dev": true, - "dependencies": { - "type-fest": "^0.20.2" - }, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/globalthis": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", - "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", "dev": true, + "license": "MIT", "dependencies": { - "define-properties": "^1.1.3" + "define-properties": "^1.2.1", + "gopd": "^1.0.1" }, "engines": { "node": ">= 0.4" @@ -4739,12 +6149,13 @@ } }, "node_modules/gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", "dev": true, - "dependencies": { - "get-intrinsic": "^1.1.3" + "license": "MIT", + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -4754,40 +6165,25 @@ "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/graphemer": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true - }, - "node_modules/hard-rejection": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", - "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", "dev": true, - "dependencies": { - "function-bind": "^1.1.1" - }, - "engines": { - "node": ">= 0.4.0" - } + "license": "MIT" }, "node_modules/has-bigints": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", - "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -4797,27 +6193,33 @@ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/has-property-descriptors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", - "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", "dev": true, + "license": "MIT", "dependencies": { - "get-intrinsic": "^1.1.1" + "es-define-property": "^1.0.0" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/has-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", - "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.0" + }, "engines": { "node": ">= 0.4" }, @@ -4826,10 +6228,11 @@ } }, "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -4838,12 +6241,13 @@ } }, "node_modules/has-tostringtag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", - "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", "dev": true, + "license": "MIT", "dependencies": { - "has-symbols": "^1.0.2" + "has-symbols": "^1.0.3" }, "engines": { "node": ">= 0.4" @@ -4852,34 +6256,46 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/hosted-git-info": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", - "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", "dev": true, + "license": "MIT", "dependencies": { - "lru-cache": "^6.0.0" + "function-bind": "^1.1.2" }, "engines": { - "node": ">=10" + "node": ">= 0.4" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "license": "MIT", + "bin": { + "he": "bin/he" } }, "node_modules/html-encoding-sniffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", - "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", + "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", "dev": true, + "license": "MIT", "dependencies": { - "whatwg-encoding": "^2.0.0" + "whatwg-encoding": "^3.1.1" }, "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/htmlparser2": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", - "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.0.0.tgz", + "integrity": "sha512-TwAZM+zE5Tq3lrEHvOlvwgj1XLWQCtaaibSN11Q+gGBAS7Y1uZSWwXXRe4iF6OXnaq1riyQAPFOBtYc77Mxq0g==", "dev": true, "funding": [ "https://github.com/fb55/htmlparser2?sponsor=1", @@ -4888,38 +6304,40 @@ "url": "https://github.com/sponsors/fb55" } ], + "license": "MIT", "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.3", - "domutils": "^3.0.1", - "entities": "^4.4.0" + "domutils": "^3.2.1", + "entities": "^6.0.0" } }, "node_modules/http-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", - "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", "dev": true, + "license": "MIT", "dependencies": { - "@tootallnate/once": "2", - "agent-base": "6", - "debug": "4" + "agent-base": "^7.1.0", + "debug": "^4.3.4" }, "engines": { - "node": ">= 6" + "node": ">= 14" } }, "node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", "dev": true, + "license": "MIT", "dependencies": { - "agent-base": "6", + "agent-base": "^7.1.2", "debug": "4" }, "engines": { - "node": ">= 6" + "node": ">= 14" } }, "node_modules/iconv-lite": { @@ -4927,6 +6345,7 @@ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", "dev": true, + "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" }, @@ -4938,42 +6357,25 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/idb/-/idb-7.1.1.tgz", "integrity": "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==", - "dev": true - }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] + "license": "ISC" }, "node_modules/ignore": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", - "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, + "license": "MIT", "engines": { "node": ">= 4" } }, "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", "dev": true, + "license": "MIT", "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -4990,24 +6392,18 @@ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.8.19" } }, - "node_modules/indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", "dev": true, + "license": "ISC", "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -5017,68 +6413,108 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/ini": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/internal-slot": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", - "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", "dev": true, + "license": "MIT", "dependencies": { - "get-intrinsic": "^1.2.0", - "has": "^1.0.3", - "side-channel": "^1.0.4" + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" }, "engines": { "node": ">= 0.4" } }, + "node_modules/ip-address": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", + "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" + }, + "engines": { + "node": ">= 12" + } + }, "node_modules/is-array-buffer": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", - "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.0", - "is-typed-array": "^1.1.10" + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true + "node_modules/is-async-function": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "async-function": "^1.0.0", + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/is-bigint": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", - "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", "dev": true, + "license": "MIT", "dependencies": { - "has-bigints": "^1.0.1" + "has-bigints": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/is-boolean-object": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", - "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -5091,13 +6527,15 @@ "version": "1.1.6", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/is-callable": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -5106,24 +6544,48 @@ } }, "node_modules/is-core-module": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.1.tgz", - "integrity": "sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==", + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", "dev": true, + "license": "MIT", "dependencies": { - "has": "^1.0.3" + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/is-date-object": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", - "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", "dev": true, + "license": "MIT", "dependencies": { - "has-tostringtag": "^1.0.0" + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -5137,6 +6599,7 @@ "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", "dev": true, + "license": "MIT", "bin": { "is-docker": "cli.js" }, @@ -5152,6 +6615,7 @@ "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -5161,24 +6625,62 @@ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } }, + "node_modules/is-finalizationregistry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } }, + "node_modules/is-generator-function": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz", + "integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-proto": "^1.0.0", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, + "license": "MIT", "dependencies": { "is-extglob": "^2.1.1" }, @@ -5186,17 +6688,32 @@ "node": ">=0.10.0" } }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-module": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/is-negative-zero": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", - "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -5209,17 +6726,20 @@ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.12.0" } }, "node_modules/is-number-object": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", - "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", "dev": true, + "license": "MIT", "dependencies": { - "has-tostringtag": "^1.0.0" + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -5233,24 +6753,7 @@ "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", "integrity": "sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==", "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-plain-obj": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", - "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", - "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -5259,16 +6762,20 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/is-regex": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", - "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" }, "engines": { "node": ">= 0.4" @@ -5282,17 +6789,35 @@ "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz", "integrity": "sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-shared-array-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", - "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2" + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -5303,6 +6828,7 @@ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" }, @@ -5311,12 +6837,14 @@ } }, "node_modules/is-string": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", - "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", "dev": true, + "license": "MIT", "dependencies": { - "has-tostringtag": "^1.0.0" + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -5326,12 +6854,15 @@ } }, "node_modules/is-symbol": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", - "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", "dev": true, + "license": "MIT", "dependencies": { - "has-symbols": "^1.0.2" + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -5341,17 +6872,27 @@ } }, "node_modules/is-typed-array": { - "version": "1.1.10", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", - "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", "dev": true, + "license": "MIT", "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0" + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "dev": true, + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -5360,12 +6901,33 @@ } }, "node_modules/is-weakref": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", - "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2" + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -5376,6 +6938,7 @@ "resolved": "https://registry.npmjs.org/is-whitespace/-/is-whitespace-0.3.0.tgz", "integrity": "sha512-RydPhl4S6JwAyj0JJjshWJEFG6hNye3pZFBRZaTUfZFwGHxzppNaNOVgQuS/E/SlhrApuMXrpnK1EEIXfdo3Dg==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -5385,6 +6948,7 @@ "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", "dev": true, + "license": "MIT", "dependencies": { "is-docker": "^2.0.0" }, @@ -5392,23 +6956,29 @@ "node": ">=8" } }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, + "license": "MIT" + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/jackspeak": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", - "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { "@isaacs/cliui": "^8.0.2" }, - "engines": { - "node": ">=14" - }, "funding": { "url": "https://github.com/sponsors/isaacs" }, @@ -5417,10 +6987,11 @@ } }, "node_modules/jake": { - "version": "10.8.7", - "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.7.tgz", - "integrity": "sha512-ZDi3aP+fG/LchyBzUM804VjddnwfSfsdeYkwt8NcbKRvo4rFkjhs456iLFn3k2ZUWvNe4i48WACDbza8fhq2+w==", + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz", + "integrity": "sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==", "dev": true, + "license": "Apache-2.0", "dependencies": { "async": "^3.2.3", "chalk": "^4.0.2", @@ -5434,85 +7005,50 @@ "node": ">=10" } }, - "node_modules/jest-worker": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz", - "integrity": "sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==", - "dev": true, - "dependencies": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^7.0.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/js-beautify": { - "version": "1.15.1", - "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.15.1.tgz", - "integrity": "sha512-ESjNzSlt/sWE8sciZH8kBF8BPlwXPwhR6pWKAw8bw4Bwj+iZcnKW6ONWUutJ7eObuBZQpiIb8S7OYspWrKt7rA==", - "dev": true, - "dependencies": { - "config-chain": "^1.1.13", - "editorconfig": "^1.0.4", - "glob": "^10.3.3", - "js-cookie": "^3.0.5", - "nopt": "^7.2.0" - }, - "bin": { - "css-beautify": "js/bin/css-beautify.js", - "html-beautify": "js/bin/html-beautify.js", - "js-beautify": "js/bin/js-beautify.js" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/js-beautify/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "node_modules/jake/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, + "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0" + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, - "node_modules/js-beautify/node_modules/glob": { - "version": "10.3.15", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.15.tgz", - "integrity": "sha512-0c6RlJt1TICLyvJYIApxb8GsXoai0KUP7AxKKAtsYXdgJR1mGEUa7DgwShbdk1nly0PYoZj01xd4hzbq3fsjpw==", + "node_modules/jake/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, + "license": "ISC", "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^2.3.6", - "minimatch": "^9.0.1", - "minipass": "^7.0.4", - "path-scurry": "^1.11.0" - }, - "bin": { - "glob": "dist/esm/bin.mjs" + "brace-expansion": "^1.1.7" }, "engines": { - "node": ">=16 || 14 >=14.18" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": "*" } }, - "node_modules/js-beautify/node_modules/minimatch": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", - "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "node_modules/js-beautify": { + "version": "1.15.4", + "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.15.4.tgz", + "integrity": "sha512-9/KXeZUKKJwqCXUdBxFJ3vPh467OCckSBmYDwSK/EtV090K+iMJ7zx2S3HLVDIWFQdqMIsZWbnaGiba18aWhaA==", "dev": true, + "license": "MIT", "dependencies": { - "brace-expansion": "^2.0.1" + "config-chain": "^1.1.13", + "editorconfig": "^1.0.4", + "glob": "^10.4.2", + "js-cookie": "^3.0.5", + "nopt": "^7.2.1" }, - "engines": { - "node": ">=16 || 14 >=14.17" + "bin": { + "css-beautify": "js/bin/css-beautify.js", + "html-beautify": "js/bin/html-beautify.js", + "js-beautify": "js/bin/js-beautify.js" }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "engines": { + "node": ">=14" } }, "node_modules/js-cookie": { @@ -5520,21 +7056,24 @@ "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==", "dev": true, + "license": "MIT", "engines": { "node": ">=14" } }, "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true, + "license": "MIT" }, "node_modules/js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dev": true, + "license": "MIT", "dependencies": { "argparse": "^2.0.1" }, @@ -5542,41 +7081,46 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsbn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", + "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", + "dev": true, + "license": "MIT" + }, "node_modules/jsdom": { - "version": "22.1.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-22.1.0.tgz", - "integrity": "sha512-/9AVW7xNbsBv6GfWho4TTNjEo9fe6Zhf9O7s0Fhhr3u+awPwAJMKwAMXnkk5vBxflqLW9hTHX/0cs+P3gW+cQw==", - "dev": true, - "dependencies": { - "abab": "^2.0.6", - "cssstyle": "^3.0.0", - "data-urls": "^4.0.0", - "decimal.js": "^10.4.3", - "domexception": "^4.0.0", - "form-data": "^4.0.0", - "html-encoding-sniffer": "^3.0.0", - "http-proxy-agent": "^5.0.0", - "https-proxy-agent": "^5.0.1", + "version": "26.1.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-26.1.0.tgz", + "integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssstyle": "^4.2.1", + "data-urls": "^5.0.0", + "decimal.js": "^10.5.0", + "html-encoding-sniffer": "^4.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.6", "is-potential-custom-element-name": "^1.0.1", - "nwsapi": "^2.2.4", - "parse5": "^7.1.2", - "rrweb-cssom": "^0.6.0", + "nwsapi": "^2.2.16", + "parse5": "^7.2.1", + "rrweb-cssom": "^0.8.0", "saxes": "^6.0.0", "symbol-tree": "^3.2.4", - "tough-cookie": "^4.1.2", - "w3c-xmlserializer": "^4.0.0", + "tough-cookie": "^5.1.1", + "w3c-xmlserializer": "^5.0.0", "webidl-conversions": "^7.0.0", - "whatwg-encoding": "^2.0.0", - "whatwg-mimetype": "^3.0.0", - "whatwg-url": "^12.0.1", - "ws": "^8.13.0", - "xml-name-validator": "^4.0.0" + "whatwg-encoding": "^3.1.1", + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.1.1", + "ws": "^8.18.0", + "xml-name-validator": "^5.0.0" }, "engines": { - "node": ">=16" + "node": ">=18" }, "peerDependencies": { - "canvas": "^2.5.0" + "canvas": "^3.0.0" }, "peerDependenciesMeta": { "canvas": { @@ -5584,47 +7128,73 @@ } } }, + "node_modules/jsdom/node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, "node_modules/jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", "dev": true, + "license": "MIT", "bin": { "jsesc": "bin/jsesc" }, "engines": { - "node": ">=4" + "node": ">=6" } }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-4.0.0.tgz", + "integrity": "sha512-lR4MXjGNgkJc7tkQ97kb2nuEMnNCyU//XYVH0MKTGcXEiSudQ5MKGKen3C5QubYy0vmq+JGitUg92uuywGEwIA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } }, "node_modules/json-schema": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", - "dev": true + "dev": true, + "license": "(AFL-2.1 OR BSD-3-Clause)" }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true, + "license": "MIT", "bin": { "json5": "lib/cli.js" }, @@ -5632,17 +7202,12 @@ "node": ">=6" } }, - "node_modules/jsonc-parser": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", - "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", - "dev": true - }, "node_modules/jsonfile": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", "dev": true, + "license": "MIT", "dependencies": { "universalify": "^2.0.0" }, @@ -5650,29 +7215,35 @@ "graceful-fs": "^4.1.6" } }, - "node_modules/jsonfile/node_modules/universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", - "dev": true, - "engines": { - "node": ">= 10.0.0" - } - }, "node_modules/jsonpointer": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.1.tgz", "integrity": "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, "node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", "dev": true, + "license": "MIT", + "dependencies": { + "is-buffer": "^1.1.5" + }, "engines": { "node": ">=0.10.0" } @@ -5682,6 +7253,7 @@ "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -5691,6 +7263,7 @@ "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", "dev": true, + "license": "MIT", "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" @@ -5700,10 +7273,11 @@ } }, "node_modules/lighthouse-logger": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-1.4.2.tgz", - "integrity": "sha512-gPWxznF6TKmUHrOQjlVo2UbaL2EJ71mb2CCeRs/2qBpi4L/g4LUVc9+3lKQ6DTUZwJswfM7ainGrLO1+fOqa2g==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-2.0.1.tgz", + "integrity": "sha512-ioBrW3s2i97noEmnXxmUq7cjIcVRjT5HBpAYy8zE11CxU9HqlWHHeRxfeN1tn8F7OEMVPIC9x1f8t3Z7US9ehQ==", "dev": true, + "license": "Apache-2.0", "dependencies": { "debug": "^2.6.9", "marky": "^1.2.2" @@ -5714,6 +7288,7 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, + "license": "MIT", "dependencies": { "ms": "2.0.0" } @@ -5722,24 +7297,17 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true + "dev": true, + "license": "MIT" }, - "node_modules/local-pkg": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.4.3.tgz", - "integrity": "sha512-SFppqq5p42fe2qcZQqqEOiVRXl+WCP1MdT6k7BDEW1j++sp5fIY+/fdRQitvKgB5BrBcmrs5m/L0v2FrU5MY1g==", + "node_modules/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", "dev": true, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" + "license": "MIT", + "dependencies": { + "uc.micro": "^2.0.0" } }, "node_modules/locate-path": { @@ -5747,6 +7315,7 @@ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, + "license": "MIT", "dependencies": { "p-locate": "^5.0.0" }, @@ -5761,134 +7330,157 @@ "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", - "dev": true - }, - "node_modules/lodash.isequal": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", - "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/lodash.sortby": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", "integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/lodash.uniqwith": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.uniqwith/-/lodash.uniqwith-4.5.0.tgz", "integrity": "sha512-7lYL8bLopMoy4CTICbxygAUq6CdRJ36vFc80DucPueUee+d5NBRxz3FdT9Pes/HEx5mPoT9jwnsEJWz1N7uq7Q==", - "dev": true + "dev": true, + "license": "MIT" }, - "node_modules/loupe": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.6.tgz", - "integrity": "sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==", + "node_modules/loglevel": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.9.2.tgz", + "integrity": "sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg==", "dev": true, - "dependencies": { - "get-func-name": "^2.0.0" + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + }, + "funding": { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/loglevel" } }, + "node_modules/loupe": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.4.tgz", + "integrity": "sha512-wJzkKwJrheKtknCOKNEtDK4iqg/MxmZheEMtSTYvnzRdEYaZzmgH976nenp8WdJRdx5Vc1X/9MO0Oszl6ezeXg==", + "dev": true, + "license": "MIT" + }, "node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "dev": true, + "license": "ISC" + }, + "node_modules/magic-string": { + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "license": "MIT", "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" + "@jridgewell/sourcemap-codec": "^1.5.0" } }, - "node_modules/magic-string": { - "version": "0.30.10", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.10.tgz", - "integrity": "sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==", + "node_modules/markdown-it": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", + "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", + "dev": true, + "license": "MIT", "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.15" + "argparse": "^2.0.1", + "entities": "^4.4.0", + "linkify-it": "^5.0.0", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" + }, + "bin": { + "markdown-it": "bin/markdown-it.mjs" } }, - "node_modules/map-obj": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz", - "integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==", + "node_modules/markdown-it/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", "dev": true, + "license": "BSD-2-Clause", "engines": { - "node": ">=8" + "node": ">=0.12" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/fb55/entities?sponsor=1" } }, "node_modules/marky": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/marky/-/marky-1.2.5.tgz", - "integrity": "sha512-q9JtQJKjpsVxCRVgQ+WapguSbKC3SQ5HEzFGPAJMStgh3QjCawp00UKv3MTTAArTmGmmPUvllHZoNbZ3gs0I+Q==", - "dev": true + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/marky/-/marky-1.3.0.tgz", + "integrity": "sha512-ocnPZQLNpvbedwTy9kNrQEsknEfgvcLMvOtz3sFeWApDq1MXH1TqkCIx58xlpESsfwQOnuBO9beyQuNGzVvuhQ==", + "dev": true, + "license": "Apache-2.0" }, - "node_modules/meow": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/meow/-/meow-9.0.0.tgz", - "integrity": "sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ==", + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", "dev": true, - "dependencies": { - "@types/minimist": "^1.2.0", - "camelcase-keys": "^6.2.2", - "decamelize": "^1.2.0", - "decamelize-keys": "^1.1.0", - "hard-rejection": "^2.1.0", - "minimist-options": "4.1.0", - "normalize-package-data": "^3.0.0", - "read-pkg-up": "^7.0.1", - "redent": "^3.0.0", - "trim-newlines": "^3.0.0", - "type-fest": "^0.18.0", - "yargs-parser": "^20.2.3" - }, + "license": "MIT", "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 0.4" } }, - "node_modules/meow/node_modules/type-fest": { - "version": "0.18.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz", - "integrity": "sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==", + "node_modules/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", + "dev": true, + "license": "MIT" + }, + "node_modules/memorystream": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", + "integrity": "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==", "dev": true, "engines": { - "node": ">=10" + "node": ">= 0.10.0" + } + }, + "node_modules/meow": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz", + "integrity": "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true - }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", "dev": true, + "license": "MIT", "engines": { "node": ">= 8" } @@ -5898,6 +7490,7 @@ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, + "license": "MIT", "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" @@ -5907,104 +7500,86 @@ } }, "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", "dev": true, + "license": "MIT", "dependencies": { - "mime-db": "1.52.0" + "mime-db": "^1.54.0" }, "engines": { "node": ">= 0.6" } }, - "node_modules/min-indent": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", - "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, + "license": "ISC", "dependencies": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^2.0.1" }, "engines": { - "node": "*" - } - }, - "node_modules/minimist-options": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz", - "integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==", - "dev": true, - "dependencies": { - "arrify": "^1.0.1", - "is-plain-obj": "^1.1.0", - "kind-of": "^6.0.3" + "node": ">=16 || 14 >=14.17" }, - "engines": { - "node": ">= 6" + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/minipass": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.1.tgz", - "integrity": "sha512-UZ7eQ+h8ywIRAW1hIEl2AqdwzJucU/Kp59+8kkZeSvafXhZjul247BvIJjEVFVeON6d7lM46XX1HXCduKAS8VA==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", "dev": true, + "license": "ISC", "engines": { "node": ">=16 || 14 >=14.17" } }, - "node_modules/mkdirp-classic": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", - "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", - "dev": true - }, - "node_modules/mlly": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.4.0.tgz", - "integrity": "sha512-ua8PAThnTwpprIaU47EPeZ/bPUVp2QYBbWMphUQpVdBI3Lgqzm5KZQ45Agm3YJedHXaIHl6pBGabaLSUPPSptg==", + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", "dev": true, - "dependencies": { - "acorn": "^8.9.0", - "pathe": "^1.1.1", - "pkg-types": "^1.0.3", - "ufo": "^1.1.2" - } + "license": "MIT" }, "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/muggle-string": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz", + "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==", + "dev": true, + "license": "MIT" }, "node_modules/nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", "funding": [ { "type": "github", "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -6016,61 +7591,32 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true - }, - "node_modules/node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", "dev": true, - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } + "license": "MIT" }, - "node_modules/node-fetch/node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "dev": true - }, - "node_modules/node-fetch/node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "dev": true - }, - "node_modules/node-fetch/node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "node_modules/netmask": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", + "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==", "dev": true, - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" + "license": "MIT", + "engines": { + "node": ">= 0.4.0" } }, "node_modules/node-releases": { - "version": "2.0.13", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", - "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==", - "dev": true + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "dev": true, + "license": "MIT" }, "node_modules/nopt": { "version": "7.2.1", "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.1.tgz", "integrity": "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==", "dev": true, + "license": "ISC", "dependencies": { "abbrev": "^2.0.0" }, @@ -6081,19 +7627,93 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/normalize-package-data": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", - "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==", + "node_modules/npm-normalize-package-bin": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-4.0.0.tgz", + "integrity": "sha512-TZKxPvItzai9kN9H/TkmCtx/ZN/hvr3vUycjlfmH0ootY9yFBzNOpiXAdIn1Iteqsvk4lQn6B5PTrt+n6h8k/w==", "dev": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm-run-all2": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/npm-run-all2/-/npm-run-all2-8.0.4.tgz", + "integrity": "sha512-wdbB5My48XKp2ZfJUlhnLVihzeuA1hgBnqB2J9ahV77wLS+/YAJAlN8I+X3DIFIPZ3m5L7nplmlbhNiFDmXRDA==", + "dev": true, + "license": "MIT", "dependencies": { - "hosted-git-info": "^4.0.1", - "is-core-module": "^2.5.0", - "semver": "^7.3.4", - "validate-npm-package-license": "^3.0.1" + "ansi-styles": "^6.2.1", + "cross-spawn": "^7.0.6", + "memorystream": "^0.3.1", + "picomatch": "^4.0.2", + "pidtree": "^0.6.0", + "read-package-json-fast": "^4.0.0", + "shell-quote": "^1.7.3", + "which": "^5.0.0" + }, + "bin": { + "npm-run-all": "bin/npm-run-all/index.js", + "npm-run-all2": "bin/npm-run-all/index.js", + "run-p": "bin/run-p/index.js", + "run-s": "bin/run-s/index.js" }, "engines": { - "node": ">=10" + "node": "^20.5.0 || >=22.0.0", + "npm": ">= 10" + } + }, + "node_modules/npm-run-all2/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/npm-run-all2/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16" + } + }, + "node_modules/npm-run-all2/node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/npm-run-all2/node_modules/which": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", + "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/nth-check": { @@ -6101,6 +7721,7 @@ "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "boolbase": "^1.0.0" }, @@ -6109,16 +7730,21 @@ } }, "node_modules/nwsapi": { - "version": "2.2.6", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.6.tgz", - "integrity": "sha512-vSZ4miHQ4FojLjmz2+ux4B0/XA16jfwt/LBzIUftDpRd8tujHFkXjMyLwjS08fIZCzesj2z7gJukOKJwqebJAQ==", - "dev": true + "version": "2.2.20", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.20.tgz", + "integrity": "sha512-/ieB+mDe4MrrKMT8z+mQL8klXydZWGR5Dowt4RAGKbJ3kIGEx3X4ljUo+6V73IXtUPWgfOlU5B9MlGxFO5T+cA==", + "dev": true, + "license": "MIT" }, "node_modules/object-inspect": { - "version": "1.12.3", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", - "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -6128,19 +7754,23 @@ "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.4" } }, "node_modules/object.assign": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", - "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "has-symbols": "^1.0.3", + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", "object-keys": "^1.1.1" }, "engines": { @@ -6155,32 +7785,53 @@ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", "dev": true, + "license": "ISC", "dependencies": { "wrappy": "1" } }, "node_modules/optionator": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", - "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", "dev": true, + "license": "MIT", "dependencies": { - "@aashutoshrathi/word-wrap": "^1.2.3", "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", - "type-check": "^0.4.0" + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" }, "engines": { "node": ">= 0.8.0" } }, + "node_modules/own-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", + "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.6", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, + "license": "MIT", "dependencies": { "yocto-queue": "^0.1.0" }, @@ -6196,6 +7847,7 @@ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, + "license": "MIT", "dependencies": { "p-limit": "^3.0.2" }, @@ -6206,20 +7858,53 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "node_modules/pac-proxy-agent": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz", + "integrity": "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==", "dev": true, + "license": "MIT", + "dependencies": { + "@tootallnate/quickjs-emscripten": "^0.23.0", + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "get-uri": "^6.0.1", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.6", + "pac-resolver": "^7.0.1", + "socks-proxy-agent": "^8.0.5" + }, "engines": { - "node": ">=6" + "node": ">= 14" } }, + "node_modules/pac-resolver": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz", + "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", + "dev": true, + "license": "MIT", + "dependencies": { + "degenerator": "^5.0.0", + "netmask": "^2.0.2" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "dev": true, + "license": "MIT", "dependencies": { "callsites": "^3.0.0" }, @@ -6227,54 +7912,59 @@ "node": ">=6" } }, - "node_modules/parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, - "engines": { - "node": ">=8" + "entities": "^6.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/inikulin/parse5?sponsor=1" } }, - "node_modules/parse5": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", - "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz", + "integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==", "dev": true, + "license": "MIT", "dependencies": { - "entities": "^4.4.0" + "domhandler": "^5.0.3", + "parse5": "^7.0.0" }, "funding": { "url": "https://github.com/inikulin/parse5?sponsor=1" } }, - "node_modules/parse5-htmlparser2-tree-adapter": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.0.0.tgz", - "integrity": "sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==", + "node_modules/parse5-parser-stream": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz", + "integrity": "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==", "dev": true, + "license": "MIT", "dependencies": { - "domhandler": "^5.0.2", "parse5": "^7.0.0" }, "funding": { "url": "https://github.com/inikulin/parse5?sponsor=1" } }, + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "dev": true, + "license": "MIT" + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -6284,6 +7974,7 @@ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -6293,6 +7984,7 @@ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -6301,13 +7993,15 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/path-scurry": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" @@ -6319,46 +8013,42 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/path-scurry/node_modules/lru-cache": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz", - "integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==", - "dev": true, - "engines": { - "node": "14 || >=16.14" - } - }, "node_modules/pathe": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.1.tgz", - "integrity": "sha512-d+RQGp0MAYTIaDBIMmOfMwz3E+LOZnxx1HZd5R18mmCZY0QBlK0LDZfPc8FW8Ed2DlvsuE6PRjroDY+wg4+j/Q==", - "dev": true + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" }, "node_modules/pathval": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", - "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz", + "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==", "dev": true, + "license": "MIT", "engines": { - "node": "*" + "node": ">= 14.16" } }, "node_modules/pend": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" }, "node_modules/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true, + "license": "MIT", "engines": { "node": ">=8.6" }, @@ -6366,115 +8056,65 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pkg-dir/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pkg-dir/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pkg-dir/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/pkg-dir/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "node_modules/pidtree": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.0.tgz", + "integrity": "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==", "dev": true, - "dependencies": { - "p-limit": "^2.2.0" + "license": "MIT", + "bin": { + "pidtree": "bin/pidtree.js" }, "engines": { - "node": ">=8" - } - }, - "node_modules/pkg-types": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.0.3.tgz", - "integrity": "sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==", - "dev": true, - "dependencies": { - "jsonc-parser": "^3.2.0", - "mlly": "^1.2.0", - "pathe": "^1.1.0" + "node": ">=0.10" } }, "node_modules/playwright": { - "version": "1.44.0", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.44.0.tgz", - "integrity": "sha512-F9b3GUCLQ3Nffrfb6dunPOkE5Mh68tR7zN32L4jCk4FjQamgesGay7/dAAe1WaMEGV04DkdJfcJzjoCKygUaRQ==", + "version": "1.53.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.53.1.tgz", + "integrity": "sha512-LJ13YLr/ocweuwxyGf1XNFWIU4M2zUSo149Qbp+A4cpwDjsxRPj7k6H25LBrEHiEwxvRbD8HdwvQmRMSvquhYw==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "playwright-core": "1.44.0" + "playwright-core": "1.53.1" }, "bin": { "playwright": "cli.js" }, "engines": { - "node": ">=16" + "node": ">=18" }, "optionalDependencies": { "fsevents": "2.3.2" } }, "node_modules/playwright-core": { - "version": "1.44.0", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.44.0.tgz", - "integrity": "sha512-ZTbkNpFfYcGWohvTTl+xewITm7EOuqIqex0c7dNZ+aXsbrLj0qI8XlGKfPpipjm0Wny/4Lt4CJsWJk1stVS5qQ==", + "version": "1.53.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.53.1.tgz", + "integrity": "sha512-Z46Oq7tLAyT0lGoFx4DOuB1IA9D1TPj0QkYxpPVUnGDqHHvDpCftu1J2hM2PiWsNMoZh8+LQaarAWcDfPBc6zg==", "dev": true, + "license": "Apache-2.0", "bin": { "playwright-core": "cli.js" }, "engines": { - "node": ">=16" + "node": ">=18" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" } }, "node_modules/postcss": { - "version": "8.4.38", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", - "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", "funding": [ { "type": "opencollective", @@ -6489,20 +8129,22 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { - "nanoid": "^3.3.7", - "picocolors": "^1.0.0", - "source-map-js": "^1.2.0" + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" }, "engines": { "node": "^10 || ^12 || >=14" } }, "node_modules/postcss-selector-parser": { - "version": "6.0.16", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.16.tgz", - "integrity": "sha512-A0RVJrX+IUkVZbW3ClroRWurercFhieevHB38sr2+l9eUClMqome3LmEmnhlNy+5Mr2EYN6B2Kaw9wYdd+VHiw==", + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", "dev": true, + "license": "MIT", "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -6516,6 +8158,7 @@ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.8.0" } @@ -6525,6 +8168,7 @@ "resolved": "https://registry.npmjs.org/pretty/-/pretty-2.0.0.tgz", "integrity": "sha512-G9xUchgTEiNpormdYBl+Pha50gOUovT18IvAe7EYMZ1/f9W/WWMPRn+xI68yXNMUk3QXHDwo/1wV/4NejVNe1w==", "dev": true, + "license": "MIT", "dependencies": { "condense-newlines": "^0.2.1", "extend-shallow": "^2.0.1", @@ -6539,6 +8183,7 @@ "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-6.1.1.tgz", "integrity": "sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ==", "dev": true, + "license": "MIT", "engines": { "node": "^14.13.1 || >=16.0.0" }, @@ -6546,37 +8191,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/pretty-format": { - "version": "29.6.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.0.tgz", - "integrity": "sha512-XH+D4n7Ey0iSR6PdAnBs99cWMZdGsdKrR33iUHQNr79w1szKTCIZDVdXuccAsHVwDBp0XeWPfNEoaxP9EZgRmQ==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.6.0", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/progress": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.4.0" } @@ -6585,118 +8205,136 @@ "version": "1.2.4", "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", - "dev": true + "dev": true, + "license": "ISC" + }, + "node_modules/proxy-agent": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.5.0.tgz", + "integrity": "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.1", + "https-proxy-agent": "^7.0.6", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.1.0", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.5" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-agent/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "dev": true - }, - "node_modules/psl": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", - "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", + "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", "dev": true, + "license": "MIT", "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "node_modules/punycode": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", - "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } }, - "node_modules/puppeteer-core": { - "version": "13.7.0", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-13.7.0.tgz", - "integrity": "sha512-rXja4vcnAzFAP1OVLq/5dWNfwBGuzcOARJ6qGV7oAZhnLmVRU8G5MsdeQEAOy332ZhkIOnn9jp15R89LKHyp2Q==", + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", "dev": true, - "dependencies": { - "cross-fetch": "3.1.5", - "debug": "4.3.4", - "devtools-protocol": "0.0.981744", - "extract-zip": "2.0.1", - "https-proxy-agent": "5.0.1", - "pkg-dir": "4.2.0", - "progress": "2.0.3", - "proxy-from-env": "1.1.0", - "rimraf": "3.0.2", - "tar-fs": "2.1.1", - "unbzip2-stream": "1.4.3", - "ws": "8.5.0" - }, + "license": "MIT", "engines": { - "node": ">=10.18.1" + "node": ">=6" } }, - "node_modules/puppeteer-core/node_modules/ws": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.5.0.tgz", - "integrity": "sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg==", + "node_modules/puppeteer-core": { + "version": "24.10.2", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-24.10.2.tgz", + "integrity": "sha512-CnzhOgrZj8DvkDqI+Yx+9or33i3Y9uUYbKyYpP4C13jWwXx/keQ38RMTMmxuLCWQlxjZrOH0Foq7P2fGP7adDQ==", "dev": true, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" + "license": "Apache-2.0", + "dependencies": { + "@puppeteer/browsers": "2.10.5", + "chromium-bidi": "5.1.0", + "debug": "^4.4.1", + "devtools-protocol": "0.0.1452169", + "typed-query-selector": "^2.12.0", + "ws": "^8.18.2" }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } + "engines": { + "node": ">=18" } }, "node_modules/pwa-asset-generator": { - "version": "6.3.2", - "resolved": "https://registry.npmjs.org/pwa-asset-generator/-/pwa-asset-generator-6.3.2.tgz", - "integrity": "sha512-mLdzO3+R79/t/j0Ng6thZR8JDsJug/4jAXeOpgV40uEQb0ZSOFT6d6AYsSCRs0Y4eI8ywjnrkXgz/qxbVLvXJw==", + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/pwa-asset-generator/-/pwa-asset-generator-8.0.5.tgz", + "integrity": "sha512-to2GpqQBC2IYzDHgKvIpa79hn53NumtUCzZdWlx9fLJzJalM7PfCcHR1hus/xJZzbT62h4Eo0diBBzZSpESyjg==", "dev": true, + "license": "MIT", "dependencies": { - "chalk": "^4.1.2", - "cheerio": "1.0.0-rc.12", - "chrome-launcher": "^0.15.0", - "find-process": "^1.4.7", - "lodash.isequal": "^4.5.0", + "chalk": "^5.4.1", + "cheerio": "^1.0.0", + "chrome-launcher": "^1.1.2", + "find-process": "^1.4.10", "lodash.uniqwith": "^4.5.0", - "meow": "^9.0.0", - "mime-types": "^2.1.35", + "meow": "^13.2.0", + "mime-types": "^3.0.1", "pretty": "^2.0.0", - "progress": "^2.0.3", - "puppeteer-core": "^13.5.2", - "slash": "^3.0.0" + "puppeteer-core": "^24.4.0", + "slash": "^5.1.0" }, "bin": { - "pwa-asset-generator": "bin/cli" + "pwa-asset-generator": "bin/cli.js" }, "engines": { - "node": ">=10.12.0" + "node": ">=18" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/pwa-asset-generator" } }, - "node_modules/querystringify": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", - "dev": true + "node_modules/pwa-asset-generator/node_modules/chalk": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", + "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } }, "node_modules/queue-microtask": { "version": "1.2.3", @@ -6716,199 +8354,69 @@ "type": "consulting", "url": "https://feross.org/support" } - ] - }, - "node_modules/quick-lru": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", - "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", - "dev": true, - "engines": { - "node": ">=8" - } + ], + "license": "MIT" }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", "dev": true, + "license": "MIT", "dependencies": { "safe-buffer": "^5.1.0" } }, - "node_modules/react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true - }, - "node_modules/read-pkg": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", - "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", - "dev": true, - "dependencies": { - "@types/normalize-package-data": "^2.4.0", - "normalize-package-data": "^2.5.0", - "parse-json": "^5.0.0", - "type-fest": "^0.6.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/read-pkg-up": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", - "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", - "dev": true, - "dependencies": { - "find-up": "^4.1.0", - "read-pkg": "^5.2.0", - "type-fest": "^0.8.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/read-pkg-up/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/read-pkg-up/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/read-pkg-up/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/read-pkg-up/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/read-pkg-up/node_modules/type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/read-pkg/node_modules/hosted-git-info": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", - "dev": true - }, - "node_modules/read-pkg/node_modules/normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "dev": true, - "dependencies": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "node_modules/read-pkg/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true, - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/read-pkg/node_modules/type-fest": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", - "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "node_modules/read-package-json-fast": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/read-package-json-fast/-/read-package-json-fast-4.0.0.tgz", + "integrity": "sha512-qpt8EwugBWDw2cgE2W+/3oxC+KTez2uSVR8JU9Q36TXPAGCaozfQUs59v4j4GFpWTaw0i6hAZSvOmu1J0uOEUg==", "dev": true, + "license": "ISC", "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" + "json-parse-even-better-errors": "^4.0.0", + "npm-normalize-package-bin": "^4.0.0" }, "engines": { - "node": ">= 6" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/redent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", - "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "node_modules/reflect.getprototypeof": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", "dev": true, + "license": "MIT", "dependencies": { - "indent-string": "^4.0.0", - "strip-indent": "^3.0.0" + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" }, "engines": { - "node": ">=8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/regenerate-unicode-properties": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.0.tgz", - "integrity": "sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ==", + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.0.tgz", + "integrity": "sha512-DqHn3DwbmmPVzeKj9woBadqmXxLvQoQIwu7nopMc72ztvxVmVk2SBhSnx67zuye5TP+lJsb/TBQsjLKhnDf3MA==", "dev": true, + "license": "MIT", "dependencies": { "regenerate": "^1.4.2" }, @@ -6916,30 +8424,19 @@ "node": ">=4" } }, - "node_modules/regenerator-runtime": { - "version": "0.13.11", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", - "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", - "dev": true - }, - "node_modules/regenerator-transform": { - "version": "0.15.1", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.1.tgz", - "integrity": "sha512-knzmNAcuyxV+gQCufkYcvOqX/qIIfHLv0u5x79kRxuGojfYVky1f15TzZEu2Avte8QGepvUNTnLskf8E6X6Vyg==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.8.4" - } - }, "node_modules/regexp.prototype.flags": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz", - "integrity": "sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==", + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "functions-have-names": "^1.2.3" + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" }, "engines": { "node": ">= 0.4" @@ -6949,15 +8446,16 @@ } }, "node_modules/regexpu-core": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.3.2.tgz", - "integrity": "sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.2.0.tgz", + "integrity": "sha512-H66BPQMrv+V16t8xtmq+UC0CBpiTBA60V8ibS1QVReIp8T1z8hwFxqcGzm9K6lgsN7sB5edVH8a+ze6Fqm4weA==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/regjsgen": "^0.8.0", "regenerate": "^1.4.2", - "regenerate-unicode-properties": "^10.1.0", - "regjsparser": "^0.9.1", + "regenerate-unicode-properties": "^10.2.0", + "regjsgen": "^0.8.0", + "regjsparser": "^0.12.0", "unicode-match-property-ecmascript": "^2.0.0", "unicode-match-property-value-ecmascript": "^2.1.0" }, @@ -6965,25 +8463,47 @@ "node": ">=4" } }, + "node_modules/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==", + "dev": true, + "license": "MIT" + }, "node_modules/regjsparser": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", - "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.12.0.tgz", + "integrity": "sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "jsesc": "~0.5.0" + "jsesc": "~3.0.2" }, "bin": { "regjsparser": "bin/parser" } }, "node_modules/regjsparser/node_modules/jsesc": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", + "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": ">=6" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" } }, "node_modules/require-from-string": { @@ -6991,29 +8511,28 @@ "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } }, - "node_modules/requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", - "dev": true - }, "node_modules/resolve": { - "version": "1.22.2", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", - "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", "dev": true, + "license": "MIT", "dependencies": { - "is-core-module": "^2.11.0", + "is-core-module": "^2.16.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -7023,56 +8542,68 @@ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true, + "license": "MIT", "engines": { "node": ">=4" } }, "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", "dev": true, + "license": "MIT", "engines": { "iojs": ">=1.0.0", "node": ">=0.10.0" } }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "node_modules/rollup": { + "version": "4.44.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.44.0.tgz", + "integrity": "sha512-qHcdEzLCiktQIfwBq420pn2dP+30uzqYxv9ETm91wdt2R9AFcWfjNAmje4NWlnCIQ5RMTzVf0ZyisOKqHR6RwA==", "dev": true, + "license": "MIT", "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" + "@types/estree": "1.0.8" }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rollup": { - "version": "3.29.4", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.4.tgz", - "integrity": "sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==", - "dev": true, "bin": { "rollup": "dist/bin/rollup" }, "engines": { - "node": ">=14.18.0", + "node": ">=18.0.0", "npm": ">=8.0.0" }, "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.44.0", + "@rollup/rollup-android-arm64": "4.44.0", + "@rollup/rollup-darwin-arm64": "4.44.0", + "@rollup/rollup-darwin-x64": "4.44.0", + "@rollup/rollup-freebsd-arm64": "4.44.0", + "@rollup/rollup-freebsd-x64": "4.44.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.44.0", + "@rollup/rollup-linux-arm-musleabihf": "4.44.0", + "@rollup/rollup-linux-arm64-gnu": "4.44.0", + "@rollup/rollup-linux-arm64-musl": "4.44.0", + "@rollup/rollup-linux-loongarch64-gnu": "4.44.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.44.0", + "@rollup/rollup-linux-riscv64-gnu": "4.44.0", + "@rollup/rollup-linux-riscv64-musl": "4.44.0", + "@rollup/rollup-linux-s390x-gnu": "4.44.0", + "@rollup/rollup-linux-x64-gnu": "4.44.0", + "@rollup/rollup-linux-x64-musl": "4.44.0", + "@rollup/rollup-win32-arm64-msvc": "4.44.0", + "@rollup/rollup-win32-ia32-msvc": "4.44.0", + "@rollup/rollup-win32-x64-msvc": "4.44.0", "fsevents": "~2.3.2" } }, "node_modules/rrweb-cssom": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.6.0.tgz", - "integrity": "sha512-APM0Gt1KoXBz0iIkkdB/kfvGOwC4UuJFeG/c+yV7wSc7q96cG/kJ0HiYCnzivD9SB53cLV1MlHFNfOuPaadYSw==", - "dev": true + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", + "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==", + "dev": true, + "license": "MIT" }, "node_modules/run-parallel": { "version": "1.2.0", @@ -7093,10 +8624,31 @@ "url": "https://feross.org/support" } ], + "license": "MIT", "dependencies": { "queue-microtask": "^1.2.2" } }, + "node_modules/safe-array-concat": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", + "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -7115,17 +8667,39 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ], + "license": "MIT" }, - "node_modules/safe-regex-test": { + "node_modules/safe-push-apply": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", - "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", + "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.3", - "is-regex": "^1.1.4" + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -7135,13 +8709,15 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/saxes": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", "dev": true, + "license": "ISC", "dependencies": { "xmlchars": "^2.2.0" }, @@ -7150,10 +8726,11 @@ } }, "node_modules/semver": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", - "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", "dev": true, + "license": "ISC", "bin": { "semver": "bin/semver.js" }, @@ -7162,19 +8739,70 @@ } }, "node_modules/serialize-javascript": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", - "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "randombytes": "^2.1.0" } }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, + "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" }, @@ -7187,19 +8815,95 @@ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } }, + "node_modules/shell-quote": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", + "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -7209,13 +8913,15 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/signal-exit": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "dev": true, + "license": "ISC", "engines": { "node": ">=14" }, @@ -7224,12 +8930,64 @@ } }, "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", + "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", "dev": true, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/smob": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/smob/-/smob-1.5.0.tgz", + "integrity": "sha512-g6T+p7QO8npa+/hNx9ohv1E5pVCmWrVCUzUXJyLdMmftX6ER0oiWY/w9knEonLpnOp6b6FenKnMfR8gqwWdwig==", + "dev": true, + "license": "MIT" + }, + "node_modules/socks": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.5.tgz", + "integrity": "sha512-iF+tNDQla22geJdTyJB1wM/qrX9DMRwWrciEPwWLPRWAUEM8sQiyxgckLxWT1f7+9VabJS0jTGGr4QgBuvi6Ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "ip-address": "^9.0.5", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", + "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 14" } }, "node_modules/source-map": { @@ -7237,6 +8995,7 @@ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.8.0-beta.0.tgz", "integrity": "sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "whatwg-url": "^7.0.0" }, @@ -7245,9 +9004,10 @@ } }, "node_modules/source-map-js": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", - "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } @@ -7257,6 +9017,7 @@ "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", "dev": true, + "license": "MIT", "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" @@ -7267,6 +9028,7 @@ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, + "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } @@ -7276,6 +9038,7 @@ "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", "integrity": "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==", "dev": true, + "license": "MIT", "dependencies": { "punycode": "^2.1.0" } @@ -7284,13 +9047,15 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", - "dev": true + "dev": true, + "license": "BSD-2-Clause" }, "node_modules/source-map/node_modules/whatwg-url": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", "dev": true, + "license": "MIT", "dependencies": { "lodash.sortby": "^4.7.0", "tr46": "^1.0.1", @@ -7302,59 +9067,56 @@ "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", "deprecated": "Please use @jridgewell/sourcemap-codec instead", - "dev": true - }, - "node_modules/spdx-correct": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", - "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", "dev": true, - "dependencies": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-exceptions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", - "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", - "dev": true + "license": "MIT" }, - "node_modules/spdx-expression-parse": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", "dev": true, - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-license-ids": { - "version": "3.0.16", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.16.tgz", - "integrity": "sha512-eWN+LnM3GR6gPu35WxNgbGl8rmY1AEmoMDvL/QD6zYmPWgywxWqJWNdLGT+ke8dKNWrcYgYjPpG5gbTfghP8rw==", - "dev": true + "license": "BSD-3-Clause" }, "node_modules/stackback": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/std-env": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.3.3.tgz", - "integrity": "sha512-Rz6yejtVyWnVjC1RFvNmYL10kgjC49EOghxWn0RFqlCHGFpQx+Xe7yW3I4ceK1SGrWIGMjD5Kbue8W/udkbMJg==", - "dev": true + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.9.0.tgz", + "integrity": "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==", + "dev": true, + "license": "MIT" }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "node_modules/stop-iteration-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", + "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "internal-slot": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/streamx": { + "version": "2.22.1", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.22.1.tgz", + "integrity": "sha512-znKXEBxfatz2GBNK02kRnCXjV+AA4kjZIUxeWSr3UGirZMJfTE9uiwKHobnbgxWyL/JWro8tTq+vOqAK1/qbSA==", "dev": true, + "license": "MIT", "dependencies": { - "safe-buffer": "~5.2.0" + "fast-fifo": "^1.3.2", + "text-decoder": "^1.1.0" + }, + "optionalDependencies": { + "bare-events": "^2.2.0" } }, "node_modules/string-width": { @@ -7362,6 +9124,7 @@ "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", "dev": true, + "license": "MIT", "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", @@ -7380,6 +9143,7 @@ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, + "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -7389,67 +9153,78 @@ "node": ">=8" } }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/string-width-cjs/node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "node_modules/string-width/node_modules/ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } + "license": "MIT" }, - "node_modules/string-width/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, + "license": "MIT", "dependencies": { - "ansi-regex": "^6.0.1" + "ansi-regex": "^5.0.1" }, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" + "node": ">=8" } }, "node_modules/string.prototype.matchall": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.8.tgz", - "integrity": "sha512-6zOCOcJ+RJAQshcTvXPHoxoQGONa3e/Lqx90wUA+wEzX78sg5Bo+1tQo4N0pohS0erG9qtCqJDjNCQBjeWVxyg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", - "get-intrinsic": "^1.1.3", - "has-symbols": "^1.0.3", - "internal-slot": "^1.0.3", - "regexp.prototype.flags": "^1.4.3", - "side-channel": "^1.0.4" + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", + "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.6", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "regexp.prototype.flags": "^1.5.3", + "set-function-name": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/string.prototype.trim": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.7.tgz", - "integrity": "sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg==", + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", + "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-object-atoms": "^1.0.0", + "has-property-descriptors": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -7459,28 +9234,37 @@ } }, "node_modules/string.prototype.trimend": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz", - "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==", + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", + "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/string.prototype.trimstart": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz", - "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -7491,6 +9275,7 @@ "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz", "integrity": "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "get-own-enumerable-property-symbols": "^3.0.0", "is-obj": "^1.0.1", @@ -7501,15 +9286,19 @@ } }, "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", "dev": true, + "license": "MIT", "dependencies": { - "ansi-regex": "^5.0.1" + "ansi-regex": "^6.0.1" }, "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, "node_modules/strip-ansi-cjs": { @@ -7518,6 +9307,7 @@ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, + "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" }, @@ -7525,32 +9315,32 @@ "node": ">=8" } }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/strip-comments": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/strip-comments/-/strip-comments-2.0.1.tgz", "integrity": "sha512-ZprKx+bBLXv067WTCALv8SSz5l2+XhpYCsVtSqlMnkAXMWDq+/ekVbl1ghqP9rUHTzv6sm/DwCOiYutU/yp1fw==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" } }, - "node_modules/strip-indent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", - "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", - "dev": true, - "dependencies": { - "min-indent": "^1.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" }, @@ -7559,12 +9349,13 @@ } }, "node_modules/strip-literal": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-1.0.1.tgz", - "integrity": "sha512-QZTsipNpa2Ppr6v1AmJHESqJ3Uz247MUS0OjrnnZjFAvEoWqxuyFuXn2xLgMtRnijJShAa1HL0gtJyUs7u7n3Q==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.0.0.tgz", + "integrity": "sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==", "dev": true, + "license": "MIT", "dependencies": { - "acorn": "^8.8.2" + "js-tokens": "^9.0.1" }, "funding": { "url": "https://github.com/sponsors/antfu" @@ -7575,6 +9366,7 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, + "license": "MIT", "dependencies": { "has-flag": "^4.0.0" }, @@ -7587,6 +9379,7 @@ "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -7598,34 +9391,34 @@ "version": "3.2.4", "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/tar-fs": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", - "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.10.tgz", + "integrity": "sha512-C1SwlQGNLe/jPNqapK8epDsXME7CAJR5RL3GcE6KWx1d9OUByzoHVcbu1VPI8tevg9H8Alae0AApHHFGzrD5zA==", "dev": true, + "license": "MIT", "dependencies": { - "chownr": "^1.1.1", - "mkdirp-classic": "^0.5.2", "pump": "^3.0.0", - "tar-stream": "^2.1.4" + "tar-stream": "^3.1.5" + }, + "optionalDependencies": { + "bare-fs": "^4.0.1", + "bare-path": "^3.0.0" } }, "node_modules/tar-stream": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", + "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", "dev": true, + "license": "MIT", "dependencies": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - }, - "engines": { - "node": ">=6" + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" } }, "node_modules/temp-dir": { @@ -7633,6 +9426,7 @@ "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz", "integrity": "sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -7642,6 +9436,7 @@ "resolved": "https://registry.npmjs.org/tempy/-/tempy-0.6.0.tgz", "integrity": "sha512-G13vtMYPT/J8A4X2SjdtBTphZlrp1gKv6hZiOjw14RCWg6GbHuQBGtjlx75xLbYV/wEc0D7G5K4rxKP/cXk8Bw==", "dev": true, + "license": "MIT", "dependencies": { "is-stream": "^2.0.0", "temp-dir": "^2.0.0", @@ -7655,26 +9450,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/tempy/node_modules/type-fest": { - "version": "0.16.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.16.0.tgz", - "integrity": "sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/terser": { - "version": "5.19.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.19.0.tgz", - "integrity": "sha512-JpcpGOQLOXm2jsomozdMDpd5f8ZHh1rR48OFgWUH3QsyZcfPgv2qDCYbcDEAYNd4OZRj2bWYKpwdll/udZCk/Q==", + "version": "5.43.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.43.1.tgz", + "integrity": "sha512-+6erLbBm0+LROX2sPXlUYx/ux5PyE9K/a92Wrt6oA+WDAoFTdpHE5tCYCI5PNzq2y8df4rA+QgHLJuR4jNymsg==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "@jridgewell/source-map": "^0.3.3", - "acorn": "^8.8.2", + "acorn": "^8.14.0", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, @@ -7685,56 +9469,138 @@ "node": ">=10" } }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true + "node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true, + "license": "MIT" }, - "node_modules/through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", - "dev": true + "node_modules/text-decoder": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz", + "integrity": "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "b4a": "^1.6.4" + } }, "node_modules/tinybench": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.5.0.tgz", - "integrity": "sha512-kRwSG8Zx4tjF9ZiyH4bhaebu+EDz1BOx9hOigYHlUW4xxI/wKIUQUqo018UlU4ar6ATPBsaMrdbKZ+tmPdohFA==", - "dev": true + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", + "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.4.4", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.4.6", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", + "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } }, "node_modules/tinypool": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.5.0.tgz", - "integrity": "sha512-paHQtnrlS1QZYKF/GnLoOM/DN9fqaGOFbCbxzAhwniySnzl9Ebk8w73/dd34DAhe/obUbPAOldTyYXQZxnPBPQ==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", + "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", + "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", + "dev": true, + "license": "MIT", "engines": { "node": ">=14.0.0" } }, "node_modules/tinyspy": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-2.1.1.tgz", - "integrity": "sha512-XPJL2uSzcOyBMky6OFrusqWlzfFrXtE0hPuMgW8A2HmaqrPo4ZQHRN/V0QXN3FSjKxpsbRrFc5LI7KOwBsT1/w==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.3.tgz", + "integrity": "sha512-t2T/WLB2WRgZ9EpE4jgPJ9w+i66UZfDc8wHh0xrwiRNN+UwH98GIJkTeZqX9rg0i0ptwzqW+uYeIF0T4F8LR7A==", "dev": true, + "license": "MIT", "engines": { "node": ">=14.0.0" } }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "node_modules/tldts": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz", + "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==", "dev": true, - "engines": { - "node": ">=4" + "license": "MIT", + "dependencies": { + "tldts-core": "^6.1.86" + }, + "bin": { + "tldts": "bin/cli.js" } }, + "node_modules/tldts-core": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz", + "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==", + "dev": true, + "license": "MIT" + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, + "license": "MIT", "dependencies": { "is-number": "^7.0.0" }, @@ -7743,46 +9609,57 @@ } }, "node_modules/tough-cookie": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz", - "integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz", + "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { - "psl": "^1.1.33", - "punycode": "^2.1.1", - "universalify": "^0.2.0", - "url-parse": "^1.5.3" + "tldts": "^6.1.32" }, "engines": { - "node": ">=6" + "node": ">=16" } }, "node_modules/tr46": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-4.1.1.tgz", - "integrity": "sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", + "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", "dev": true, + "license": "MIT", "dependencies": { - "punycode": "^2.3.0" + "punycode": "^2.3.1" }, "engines": { - "node": ">=14" + "node": ">=18" } }, - "node_modules/trim-newlines": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", - "integrity": "sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==", + "node_modules/ts-api-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", + "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", "dev": true, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" } }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "dev": true, + "license": "MIT", "dependencies": { "prelude-ls": "^1.2.1" }, @@ -7790,38 +9667,68 @@ "node": ">= 0.8.0" } }, - "node_modules/type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "node_modules/type-fest": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.16.0.tgz", + "integrity": "sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg==", "dev": true, + "license": "(MIT OR CC0-1.0)", "engines": { - "node": ">=4" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, "engines": { - "node": ">=10" + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/typed-array-byte-offset": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.0.tgz", - "integrity": "sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", + "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", "dev": true, + "license": "MIT", "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", "for-each": "^0.3.3", - "has-proto": "^1.0.1", - "is-typed-array": "^1.1.10" + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.9" }, "engines": { "node": ">= 0.4" @@ -7831,61 +9738,119 @@ } }, "node_modules/typed-array-length": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", - "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", + "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", + "call-bind": "^1.0.7", "for-each": "^0.3.3", - "is-typed-array": "^1.1.9" + "gopd": "^1.0.1", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0", + "reflect.getprototypeof": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/ufo": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.1.2.tgz", - "integrity": "sha512-TrY6DsjTQQgyS3E3dBaOXf0TpPD8u9FVrVYmKVegJuFw51n/YB9XPt+U6ydzFG5ZIN7+DIjPbNmXoBj9esYhgQ==", - "dev": true + "node_modules/typed-query-selector": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.0.tgz", + "integrity": "sha512-SbklCd1F0EiZOyPiW192rrHZzZ5sBijB6xM+cpmrwDqObvdtunOHHIk9fCGsoK5JVIYXoyEp4iEdE3upFH3PAg==", + "dev": true, + "license": "MIT" + }, + "node_modules/typescript": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.34.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.34.1.tgz", + "integrity": "sha512-XjS+b6Vg9oT1BaIUfkW3M3LvqZE++rbzAMEHuccCfO/YkP43ha6w3jTEMilQxMF92nVOYCcdjv1ZUhAa1D/0ow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.34.1", + "@typescript-eslint/parser": "8.34.1", + "@typescript-eslint/utils": "8.34.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/uc.micro": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", + "dev": true, + "license": "MIT" }, "node_modules/unbox-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", - "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", + "call-bound": "^1.0.3", "has-bigints": "^1.0.2", - "has-symbols": "^1.0.3", - "which-boxed-primitive": "^1.0.2" + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/unbzip2-stream": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", - "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", + "node_modules/undici": { + "version": "7.10.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.10.0.tgz", + "integrity": "sha512-u5otvFBOBZvmdjWLVW+5DAc9Nkq8f24g0O9oY7qw2JVIF1VocIFoyz9JFkuVOS2j41AufeO0xnlweJ2RLT8nGw==", "dev": true, - "dependencies": { - "buffer": "^5.2.1", - "through": "^2.3.8" + "license": "MIT", + "engines": { + "node": ">=20.18.1" } }, "node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "dev": true + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz", + "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==", + "dev": true, + "license": "MIT" }, "node_modules/unicode-canonical-property-names-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", - "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", + "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==", "dev": true, + "license": "MIT", "engines": { "node": ">=4" } @@ -7895,6 +9860,7 @@ "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", "dev": true, + "license": "MIT", "dependencies": { "unicode-canonical-property-names-ecmascript": "^2.0.0", "unicode-property-aliases-ecmascript": "^2.0.0" @@ -7904,10 +9870,11 @@ } }, "node_modules/unicode-match-property-value-ecmascript": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz", - "integrity": "sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.0.tgz", + "integrity": "sha512-4IehN3V/+kkr5YeSSDDQG8QLqO26XpL2XP3GQtqwlT/QYSECAwFztxVHjlbh0+gjJ3XmNLS0zDsbgs9jWKExLg==", "dev": true, + "license": "MIT", "engines": { "node": ">=4" } @@ -7917,6 +9884,7 @@ "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", "dev": true, + "license": "MIT", "engines": { "node": ">=4" } @@ -7926,6 +9894,7 @@ "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", "dev": true, + "license": "MIT", "dependencies": { "crypto-random-string": "^2.0.0" }, @@ -7934,12 +9903,13 @@ } }, "node_modules/universalify": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", - "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", "dev": true, + "license": "MIT", "engines": { - "node": ">= 4.0.0" + "node": ">= 10.0.0" } }, "node_modules/upath": { @@ -7947,15 +9917,16 @@ "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", "dev": true, + "license": "MIT", "engines": { "node": ">=4", "yarn": "*" } }, "node_modules/update-browserslist-db": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz", - "integrity": "sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", "dev": true, "funding": [ { @@ -7971,9 +9942,10 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" + "escalade": "^3.2.0", + "picocolors": "^1.1.1" }, "bin": { "update-browserslist-db": "cli.js" @@ -7987,71 +9959,64 @@ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "punycode": "^2.1.0" } }, - "node_modules/url-parse": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", - "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", - "dev": true, - "dependencies": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" - } - }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true - }, - "node_modules/validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", "dev": true, - "dependencies": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } + "license": "MIT" }, "node_modules/vite": { - "version": "4.5.3", - "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.3.tgz", - "integrity": "sha512-kQL23kMeX92v3ph7IauVkXkikdDRsYMGTVl5KY2E9OY4ONLvkHf04MDTbnfo6NKxZiDLWzVpP5oTa8hQD8U3dg==", + "version": "6.3.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz", + "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==", "dev": true, + "license": "MIT", "dependencies": { - "esbuild": "^0.18.10", - "postcss": "^8.4.27", - "rollup": "^3.27.1" + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" }, "bin": { "vite": "bin/vite.js" }, "engines": { - "node": "^14.18.0 || >=16.0.0" + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" }, "funding": { "url": "https://github.com/vitejs/vite?sponsor=1" }, "optionalDependencies": { - "fsevents": "~2.3.2" + "fsevents": "~2.3.3" }, "peerDependencies": { - "@types/node": ">= 14", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", + "sass-embedded": "*", "stylus": "*", "sugarss": "*", - "terser": "^5.4.0" + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" }, "peerDependenciesMeta": { "@types/node": { "optional": true }, + "jiti": { + "optional": true + }, "less": { "optional": true }, @@ -8061,6 +10026,9 @@ "sass": { "optional": true }, + "sass-embedded": { + "optional": true + }, "stylus": { "optional": true }, @@ -8069,43 +10037,50 @@ }, "terser": { "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true } } }, "node_modules/vite-node": { - "version": "0.32.4", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-0.32.4.tgz", - "integrity": "sha512-L2gIw+dCxO0LK14QnUMoqSYpa9XRGnTTTDjW2h19Mr+GR0EFj4vx52W41gFXfMLqpA00eK4ZjOVYo1Xk//LFEw==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz", + "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==", "dev": true, + "license": "MIT", "dependencies": { "cac": "^6.7.14", - "debug": "^4.3.4", - "mlly": "^1.4.0", - "pathe": "^1.1.1", - "picocolors": "^1.0.0", - "vite": "^3.0.0 || ^4.0.0" + "debug": "^4.4.1", + "es-module-lexer": "^1.7.0", + "pathe": "^2.0.3", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" }, "bin": { "vite-node": "vite-node.mjs" }, "engines": { - "node": ">=v14.18.0" + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/vite-plugin-pwa": { - "version": "0.16.7", - "resolved": "https://registry.npmjs.org/vite-plugin-pwa/-/vite-plugin-pwa-0.16.7.tgz", - "integrity": "sha512-4WMA5unuKlHs+koNoykeuCfTcqEGbiTRr8sVYUQMhc6tWxZpSRnv9Ojk4LKmqVhoPGHfBVCdGaMo8t9Qidkc1Q==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/vite-plugin-pwa/-/vite-plugin-pwa-1.0.0.tgz", + "integrity": "sha512-X77jo0AOd5OcxmWj3WnVti8n7Kw2tBgV1c8MCXFclrSlDV23ePzv2eTDIALXI2Qo6nJ5pZJeZAuX0AawvRfoeA==", "dev": true, + "license": "MIT", "dependencies": { - "debug": "^4.3.4", - "fast-glob": "^3.3.1", + "debug": "^4.3.6", "pretty-bytes": "^6.1.1", - "workbox-build": "^7.0.0", - "workbox-window": "^7.0.0" + "tinyglobby": "^0.2.10", + "workbox-build": "^7.3.0", + "workbox-window": "^7.3.0" }, "engines": { "node": ">=16.0.0" @@ -8114,98 +10089,164 @@ "url": "https://github.com/sponsors/antfu" }, "peerDependencies": { - "vite": "^3.1.0 || ^4.0.0 || ^5.0.0-0", - "workbox-build": "^7.0.0", - "workbox-window": "^7.0.0" + "@vite-pwa/assets-generator": "^1.0.0", + "vite": "^3.1.0 || ^4.0.0 || ^5.0.0 || ^6.0.0", + "workbox-build": "^7.3.0", + "workbox-window": "^7.3.0" + }, + "peerDependenciesMeta": { + "@vite-pwa/assets-generator": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/fdir": { + "version": "6.4.6", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", + "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } } }, - "node_modules/vitest": { - "version": "0.32.4", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-0.32.4.tgz", - "integrity": "sha512-3czFm8RnrsWwIzVDu/Ca48Y/M+qh3vOnF16czJm98Q/AN1y3B6PBsyV8Re91Ty5s7txKNjEhpgtGPcfdbh2MZg==", + "node_modules/vite/node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, - "dependencies": { - "@types/chai": "^4.3.5", - "@types/chai-subset": "^1.3.3", - "@types/node": "*", - "@vitest/expect": "0.32.4", - "@vitest/runner": "0.32.4", - "@vitest/snapshot": "0.32.4", - "@vitest/spy": "0.32.4", - "@vitest/utils": "0.32.4", - "acorn": "^8.9.0", - "acorn-walk": "^8.2.0", - "cac": "^6.7.14", - "chai": "^4.3.7", - "debug": "^4.3.4", - "local-pkg": "^0.4.3", - "magic-string": "^0.30.0", - "pathe": "^1.1.1", - "picocolors": "^1.0.0", - "std-env": "^3.3.3", - "strip-literal": "^1.0.1", - "tinybench": "^2.5.0", - "tinypool": "^0.5.0", - "vite": "^3.0.0 || ^4.0.0", - "vite-node": "0.32.4", - "why-is-node-running": "^2.2.2" + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/vite/node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/vitest": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz", + "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/expect": "3.2.4", + "@vitest/mocker": "3.2.4", + "@vitest/pretty-format": "^3.2.4", + "@vitest/runner": "3.2.4", + "@vitest/snapshot": "3.2.4", + "@vitest/spy": "3.2.4", + "@vitest/utils": "3.2.4", + "chai": "^5.2.0", + "debug": "^4.4.1", + "expect-type": "^1.2.1", + "magic-string": "^0.30.17", + "pathe": "^2.0.3", + "picomatch": "^4.0.2", + "std-env": "^3.9.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.14", + "tinypool": "^1.1.1", + "tinyrainbow": "^2.0.0", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", + "vite-node": "3.2.4", + "why-is-node-running": "^2.3.0" }, "bin": { "vitest": "vitest.mjs" }, "engines": { - "node": ">=v14.18.0" + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { "@edge-runtime/vm": "*", - "@vitest/browser": "*", - "@vitest/ui": "*", + "@types/debug": "^4.1.12", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@vitest/browser": "3.2.4", + "@vitest/ui": "3.2.4", "happy-dom": "*", - "jsdom": "*", - "playwright": "*", - "safaridriver": "*", - "webdriverio": "*" + "jsdom": "*" }, "peerDependenciesMeta": { "@edge-runtime/vm": { "optional": true }, - "@vitest/browser": { - "optional": true - }, - "@vitest/ui": { + "@types/debug": { "optional": true }, - "happy-dom": { + "@types/node": { "optional": true }, - "jsdom": { + "@vitest/browser": { "optional": true }, - "playwright": { + "@vitest/ui": { "optional": true }, - "safaridriver": { + "happy-dom": { "optional": true }, - "webdriverio": { + "jsdom": { "optional": true } } }, + "node_modules/vitest/node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/vscode-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz", + "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==", + "dev": true, + "license": "MIT" + }, "node_modules/vue": { - "version": "3.4.27", - "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.27.tgz", - "integrity": "sha512-8s/56uK6r01r1icG/aEOHqyMVxd1bkYcSe9j8HcKtr/xTOFWvnzIVTehNW+5Yt89f+DLBe4A569pnZLS5HzAMA==", + "version": "3.5.17", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.17.tgz", + "integrity": "sha512-LbHV3xPN9BeljML+Xctq4lbz2lVHCR6DtbpTf5XIO6gugpXUN49j2QQPcMj086r9+AkJ0FfUT8xjulKKBkkr9g==", + "license": "MIT", "dependencies": { - "@vue/compiler-dom": "3.4.27", - "@vue/compiler-sfc": "3.4.27", - "@vue/runtime-dom": "3.4.27", - "@vue/server-renderer": "3.4.27", - "@vue/shared": "3.4.27" + "@vue/compiler-dom": "3.5.17", + "@vue/compiler-sfc": "3.5.17", + "@vue/runtime-dom": "3.5.17", + "@vue/server-renderer": "3.5.17", + "@vue/shared": "3.5.17" }, "peerDependencies": { "typescript": "*" @@ -8217,50 +10258,67 @@ } }, "node_modules/vue-component-type-helpers": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/vue-component-type-helpers/-/vue-component-type-helpers-2.0.19.tgz", - "integrity": "sha512-cN3f1aTxxKo4lzNeQAkVopswuImUrb5Iurll9Gaw5cqpnbTAxtEMM1mgi6ou4X79OCyqYv1U1mzBHJkzmiK82w==", - "dev": true + "version": "2.2.10", + "resolved": "https://registry.npmjs.org/vue-component-type-helpers/-/vue-component-type-helpers-2.2.10.tgz", + "integrity": "sha512-iDUO7uQK+Sab2tYuiP9D1oLujCWlhHELHMgV/cB13cuGbG4qwkLHvtfWb6FzvxrIOPDnU0oHsz2MlQjhYDeaHA==", + "dev": true, + "license": "MIT" }, "node_modules/vue-eslint-parser": { - "version": "9.4.2", - "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.4.2.tgz", - "integrity": "sha512-Ry9oiGmCAK91HrKMtCrKFWmSFWvYkpGglCeFAIqDdr9zdXmMMpJOmUJS7WWsW7fX81h6mwHmUZCQQ1E0PkSwYQ==", + "version": "10.1.3", + "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-10.1.3.tgz", + "integrity": "sha512-dbCBnd2e02dYWsXoqX5yKUZlOt+ExIpq7hmHKPb5ZqKcjf++Eo0hMseFTZMLKThrUk61m+Uv6A2YSBve6ZvuDQ==", "dev": true, + "license": "MIT", "dependencies": { - "debug": "^4.3.4", - "eslint-scope": "^7.1.1", - "eslint-visitor-keys": "^3.3.0", - "espree": "^9.3.1", - "esquery": "^1.4.0", + "debug": "^4.4.0", + "eslint-scope": "^8.2.0", + "eslint-visitor-keys": "^4.2.0", + "espree": "^10.3.0", + "esquery": "^1.6.0", "lodash": "^4.17.21", - "semver": "^7.3.6" + "semver": "^7.6.3" }, "engines": { - "node": "^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://github.com/sponsors/mysticatea" }, "peerDependencies": { - "eslint": ">=6.0.0" + "eslint": "^8.57.0 || ^9.0.0" + } + }, + "node_modules/vue-eslint-parser/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, "node_modules/vue-feather": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/vue-feather/-/vue-feather-2.0.0.tgz", "integrity": "sha512-GBvxJWu2ycGTpB8duYWnc5S/TwWPPb2G5Ft2NbkwK1vZkUDUOTYqIb4Nh1HOL6A37Isfrd0Guun0lesS97PfxA==", + "license": "MIT", "peerDependencies": { "feather-icons": "^4.28.0", "vue": "^3.0.0" } }, "node_modules/vue-router": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.3.2.tgz", - "integrity": "sha512-hKQJ1vDAZ5LVkKEnHhmm1f9pMiWIBNGF5AwU67PdH7TyXCj/a4hTccuUuYCAMgJK6rO/NVYtQIEN3yL8CECa7Q==", + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.5.1.tgz", + "integrity": "sha512-ogAF3P97NPm8fJsE4by9dwSYtDwXIY1nFY9T6DyQnGHd1E2Da94w9JIolpe42LJGIl0DwOHBi8TcRPlPGwbTtw==", + "license": "MIT", "dependencies": { - "@vue/devtools-api": "^6.5.1" + "@vue/devtools-api": "^6.6.4" }, "funding": { "url": "https://github.com/sponsors/posva" @@ -8269,16 +10327,44 @@ "vue": "^3.2.0" } }, + "node_modules/vue-tsc": { + "version": "2.2.10", + "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-2.2.10.tgz", + "integrity": "sha512-jWZ1xSaNbabEV3whpIDMbjVSVawjAyW+x1n3JeGQo7S0uv2n9F/JMgWW90tGWNFRKya4YwKMZgCtr0vRAM7DeQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/typescript": "~2.4.11", + "@vue/language-core": "2.2.10" + }, + "bin": { + "vue-tsc": "bin/vue-tsc.js" + }, + "peerDependencies": { + "typescript": ">=5.0.0" + } + }, "node_modules/w3c-xmlserializer": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz", - "integrity": "sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", "dev": true, + "license": "MIT", "dependencies": { - "xml-name-validator": "^4.0.0" + "xml-name-validator": "^5.0.0" }, "engines": { - "node": ">=14" + "node": ">=18" + } + }, + "node_modules/w3c-xmlserializer/node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" } }, "node_modules/webidl-conversions": { @@ -8286,42 +10372,46 @@ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", "dev": true, + "license": "BSD-2-Clause", "engines": { "node": ">=12" } }, "node_modules/whatwg-encoding": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", - "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", "dev": true, + "license": "MIT", "dependencies": { "iconv-lite": "0.6.3" }, "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/whatwg-mimetype": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", - "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", "dev": true, + "license": "MIT", "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/whatwg-url": { - "version": "12.0.1", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-12.0.1.tgz", - "integrity": "sha512-Ed/LrqB8EPlGxjS+TrsXcpUond1mhccS3pchLhzSgPCnTimUCKj3IZE75pAs5m6heB2U2TMerKFUXheyHY+VDQ==", + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", + "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", "dev": true, + "license": "MIT", "dependencies": { - "tr46": "^4.1.1", + "tr46": "^5.1.0", "webidl-conversions": "^7.0.0" }, "engines": { - "node": ">=14" + "node": ">=18" } }, "node_modules/which": { @@ -8329,6 +10419,7 @@ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, + "license": "ISC", "dependencies": { "isexe": "^2.0.0" }, @@ -8340,33 +10431,86 @@ } }, "node_modules/which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.2.1", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", - "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", "dev": true, + "license": "MIT", "dependencies": { - "is-bigint": "^1.0.1", - "is-boolean-object": "^1.1.0", - "is-number-object": "^1.0.4", - "is-string": "^1.0.5", - "is-symbol": "^1.0.3" + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/which-typed-array": { - "version": "1.1.10", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.10.tgz", - "integrity": "sha512-uxoA5vLUfRPdjCuJ1h5LlYdmTLbYfums398v3WLkM+i/Wltl2/XyZpQWKbN++ck5L64SR/grOHqtXCUKmlZPNA==", + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", + "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", "dev": true, + "license": "MIT", "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0", - "is-typed-array": "^1.1.10" + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -8376,10 +10520,11 @@ } }, "node_modules/why-is-node-running": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.2.2.tgz", - "integrity": "sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", "dev": true, + "license": "MIT", "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" @@ -8391,38 +10536,52 @@ "node": ">=8" } }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/workbox-background-sync": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/workbox-background-sync/-/workbox-background-sync-7.0.0.tgz", - "integrity": "sha512-S+m1+84gjdueM+jIKZ+I0Lx0BDHkk5Nu6a3kTVxP4fdj3gKouRNmhO8H290ybnJTOPfBDtTMXSQA/QLTvr7PeA==", + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/workbox-background-sync/-/workbox-background-sync-7.3.0.tgz", + "integrity": "sha512-PCSk3eK7Mxeuyatb22pcSx9dlgWNv3+M8PqPaYDokks8Y5/FX4soaOqj3yhAZr5k6Q5JWTOMYgaJBpbw11G9Eg==", "dev": true, + "license": "MIT", "dependencies": { "idb": "^7.0.1", - "workbox-core": "7.0.0" + "workbox-core": "7.3.0" } }, "node_modules/workbox-broadcast-update": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/workbox-broadcast-update/-/workbox-broadcast-update-7.0.0.tgz", - "integrity": "sha512-oUuh4jzZrLySOo0tC0WoKiSg90bVAcnE98uW7F8GFiSOXnhogfNDGZelPJa+6KpGBO5+Qelv04Hqx2UD+BJqNQ==", + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/workbox-broadcast-update/-/workbox-broadcast-update-7.3.0.tgz", + "integrity": "sha512-T9/F5VEdJVhwmrIAE+E/kq5at2OY6+OXXgOWQevnubal6sO92Gjo24v6dCVwQiclAF5NS3hlmsifRrpQzZCdUA==", "dev": true, + "license": "MIT", "dependencies": { - "workbox-core": "7.0.0" + "workbox-core": "7.3.0" } }, "node_modules/workbox-build": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/workbox-build/-/workbox-build-7.0.0.tgz", - "integrity": "sha512-CttE7WCYW9sZC+nUYhQg3WzzGPr4IHmrPnjKiu3AMXsiNQKx+l4hHl63WTrnicLmKEKHScWDH8xsGBdrYgtBzg==", + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/workbox-build/-/workbox-build-7.3.0.tgz", + "integrity": "sha512-JGL6vZTPlxnlqZRhR/K/msqg3wKP+m0wfEUVosK7gsYzSgeIxvZLi1ViJJzVL7CEeI8r7rGFV973RiEqkP3lWQ==", "dev": true, + "license": "MIT", "dependencies": { "@apideck/better-ajv-errors": "^0.3.1", - "@babel/core": "^7.11.1", + "@babel/core": "^7.24.4", "@babel/preset-env": "^7.11.0", "@babel/runtime": "^7.11.2", "@rollup/plugin-babel": "^5.2.0", - "@rollup/plugin-node-resolve": "^11.2.1", + "@rollup/plugin-node-resolve": "^15.2.3", "@rollup/plugin-replace": "^2.4.1", + "@rollup/plugin-terser": "^0.4.3", "@surma/rollup-plugin-off-main-thread": "^2.2.3", "ajv": "^8.6.0", "common-tags": "^1.8.0", @@ -8432,27 +10591,26 @@ "lodash": "^4.17.20", "pretty-bytes": "^5.3.0", "rollup": "^2.43.1", - "rollup-plugin-terser": "^7.0.0", "source-map": "^0.8.0-beta.0", "stringify-object": "^3.3.0", "strip-comments": "^2.0.1", "tempy": "^0.6.0", "upath": "^1.2.0", - "workbox-background-sync": "7.0.0", - "workbox-broadcast-update": "7.0.0", - "workbox-cacheable-response": "7.0.0", - "workbox-core": "7.0.0", - "workbox-expiration": "7.0.0", - "workbox-google-analytics": "7.0.0", - "workbox-navigation-preload": "7.0.0", - "workbox-precaching": "7.0.0", - "workbox-range-requests": "7.0.0", - "workbox-recipes": "7.0.0", - "workbox-routing": "7.0.0", - "workbox-strategies": "7.0.0", - "workbox-streams": "7.0.0", - "workbox-sw": "7.0.0", - "workbox-window": "7.0.0" + "workbox-background-sync": "7.3.0", + "workbox-broadcast-update": "7.3.0", + "workbox-cacheable-response": "7.3.0", + "workbox-core": "7.3.0", + "workbox-expiration": "7.3.0", + "workbox-google-analytics": "7.3.0", + "workbox-navigation-preload": "7.3.0", + "workbox-precaching": "7.3.0", + "workbox-range-requests": "7.3.0", + "workbox-recipes": "7.3.0", + "workbox-routing": "7.3.0", + "workbox-strategies": "7.3.0", + "workbox-streams": "7.3.0", + "workbox-sw": "7.3.0", + "workbox-window": "7.3.0" }, "engines": { "node": ">=16.0.0" @@ -8463,6 +10621,7 @@ "resolved": "https://registry.npmjs.org/@apideck/better-ajv-errors/-/better-ajv-errors-0.3.6.tgz", "integrity": "sha512-P+ZygBLZtkp0qqOAJJVX4oX/sFo5JR3eBWwwuqHHhK0GIgQOKWrAfiAaWX0aArHkRWHMuggFEgAZNxVPwPZYaA==", "dev": true, + "license": "MIT", "dependencies": { "json-schema": "^0.4.0", "jsonpointer": "^5.0.0", @@ -8480,6 +10639,7 @@ "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", "integrity": "sha512-WFfdLWU/xVWKeRQnKmIAQULUI7Il0gZnBIH/ZFO069wYIfPu+8zrfp/KMW0atmELoRDq8FbiP3VCss9MhCut7Q==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-module-imports": "^7.10.4", "@rollup/pluginutils": "^3.1.0" @@ -8498,31 +10658,12 @@ } } }, - "node_modules/workbox-build/node_modules/@rollup/plugin-node-resolve": { - "version": "11.2.1", - "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-11.2.1.tgz", - "integrity": "sha512-yc2n43jcqVyGE2sqV5/YCmocy9ArjVAP/BeXyTtADTBBX6V0e5UMqwO8CdQ0kzjb6zu5P1qMzsScCMRvE9OlVg==", - "dev": true, - "dependencies": { - "@rollup/pluginutils": "^3.1.0", - "@types/resolve": "1.17.1", - "builtin-modules": "^3.1.0", - "deepmerge": "^4.2.2", - "is-module": "^1.0.0", - "resolve": "^1.19.0" - }, - "engines": { - "node": ">= 10.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0" - } - }, "node_modules/workbox-build/node_modules/@rollup/plugin-replace": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-2.4.2.tgz", "integrity": "sha512-IGcu+cydlUMZ5En85jxHH4qj2hta/11BHq95iHEyb2sbgiN0eCdzvUcHw5gt9pBL5lTi4JDYJ1acCoMGpTvEZg==", "dev": true, + "license": "MIT", "dependencies": { "@rollup/pluginutils": "^3.1.0", "magic-string": "^0.25.7" @@ -8536,6 +10677,7 @@ "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz", "integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==", "dev": true, + "license": "MIT", "dependencies": { "@types/estree": "0.0.39", "estree-walker": "^1.0.1", @@ -8548,48 +10690,106 @@ "rollup": "^1.20.0||^2.0.0" } }, + "node_modules/workbox-build/node_modules/@types/estree": { + "version": "0.0.39", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", + "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==", + "dev": true, + "license": "MIT" + }, "node_modules/workbox-build/node_modules/ajv": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", - "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, + "license": "MIT", "dependencies": { - "fast-deep-equal": "^3.1.1", + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" + "require-from-string": "^2.0.2" }, "funding": { "type": "github", "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/workbox-build/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/workbox-build/node_modules/estree-walker": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz", "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==", - "dev": true + "dev": true, + "license": "MIT" + }, + "node_modules/workbox-build/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } }, "node_modules/workbox-build/node_modules/json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/workbox-build/node_modules/magic-string": { "version": "0.25.9", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", "dev": true, + "license": "MIT", "dependencies": { "sourcemap-codec": "^1.4.8" } }, + "node_modules/workbox-build/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/workbox-build/node_modules/pretty-bytes": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", "integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" }, @@ -8598,10 +10798,11 @@ } }, "node_modules/workbox-build/node_modules/rollup": { - "version": "2.79.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.1.tgz", - "integrity": "sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==", + "version": "2.79.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.2.tgz", + "integrity": "sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ==", "dev": true, + "license": "MIT", "bin": { "rollup": "dist/bin/rollup" }, @@ -8612,144 +10813,141 @@ "fsevents": "~2.3.2" } }, - "node_modules/workbox-build/node_modules/rollup-plugin-terser": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/rollup-plugin-terser/-/rollup-plugin-terser-7.0.2.tgz", - "integrity": "sha512-w3iIaU4OxcF52UUXiZNsNeuXIMDvFrr+ZXK6bFZ0Q60qyVfq4uLptoS4bbq3paG3x216eQllFZX7zt6TIImguQ==", - "deprecated": "This package has been deprecated and is no longer maintained. Please use @rollup/plugin-terser", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.10.4", - "jest-worker": "^26.2.1", - "serialize-javascript": "^4.0.0", - "terser": "^5.0.0" - }, - "peerDependencies": { - "rollup": "^2.0.0" - } - }, "node_modules/workbox-cacheable-response": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/workbox-cacheable-response/-/workbox-cacheable-response-7.0.0.tgz", - "integrity": "sha512-0lrtyGHn/LH8kKAJVOQfSu3/80WDc9Ma8ng0p2i/5HuUndGttH+mGMSvOskjOdFImLs2XZIimErp7tSOPmu/6g==", + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/workbox-cacheable-response/-/workbox-cacheable-response-7.3.0.tgz", + "integrity": "sha512-eAFERIg6J2LuyELhLlmeRcJFa5e16Mj8kL2yCDbhWE+HUun9skRQrGIFVUagqWj4DMaaPSMWfAolM7XZZxNmxA==", "dev": true, + "license": "MIT", "dependencies": { - "workbox-core": "7.0.0" + "workbox-core": "7.3.0" } }, "node_modules/workbox-core": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/workbox-core/-/workbox-core-7.0.0.tgz", - "integrity": "sha512-81JkAAZtfVP8darBpfRTovHg8DGAVrKFgHpOArZbdFd78VqHr5Iw65f2guwjE2NlCFbPFDoez3D3/6ZvhI/rwQ==", - "dev": true + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/workbox-core/-/workbox-core-7.3.0.tgz", + "integrity": "sha512-Z+mYrErfh4t3zi7NVTvOuACB0A/jA3bgxUN3PwtAVHvfEsZxV9Iju580VEETug3zYJRc0Dmii/aixI/Uxj8fmw==", + "dev": true, + "license": "MIT" }, "node_modules/workbox-expiration": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/workbox-expiration/-/workbox-expiration-7.0.0.tgz", - "integrity": "sha512-MLK+fogW+pC3IWU9SFE+FRStvDVutwJMR5if1g7oBJx3qwmO69BNoJQVaMXq41R0gg3MzxVfwOGKx3i9P6sOLQ==", + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/workbox-expiration/-/workbox-expiration-7.3.0.tgz", + "integrity": "sha512-lpnSSLp2BM+K6bgFCWc5bS1LR5pAwDWbcKt1iL87/eTSJRdLdAwGQznZE+1czLgn/X05YChsrEegTNxjM067vQ==", "dev": true, + "license": "MIT", "dependencies": { "idb": "^7.0.1", - "workbox-core": "7.0.0" + "workbox-core": "7.3.0" } }, "node_modules/workbox-google-analytics": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/workbox-google-analytics/-/workbox-google-analytics-7.0.0.tgz", - "integrity": "sha512-MEYM1JTn/qiC3DbpvP2BVhyIH+dV/5BjHk756u9VbwuAhu0QHyKscTnisQuz21lfRpOwiS9z4XdqeVAKol0bzg==", + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/workbox-google-analytics/-/workbox-google-analytics-7.3.0.tgz", + "integrity": "sha512-ii/tSfFdhjLHZ2BrYgFNTrb/yk04pw2hasgbM70jpZfLk0vdJAXgaiMAWsoE+wfJDNWoZmBYY0hMVI0v5wWDbg==", "dev": true, + "license": "MIT", "dependencies": { - "workbox-background-sync": "7.0.0", - "workbox-core": "7.0.0", - "workbox-routing": "7.0.0", - "workbox-strategies": "7.0.0" + "workbox-background-sync": "7.3.0", + "workbox-core": "7.3.0", + "workbox-routing": "7.3.0", + "workbox-strategies": "7.3.0" } }, "node_modules/workbox-navigation-preload": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/workbox-navigation-preload/-/workbox-navigation-preload-7.0.0.tgz", - "integrity": "sha512-juWCSrxo/fiMz3RsvDspeSLGmbgC0U9tKqcUPZBCf35s64wlaLXyn2KdHHXVQrb2cqF7I0Hc9siQalainmnXJA==", + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/workbox-navigation-preload/-/workbox-navigation-preload-7.3.0.tgz", + "integrity": "sha512-fTJzogmFaTv4bShZ6aA7Bfj4Cewaq5rp30qcxl2iYM45YD79rKIhvzNHiFj1P+u5ZZldroqhASXwwoyusnr2cg==", "dev": true, + "license": "MIT", "dependencies": { - "workbox-core": "7.0.0" + "workbox-core": "7.3.0" } }, "node_modules/workbox-precaching": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/workbox-precaching/-/workbox-precaching-7.0.0.tgz", - "integrity": "sha512-EC0vol623LJqTJo1mkhD9DZmMP604vHqni3EohhQVwhJlTgyKyOkMrZNy5/QHfOby+39xqC01gv4LjOm4HSfnA==", + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/workbox-precaching/-/workbox-precaching-7.3.0.tgz", + "integrity": "sha512-ckp/3t0msgXclVAYaNndAGeAoWQUv7Rwc4fdhWL69CCAb2UHo3Cef0KIUctqfQj1p8h6aGyz3w8Cy3Ihq9OmIw==", "dev": true, + "license": "MIT", "dependencies": { - "workbox-core": "7.0.0", - "workbox-routing": "7.0.0", - "workbox-strategies": "7.0.0" + "workbox-core": "7.3.0", + "workbox-routing": "7.3.0", + "workbox-strategies": "7.3.0" } }, "node_modules/workbox-range-requests": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/workbox-range-requests/-/workbox-range-requests-7.0.0.tgz", - "integrity": "sha512-SxAzoVl9j/zRU9OT5+IQs7pbJBOUOlriB8Gn9YMvi38BNZRbM+RvkujHMo8FOe9IWrqqwYgDFBfv6sk76I1yaQ==", + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/workbox-range-requests/-/workbox-range-requests-7.3.0.tgz", + "integrity": "sha512-EyFmM1KpDzzAouNF3+EWa15yDEenwxoeXu9bgxOEYnFfCxns7eAxA9WSSaVd8kujFFt3eIbShNqa4hLQNFvmVQ==", "dev": true, + "license": "MIT", "dependencies": { - "workbox-core": "7.0.0" + "workbox-core": "7.3.0" } }, "node_modules/workbox-recipes": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/workbox-recipes/-/workbox-recipes-7.0.0.tgz", - "integrity": "sha512-DntcK9wuG3rYQOONWC0PejxYYIDHyWWZB/ueTbOUDQgefaeIj1kJ7pdP3LZV2lfrj8XXXBWt+JDRSw1lLLOnww==", + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/workbox-recipes/-/workbox-recipes-7.3.0.tgz", + "integrity": "sha512-BJro/MpuW35I/zjZQBcoxsctgeB+kyb2JAP5EB3EYzePg8wDGoQuUdyYQS+CheTb+GhqJeWmVs3QxLI8EBP1sg==", "dev": true, + "license": "MIT", "dependencies": { - "workbox-cacheable-response": "7.0.0", - "workbox-core": "7.0.0", - "workbox-expiration": "7.0.0", - "workbox-precaching": "7.0.0", - "workbox-routing": "7.0.0", - "workbox-strategies": "7.0.0" + "workbox-cacheable-response": "7.3.0", + "workbox-core": "7.3.0", + "workbox-expiration": "7.3.0", + "workbox-precaching": "7.3.0", + "workbox-routing": "7.3.0", + "workbox-strategies": "7.3.0" } }, "node_modules/workbox-routing": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/workbox-routing/-/workbox-routing-7.0.0.tgz", - "integrity": "sha512-8YxLr3xvqidnbVeGyRGkaV4YdlKkn5qZ1LfEePW3dq+ydE73hUUJJuLmGEykW3fMX8x8mNdL0XrWgotcuZjIvA==", + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/workbox-routing/-/workbox-routing-7.3.0.tgz", + "integrity": "sha512-ZUlysUVn5ZUzMOmQN3bqu+gK98vNfgX/gSTZ127izJg/pMMy4LryAthnYtjuqcjkN4HEAx1mdgxNiKJMZQM76A==", "dev": true, + "license": "MIT", "dependencies": { - "workbox-core": "7.0.0" + "workbox-core": "7.3.0" } }, "node_modules/workbox-strategies": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/workbox-strategies/-/workbox-strategies-7.0.0.tgz", - "integrity": "sha512-dg3qJU7tR/Gcd/XXOOo7x9QoCI9nk74JopaJaYAQ+ugLi57gPsXycVdBnYbayVj34m6Y8ppPwIuecrzkpBVwbA==", + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/workbox-strategies/-/workbox-strategies-7.3.0.tgz", + "integrity": "sha512-tmZydug+qzDFATwX7QiEL5Hdf7FrkhjaF9db1CbB39sDmEZJg3l9ayDvPxy8Y18C3Y66Nrr9kkN1f/RlkDgllg==", "dev": true, + "license": "MIT", "dependencies": { - "workbox-core": "7.0.0" + "workbox-core": "7.3.0" } }, "node_modules/workbox-streams": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/workbox-streams/-/workbox-streams-7.0.0.tgz", - "integrity": "sha512-moVsh+5to//l6IERWceYKGiftc+prNnqOp2sgALJJFbnNVpTXzKISlTIsrWY+ogMqt+x1oMazIdHj25kBSq/HQ==", + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/workbox-streams/-/workbox-streams-7.3.0.tgz", + "integrity": "sha512-SZnXucyg8x2Y61VGtDjKPO5EgPUG5NDn/v86WYHX+9ZqvAsGOytP0Jxp1bl663YUuMoXSAtsGLL+byHzEuMRpw==", "dev": true, + "license": "MIT", "dependencies": { - "workbox-core": "7.0.0", - "workbox-routing": "7.0.0" + "workbox-core": "7.3.0", + "workbox-routing": "7.3.0" } }, "node_modules/workbox-sw": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/workbox-sw/-/workbox-sw-7.0.0.tgz", - "integrity": "sha512-SWfEouQfjRiZ7GNABzHUKUyj8pCoe+RwjfOIajcx6J5mtgKkN+t8UToHnpaJL5UVVOf5YhJh+OHhbVNIHe+LVA==", - "dev": true + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/workbox-sw/-/workbox-sw-7.3.0.tgz", + "integrity": "sha512-aCUyoAZU9IZtH05mn0ACUpyHzPs0lMeJimAYkQkBsOWiqaJLgusfDCR+yllkPkFRxWpZKF8vSvgHYeG7LwhlmA==", + "dev": true, + "license": "MIT" }, "node_modules/workbox-window": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/workbox-window/-/workbox-window-7.0.0.tgz", - "integrity": "sha512-j7P/bsAWE/a7sxqTzXo3P2ALb1reTfZdvVp6OJ/uLr/C2kZAMvjeWGm8V4htQhor7DOvYg0sSbFN2+flT5U0qA==", + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/workbox-window/-/workbox-window-7.3.0.tgz", + "integrity": "sha512-qW8PDy16OV1UBaUNGlTVcepzrlzyzNW/ZJvFQQs2j2TzGsg6IKjcpZC1RSquqQnTOafl5pCj5bGfAHlCjOOjdA==", "dev": true, + "license": "MIT", "dependencies": { "@types/trusted-types": "^2.0.2", - "workbox-core": "7.0.0" + "workbox-core": "7.3.0" } }, "node_modules/wrap-ansi": { @@ -8757,6 +10955,7 @@ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", @@ -8775,6 +10974,7 @@ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -8787,17 +10987,29 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/wrap-ansi-cjs/node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, + "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -8807,16 +11019,17 @@ "node": ">=8" } }, - "node_modules/wrap-ansi/node_modules/ansi-regex": { + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, - "engines": { - "node": ">=12" + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" + "engines": { + "node": ">=8" } }, "node_modules/wrap-ansi/node_modules/ansi-styles": { @@ -8824,6 +11037,7 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" }, @@ -8831,32 +11045,19 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/wrap-ansi/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/ws": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", - "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "version": "8.18.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz", + "integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=10.0.0" }, @@ -8878,6 +11079,7 @@ "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", "dev": true, + "license": "Apache-2.0", "engines": { "node": ">=12" } @@ -8886,21 +11088,98 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", - "dev": true + "dev": true, + "license": "MIT" + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } }, "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } }, "node_modules/yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", "dev": true, + "license": "ISC", "engines": { - "node": ">=10" + "node": ">=12" + } + }, + "node_modules/yargs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/yargs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" } }, "node_modules/yauzl": { @@ -8908,6 +11187,7 @@ "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", "dev": true, + "license": "MIT", "dependencies": { "buffer-crc32": "~0.2.3", "fd-slicer": "~1.1.0" @@ -8918,12 +11198,23 @@ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zod": { + "version": "3.25.67", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.67.tgz", + "integrity": "sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } } } } diff --git a/package.json b/package.json @@ -1,34 +1,51 @@ { "name": "running-tools", - "version": "1.4.1", + "version": "1.5.0", "description": "A collection of tools for runners and their coaches that calculate splits, predict race times, convert units, and more", + "repository": { + "type": "git", + "url": "git+https://git.ashermorgan.net/running-tools/" + }, "private": true, "type": "module", "scripts": { "dev": "vite", - "build": "vite build", + "build": "run-p type-check \"build-only {@}\" --", "preview": "vite preview", - "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs --fix --ignore-path .gitignore", + "lint": "eslint . --fix", "test:unit": "vitest", - "test:e2e": "npx playwright test" + "test:e2e": "playwright test", + "build-only": "vite build", + "type-check": "vue-tsc --build" }, "dependencies": { "feather-icons": "^4.29.2", - "vue": "^3.4.27", + "vue": "^3.5.17", "vue-feather": "^2.0.0", "vue-router": "^4.3.2" }, "devDependencies": { - "@playwright/test": "^1.44.0", - "@types/node": "^20.12.12", - "@vitejs/plugin-vue": "^4.6.2", + "@eslint/js": "^9.22.0", + "@playwright/test": "^1.53.1", + "@tsconfig/node22": "^22.0.2", + "@types/markdown-it": "^14.1.2", + "@types/node": "^24.0.7", + "@vitejs/plugin-vue": "^5.2.4", + "@vue/eslint-config-typescript": "^14.5.1", "@vue/test-utils": "^2.4.6", - "eslint": "^8.57.0", - "eslint-plugin-vue": "^9.26.0", - "jsdom": "^22.1.0", - "pwa-asset-generator": "^6.3.1", - "vite": "^4.5.3", - "vite-plugin-pwa": "^0.16.7", - "vitest": "^0.32.4" + "@vue/tsconfig": "^0.7.0", + "eslint": "^9.22.0", + "eslint-plugin-playwright": "^2.2.0", + "eslint-plugin-vue": "^10.2.0", + "jsdom": "^26.1.0", + "markdown-it": "^14.1.0", + "npm-run-all2": "^8.0.4", + "pwa-asset-generator": "^8.0.5", + "typescript": "^5.8.3", + "typescript-eslint": "^8.34.1", + "vite": "^6.3.5", + "vite-plugin-pwa": "^1.0.0", + "vitest": "^3.2.4", + "vue-tsc": "^2.2.10" } } diff --git a/playwright.config.js b/playwright.config.js @@ -12,16 +12,22 @@ import { defineConfig, devices } from '@playwright/test'; */ export default defineConfig({ testDir: './tests/e2e', + /* Run tests in files in parallel */ fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ forbidOnly: !!process.env.CI, + /* Retry on CI only */ retries: process.env.CI ? 2 : 0, + /* Opt out of parallel tests on CI. */ workers: process.env.CI ? 1 : undefined, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ - reporter: 'html', + reporter: 'line', + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ use: { /* Base URL to use in actions like `await page.goto('/')`. */ @@ -44,6 +50,9 @@ export default defineConfig({ }, ], + /* Folder for test artifacts such as screenshots, videos, traces, etc. */ + outputDir: './tests/e2e/results/', + /* Run your local dev server before starting the tests */ webServer: { command: 'npm run dev', diff --git a/src/App.vue b/src/App.vue @@ -1,11 +1,12 @@ -<script setup> -import { RouterLink, RouterView } from 'vue-router' +<script setup lang="ts"> +import { RouterLink, RouterView } from 'vue-router'; +import type { RouteRecordNameGeneric } from 'vue-router'; import VueFeather from 'vue-feather'; </script> <template> <header> - <router-link :to="{ name: $route.meta.back }" v-if="$route.meta.back" + <router-link :to="{ name: $route.meta.back as RouteRecordNameGeneric }" v-if="$route.meta.back" class="icon" title="Back"> <vue-feather type="chevron-left" aria-hidden="true"/> </router-link> @@ -34,7 +35,7 @@ header a { height: 2em; width: 2em; } -header ::v-deep(.feather-chevron-left) { +header :deep(.feather-chevron-left) { padding: 0em; color: #000000; } diff --git a/src/assets/global.css b/src/assets/global.css @@ -144,6 +144,29 @@ header h1 { } } +/* long-form text styles */ +.text { + max-width: 800px; + margin: auto; + font-size: 1.1em; +} +.text h2 { + text-align: center; +} +.text * + h2, .text :not(h2) + h3 { + margin-top: 1em; +} +.text p, .text ul { + margin-bottom: 0.5em; +} +.text p { + line-height: 1.3; +} +.text li { + margin-bottom: 0.2em; + margin-left: 1.5em; +} + /* light/default theme */ :root { /* The theme color of the app */ diff --git a/src/components/AdvancedOptionsInput.vue b/src/components/AdvancedOptionsInput.vue @@ -0,0 +1,111 @@ +<template> + <div> + Default units: + <select v-model="globalOptions.defaultUnitSystem" aria-label="Default units"> + <option value="imperial">Miles</option> + <option value="metric">Kilometers</option> + </select> + </div> + + <div> + Target set: + <target-set-selector :setType="props.type" + :default-unit-system="globalOptions.defaultUnitSystem" + v-model:selected-target-set="options.selectedTargetSet" v-model:target-sets="targetSets" + :customWorkoutNames="props.type === Calculators.Workout ? + (options as WorkoutOptions).customTargetNames : false"/> + </div> + + <div v-if="props.type === Calculators.Workout"> + Workout name customization: + <select v-model="(options as WorkoutOptions).customTargetNames" + aria-label="Workout name customization"> + <option :value="false">Disabled</option> + <option :value="true">Enabled</option> + </select> + </div> + + <div v-if="batchOptions && props.type === Calculators.Workout" + v-show="(options as WorkoutOptions).customTargetNames"> + Batch column label: + <input v-model="batchOptions.label" :placeholder="formatDistance(batchOptions.input, false)" + aria-label="Batch column label"/> + </div> + + <div v-if="props.type === Calculators.Race || props.type === Calculators.Workout"> + Prediction model: + <select v-model="globalOptions.racePredictionOptions.model" + aria-label="Prediction model"> + <option :value="RacePredictionModels.AverageModel">Average</option> + <option :value="RacePredictionModels.PurdyPointsModel">Purdy Points Model</option> + <option :value="RacePredictionModels.VO2MaxModel">V&#775;O&#8322; Max Model</option> + <option :value="RacePredictionModels.CameronModel">Cameron's Model</option> + <option :value="RacePredictionModels.RiegelModel">Riegel's Model</option> + </select> + </div> + + <div v-if="props.type === Calculators.Race || props.type === Calculators.Workout" + v-show="globalOptions.racePredictionOptions.model == RacePredictionModels.AverageModel + || globalOptions.racePredictionOptions.model == RacePredictionModels.RiegelModel"> + Riegel exponent: + <decimal-input v-model="globalOptions.racePredictionOptions.riegelExponent" + aria-label="Riegel exponent" :min="1" :max="1.3" :digits="2" :step="0.01"/> + (default: 1.06) + </div> +</template> + +<script setup lang="ts"> +import { Calculators } from '@/core/calculators'; +import type { BatchOptions, GlobalOptions, PaceOptions, RaceOptions, SplitOptions, + WorkoutOptions } from '@/core/calculators'; +import { RacePredictionModels } from '@/core/racePrediction'; +import type { TargetSets } from '@/core/targets'; +import { formatDistance } from '@/core/units'; + +import DecimalInput from '@/components/DecimalInput.vue'; +import TargetSetSelector from '@/components/TargetSetSelector.vue'; + +import useObjectModel from '@/composables/useObjectModel'; + +type CalculatorOptions = PaceOptions | RaceOptions | SplitOptions | WorkoutOptions; + +const props = defineProps<{ + /* + * The batch calculator options (if applicable) + */ + batchOptions?: BatchOptions, + + /* + * The global options + */ + globalOptions: GlobalOptions, + + /* + * The calculator options + */ + options: CalculatorOptions, + + /* + * The calculator type + */ + type: Calculators, + + /* + * The calculator target sets + */ + targetSets: TargetSets, +}>(); + +// Generate internal refs tied to batchOptions, globalOptions, options and targetSets props +const emit = defineEmits([ + 'update:batchOptions', 'update:globalOptions', 'update:options', 'update:targetSets' +]); +const batchOptions = useObjectModel<BatchOptions | undefined>(() => props.batchOptions, (x) => + emit('update:batchOptions', x)); +const globalOptions = useObjectModel<GlobalOptions>(() => props.globalOptions, (x) => + emit('update:globalOptions', x)); +const options = useObjectModel<CalculatorOptions>(() => props.options, (x) => + emit('update:options', x)); +const targetSets = useObjectModel<TargetSets>(() => props.targetSets, (x) => + emit('update:targetSets', x)); +</script> diff --git a/src/components/DecimalInput.vue b/src/components/DecimalInput.vue @@ -2,9 +2,9 @@ <input ref="inputElement" type="number" step="any" required @blur="onblur" v-model="stringValue"> </template> -<script setup> +<script setup lang="ts"> import { ref, watch } from 'vue'; -import { formatNumber } from '@/utils/format'; +import { formatNumber } from '@/core/units'; /** * The component value @@ -21,7 +21,7 @@ const props = defineProps({ padding: { type: Number, default: 0, - validator(value) { + validator(value: number) { return value >= 0; }, }, @@ -32,7 +32,7 @@ const props = defineProps({ digits: { type: Number, default: 1, - validator(value) { + validator(value: number) { return value > 0; }, }, @@ -51,31 +51,25 @@ const stringValue = ref(format(model.value)); /** * The input element */ -const inputElement = ref(null); +const inputElement = ref(); /* * Update the internal value when the component value changes */ watch(model, (newValue) => { - if (newValue !== internalValue.value) { + if (Math.abs(newValue - internalValue.value) > 0.00001) { internalValue.value = newValue; stringValue.value = format(internalValue.value); } }); /** - * Update the component value when the internal value changes - */ -watch(internalValue, (newValue) => { - model.value = newValue; -}); - -/** * Update the internal value when the raw string value changes */ watch(stringValue, (newValue) => { if (inputElement.value.validity.valid) { internalValue.value = Number(newValue); + model.value = internalValue.value; } }); @@ -90,10 +84,10 @@ function onblur() { /** * Format a decimal as a string - * @param {Number} value The decimal - * @returns {String} The formated string + * @param {number} value The decimal + * @returns {string} The formated string */ -function format(value) { +function format(value: number): string { return formatNumber(value, props.padding, props.digits, true); } </script> diff --git a/src/components/DoubleOutputTable.vue b/src/components/DoubleOutputTable.vue @@ -26,66 +26,61 @@ </div> </template> -<script setup> +<script setup lang="ts"> import { computed } from 'vue'; -import { formatDuration, formatNumber } from '@/utils/format'; -import { DISTANCE_UNITS } from '@/utils/units'; -const props = defineProps({ - /** +import { ResultType } from '@/core/calculators'; +import type { TargetResult } from '@/core/calculators'; +import type { Target } from '@/core/targets'; +import { formatDuration } from '@/core/units'; +import type { Distance, DistanceTime } from '@/core/units'; + +interface Props { + /* * The method that generates the target table rows */ - calculateResult: { - type: Function, - required: true, - }, + calculateResult: (x: DistanceTime, y: Target) => TargetResult, - /** - * The target set + /* + * The input distance */ - targets: { - type: Array, - default: () => [], - }, + inputDistance: Distance, - /** + /* * The set of input times */ - inputTimes: { - type: Array, - default: () => [], - }, + inputTimes: Array<number>, - /** - * The input distance + /* + * The label to use for the first column */ - inputDistance: { - type: Object, - default: () => ({ - distanceValue: 5, - distanceUnit: 'kilometers', - }), - } -}); + label: string, + + /* + * The target set + */ + targets: Array<Target>, +} + +const props = defineProps<Props>(); -/** +/* * The target table results */ const results = computed(() => { // Calculate results - const results = [[ - formatNumber(props.inputDistance.distanceValue, 0, 2, false) + ' ' - + DISTANCE_UNITS[props.inputDistance.distanceUnit].symbol + const results: Array<Array<string>> = [[ + props.label ]]; props.inputTimes.forEach((input, y) => { - let row = [formatDuration(input, 3, 2, false)]; + const row = [formatDuration(input, 3, 2, false)]; props.targets.forEach(target => { - let result = props.calculateResult({ ...props.inputDistance, time: input }, target); + const result = props.calculateResult({ ...props.inputDistance, time: input }, target); if (y === 0) { - results[0].push(result[result.result === 'key' ? 'value' : 'key']); + results[0].push(result[result.result === ResultType.Key ? 'value' : 'key']); } row.push(result[result.result]); diff --git a/src/components/IntegerInput.vue b/src/components/IntegerInput.vue @@ -2,7 +2,7 @@ <input ref="inputElement" type="number" step="1" required @blur="onblur" v-model="stringValue"> </template> -<script setup> +<script setup lang="ts"> import { ref, watch } from 'vue'; /** @@ -20,7 +20,7 @@ const props = defineProps({ padding: { type: Number, default: 0, - validator(value) { + validator(value: number) { return value >= 0; }, }, @@ -39,31 +39,25 @@ const stringValue = ref(format(model.value)); /** * The input element */ -const inputElement = ref(null); +const inputElement = ref(); /** * Update the internal value when the component value changes */ watch(model, (newValue) => { - if (newValue !== internalValue.value) { + if (Math.abs(newValue - internalValue.value) > 0.00001) { internalValue.value = newValue; stringValue.value = format(internalValue.value); } }); /** - * Update the component value when the internal value changes - */ -watch(internalValue, (newValue) => { - model.value = newValue; -}); - -/** * Update the internal value when the raw string value changes */ watch(stringValue, (newValue) => { if (inputElement.value.validity.valid) { internalValue.value = Number(newValue); + model.value = internalValue.value; } }); @@ -78,10 +72,10 @@ function onblur() { /** * Format an integer as a string - * @param {Number} value The integer - * @returns {String} The formated string + * @param {number} value The integer + * @returns {string} The formated string */ -function format(value) { +function format(value: number): string { return value.toString().padStart(props.padding, '0'); } </script> diff --git a/src/components/PaceInput.vue b/src/components/PaceInput.vue @@ -5,8 +5,8 @@ <decimal-input v-model="model.distanceValue" :aria-label="label + ' distance value'" :min="0" :digits="2"/> <select v-model="model.distanceUnit" :aria-label="label + ' distance unit'"> - <option v-for="(value, key) in DISTANCE_UNITS" :key="key" :value="key"> - {{ value.name }} + <option v-for="key in DistanceUnits" :key="key" :value="key"> + {{ DistanceUnitData[key].name }} </option> </select> </div> @@ -17,34 +17,32 @@ </div> </template> -<script setup> +<script setup lang="ts"> +import { DistanceUnits, DistanceUnitData } from '@/core/units'; +import type { DistanceTime } from '@/core/units'; + import DecimalInput from '@/components/DecimalInput.vue'; import TimeInput from '@/components/TimeInput.vue'; +import useObjectModel from '@/composables/useObjectModel'; -import { DISTANCE_UNITS } from '@/utils/units'; - -/** - * The component value - */ -const model = defineModel({ - type: Object, - default: { - distanceValue: 5, - distanceUnit: 'kilometers', - time: 1200, - }, -}); +interface Props { + /** + * The prefix for each field's aria-label (defaults to 'Input') + */ + label?: string, -defineProps({ /** - * The prefix for each field's aria-label + * The component value */ - label: { - type: String, - default: 'Input', - }, -}); + modelValue: DistanceTime, +}; + +const props = withDefaults(defineProps<Props>(), { label: 'Input' }); +// Generate internal ref tied to modelValue prop +const emit = defineEmits(['update:modelValue']); +const model = useObjectModel<DistanceTime>(() => props.modelValue, + (x) => emit('update:modelValue', x)); </script> <style scoped> diff --git a/src/components/RaceOptions.vue b/src/components/RaceOptions.vue @@ -1,30 +0,0 @@ -<template> - <div> - Prediction Model: - <select v-model="model.model" aria-label="Prediction model"> - <option value="AverageModel">Average</option> - <option value="PurdyPointsModel">Purdy Points Model</option> - <option value="VO2MaxModel">V&#775;O&#8322; Max Model</option> - <option value="CameronModel">Cameron's Model</option> - <option value="RiegelModel">Riegel's Model</option> - </select> - </div> - <div> - Riegel Exponent: - <decimal-input v-model="model.riegelExponent" aria-label="Riegel exponent" :min="1" :max="1.3" - :digits="2" :step="0.01"/> - (default: 1.06) - </div> -</template> - -<script setup> -import DecimalInput from '@/components/DecimalInput.vue'; - -const model = defineModel({ - type: Object, - default: { - model: 'AverageModel', - riegelExponent: 1.06, - }, -}); -</script> diff --git a/src/components/SingleOutputTable.vue b/src/components/SingleOutputTable.vue @@ -36,41 +36,37 @@ </div> </template> -<script setup> +<script setup lang="ts"> import { computed } from 'vue'; -const props = defineProps({ +import type { TargetResult } from '@/core/calculators'; +import type { Target } from '@/core/targets'; + +interface Props { /** * The method that generates the target table rows */ - calculateResult: { - type: Function, - required: true, - }, + calculateResult: (x: Target) => TargetResult, /** * The target set */ - targets: { - type: Array, - default: () => [], - }, + targets: Array<Target>, /** - * Whether to show result paces + * Whether to show result paces (defaults to false) */ - showPace: { - type: Boolean, - default: false, - }, -}); + showPace?: boolean, +}; + +const props = withDefaults(defineProps<Props>(), { showPace: false }); /** * The target table results */ const results = computed(() => { // Calculate results - const result = []; + const result: Array<TargetResult> = []; props.targets.forEach((row) => { // Add result result.push(props.calculateResult(row)); diff --git a/src/components/SplitOutputTable.vue b/src/components/SplitOutputTable.vue @@ -18,22 +18,19 @@ <tbody> <tr v-for="(item, index) in results" :key="index"> <td> - {{ formatNumber(item.distanceValue, 0, 2, false) }} - {{ DISTANCE_UNITS[item.distanceUnit].symbol }} + {{ formatDistance(model[index] as Distance, false) }} </td> <td> - {{ formatDuration(item.time, 3, 2, true) }} + {{ formatDuration(item.total.time, 3, 2, true) }} </td> <td> - <time-input v-model="targets[index].splitTime" label="Split duration" :showHours="false"/> + <time-input v-model="model[index].splitTime" label="Split duration" :showHours="false"/> </td> <td> - {{ formatDuration(item.pace, 3, 0, true) }} - / {{ DISTANCE_UNITS[getDefaultDistanceUnit(defaultUnitSystem)] - .symbol }} + {{ formatPace(item.split, getDefaultPaceUnit(defaultUnitSystem)) }} </td> </tr> @@ -46,63 +43,74 @@ </table> </template> -<script setup> +<script setup lang="ts"> import { computed } from 'vue'; -import { formatDuration, formatNumber } from '@/utils/format'; -import { DISTANCE_UNITS, convertDistance, getDefaultDistanceUnit } from '@/utils/units'; +import type { SplitTarget } from '@/core/targets'; +import { DistanceUnits, UnitSystems, convertDistance, formatDistance, formatDuration, + formatPace, getDefaultPaceUnit } from '@/core/units'; +import type { Distance, DistanceTime } from '@/core/units'; import TimeInput from '@/components/TimeInput.vue'; +import useObjectModel from '@/composables/useObjectModel'; -/** - * The split targets - */ -const targets = defineModel({ - type: Array, - default: () => [], -}) +interface SplitTargetResult { + split: DistanceTime, + total: DistanceTime, +}; -const props = defineProps({ +interface Props { /** - * The unit system to use when showing result paces + * The unit system to use when showing result paces (defaults to metric) */ - defaultUnitSystem: { - type: String, - default: 'metric', - }, -}); + defaultUnitSystem?: UnitSystems, + + /** + * The split targets + */ + modelValue: Array<SplitTarget>, +}; + +const props = withDefaults(defineProps<Props>(), { defaultUnitSystem: UnitSystems.Metric }); + +// Generate internal ref tied to modelValue prop +const emit = defineEmits(['update:modelValue']); +const model = useObjectModel<Array<SplitTarget>>(() => props.modelValue, + (x) => emit('update:modelValue', x)); /** * The target table results */ const results = computed(() => { // Initialize results array - const results = []; + const results: Array<SplitTargetResult> = []; - for (let i = 0; i < targets.value.length; i += 1) { + for (let i = 0; i < model.value.length; i += 1) { // Calculate split and total times - const splitTime = targets.value[i].splitTime || 0; - const totalTime = i === 0 ? splitTime : results[i - 1].time + splitTime; + const splitTime = model.value[i].splitTime || 0; + const totalTime = i === 0 ? splitTime : results[i - 1].total.time + splitTime; // Calculate split and total distances const totalDistance = convertDistance( - targets.value[i].distanceValue, - targets.value[i].distanceUnit, 'meters', + model.value[i].distanceValue, + model.value[i].distanceUnit, + DistanceUnits.Meters, ); - const splitDistance = i === 0 ? totalDistance : totalDistance - results[i - 1].distance; - - // Calculate pace - const pace = splitTime / convertDistance(splitDistance, 'meters', - getDefaultDistanceUnit(props.defaultUnitSystem)); + const splitDistance = i === 0 ? totalDistance : + totalDistance - results[i - 1].total.distanceValue; // Add row to results array results.push({ - distance: totalDistance, - distanceValue: targets.value[i].distanceValue, - distanceUnit: targets.value[i].distanceUnit, - time: totalTime, - splitTime, - pace, + split: { + distanceValue: splitDistance, + distanceUnit: DistanceUnits.Meters, + time: splitTime, + }, + total: { + distanceValue: totalDistance, + distanceUnit: DistanceUnits.Meters, + time: totalTime, + }, }); } diff --git a/src/components/TargetEditor.vue b/src/components/TargetEditor.vue @@ -4,7 +4,7 @@ <tr> <th> Edit - <input v-model="internalValue.name" placeholder="Target set label" + <input v-model="model.name" placeholder="Target set label" aria-label="Target set label"/> <button class="icon" :title="isCustomSet ? 'Delete target set' : 'Revert target set'" @click="emit('revert')"> @@ -21,19 +21,24 @@ </thead> <tbody> - <tr v-for="(item, index) in internalValue.targets" :key="index"> + <tr v-for="(item, index) in model.targets" :key="index"> <td> - <span v-if="setType === 'workout'"> - <decimal-input v-model="item.splitValue" aria-label="Split distance value" - :min="0" :digits="2"/> - <select v-model="item.splitUnit" aria-label="Split distance unit"> - <option v-for="(value, key) in DISTANCE_UNITS" :key="key" :value="key"> + <span v-if="setType === Calculators.Workout && customWorkoutNames"> + <input v-model="(item as WorkoutTarget).customName" aria-label="Custom target name" + :placeholder="workoutTargetToString(item as WorkoutTarget)"/>: + </span> + + <span v-if="setType === Calculators.Workout"> + <decimal-input v-model="(item as WorkoutTarget).splitValue" + aria-label="Split distance value" :min="0" :digits="2"/> + <select v-model="(item as WorkoutTarget).splitUnit" aria-label="Split distance unit"> + <option v-for="(value, key) in DistanceUnitData" :key="key" :value="key"> {{ value.name }} </option> </select> </span> - <span v-if="setType === 'workout'"> + <span v-if="setType === Calculators.Workout"> &nbsp;@&nbsp; </span> @@ -41,7 +46,7 @@ <decimal-input v-model="item.distanceValue" aria-label="Target distance value" :min="0" :digits="2"/> <select v-model="item.distanceUnit" aria-label="Target distance unit"> - <option v-for="(value, key) in DISTANCE_UNITS" :key="key" :value="key"> + <option v-for="(value, key) in DistanceUnitData" :key="key" :value="key"> {{ value.name }} </option> </select> @@ -59,7 +64,7 @@ </td> </tr> - <tr v-if="internalValue.targets.length === 0" class="empty-message"> + <tr v-if="model.targets.length === 0" class="empty-message"> <td colspan="2"> There aren't any targets in this set yet. </td> @@ -72,7 +77,8 @@ <button title="Add distance target" @click="addDistanceTarget"> Add distance target </button> - <button title="Add time target" @click="addTimeTarget" v-if="setType !== 'split'"> + <button title="Add time target" @click="addTimeTarget" + v-if="setType !== Calculators.Split"> Add time target </button> </td> @@ -81,90 +87,72 @@ </table> </template> -<script setup> -import { watch, ref } from 'vue'; - +<script setup lang="ts"> import VueFeather from 'vue-feather'; -import { DISTANCE_UNITS, getDefaultDistanceUnit } from '@/utils/units'; +import { Calculators } from '@/core/calculators'; +import { TargetTypes, workoutTargetToString } from '@/core/targets'; +import type { StandardTargetSet, TargetSet, WorkoutTarget, WorkoutTargetSet } from '@/core/targets'; +import { DistanceUnitData, UnitSystems, getDefaultDistanceUnit } from '@/core/units'; import DecimalInput from '@/components/DecimalInput.vue'; import TimeInput from '@/components/TimeInput.vue'; +import useObjectModel from '@/composables/useObjectModel'; -/** - * The component value - */ -const model = defineModel({ - type: Object, - default: { - name: 'New target set', - targets: [], - } -}); +interface Props { + /** + * Whether to allow custom names for workout targets (defaults to false) + */ + customWorkoutNames?: boolean, + /** + * The unit system to use when creating distance targets (defaults to metric) + */ + defaultUnitSystem?: UnitSystems, -const props = defineProps({ /** - * Whether the target set is a custom or default set + * Whether the target set is a custom or default set (defaults to false) */ - isCustomSet: { - type: Boolean, - default: false, - }, + isCustomSet?: boolean, /** - * The unit system to use when creating distance targets + * The component value */ - defaultUnitSystem: { - type: String, - default: 'metric', - }, + modelValue: TargetSet, /** - * The target set type ('standard', 'split', or 'workout') + * The target set type (defaults to pace calculator target sets) */ - setType: { - type: String, - default: 'standard' - }, + setType?: Calculators, +} + +const props = withDefaults(defineProps<Props>(), { + customWorkoutNames: false, + defaultUnitSystem: UnitSystems.Metric, + isCustomSet: false, + setType: Calculators.Pace, }); // Declare emitted events -const emit = defineEmits(['revert', 'close']); - -/** - * The internal value - */ -const internalValue = ref(model.value); +const emit = defineEmits(['close', 'revert', 'update:modelValue']); -/** - * Update the internal value when the component value changes - */ -watch(model, (newValue) => { - internalValue.value = newValue; -}, { deep: true }); - -/** - * Update the component value when the internal value changes - */ -watch(internalValue, (newValue) => { - model.value = newValue; -}, { deep: true }); +// Generate internal ref tied to modelValue prop +const model = useObjectModel<TargetSet>(() => props.modelValue, (x) => emit('update:modelValue', x)); /** * Add a new distance based target */ function addDistanceTarget() { - if (props.setType === 'workout') { - internalValue.value.targets.push({ - type: 'distance', + if (props.setType === Calculators.Workout) { + (model.value as WorkoutTargetSet).targets.push({ + type: TargetTypes.Distance, distanceValue: 1, distanceUnit: getDefaultDistanceUnit(props.defaultUnitSystem), splitValue: 1, splitUnit: getDefaultDistanceUnit(props.defaultUnitSystem), }); } else { - internalValue.value.targets.push({ - type: 'distance', + (model.value as StandardTargetSet).targets.push({ + type: TargetTypes.Distance, distanceValue: 1, distanceUnit: getDefaultDistanceUnit(props.defaultUnitSystem), }); @@ -175,16 +163,16 @@ function addDistanceTarget() { * Add a new time based target */ function addTimeTarget() { - if (props.setType === 'workout') { - internalValue.value.targets.push({ - type: 'time', + if (props.setType === Calculators.Workout) { + (model.value as WorkoutTargetSet).targets.push({ + type: TargetTypes.Time, time: 600, splitValue: 1, splitUnit: getDefaultDistanceUnit(props.defaultUnitSystem), }); } else { - internalValue.value.targets.push({ - type: 'time', + (model.value as StandardTargetSet).targets.push({ + type: TargetTypes.Time, time: 600, }); } @@ -192,10 +180,10 @@ function addTimeTarget() { /** * Remove a target - * @param {Number} index The index of the target + * @param {number} index The index of the target */ -function removeTarget(index) { - internalValue.value.targets.splice(index, 1); +function removeTarget(index: number) { + model.value.targets.splice(index, 1); } </script> @@ -204,7 +192,7 @@ function removeTarget(index) { .target-editor th .icon { margin-left: 0.3em; } -.target-editor tbody td:first-child::not(.empty-message) { +.target-editor tbody td:first-child:not(.empty-message td) { display: flex; gap: 0.2em; flex-wrap: wrap; diff --git a/src/components/TargetSetSelector.vue b/src/components/TargetSetSelector.vue @@ -13,59 +13,72 @@ <dialog ref="dialogElement" class="target-set-editor-dialog" aria-label="Edit target set"> <target-editor @close="sortTargetSet(); dialogElement.close()" - @revert="revertTargetSet" :default-unit-system="defaultUnitSystem" :setType="setType" + @revert="revertTargetSet" :customWorkoutNames="customWorkoutNames" + :default-unit-system="defaultUnitSystem" :setType="setType" v-model="targetSets[internalValue]" :isCustomSet="!internalValue.startsWith('_')"/> </dialog> </span> </template> -<script setup> +<script setup lang="ts"> import { computed, nextTick, ref } from 'vue'; import VueFeather from 'vue-feather'; -import { sort, defaultTargetSets } from '@/utils/targets'; +import { Calculators } from '@/core/calculators'; +import { sort, defaultTargetSets } from '@/core/targets'; +import type { TargetSet, TargetSets } from '@/core/targets'; +import { deepCopy } from '@/core/utils'; +import { UnitSystems } from '@/core/units'; import TargetEditor from '@/components/TargetEditor.vue'; +import useObjectModel from '@/composables/useObjectModel'; /** * The selected target set */ const model = defineModel('selectedTargetSet', { type: String, - default: '_new', + required: true, }); -/** - * The target sets - */ -const targetSets = defineModel('targetSets', { - type: Object, - default: {}, -}); +interface Props { + /** + * Whether to allow custom names for workout targets (defaults to false) + */ + customWorkoutNames?: boolean, -defineProps({ /** - * The unit system to use when creating distance targets + * The unit system to use when creating distance targets (defaults to metric) */ - defaultUnitSystem: { - type: String, - default: 'metric', - }, + defaultUnitSystem?: UnitSystems, /** - * The target set type ('standard', 'split', or 'workout') + * The target set type (defaults to pace calculator target sets) */ - setType: { - type: String, - default: 'standard' - }, + setType?: Calculators, + + /** + * The target sets + */ + targetSets: TargetSets, +}; + + +const props = withDefaults(defineProps<Props>(), { + customWorkoutNames: false, + defaultUnitSystem: UnitSystems.Metric, + setType: Calculators.Pace, }); +// Generate internal ref tied to modelValue prop +const emit = defineEmits(['update:targetSets']); +const targetSets = useObjectModel<TargetSets>(() => props.targetSets, (x) => emit('update:targetSets', x)); + /** * The dialog element */ -const dialogElement = ref(null); +const dialogElement = ref(); /** * The internal value @@ -77,7 +90,7 @@ const internalValue = computed({ } return model.value; }, - set: async (newValue) => { + set: async (newValue: string) => { if (newValue == '_new') { await nextTick(); // <select> won't update if value changed immediately newTargetSet(); @@ -101,10 +114,13 @@ function editTargetSet() { * Create and select a new target */ function newTargetSet() { - let key = Date.now().toString(); - targetSets.value[key] = { - name: 'New target set', - targets: [], + const key = Date.now().toString(); + targetSets.value = { + ...targetSets.value, + [key]: { + name: 'New target set', + targets: [], + }, }; model.value = key; editTargetSet(); @@ -117,7 +133,7 @@ function revertTargetSet() { if (internalValue.value.startsWith('_')) { // Revert default set targetSets.value[internalValue.value] = - JSON.parse(JSON.stringify(defaultTargetSets[internalValue.value])); + deepCopy<TargetSet>(defaultTargetSets[internalValue.value]); sortTargetSet(); } else { // Remove custom set diff --git a/src/components/TimeInput.vue b/src/components/TimeInput.vue @@ -13,7 +13,7 @@ </div> </template> -<script setup> +<script setup lang="ts"> import { computed, ref, watch } from 'vue'; import IntegerInput from '@/components/IntegerInput.vue'; @@ -25,27 +25,26 @@ import DecimalInput from '@/components/DecimalInput.vue'; const model = defineModel({ type: Number, default: 0, - validator(value) { + validator(value: number) { return value >= 0 && value <= 359999.99; }, }); -const props = defineProps({ +interface Props { /** - * Whether to show the hour field + * Whether to show the hour field (defaults to true) */ - showHours: { - type: Boolean, - default: true, - }, + showHours?: boolean, /** - * The prefix for each field's aria-label + * The prefix for each field's aria-label (defaults to 'Input') */ - label: { - type: String, - default: '', - }, + label?: string, +} + +const props = withDefaults(defineProps<Props>(), { + showHours: true, + label: 'Input', }); /** @@ -99,7 +98,7 @@ const seconds = computed({ /** * Update the internal value when the component value changes */ -watch(model, (newValue) => { +watch(model, (newValue: number) => { if (newValue !== internalValue.value) { internalValue.value = newValue; } @@ -108,7 +107,7 @@ watch(model, (newValue) => { /** * Update the component value when the internal value changes */ -watch(internalValue, (newValue) => { +watch(internalValue, (newValue: number) => { model.value = newValue; }); @@ -116,7 +115,7 @@ watch(internalValue, (newValue) => { * Process up and down arrow presses * @param {Object} e The keydown event args */ -function onkeydown(e, step = 1) { +function onkeydown(e: KeyboardEvent, step: number = 1) { if (e.key === 'ArrowUp') { if (Math.floor(internalValue.value) + step > max.value) { internalValue.value = max.value; diff --git a/src/composables/useObjectModel.ts b/src/composables/useObjectModel.ts @@ -0,0 +1,35 @@ +import { ref, watch } from 'vue'; +import type { Ref } from 'vue'; + +import { deepCopy, deepEqual } from '@/core/utils'; + +/* + * Generate an internal ref that implements support for v-model with objects + * @param {Function} prop A function returning the prop + * @param {Function} emit A function for emitting update events + * @returns {Ref<object>} The internal ref + */ +export default function defineObjectModel<T>(prop: () => T, emit: (x: T) => void): Ref<T> { + /** + * The internal value + */ + const internalValue: Ref<T> = ref<T>(prop()) as Ref<T>; + + /** + * Update the internal value when the component value changes + */ + watch(prop, (newValue: T) => { + if (!deepEqual<T>(internalValue.value, newValue)) { + internalValue.value = deepCopy<T>(newValue); + } + }, { deep: true }); + + /** + * Update the component value when the internal value changes + */ + watch(internalValue, (newValue: T) => { + emit(deepCopy<T>(newValue)); + }, { deep: true }); + + return internalValue; +} diff --git a/src/composables/useStorage.js b/src/composables/useStorage.js @@ -1,36 +0,0 @@ -import { ref, onActivated, watchEffect } from 'vue'; - -// The global localStorage prefix -const prefix = 'running-tools'; - -/* - * Create a reactive value that is synced with a localStorage item - * @param {String} key The localStorage item's key - * @defaultValue {Object} defaultValue The default value - */ -export default function useStorage(key, defaultValue) { - const clonedDefault = JSON.parse(JSON.stringify(defaultValue)); - const value = ref(clonedDefault); - - // (Re)load value from localStorage - function updateValue() { - let parsedValue; - try { - parsedValue = JSON.parse(localStorage.getItem(`${prefix}.${key}`)); - } catch { - parsedValue = null; - } - if (parsedValue !== null) value.value = parsedValue; - } - updateValue(); - onActivated(updateValue); - - // Save value to localStorage when modified - watchEffect(() => { - if (typeof localStorage !== 'undefined') { - localStorage.setItem(`${prefix}.${key}`, JSON.stringify(value.value)); - } - }) - - return value -} diff --git a/src/composables/useStorage.ts b/src/composables/useStorage.ts @@ -0,0 +1,32 @@ +import { ref, onActivated, watchEffect } from 'vue'; +import type { Ref } from 'vue'; + +import { deepCopy, getLocalStorage, setLocalStorage } from '@/core/utils'; + +/* + * Create a reactive value that is synced with a localStorage item + * @param {string} key The localStorage item's key + * @param {Type} defaultValue The default value + * @returns {Ref<Type>} The synchronized ref + */ +export default function useStorage<Type>(key: string, defaultValue: Type): Ref<Type> { + const clonedDefault: Type = deepCopy(defaultValue); + const value: Ref<Type> = ref<Type>(clonedDefault) as Ref<Type>; + + // (Re)load value from localStorage + function updateValue() { + const parsedValue = getLocalStorage<Type>(key); + if (parsedValue !== null) value.value = parsedValue; + } + updateValue(); + onActivated(updateValue); + + // Save value to localStorage when modified + watchEffect(() => { + if (typeof localStorage !== 'undefined') { + setLocalStorage<Type>(key, value.value); + } + }) + + return value +} diff --git a/src/core/calculators.ts b/src/core/calculators.ts @@ -0,0 +1,263 @@ +/* + * Contains types and functions for core calculator functionality + */ + +import * as racePrediction from '@/core/racePrediction'; +import type { RacePredictionOptions } from '@/core/racePrediction'; +import { TargetTypes, workoutTargetToString } from '@/core/targets'; +import type { StandardTarget, WorkoutTarget } from '@/core/targets'; +import { DistanceUnits, UnitSystems, convertDistance, detectDefaultUnitSystem, formatDistance, + formatDuration, formatPace, getDefaultDistanceUnit, getDefaultPaceUnit } from '@/core/units'; +import type { DistanceTime } from '@/core/units'; + +/* + * The four main calculators (batch and unit calculators not included) + * + * Used to determine available options and target set format + */ +export enum Calculators { + Pace = 'pace', + Race = 'race', + Split = 'split', + Workout = 'workout', +} + +/* + * The type for the available race statistics + */ +export interface RaceStats { + purdyPoints: number, + vo2Max: number, + vo2: number, + vo2MaxPercentage: number, +}; + +/* + * The type for the options specific to each calculator + */ +export interface GlobalOptions { + defaultUnitSystem: UnitSystems, + racePredictionOptions: RacePredictionOptions, +}; +export interface SplitOptions { + selectedTargetSet: string, +}; +export interface PaceOptions extends SplitOptions { + input: DistanceTime, +}; +export type RaceOptions = PaceOptions; +export interface WorkoutOptions extends PaceOptions { + customTargetNames: boolean, +}; +export interface BatchOptions { + calculator: Calculators.Pace | Calculators.Race | Calculators.Workout, + increment: number, + input: DistanceTime, + label: string, + rows: number, +}; + +/* + * The two possible result fields of a target result: "key" and "value" + */ +export enum ResultType { + Key = 'key', + Value = 'value', +}; + +/* + * The type for target results + */ +export interface TargetResult { + key: string, + value: string, + pace: string, + result: ResultType, + sort: number, +}; + +/* + * The default input and options for each calculator + */ +export const defaultGlobalOptions: GlobalOptions = { + defaultUnitSystem: detectDefaultUnitSystem(), + racePredictionOptions: racePrediction.defaultRacePredictionOptions, +}; +export const defaultInput: DistanceTime = { + distanceValue: 5, + distanceUnit: DistanceUnits.Kilometers, + time: 1200, +}; +export const defaultBatchOptions: BatchOptions = { + calculator: Calculators.Workout, + increment: 15, + input: defaultInput, + label: '', + rows: 20, +}; +export const defaultPaceOptions: PaceOptions = { + input: defaultInput, + selectedTargetSet: '_pace_targets', +}; +export const defaultRaceOptions: RaceOptions = { + input: defaultInput, + selectedTargetSet: '_race_targets', +}; +export const defaultSplitOptions: SplitOptions = { + selectedTargetSet: '_split_targets', +}; +export const defaultWorkoutOptions: WorkoutOptions = { + customTargetNames: false, + input: defaultInput, + selectedTargetSet: '_workout_targets', +}; + +/** + * Calculate results for a standard target + * @param {DistanceTime} input The input pace + * @param {StandardTarget} target The standard target + * @param {Function} calculateTime The function for calculating time results + * @param {Function} calculateDistance The function for calculating distance results + * @param {UnitSystems} defaultUnitSystem The default unit system (imperial or metric) + * @param {Boolean} preciseDurations Whether to return precise, unrounded, durations + * @returns {TargetResult} The result + */ +function calculateStandardResult(input: DistanceTime, target: StandardTarget, + calculateTime: (d1: number, t1: number, d2: number) => number, + calculateDistance: (t1: number, d1: number, t2: number) => number, defaultUnitSystem: UnitSystems, + preciseDurations: boolean = true): TargetResult { + + let distanceValue, distanceUnit, time; + const d1 = convertDistance(input.distanceValue, input.distanceUnit, DistanceUnits.Meters); + if (target.type === TargetTypes.Distance) { + // Add target distance to result + distanceValue = target.distanceValue; + distanceUnit = target.distanceUnit; + + // Calculate time result + const d2 = convertDistance(target.distanceValue, target.distanceUnit, DistanceUnits.Meters); + time = calculateTime(d1, input.time, d2); + } else { + // Add target time to result + time = target.time; + + // Calculate distance result + const d2 = calculateDistance(input.time, d1, target.time); + const units = getDefaultDistanceUnit(defaultUnitSystem); + distanceValue = convertDistance(d2, DistanceUnits.Meters, units); + distanceUnit = units; + } + + return { + // Convert distance to key string + key: formatDistance({ distanceValue, distanceUnit }, target.type === TargetTypes.Time), + + // Convert time to time string + value: formatDuration(time, 3, preciseDurations ? 2 : 0, target.type === TargetTypes.Distance), + + // Convert pace to pace string + pace: formatPace({ time, distanceValue, distanceUnit }, getDefaultPaceUnit(defaultUnitSystem)), + + // Convert dist/time result to key/value + result: target.type === TargetTypes.Distance ? ResultType.Value : ResultType.Key, + + // Use time (in seconds) as sort key + sort: time, + }; +} + +/** + * Calculate paces from a target + * @param {DistanceTime} input The input pace + * @param {StandardTarget} target The pace target + * @param {UnitSystems} defaultUnitSystem The default unit system (imperial or metric) + * @param {Boolean} preciseDurations Whether to return precise, unrounded, durations + * @returns {TargetResult} The result + */ +export function calculatePaceResults(input: DistanceTime, target: StandardTarget, + defaultUnitSystem: UnitSystems, + preciseDurations: boolean): TargetResult { + + return calculateStandardResult(input, target, (d1, t1, d2) => ((t1 / d1) * d2), + (t1, d1, t2) => ((d1 / t1) * t2), defaultUnitSystem, preciseDurations); +} + +/** + * Predict race results from a target + * @param {DistanceTime} input The input race + * @param {StandardTarget} target The race target + * @param {RacePredictionOptions} racePredictionOptions The race prediction options + * @param {UnitSystems} defaultUnitSystem The default unit system (imperial or metric) + * @param {Boolean} preciseDurations Whether to return precise, unrounded, durations + * @returns {TargetResult} The result + */ +export function calculateRaceResults(input: DistanceTime, target: StandardTarget, + racePredictionOptions: RacePredictionOptions, + defaultUnitSystem: UnitSystems, preciseDurations: boolean + ): TargetResult { + + return calculateStandardResult(input, target, + (d1, t1, d2) => racePrediction.predictTime(d1, t1, d2, racePredictionOptions), + (t1, d1, t2) => racePrediction.predictDistance(t1, d1, t2, racePredictionOptions), + defaultUnitSystem, preciseDurations); +} + +/** + * Calculate race statistics from an input race + * @param {DistanceTime} input The input race + * @returns {RaceStats} The race statistics + */ +export function calculateRaceStats(input: DistanceTime): RaceStats { + const d1 = convertDistance(input.distanceValue, input.distanceUnit, DistanceUnits.Meters); + + return { + purdyPoints: racePrediction.getPurdyPoints(d1, input.time), + vo2Max: racePrediction.getVO2Max(d1, input.time), + vo2: racePrediction.getVO2(d1, input.time), + vo2MaxPercentage: racePrediction.getVO2Percentage(input.time) * 100, + } +} + +/** + * Predict workout results from a target + * @param {DistanceTime} input The input race + * @param {WorkoutTarget} target The workout target + * @param {RacePredictionOptions} racePredictionOptions The race prediction options + * @param {Boolean} customTargetNames Whether to use custom target names + * @param {Boolean} preciseDurations Whether to return precise, unrounded, durations + * @returns {TargetResult} The result + */ +export function calculateWorkoutResults(input: DistanceTime, target: WorkoutTarget, + racePredictionOptions: RacePredictionOptions, + customTargetNames: boolean, preciseDurations: boolean + ): TargetResult { + // Initialize distance and time variables + const d1 = convertDistance(input.distanceValue, input.distanceUnit, DistanceUnits.Meters); + const t1 = input.time; + const d3 = convertDistance(target.splitValue, target.splitUnit, DistanceUnits.Meters); + let d2, t2; + + // Calculate result + if (target.type === 'distance') { + // Convert target distance into meters + d2 = convertDistance(target.distanceValue, target.distanceUnit, DistanceUnits.Meters); + + // Get workout split prediction + t2 = racePrediction.predictTime(d1, input.time, d2, racePredictionOptions); + } else { + t2 = target.time; + + // Get workout split prediction + d2 = racePrediction.predictDistance(t1, d1, t2, racePredictionOptions); + } + const t3 = (t2 / d2) * d3; + + // Return result + return { + key: (customTargetNames && target.customName) || workoutTargetToString(target), + value: formatDuration(t3, 3, preciseDurations ? 2 : 0, true), + pace: '', // Pace not used in workout calculator + result: ResultType.Value, + sort: t3, + } +} diff --git a/src/core/migrations.ts b/src/core/migrations.ts @@ -0,0 +1,182 @@ +/* + * Contains a function for migrating localStorage items after app updates + */ + +import { defaultBatchOptions, defaultGlobalOptions, defaultPaceOptions, defaultRaceOptions, + defaultSplitOptions, defaultWorkoutOptions } from '@/core/calculators'; +import { deepCopy, getLocalStorage, setLocalStorage, unsetLocalStorage } from '@/core/utils'; + +/* + * The type for string-indexable objects + */ +type dict = { + [key: string]: json, +}; + +/* + * The type for JSON-compatable values + */ +type json = dict | string | number | boolean; + +/** + * Get the value of an arbitrary property on an object + * @param {dict} obj The object + * @param {string} key The property path + * @returns {json | undefined} The value of the property + */ +function getObjProperty(obj: dict, key: string): json | undefined { + const keys = key.split("."); + while (true) { + if (keys.length === 0) { + return obj; + } else if (obj[keys[0]] === undefined) { + return undefined; + } else { + obj = obj[keys[0]] as dict; + keys.shift(); + } + } +} + +/** + * Set the value of an arbitrary property on an object + * @param {dict} obj The object + * @param {string} key The property path + * @param {json} value The new value of the property + */ +function setObjProperty(obj: dict, key: string, value: json) { + const keys = key.split("."); + while (true) { + if (keys.length === 1) { + obj[keys[0]] = value; + return; + } else if (obj[keys[0]] === undefined) { + obj[keys[0]] = {}; + obj = obj[keys[0]] as dict; + keys.shift(); + } else { + obj = obj[keys[0]] as dict; + keys.shift(); + } + } +} + +/** + * Remove an arbitrary property on an object + * @param {dict} obj The object + * @param {string} key The property path + */ +function removeObjProperty(obj: dict, key: string) { + const keys = key.split("."); + while (true) { + if (keys.length === 1) { + delete obj[keys[0]]; + return; + } else if (obj[keys[0]] === undefined) { + return; + } else { + obj = obj[keys[0]] as dict; + keys.shift(); + } + } +} + +/** + * Add a property to an existing localStorage item + * @param {string} dest The localStorage item + * @param {string} key The localStorage item property path + * @param {object | string | number | boolean} value The default property value + */ +function addProperty(dest: string, key: string, value: object | string | number | boolean) { + const dest_value = getLocalStorage<dict>(dest); + if (dest_value !== null && getObjProperty(dest_value, key) === undefined) { + setObjProperty(dest_value, key, deepCopy(value as json)); + setLocalStorage(dest, dest_value); + } +} + +/** + * Move an existing localStorage property to a new location + * @param {string} src The original localStorage item + * @param {string} src_key The original localStorage item property path + * @param {string} dest The new parent localStorage item + * @param {string} dest_key The new localStorage item property path + * @param {object} dest_default The default value of the new parent localStorage item + */ +function moveProperty(src: string, src_key: string, dest: string, dest_key: string, + dest_default: object) { + const src_value = getLocalStorage<dict>(src); + const dest_value = getLocalStorage<dict>(dest) || deepCopy(dest_default as dict); + if (src_value !== null && getObjProperty(src_value, src_key) !== undefined) { + setObjProperty(dest_value, dest_key, getObjProperty(src_value, src_key) as json); + setLocalStorage(dest, dest_value); + removeObjProperty(src_value, src_key); + setLocalStorage(src, src_value); + } + addProperty(dest, dest_key, getObjProperty(dest_default as dict, dest_key) as json); +} + +/** + * Move an existing localStorage item to a property of another localStorage item + * @param {string} src The original localStorage item + * @param {string} dest The new parent localStorage item + * @param {string} dest_key The new localStorage item property path + * @param {object} dest_default The default value of the new parent localStorage item + */ +function moveItemToProperty(src: string, dest: string, dest_key: string, dest_default: object) { + const src_value = getLocalStorage<dict>(src); + const dest_value = getLocalStorage<dict>(dest) || deepCopy(dest_default as dict); + if (src_value !== null) { + setObjProperty(dest_value, dest_key, src_value); + setLocalStorage(dest, dest_value); + unsetLocalStorage(src); + } + addProperty(dest, dest_key, (dest_default as dict)[dest_key]); +} + +/** + * Migrate outdated localStorage options + */ +export function migrateLocalStorage() { + // Move default-unit-system to global-options.defaultUnitSystem (>1.4.1) + moveItemToProperty('default-unit-system', 'global-options', 'defaultUnitSystem', + defaultGlobalOptions); + + // Move {race,workout}-calculator-options.{model,riegelExponent} into + // global-options.racePredictionOptions (>1.4.1) + moveProperty('workout-calculator-options', 'model', 'global-options', + 'racePredictionOptions.model', defaultGlobalOptions); + moveProperty('workout-calculator-options', 'riegelExponent', 'global-options', + 'racePredictionOptions.riegelExponent', defaultGlobalOptions); + moveProperty('race-calculator-options', 'model', 'global-options', + 'racePredictionOptions.model', defaultGlobalOptions); + moveProperty('race-calculator-options', 'riegelExponent', 'global-options', + 'racePredictionOptions.riegelExponent', defaultGlobalOptions); + + // Add label property to batch-calculator-options (>1.4.1) + addProperty('batch-calculator-options', 'label', defaultBatchOptions.label); + + // Add customTargetNames property to workout-calculator-options (>1.4.1) + addProperty('workout-calculator-options', 'customTargetNames', + defaultWorkoutOptions.customTargetNames); + + // Move *-calculator-input into *-calculator-options (>1.4.1) + moveItemToProperty('batch-calculator-input', 'batch-calculator-options', + 'input', defaultBatchOptions); + moveItemToProperty('pace-calculator-input', 'pace-calculator-options', + 'input', defaultPaceOptions); + moveItemToProperty('race-calculator-input', 'race-calculator-options', + 'input', defaultRaceOptions); + moveItemToProperty('workout-calculator-input', 'workout-calculator-options', + 'input', defaultWorkoutOptions); + + // Move *-calculator-target-set into *-calculator-options (>1.4.1) + moveItemToProperty('pace-calculator-target-set', 'pace-calculator-options', + 'selectedTargetSet', defaultPaceOptions); + moveItemToProperty('race-calculator-target-set', 'race-calculator-options', + 'selectedTargetSet', defaultRaceOptions); + moveItemToProperty('split-calculator-target-set', 'split-calculator-options', + 'selectedTargetSet', defaultSplitOptions); + moveItemToProperty('workout-calculator-target-set', 'workout-calculator-options', + 'selectedTargetSet', defaultWorkoutOptions); +} diff --git a/src/core/racePrediction.ts b/src/core/racePrediction.ts @@ -0,0 +1,505 @@ +/* + * Implements various race prediction models + */ + +/* + * The available race prediction models + */ +export enum RacePredictionModels { + AverageModel = 'AverageModel', + PurdyPointsModel = 'PurdyPointsModel', + VO2MaxModel = 'VO2MaxModel', + RiegelModel = 'RiegelModel', + CameronModel = 'CameronModel', +}; + +/* + * The type for race prediction options + */ +export interface RacePredictionOptions { + model: RacePredictionModels, + riegelExponent: number, +}; + +/* + * The default race prediction options + */ +export const defaultRacePredictionOptions = { + model: RacePredictionModels.AverageModel, + riegelExponent: 1.06, +}; + +/* + * The type for internal variables used by the Purdy Points race prediction model + */ +interface PurdyPointsVariables { + twsec: number, + a: number, + b: number, +}; + +/** + * Estimate the point at which a function returns a target value using Newton's Method + * @param {number} initialEstimate The initial estimate + * @param {number} target The target function output + * @param {Function} method The function + * @param {Function} derivative The function derivative + * @param {number} precision The acceptable precision + * @returns {number} The refined estimate + */ +function NewtonsMethod(initialEstimate: number, target: number, method: (x: number) => number, + derivative: (x: number) => number, precision: number): number { + // Initialize estimate + let estimate = initialEstimate; + let estimateValue; + + for (let i = 0; i < 500; i += 1) { + // Evaluate function at estimate + estimateValue = method(estimate); + + // Check if estimate is close enough (usually occurs way before i = 500) + if (Math.abs(target - estimateValue) < precision) { + break; + } + + // Refine estimate + estimate -= (estimateValue - target) / derivative(estimate); + } + + // Return refined estimate + return estimate; +} + +/* + * Methods that implement the Purdy Points race prediction model + * https://www.cs.uml.edu/~phoffman/xcinfo3.html + */ +const PurdyPointsModel = { + /** + * Calculate the Purdy Point variables for a distance + * @param {number} d The distance in meters + * @returns {PurdyPointsVariables} The Purdy Point variables + */ + getVariables(d: number): PurdyPointsVariables { + // Declare constants + const c1 = 11.15895; + const c2 = 4.304605; + const c3 = 0.5234627; + const c4 = 4.031560; + const c5 = 2.316157; + const r1 = 3.796158e-2; + const r2 = 1.646772e-3; + const r3 = 4.107670e-4; + const r4 = 7.068099e-6; + const r5 = 5.220990e-9; + + // Calculate world record velocity from running curve + const v = (-c1 * Math.exp(-r1 * d)) + + (c2 * Math.exp(-r2 * d)) + + (c3 * Math.exp(-r3 * d)) + + (c4 * Math.exp(-r4 * d)) + + (c5 * Math.exp(-r5 * d)); + + // Calculate world record time + const twsec = d / v; + + // Calculate constants + const k = 0.0654 - (0.00258 * v); + const a = 85 / k; + const b = 1 - (1035 / a); + + // Return Purdy Point variables + return { + twsec, + a, + b, + }; + }, + + /** + * Get the Purdy Points for a race + * @param {number} d The distance of the race in meters + * @param {number} t The finish time of the race in seconds + * @returns {number} The Purdy Points for the race + */ + getPurdyPoints(d: number, t: number): number { + // Get variables + const variables = PurdyPointsModel.getVariables(d); + + // Calculate Purdy Points + const points = variables.a * ((variables.twsec / t) - variables.b); + + // Return Purdy Points + return points; + }, + + /** + * Predict a race time using the Purdy Points Model + * @param {number} d1 The distance of the input race in meters + * @param {number} t1 The finish time of the input race in seconds + * @param {number} d2 The distance of the output race in meters + * @returns {number} The predicted time for the output race in seconds + */ + predictTime(d1: number, t1: number, d2: number): number { + // Calculate Purdy Points for distance 1 + const points = PurdyPointsModel.getPurdyPoints(d1, t1); + + // Calculate time for distance 2 + const variables = PurdyPointsModel.getVariables(d2); + const seconds = (variables.a * variables.twsec) / (points + (variables.a * variables.b)); + + // Return predicted time + return seconds; + }, + + /** + * Calculate the derivative with respect to distance of the Purdy Points curve at a specific point + * @param {number} d1 The distance of the input race in meters + * @param {number} t1 The finish time of the input race in seconds + * @param {number} d2 The distance of the output race in meters + * @return {number} The derivative with respect to distance + */ + derivative(d1: number, t1: number, d2: number): number { + const result = (85 * d2) / (((2316157 * Math.exp(-(522099 * d2) / 100000000000000)) / 1000000 + + (100789 * Math.exp(-(7068099 * d2) / 1000000000000)) / 25000 + (5234627 * Math.exp(-(410767 + * d2) / 1000000000)) / 10000000 + (860921 * Math.exp(-(411693 * d2) / 250000000)) / 200000 + - (223179 * Math.exp(-(1898079 * d2) / 50000000)) / 20000) * (327 / 5000 - (129 * ((2316157 + * Math.exp(-(522099 * d2) / 100000000000000)) / 1000000 + (100789 * Math.exp(-(7068099 * d2) + / 1000000000000)) / 25000 + (5234627 * Math.exp(-(410767 * d2) / 1000000000)) / 10000000 + + (860921 * Math.exp(-(411693 * d2) / 250000000)) / 200000 - (223179 * Math.exp(-(1898079 + * d2) / 50000000)) / 20000)) / 50000) * ((85 * (1 - (207 * (327 / 5000 - (129 * ((2316157 + * Math.exp(-(522099 * d2) / 100000000000000)) / 1000000 + (100789 * Math.exp(-(7068099 * d2) + / 1000000000000)) / 25000 + (5234627 * Math.exp(-(410767 * d2) / 1000000000)) / 10000000 + + (860921 * Math.exp(-(411693 * d2) / 250000000)) / 200000 - (223179 * Math.exp(-(1898079 + * d2) / 50000000)) / 20000)) / 50000)) / 17)) / (327 / 5000 - (129 * ((2316157 + * Math.exp(-(522099 * d2) / 100000000000000)) / 1000000 + (100789 * Math.exp(-(7068099 * d2) + / 1000000000000)) / 25000 + (5234627 * Math.exp(-(410767 * d2) / 1000000000)) / 10000000 + + (860921 * Math.exp(-(411693 * d2) / 250000000)) / 200000 - (223179 * Math.exp(-(1898079 + * d2) / 50000000)) / 20000)) / 50000) + (85 * (d1 / (((2316157 * Math.exp(-(522099 * d1) + / 100000000000000)) / 1000000 + (100789 * Math.exp(-(7068099 * d1) / 1000000000000)) / 25000 + + (5234627 * Math.exp(-(410767 * d1) / 1000000000)) / 10000000 + (860921 * Math.exp(-(411693 + * d1) / 250000000)) / 200000 - (223179 * Math.exp(-(1898079 * d1) / 50000000)) / 20000) * t1) + + (207 * (327 / 5000 - (129 * ((2316157 * Math.exp(-(522099 * d1) / 100000000000000)) + / 1000000 + (100789 * Math.exp(-(7068099 * d1) / 1000000000000)) / 25000 + (5234627 + * Math.exp(-(410767 * d1) / 1000000000)) / 10000000 + (860921 * Math.exp(-(411693 * d1) + / 250000000)) / 200000 - (223179 * Math.exp(-(1898079 * d1) / 50000000)) / 20000)) / 50000)) + / 17 - 1)) / (327 / 5000 - (129 * ((2316157 * Math.exp(-(522099 * d1) / 100000000000000)) + / 1000000 + (100789 * Math.exp(-(7068099 * d1) / 1000000000000)) / 25000 + (5234627 + * Math.exp(-(410767 * d1) / 1000000000)) / 10000000 + (860921 * Math.exp(-(411693 * d1) + / 250000000)) / 200000 - (223179 * Math.exp(-(1898079 * d1) / 50000000)) / 20000)) / 50000))); + return result; + }, + + /** + * Predict a race distance using the Purdy Points Model + * @param {number} t1 The finish time of the input race in seconds + * @param {number} d1 The distance of the input race in meters + * @param {number} t2 The finish time of the output race in seconds + * @returns {number} The predicted distance for the output race in meters + */ + predictDistance(t1: number, d1: number, t2: number): number { + // Initialize estimate + let estimate = (d1 * t2) / t1; + + // Refine estimate (derivative on its own is too slow) + const method = (x: number) => PurdyPointsModel.predictTime(d1, t1, x); + const derivative = (x: number) => PurdyPointsModel.derivative(d1, t1, x) / 500; + estimate = NewtonsMethod(estimate, t2, method, derivative, 0.01); + + // Return estimate + return estimate; + }, +}; + +/* + * Methods that implement the VO2 Max race prediction model + * http://run-down.com/statistics/calcs_explained.php + * https://vdoto2.com/Calculator + */ +const VO2MaxModel = { + /** + * Calculate the VO2 of a runner during a race + * @param {number} d The race distance in meters + * @param {number} t The finish time in seconds + * @returns {number} The VO2 + */ + getVO2(d: number, t: number): number { + const minutes = t / 60; + const v = d / minutes; + const result = -4.6 + (0.182258 * v) + (0.000104 * (v ** 2)); + return result; + }, + + /** + * Calculate the percentage of VO2 max a runner is at during a race + * @param {number} t The race time in seconds + * @returns {number} The percentage of VO2 max + */ + getVO2Percentage(t: number): number { + const minutes = t / 60; + const result = 0.8 + (0.189439 * Math.exp(-0.012778 * minutes)) + (0.298956 * Math.exp(-0.193261 + * minutes)); + return result; + }, + + /** + * Calculate a runner's VO2 max from a race result + * @param {number} d The race distance in meters + * @param {number} t The finish time in seconds + * @returns {number} The runner's VO2 max + */ + getVO2Max(d: number, t: number): number { + const result = VO2MaxModel.getVO2(d, t) / VO2MaxModel.getVO2Percentage(t); + return result; + }, + + /** + * Calculate the derivative with respect to time of the VO2 max curve at a specific point + * @param {number} d The race distance in meters + * @param {number} t The finish time in seconds + * @return {number} The derivative with respect to time + */ + VO2MaxTimeDerivative(d: number, t: number): number { + const result = (-(273 * d) / (25 * (t ** 2)) - (468 * (d ** 2)) / (625 * (t ** 3))) / ((189 + * Math.exp(-(2 * t) / 9375)) / 1000 + (299 * Math.exp(-(193 * t) / 60000)) / 1000 + 4 / 5) + - (((273 * d) / (25 * t) + (234 * (d ** 2)) / (625 * (t ** 2)) - 23 / 5) * (-(63 + * Math.exp(-(2 * t) / 9375)) / 1562500 - (57707 * Math.exp(-(193 * t) / 60000)) / 60000000)) + / (((189 * Math.exp(-(2 * t) / 9375)) / 1000 + (299 * Math.exp(-(193 * t) / 60000)) / 1000 + + 4 / 5) ** 2); + return result; + }, + + /** + * Predict a race time using the VO2 Max Model + * @param {number} d1 The distance of the input race in meters + * @param {number} t1 The finish time of the input race in seconds + * @param {number} d2 The distance of the output race in meters + * @returns {number} The predicted time for the output race in seconds + */ + predictTime(d1: number, t1: number, d2: number): number { + // Calculate input VO2 max + const inputVO2Max = VO2MaxModel.getVO2Max(d1, t1); + + // Initialize estimate + let estimate = (t1 * d2) / d1; + + // Refine estimate + const method = (x: number) => VO2MaxModel.getVO2Max(d2, x); + const derivative = (x: number) => VO2MaxModel.VO2MaxTimeDerivative(d2, x); + estimate = NewtonsMethod(estimate, inputVO2Max, method, derivative, 0.0001); + + // Return estimate + return estimate; + }, + + /** + * Calculate the derivative with respect to distance of the VO2 max curve at a specific point + * @param {number} d The race distance in meters + * @param {number} t The finish time in seconds + * @return {number} The derivative with respect to distance + */ + VO2MaxDistanceDerivative(d: number, t: number): number { + const result = ((468 * d) / (625 * (t ** 2)) + 273 / (25 * t)) / ((189 * Math.exp(-(2 * t) + / 9375)) / 1000 + (299 * Math.exp(-(193 * t) / 60000)) / 1000 + 4 / 5); + return result; + }, + + /** + * Predict a race distance using the VO2 Max Model + * @param {number} t1 The finish time of the input race in seconds + * @param {number} d1 The distance of the input race in meters + * @param {number} t2 The finish time of the output race in seconds + * @returns {number} The predicted distance for the output race in meters + */ + predictDistance(t1: number, d1: number, t2: number): number { + // Calculate input VO2 max + const inputVO2 = VO2MaxModel.getVO2Max(d1, t1); + + // Initialize estimate + let estimate = (d1 * t2) / t1; + + // Refine estimate + const method = (x: number) => VO2MaxModel.getVO2Max(x, t2); + const derivative = (x: number) => VO2MaxModel.VO2MaxDistanceDerivative(x, t2); + estimate = NewtonsMethod(estimate, inputVO2, method, derivative, 0.0001); + + // Return estimate + return estimate; + }, +}; + +/* + * Methods that implement Dave Cameron's race prediction model + * https://www.cs.uml.edu/~phoffman/cammod.html + */ +const CameronModel = { + /** + * Predict a race time using Dave Cameron's Model + * @param {number} d1 The distance of the input race in meters + * @param {number} t1 The finish time of the input race in seconds + * @param {number} d2 The distance of the output race in meters + * @returns {number} The predicted time for the output race in seconds + */ + predictTime(d1: number, t1: number, d2: number): number { + const a = 13.49681 - (0.000030363 * d1) + (835.7114 / (d1 ** 0.7905)); + const b = 13.49681 - (0.000030363 * d2) + (835.7114 / (d2 ** 0.7905)); + return (t1 / d1) * (a / b) * d2; + }, + + /** + * Calculate the derivative with respect to distance of the Cameron curve at a specific point + * @param {number} d1 The distance of the input race in meters + * @param {number} t1 The finish time of the input race in seconds + * @param {number} d2 The distance of the output race in meters + * @return {number} The derivative with respect to distance + */ + derivative(d1: number, t1: number, d2: number): number { + const result = -(100 * (30363 * (d1 ** (3581 / 2000)) - 13496810000 * (d1 ** (1581 / 2000)) + - 835711400000) * t1 * (134968100 * (d2 ** (3581 / 2000)) + 14963412617 * d2)) / ((d1 ** (3581 + / 2000)) * (d2 ** (419 / 2000)) * ((30363 * (d2 ** (3581 / 2000)) - 13496810000 * (d2 ** (1581 + / 2000)) - 835711400000) ** 2)); + return result; + }, + + /** + * Predict a race distance using Dave Cameron's Model + * @param {number} t1 The finish time of the input race in seconds + * @param {number} d1 The distance of the input race in meters + * @param {number} t2 The finish time of the output race in seconds + * @returns {number} The predicted distance for the output race in meters + */ + predictDistance(t1: number, d1: number, t2: number): number { + // Initialize estimate + let estimate = (d1 * t2) / t1; + + // Refine estimate + const method = (x: number) => CameronModel.predictTime(d1, t1, x); + const derivative = (x: number) => CameronModel.derivative(d1, t1, x); + estimate = NewtonsMethod(estimate, t2, method, derivative, 0.01); + + // Return estimate + return estimate; + }, +}; + +/* + * Methods that implement Pete Riegel's race prediction model + * https://en.wikipedia.org/wiki/Peter_Riegel + */ +const RiegelModel = { + /** + * Predict a race time using Pete Riegel's Model + * @param {number} d1 The distance of the input race in meters + * @param {number} t1 The finish time of the input race in seconds + * @param {number} d2 The distance of the output race in meters + * @param {number} c The value of the exponent in the equation + * @returns {number} The predicted time for the output race in seconds + */ + predictTime(d1: number, t1: number, d2: number, c: number = 1.06): number { + return t1 * ((d2 / d1) ** c); + }, + + /** + * Predict a race distance using Pete Riegel's Model + * @param {number} t1 The finish time of the input race in seconds + * @param {number} d1 The distance of the input race in meters + * @param {number} t2 The finish time of the output race in seconds + * @param {number} c The value of the exponent in the equation + * @returns {number} The predicted distance for the output race in meters + */ + predictDistance(t1: number, d1: number, t2: number, c: number = 1.06) { + return d1 * ((t2 / t1) ** (1 / c)); + }, +}; + +/* + * Methods that average the results of different race prediction models + */ +const AverageModel = { + /** + * Predict a race time by averaging the results of different models + * @param {number} d1 The distance of the input race in meters + * @param {number} t1 The finish time of the input race in seconds + * @param {number} d2 The distance of the output race in meters + * @param {number} c The value of the exponent in Pete Riegel's Model + * @returns {number} The predicted time for the output race in seconds + */ + predictTime(d1: number, t1: number, d2: number, c: number = 1.06): number { + const purdy = PurdyPointsModel.predictTime(d1, t1, d2); + const vo2max = VO2MaxModel.predictTime(d1, t1, d2); + const cameron = CameronModel.predictTime(d1, t1, d2); + const riegel = RiegelModel.predictTime(d1, t1, d2, c); + return (purdy + vo2max + cameron + riegel) / 4; + }, + + /** + * Predict a race distance by averaging the results of different models + * @param {number} t1 The finish time of the input race in seconds + * @param {number} d1 The distance of the input race in meters + * @param {number} t2 The finish time of the output race in seconds + * @param {number} c The value of the exponent in Pete Riegel's Model + * @returns {number} The predicted distance for the output race in meters + */ + predictDistance(t1: number, d1: number, t2: number, c: number = 1.06) { + const purdy = PurdyPointsModel.predictDistance(t1, d1, t2); + const vo2max = VO2MaxModel.predictDistance(t1, d1, t2); + const cameron = CameronModel.predictDistance(t1, d1, t2); + const riegel = RiegelModel.predictDistance(t1, d1, t2, c); + return (purdy + vo2max + cameron + riegel) / 4; + }, +}; + +/** + * Predict a race time + * @param {number} d1 The distance of the input race in meters + * @param {number} t1 The finish time of the input race in seconds + * @param {number} d2 The distance of the output race in meters + * @param {RacePredictionOptions} options The race prediction options + * @param {number} The predicted finish time in seconds + */ +export function predictTime(d1: number, t1: number, d2: number, + options: RacePredictionOptions): number { + switch (options.model) { + default: + case RacePredictionModels.AverageModel: + return AverageModel.predictTime(d1, t1, d2, options.riegelExponent); + case RacePredictionModels.PurdyPointsModel: + return PurdyPointsModel.predictTime(d1, t1, d2); + case RacePredictionModels.VO2MaxModel: + return VO2MaxModel.predictTime(d1, t1, d2); + case RacePredictionModels.RiegelModel: + return RiegelModel.predictTime(d1, t1, d2, options.riegelExponent); + case RacePredictionModels.CameronModel: + return CameronModel.predictTime(d1, t1, d2); + } +} + +/** + * Predict a race distance + * @param {number} t1 The finish time of the input race in seconds + * @param {number} d1 The distance of the input race in meters + * @param {number} t2 The finish time of the output race in seconds + * @param {RacePredictionOptions} options The race prediction options + * @param {number} The predicted finish distance in meters + */ +export function predictDistance(t1: number, d1: number, t2: number, + options: RacePredictionOptions): number { + switch (options.model) { + default: + case RacePredictionModels.AverageModel: + return AverageModel.predictDistance(t1, d1, t2, options.riegelExponent); + case RacePredictionModels.PurdyPointsModel: + return PurdyPointsModel.predictDistance(t1, d1, t2); + case RacePredictionModels.VO2MaxModel: + return VO2MaxModel.predictDistance(t1, d1, t2); + case RacePredictionModels.RiegelModel: + return RiegelModel.predictDistance(t1, d1, t2, options.riegelExponent); + case RacePredictionModels.CameronModel: + return CameronModel.predictDistance(t1, d1, t2); + } +} + +export const getPurdyPoints = PurdyPointsModel.getPurdyPoints; +export const getVO2 = VO2MaxModel.getVO2; +export const getVO2Percentage = VO2MaxModel.getVO2Percentage; +export const getVO2Max = VO2MaxModel.getVO2Max; diff --git a/src/core/targets.ts b/src/core/targets.ts @@ -0,0 +1,211 @@ +/* + * Contains types and helper functions for calculator target sets + */ + +import { DistanceUnits, convertDistance, formatDistance, formatDuration } from '@/core/units'; +import type { Distance } from '@/core/units'; + +/* + * The two basic types of targets: those defined by distance and those defined by time + */ +export enum TargetTypes { + Distance = 'distance', + Time = 'time', +}; + +/* + * The types for basic standard targets and target sets used by the pace and race calculators + */ +interface DistanceTarget { + type: TargetTypes.Distance, + distanceValue: number, + distanceUnit: DistanceUnits, +}; +interface TimeTarget { + type: TargetTypes.Time, + time: number, +}; +export type StandardTarget = DistanceTarget | TimeTarget; +export interface StandardTargetSet { + name: string, + targets: Array<StandardTarget>, +}; +export interface StandardTargetSets { + [key: string]: StandardTargetSet, +}; + +/* + * The types for split calculator targets and target sets + */ +export type SplitTarget = DistanceTarget & { + splitTime?: number +}; +export interface SplitTargetSet { + name: string, + targets: Array<SplitTarget>, +}; +export interface SplitTargetSets { + [key: string]: SplitTargetSet, +}; + +/* + * The types for workout calculator targets and target sets + */ +export type WorkoutTarget = StandardTarget & { + splitValue: number, + splitUnit: DistanceUnits, + customName?: string, +}; +export interface WorkoutTargetSet { + name: string, + targets: Array<WorkoutTarget>, +}; +export interface WorkoutTargetSets { + [key: string]: WorkoutTargetSet, +}; + +/* + * The types for generic targets and target sets + */ +export type Target = StandardTarget | SplitTarget | WorkoutTarget; +export type TargetSet = StandardTargetSet | SplitTargetSet | WorkoutTargetSet; +export type TargetSets = StandardTargetSets | SplitTargetSets | WorkoutTargetSets; + +/** + * Sort an array of targets + * @param {Array<Target>} targets The array of targets + * @returns {Array<Target>} The sorted targets + */ +export function sort(targets: Array<Target>): Array<Target> { + return [ + ...targets.filter((item) => item.type === TargetTypes.Distance) + .sort((a, b) => convertDistance(a.distanceValue, a.distanceUnit, DistanceUnits.Meters) + - convertDistance(b.distanceValue, b.distanceUnit, DistanceUnits.Meters)), + + ...targets.filter((item) => item.type === TargetTypes.Time) + .sort((a, b) => a.time - b.time), + ]; +} + +/** + * Generate a string description of a workout target + * @param {WorkoutTarget} target The workout target + * @return {string} The string description + */ +export function workoutTargetToString(target: WorkoutTarget): string { + let result = formatDistance({ distanceValue: target.splitValue, distanceUnit: target.splitUnit }, + false); + + if (target.type === TargetTypes.Time) { + result += ' @ ' + formatDuration(target.time, 3, 2, false); + } else if (target.distanceValue != target.splitValue || target.distanceUnit != target.splitUnit) { + result += ' @ ' + formatDistance(target as Distance, false); + } + return result; +} + +/* + * The default target sets for each calculator + */ +export const defaultTargetSets: { [key: string]: TargetSet } = { + '_pace_targets': { + name: 'Common Pace Targets', + targets: sort([ + { type: TargetTypes.Distance, distanceValue: 100, distanceUnit: DistanceUnits.Meters }, + { type: TargetTypes.Distance, distanceValue: 200, distanceUnit: DistanceUnits.Meters }, + { type: TargetTypes.Distance, distanceValue: 300, distanceUnit: DistanceUnits.Meters }, + { type: TargetTypes.Distance, distanceValue: 400, distanceUnit: DistanceUnits.Meters }, + { type: TargetTypes.Distance, distanceValue: 600, distanceUnit: DistanceUnits.Meters }, + { type: TargetTypes.Distance, distanceValue: 800, distanceUnit: DistanceUnits.Meters }, + { type: TargetTypes.Distance, distanceValue: 1000, distanceUnit: DistanceUnits.Meters }, + { type: TargetTypes.Distance, distanceValue: 1200, distanceUnit: DistanceUnits.Meters }, + { type: TargetTypes.Distance, distanceValue: 1500, distanceUnit: DistanceUnits.Meters }, + { type: TargetTypes.Distance, distanceValue: 1600, distanceUnit: DistanceUnits.Meters }, + { type: TargetTypes.Distance, distanceValue: 3200, distanceUnit: DistanceUnits.Meters }, + + { type: TargetTypes.Distance, distanceValue: 2, distanceUnit: DistanceUnits.Kilometers }, + { type: TargetTypes.Distance, distanceValue: 3, distanceUnit: DistanceUnits.Kilometers }, + { type: TargetTypes.Distance, distanceValue: 4, distanceUnit: DistanceUnits.Kilometers }, + { type: TargetTypes.Distance, distanceValue: 5, distanceUnit: DistanceUnits.Kilometers }, + { type: TargetTypes.Distance, distanceValue: 6, distanceUnit: DistanceUnits.Kilometers }, + { type: TargetTypes.Distance, distanceValue: 8, distanceUnit: DistanceUnits.Kilometers }, + { type: TargetTypes.Distance, distanceValue: 10, distanceUnit: DistanceUnits.Kilometers }, + + { type: TargetTypes.Distance, distanceValue: 1, distanceUnit: DistanceUnits.Miles }, + { type: TargetTypes.Distance, distanceValue: 2, distanceUnit: DistanceUnits.Miles }, + { type: TargetTypes.Distance, distanceValue: 3, distanceUnit: DistanceUnits.Miles }, + { type: TargetTypes.Distance, distanceValue: 5, distanceUnit: DistanceUnits.Miles }, + { type: TargetTypes.Distance, distanceValue: 6, distanceUnit: DistanceUnits.Miles }, + { type: TargetTypes.Distance, distanceValue: 8, distanceUnit: DistanceUnits.Miles }, + { type: TargetTypes.Distance, distanceValue: 10, distanceUnit: DistanceUnits.Miles }, + + { type: TargetTypes.Distance, distanceValue: 0.5, distanceUnit: DistanceUnits.Marathons }, + { type: TargetTypes.Distance, distanceValue: 1, distanceUnit: DistanceUnits.Marathons }, + + { type: TargetTypes.Time, time: 600 }, + { type: TargetTypes.Time, time: 1800 }, + { type: TargetTypes.Time, time: 3600 }, + ]), + }, '_race_targets': { + name: 'Common Race Targets', + targets: sort([ + { type: TargetTypes.Distance, distanceValue: 400, distanceUnit: DistanceUnits.Meters }, + { type: TargetTypes.Distance, distanceValue: 800, distanceUnit: DistanceUnits.Meters }, + { type: TargetTypes.Distance, distanceValue: 1500, distanceUnit: DistanceUnits.Meters }, + { type: TargetTypes.Distance, distanceValue: 1600, distanceUnit: DistanceUnits.Meters }, + { type: TargetTypes.Distance, distanceValue: 1, distanceUnit: DistanceUnits.Miles }, + { type: TargetTypes.Distance, distanceValue: 3000, distanceUnit: DistanceUnits.Meters }, + { type: TargetTypes.Distance, distanceValue: 3200, distanceUnit: DistanceUnits.Meters }, + { type: TargetTypes.Distance, distanceValue: 2, distanceUnit: DistanceUnits.Miles }, + + { type: TargetTypes.Distance, distanceValue: 3, distanceUnit: DistanceUnits.Miles }, + { type: TargetTypes.Distance, distanceValue: 5, distanceUnit: DistanceUnits.Kilometers }, + { type: TargetTypes.Distance, distanceValue: 6, distanceUnit: DistanceUnits.Kilometers }, + { type: TargetTypes.Distance, distanceValue: 8, distanceUnit: DistanceUnits.Kilometers }, + { type: TargetTypes.Distance, distanceValue: 10, distanceUnit: DistanceUnits.Kilometers }, + { type: TargetTypes.Distance, distanceValue: 15, distanceUnit: DistanceUnits.Kilometers }, + + { type: TargetTypes.Distance, distanceValue: 0.5, distanceUnit: DistanceUnits.Marathons }, + { type: TargetTypes.Distance, distanceValue: 1, distanceUnit: DistanceUnits.Marathons }, + ]), + }, '_split_targets': { + name: '5K Mile Splits', + targets: [ + { type: TargetTypes.Distance, distanceValue: 1, distanceUnit: DistanceUnits.Miles }, + { type: TargetTypes.Distance, distanceValue: 2, distanceUnit: DistanceUnits.Miles }, + { type: TargetTypes.Distance, distanceValue: 5, distanceUnit: DistanceUnits.Kilometers }, + ], + }, '_workout_targets': { + name: 'Common Workout Targets', + targets: [ + { + splitValue: 400, splitUnit: DistanceUnits.Meters, + type: TargetTypes.Distance, distanceValue: 1, distanceUnit: DistanceUnits.Miles, + }, + { + splitValue: 800, splitUnit: DistanceUnits.Meters, + type: TargetTypes.Distance, distanceValue: 5, distanceUnit: DistanceUnits.Kilometers, + }, + { + splitValue: 1600, splitUnit: DistanceUnits.Meters, + type: TargetTypes.Time, time: 3600, + }, + { + splitValue: 1, splitUnit: DistanceUnits.Miles, + type: TargetTypes.Distance, distanceValue: 1, distanceUnit: DistanceUnits.Marathons, + }, + ], + }, +}; +export const defaultPaceTargetSets: StandardTargetSets = { + '_pace_targets': defaultTargetSets._pace_targets, +}; +export const defaultRaceTargetSets: StandardTargetSets = { + '_race_targets': defaultTargetSets._race_targets, +}; +export const defaultSplitTargetSets: SplitTargetSets = { + '_split_targets': defaultTargetSets._split_targets as SplitTargetSet, +}; +export const defaultWorkoutTargetSets: WorkoutTargetSets = { + '_workout_targets': defaultTargetSets._workout_targets as WorkoutTargetSet, +}; diff --git a/src/core/units.ts b/src/core/units.ts @@ -0,0 +1,396 @@ +/* + * Implements handling of distance, pace, speed, and time units + */ + +/* + * The type for the data available for each unit + */ +export interface UnitData { + name: string, + symbol: string, + value: number, +}; + +/* + * The available time units + */ +export enum TimeUnits { + Seconds = 'seconds', + Minutes = 'minutes', + Hours = 'hours', +}; +export const TimeUnitData: { [key in TimeUnits]: UnitData } = { + [TimeUnits.Seconds]: { + name: 'Seconds', + symbol: 's', + value: 1, + }, + [TimeUnits.Minutes]: { + name: 'Minutes', + symbol: 'min', + value: 60, + }, + [TimeUnits.Hours]: { + name: 'Hours', + symbol: 'hr', + value: 3600, + }, +}; + +/* + * The available distance units + */ +export enum DistanceUnits { + Meters = 'meters', + Yards = 'yards', + Kilometers = 'kilometers', + Miles = 'miles', + Marathons = 'marathons', +}; +export const DistanceUnitData: { [key in DistanceUnits]: UnitData } = { + [DistanceUnits.Meters]: { + name: 'Meters', + symbol: 'm', + value: 1, + }, + [DistanceUnits.Yards]: { + name: 'Yards', + symbol: 'yd', + value: 0.9144, + }, + [DistanceUnits.Kilometers]: { + name: 'Kilometers', + symbol: 'km', + value: 1000, + }, + [DistanceUnits.Miles]: { + name: 'Miles', + symbol: 'mi', + value: 1609.344, + }, + [DistanceUnits.Marathons]: { + name: 'Marathons', + symbol: 'Mar', + value: 42195, + }, +}; + +/* + * The available speed units + */ +export enum SpeedUnits { + MetersPerSecond = 'meters_per_second', + KilometersPerHour = 'kilometers_per_hour', + MilesPerHour = 'miles_per_hour', +}; +export const SpeedUnitData: { [key in SpeedUnits]: UnitData } = { + [SpeedUnits.MetersPerSecond]: { + name: 'Meters per Second', + symbol: 'm/s', + value: 1, + }, + [SpeedUnits.KilometersPerHour]: { + name: 'Kilometers per Hour', + symbol: 'kph', + value: DistanceUnitData[DistanceUnits.Kilometers].value / TimeUnitData[TimeUnits.Hours].value, + }, + [SpeedUnits.MilesPerHour]: { + name: 'Miles per Hour', + symbol: 'mph', + value: DistanceUnitData[DistanceUnits.Miles].value / TimeUnitData[TimeUnits.Hours].value, + }, +}; + +/* + * The available pace units + */ +export enum PaceUnits { + SecondsPerMeter = 'seconds_per_meter', + TimePerKilometer = 'seconds_per_kilometer', + TimePerMile = 'seconds_per_mile', +}; +export const PaceUnitData: { [key in PaceUnits]: UnitData } = { + [PaceUnits.SecondsPerMeter]: { + name: 'Seconds per Meter', + symbol: 's/m', + value: 1, + }, + [PaceUnits.TimePerKilometer]: { + name: 'Time per Kilometer', + symbol: '/ km', + value: TimeUnitData[TimeUnits.Seconds].value / DistanceUnitData[DistanceUnits.Kilometers].value, + }, + [PaceUnits.TimePerMile]: { + name: 'Time per Mile', + symbol: '/ mi', + value: TimeUnitData[TimeUnits.Seconds].value / DistanceUnitData[DistanceUnits.Miles].value, + }, +}; + +/* + * The available speed and pace units + */ +export type SpeedPaceUnits = SpeedUnits | PaceUnits; + +/* + * The type for a distance input + */ +export interface Distance { + distanceValue: number, + distanceUnit: DistanceUnits, +}; + +/* + * The type for a distance/time input pair + */ +export interface DistanceTime extends Distance { + time: number, +}; + +/* + * The available unit systems + */ +export enum UnitSystems { + Metric = 'metric', + Imperial = 'imperial', +}; + +/** + * Convert between time units + * @param {number} inputValue The input value + * @param {string} inputUnit The unit of the input + * @param {string} outputUnit The unit of the output + * @returns {number} The output + */ +export function convertTime(inputValue: number, inputUnit: TimeUnits, + outputUnit: TimeUnits): number { + return (inputValue * TimeUnitData[inputUnit].value) / TimeUnitData[outputUnit].value; +} + +/** + * Convert between distance units + * @param {number} inputValue The input value + * @param {string} inputUnit The unit of the input + * @param {string} outputUnit The unit of the output + * @returns {number} The output + */ +export function convertDistance(inputValue: number, inputUnit: DistanceUnits, + outputUnit: DistanceUnits): number { + return (inputValue * DistanceUnitData[inputUnit].value) / DistanceUnitData[outputUnit].value; +} + +/** + * Convert between speed units + * @param {number} inputValue The input value + * @param {string} inputUnit The unit of the input + * @param {string} outputUnit The unit of the output + * @returns {number} The output + */ +export function convertSpeed(inputValue: number, inputUnit: SpeedUnits, + outputUnit: SpeedUnits): number { + return (inputValue * SpeedUnitData[inputUnit].value) / SpeedUnitData[outputUnit].value; +} + +/** + * Convert between pace units + * @param {number} inputValue The input value + * @param {string} inputUnit The unit of the input + * @param {string} outputUnit The unit of the output + * @returns {number} The output + */ +export function convertPace(inputValue: number, inputUnit: PaceUnits, + outputUnit: PaceUnits): number { + return (inputValue * PaceUnitData[inputUnit].value) / PaceUnitData[outputUnit].value; +} + +/** + * Convert between speed and/or pace units + * @param {number} inputValue The input value + * @param {string} inputUnit The unit of the input + * @param {string} outputUnit The unit of the output + * @returns {number} The output + */ +export function convertSpeedPace(inputValue: number, inputUnit: SpeedPaceUnits, + outputUnit: SpeedPaceUnits): number { + // Calculate input speed + let speed; + if (inputUnit in PaceUnitData) { + speed = 1 / (inputValue * PaceUnitData[inputUnit as PaceUnits].value); + } else { + speed = inputValue * SpeedUnitData[inputUnit as SpeedUnits].value; + } + + // Calculate output + if (outputUnit in PaceUnitData) { + return (1 / speed) / PaceUnitData[outputUnit as PaceUnits].value; + } + return speed / SpeedUnitData[outputUnit as SpeedUnits].value; +} + +/** + * Detect the user's default unit system + * @returns {UnitSystems} The default unit system + */ +export function detectDefaultUnitSystem(): UnitSystems { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const language = (navigator.language || (navigator as any).userLanguage).toLowerCase(); + if (language.endsWith('-us') || language.endsWith('-mm')) { + return UnitSystems.Imperial; + } + return UnitSystems.Metric; +} + +/** + * Format a number as a string + * @param {number} value The number + * @param {number} minPadding The minimum number of digits to show before the decimal point + * @param {number} maxDigits The maximum number of digits to show after the decimal point + * @param {boolean} extraDigits Whether to show extra zeros after the decimal point + * @returns {string} The formatted number + */ +export function formatNumber(value: number, minPadding: number = 0, maxDigits: number = 2, + extraDigits: boolean = true): string { + + // Initialize result + let result = ''; + + // Remove sign + const negative = value < 0; + const fixedValue = Math.abs(value); + + // Address edge cases + if (Number.isNaN(fixedValue)) { + return 'NaN'; + } + if (fixedValue === Infinity) { + return negative ? '-Infinity' : 'Infinity'; + } + + // Convert number to string + if (extraDigits) { + result = fixedValue.toFixed(maxDigits); + } else { + const power = 10 ** maxDigits; + result = (Math.round((fixedValue + Number.EPSILON) * power) / power).toString(); + } + + // Add padding + const currentPadding = result.split('.')[0].length; + result = result.padStart(result.length - currentPadding + minPadding, '0'); + + // Add negative sign + if (negative) { + result = `-${result}`; + } + + // Return result + return result; +} + +/** + * Format a distance as a string + * @param {Distance} input The distance + * @param {boolean} extraDigits Whether to show extra zeros after the decimal point + * @returns {string} The formatted distance + */ +export function formatDistance(input: Distance, extraDigits: boolean) { + return formatNumber(input.distanceValue, 0, 2, extraDigits) + ' ' + + DistanceUnitData[input.distanceUnit].symbol; +} + +/** + * Format a duration as a string + * @param {number} value The duration (in seconds) + * @param {number} minPadding The minimum number of digits to show before the decimal point + * @param {number} maxDigits The maximum number of digits to show after the decimal point + * @param {boolean} extraDigits Whether to show extra zeros after the decimal point + * @returns {string} The formatted duration + */ +export function formatDuration(value: number, minPadding: number = 6, maxDigits: number = 2, + extraDigits: boolean = true): string { + // Check if value is NaN + if (Number.isNaN(value)) { + return 'NaN'; + } + + // Initialize result + let result = ''; + + // Check value sign + if (value < 0) { + result += '-'; + } + + // Check if value is valid + if (Math.abs(value) === Infinity) { + return `${result}Infinity`; + } + + // Validate padding + let fixedPadding = Math.min(minPadding, 6); + + // Prevent rounding errors + const fixedValue = parseFloat(Math.abs(value).toFixed(maxDigits)); + + // Calculate parts + const hours = Math.floor(fixedValue / 3600); + const minutes = Math.floor((fixedValue % 3600) / 60); + const seconds = fixedValue % 60; + + // Format parts + if (hours !== 0 || fixedPadding >= 5) { + result += hours.toString().padStart(fixedPadding - 4, '0'); + result += ':'; + fixedPadding = 4; + } + if (minutes !== 0 || fixedPadding >= 3) { + result += minutes.toString().padStart(fixedPadding - 2, '0'); + result += ':'; + fixedPadding = 2; + } + result += formatNumber(seconds, fixedPadding, maxDigits, extraDigits); + + // Return result + return result; +} + +/** + * Calculate the pace of a distance/time pair and format it as a string + * @param {DistanceTime} input The input distance/time pair + * @param {PaceUnits} unit The desired pace unit + * @returns {string} The formatted pace + */ +export function formatPace(input: DistanceTime, unit: PaceUnits) { + const dist = convertDistance(input.distanceValue, input.distanceUnit, DistanceUnits.Meters); + const pace = convertPace(input.time / dist, PaceUnits.SecondsPerMeter, unit) + const result = formatDuration(pace, 3, 0, true) + ' ' + PaceUnitData[unit].symbol; + return result; +} + +/** + * Get the default distance unit in a unit system + * @param {UnitSystems} unitSystem The unit system + * @returns {DistanceUnits} The default distance unit + */ +export function getDefaultDistanceUnit(unitSystem: UnitSystems): DistanceUnits { + return unitSystem === UnitSystems.Metric ? DistanceUnits.Kilometers : DistanceUnits.Miles; +} + +/** + * Get the default speed unit in a unit system + * @param {UnitSystems} unitSystem The unit system + * @returns {SpeedUnits} The default speed unit + */ +export function getDefaultSpeedUnit(unitSystem: UnitSystems): SpeedUnits { + return unitSystem === UnitSystems.Metric ? SpeedUnits.KilometersPerHour + : SpeedUnits.MilesPerHour; +} + +/** + * Get the default pace unit in a unit system + * @param {UnitSystems} unitSystem The unit system + * @returns {PaceUnits} The default pace unit + */ +export function getDefaultPaceUnit(unitSystem: UnitSystems): PaceUnits { + return unitSystem === UnitSystems.Metric ? PaceUnits.TimePerKilometer : PaceUnits.TimePerMile; +} diff --git a/src/core/utils.ts b/src/core/utils.ts @@ -0,0 +1,55 @@ +/* + * Contains utility functions for handling nested objects and interacting with localStorage + */ + +// The global localStorage prefix +const LocalStoragePrefix = 'running-tools'; + +/** + * Create a deep copy of an object + * @param {Type} value The object to copy + * @returns {Type} The copied object + */ +export function deepCopy<Type>(value: Type): Type { + return JSON.parse(JSON.stringify(value)); +} + +/** + * Test whether two objects are deeply equal + * @param {Type} value1 The first object + * @param {Type} value2 The second object + * @returns {boolean} Whether the two objects are equal + */ +export function deepEqual<Type>(value1: Type, value2: Type): boolean { + return JSON.stringify(value1) === JSON.stringify(value2); +} + +/** + * Read an object from a localStorage item + * @param {string} key The localStorage item's key + * @returns {Type} The object + */ +export function getLocalStorage<Type>(key: string): Type | null { + try { + return JSON.parse(localStorage.getItem(`${LocalStoragePrefix}.${key}`) || ''); + } catch { + return null; + } +} + +/** + * Write an object to a localStorage item + * @param {string} key The localStorage item's key + * @param {Type} value The object to write + */ +export function setLocalStorage<Type>(key: string, value: Type) { + localStorage.setItem(`${LocalStoragePrefix}.${key}`, JSON.stringify(value)); +} + +/** + * Delete a localStorage item + * @param {string} key The localStorage item's key + */ +export function unsetLocalStorage(key: string) { + localStorage.removeItem(`${LocalStoragePrefix}.${key}`); +} diff --git a/src/main.js b/src/main.js @@ -1,11 +0,0 @@ -import './assets/global.css'; - -import { createApp } from 'vue'; -import App from './App.vue'; -import router from './router'; - -const app = createApp(App); - -app.use(router); - -app.mount('#app'); diff --git a/src/main.ts b/src/main.ts @@ -0,0 +1,13 @@ +import { createApp } from 'vue'; + +import App from '@/App.vue'; +import router from '@/router'; +import { migrateLocalStorage } from '@/core/migrations'; + +import '@/assets/global.css'; + +migrateLocalStorage(); + +const app = createApp(App); +app.use(router); +app.mount('#app'); diff --git a/src/router/index.js b/src/router/index.js @@ -1,110 +0,0 @@ -import { createRouter, createWebHashHistory } from 'vue-router'; -import HomePage from '@/views/HomePage.vue'; -import AboutPage from '@/views/AboutPage.vue'; -import BatchCalculator from '@/views/BatchCalculator.vue'; -import PaceCalculator from '@/views/PaceCalculator.vue'; -import RaceCalculator from '@/views/RaceCalculator.vue'; -import SplitCalculator from '@/views/SplitCalculator.vue'; -import WorkoutCalculator from '@/views/WorkoutCalculator.vue'; -import UnitCalculator from '@/views/UnitCalculator.vue'; -import NotFoundPage from '@/views/NotFoundPage.vue'; - -const router = createRouter({ - history: createWebHashHistory(import.meta.env.BASE_URL), - routes: [ - { - path: '/', - redirect: '/home', - }, - { - path: '/home', - name: 'home', - component: HomePage, - meta: { - title: null, - back: null, - }, - }, - { - path: '/about', - name: 'about', - component: AboutPage, - meta: { - title: 'About', - back: 'home', - }, - }, - { - path: '/calculate', - redirect: '/home', - }, - { - path: '/calculate/batch', - name: 'calculate-batch', - component: BatchCalculator, - meta: { - title: 'Batch Calculator', - back: 'home', - }, - }, - { - path: '/calculate/paces', - name: 'calculate-paces', - component: PaceCalculator, - meta: { - title: 'Pace Calculator', - back: 'home', - }, - }, - { - path: '/calculate/races', - name: 'calculate-races', - component: RaceCalculator, - meta: { - title: 'Race Calculator', - back: 'home', - }, - }, - { - path: '/calculate/splits', - name: 'calculate-splits', - component: SplitCalculator, - meta: { - title: 'Split Calculator', - back: 'home', - }, - }, - { - path: '/calculate/units', - name: 'calculate-units', - component: UnitCalculator, - meta: { - title: 'Unit Calculator', - back: 'home', - }, - }, - { - path: '/calculate/workouts', - name: 'calculate-workouts', - component: WorkoutCalculator, - meta: { - title: 'Workout Calculator', - back: 'home', - }, - }, - { - path: '/:pathMatch(.*)*', - component: NotFoundPage, - }, - ] -}); - -router.afterEach((to) => { - if (to.meta.title) { - document.title = `${to.meta.title} - Running Tools`; - } else { - document.title = 'Running Tools'; - } -}); - -export default router; diff --git a/src/router/index.ts b/src/router/index.ts @@ -0,0 +1,120 @@ +import { createRouter, createWebHashHistory } from 'vue-router'; +import HomePage from '@/views/HomePage.vue'; +import AboutPage from '@/views/AboutPage.vue'; +import BatchCalculator from '@/views/BatchCalculator.vue'; +import ChangeLog from '@/views/ChangeLog.vue'; +import PaceCalculator from '@/views/PaceCalculator.vue'; +import RaceCalculator from '@/views/RaceCalculator.vue'; +import SplitCalculator from '@/views/SplitCalculator.vue'; +import WorkoutCalculator from '@/views/WorkoutCalculator.vue'; +import UnitCalculator from '@/views/UnitCalculator.vue'; +import NotFoundPage from '@/views/NotFoundPage.vue'; + +const router = createRouter({ + history: createWebHashHistory(import.meta.env.BASE_URL), + routes: [ + { + path: '/', + redirect: '/home', + }, + { + path: '/home', + name: 'home', + component: HomePage, + meta: { + title: null, + back: null, + }, + }, + { + path: '/about', + name: 'about', + component: AboutPage, + meta: { + title: 'About', + back: 'home', + }, + }, + { + path: '/changelog', + name: 'changelog', + component: ChangeLog, + meta: { + title: 'Change Log', + back: 'home', + }, + }, + { + path: '/calculate', + redirect: '/home', + }, + { + path: '/calculate/batch', + name: 'calculate-batch', + component: BatchCalculator, + meta: { + title: 'Batch Calculator', + back: 'home', + }, + }, + { + path: '/calculate/paces', + name: 'calculate-paces', + component: PaceCalculator, + meta: { + title: 'Pace Calculator', + back: 'home', + }, + }, + { + path: '/calculate/races', + name: 'calculate-races', + component: RaceCalculator, + meta: { + title: 'Race Calculator', + back: 'home', + }, + }, + { + path: '/calculate/splits', + name: 'calculate-splits', + component: SplitCalculator, + meta: { + title: 'Split Calculator', + back: 'home', + }, + }, + { + path: '/calculate/units', + name: 'calculate-units', + component: UnitCalculator, + meta: { + title: 'Unit Calculator', + back: 'home', + }, + }, + { + path: '/calculate/workouts', + name: 'calculate-workouts', + component: WorkoutCalculator, + meta: { + title: 'Workout Calculator', + back: 'home', + }, + }, + { + path: '/:pathMatch(.*)*', + component: NotFoundPage, + }, + ] +}); + +router.afterEach((to) => { + if (to.meta.title) { + document.title = `${to.meta.title} - Running Tools`; + } else { + document.title = 'Running Tools'; + } +}); + +export default router; diff --git a/src/utils/calculators.js b/src/utils/calculators.js @@ -1,173 +0,0 @@ -import { formatDuration, formatNumber } from '@/utils/format'; -import * as paceUtils from '@/utils/paces'; -import * as raceUtils from '@/utils/races'; -import { DISTANCE_UNITS, convertDistance, getDefaultDistanceUnit } from '@/utils/units'; - -/** - * Format a distance/time result as a key/value result - * @param {Object} result The distance/time result - * @param {String} defaultUnitSystem The default unit system (imperial or metric) - * @returns {Object} The key/value result - */ -export function formatDistTimeResult(result, defaultUnitSystem) { - // Calculate numerical pace - const pace = result.time / convertDistance(result.distanceValue, result.distanceUnit, - getDefaultDistanceUnit(defaultUnitSystem)); - - return { - // Convert distance to key string - key: formatNumber(result.distanceValue, 0, 2, result.result === 'distance') + ' ' - + DISTANCE_UNITS[result.distanceUnit].symbol, - - // Convert time to time string - value: formatDuration(result.time, 3, 2, result.result === 'time'), - - // Convert pace to pace string - pace: formatDuration(pace, 3, 0, true) + ' / ' - + DISTANCE_UNITS[getDefaultDistanceUnit(defaultUnitSystem)].symbol, - - // Convert dist/time result to key/value - result: result.result === 'time' ? 'value' : 'key', - - // Use time (in seconds) as sort key - sort: result.time, - }; -} - -/** - * Calculate paces from a target - * @param {Object} input The input pace - * @param {Object} target The pace target - * @param {String} defaultUnitSystem The default unit system (imperial or metric) - * @returns {Object} The result - */ -export function calculatePaceResults(input, target, defaultUnitSystem) { - const result = { - distanceValue: target.distanceValue, - distanceUnit: target.distanceUnit, - time: target.time, - result: target.type === 'distance' ? 'time' : 'distance', - }; - - const d1 = convertDistance(input.distanceValue, input.distanceUnit, 'meters'); - - // Add missing value to result - if (target.type === 'distance') { - // Convert target distance into meters - const d2 = convertDistance(target.distanceValue, target.distanceUnit, 'meters'); - - // Calculate time to travel distance at input pace - result.time = paceUtils.calculateTime(d1, input.time, d2); - } else { - // Calculate distance traveled in time at input pace - const d2 = paceUtils.calculateDistance(input.time, d1, target.time); - - // Convert output distance into default distance unit - const units = getDefaultDistanceUnit(defaultUnitSystem); - result.distanceValue = convertDistance(d2, 'meters', units); - result.distanceUnit = units; - } - - // Return result - return formatDistTimeResult(result, defaultUnitSystem); -} - -/** - * Predict race results from a target - * @param {Object} input The input race - * @param {Object} target The race target - * @param {Object} options The race prediction options - * @param {String} defaultUnitSystem The default unit system (imperial or metric) - * @returns {Object} The result - */ -export function calculateRaceResults(input, target, options, defaultUnitSystem) { - const result = { - distanceValue: target.distanceValue, - distanceUnit: target.distanceUnit, - time: target.time, - result: target.type === 'distance' ? 'time' : 'distance', - }; - - const d1 = convertDistance(input.distanceValue, input.distanceUnit, 'meters'); - - // Add missing value to result - if (target.type === 'distance') { - // Convert target distance into meters - const d2 = convertDistance(target.distanceValue, target.distanceUnit, 'meters'); - - // Get prediction - result.time = raceUtils.predictTime(d1, input.time, d2, options.model, options.riegelExponent); - } else { - // Get prediction - let distance = raceUtils.predictDistance(input.time, d1, target.time, options.model, - options.riegelExponent); - - // Convert output distance into default distance unit - distance = convertDistance(distance, 'meters', - getDefaultDistanceUnit(defaultUnitSystem)); - - // Update result - result.distanceValue = distance; - result.distanceUnit = getDefaultDistanceUnit(defaultUnitSystem); - } - - // Return result - return formatDistTimeResult(result, defaultUnitSystem); -} - -/** - * Calculate race statistics from an input race - * @param {Object} input The input race - * @returns {Object} The race statistics - */ -export function calculateRaceStats(input) { - const d1 = convertDistance(input.distanceValue, input.distanceUnit, 'meters'); - - return { - purdyPoints: raceUtils.getPurdyPoints(d1, input.time), - vo2Max: raceUtils.getVO2Max(d1, input.time), - vo2: raceUtils.getVO2(d1, input.time), - vo2MaxPercentage: raceUtils.getVO2Percentage(input.time) * 100, - } -} - -/** - * Predict workout results from a target - * @param {Object} input The input race - * @param {Object} target The workout target - * @param {Object} options The race prediction options - * @returns {Object} The result - */ -export function calculateWorkoutResults(input, target, options) { - const d1 = convertDistance(input.distanceValue, input.distanceUnit, 'meters'); - const t1 = input.time; - const d3 = convertDistance(target.splitValue, target.splitUnit, 'meters'); - let d2, t2, t3; - - // Calculate pace - let key = formatNumber(target.splitValue, 0, 2, false) + ' ' - + DISTANCE_UNITS[target.splitUnit].symbol + ' @ '; - if (target.type === 'distance') { - // Convert target distance into meters - d2 = convertDistance(target.distanceValue, target.distanceUnit, 'meters'); - t2 = raceUtils.predictTime(d1, input.time, d2, options.model, options.riegelExponent); - key += formatNumber(target.distanceValue, 0, 2, false) + ' ' - + DISTANCE_UNITS[target.distanceUnit].symbol; - } else { - t2 = target.time; - d2 = raceUtils.predictDistance(t1, d1, t2, options.model, - options.riegelExponent); - key += formatDuration(target.time, 3, 2, false); - } - - t3 = paceUtils.calculateTime(d2, t2, d3); - - // Calculate time - return { - key: key, - value: formatDuration(t3, 3, 2, true), - pace: '', // Pace not used in workout calculator - result: 'value', - sort: t3, - } -} diff --git a/src/utils/format.js b/src/utils/format.js @@ -1,99 +0,0 @@ -/** - * Format a number as a string - * @param {Number} value The number - * @param {Number} minPadding The minimum number of digits to show before the decimal point - * @param {Number} maxDigits The maximum number of digits to show after the decimal point - * @param {Boolean} extraDigits Whether to show extra zeros after the decimal point - * @returns {String} The formatted value - */ -export function formatNumber(value, minPadding = 0, maxDigits = 2, extraDigits = true) { - // Initialize result - let result = ''; - - // Remove sign - const negative = value < 0; - const fixedValue = Math.abs(value); - - // Address edge cases - if (Number.isNaN(fixedValue)) { - return 'NaN'; - } - if (fixedValue === Infinity) { - return negative ? '-Infinity' : 'Infinity'; - } - - // Convert number to string - if (extraDigits) { - result = fixedValue.toFixed(maxDigits); - } else { - const power = 10 ** maxDigits; - result = (Math.round((fixedValue + Number.EPSILON) * power) / power).toString(); - } - - // Add padding - const currentPadding = result.split('.')[0].length; - result = result.padStart(result.length - currentPadding + minPadding, '0'); - - // Add negative sign - if (negative) { - result = `-${result}`; - } - - // Return result - return result; -} - -/** - * Format a duration as a string - * @param {Number} value The duration (in seconds) - * @param {Number} minPadding The minimum number of digits to show before the decimal point - * @param {Number} maxDigits The maximum number of digits to show after the decimal point - * @param {Boolean} extraDigits Whether to show extra zeros after the decimal point - * @returns {String} The formatted value - */ -export function formatDuration(value, minPadding = 6, maxDigits = 2, extraDigits = true) { - // Check if value is NaN - if (Number.isNaN(value)) { - return 'NaN'; - } - - // Initialize result - let result = ''; - - // Check value sign - if (value < 0) { - result += '-'; - } - - // Check if value is valid - if (Math.abs(value) === Infinity) { - return `${result}Infinity`; - } - - // Validate padding - let fixedPadding = Math.min(minPadding, 6); - - // Prevent rounding errors - const fixedValue = parseFloat(Math.abs(value).toFixed(maxDigits)); - - // Calculate parts - const hours = Math.floor(fixedValue / 3600); - const minutes = Math.floor((fixedValue % 3600) / 60); - const seconds = fixedValue % 60; - - // Format parts - if (hours !== 0 || fixedPadding >= 5) { - result += hours.toString().padStart(fixedPadding - 4, '0'); - result += ':'; - fixedPadding = 4; - } - if (minutes !== 0 || fixedPadding >= 3) { - result += minutes.toString().padStart(fixedPadding - 2, '0'); - result += ':'; - fixedPadding = 2; - } - result += formatNumber(seconds, fixedPadding, maxDigits, extraDigits); - - // Return result - return result; -} diff --git a/src/utils/paces.js b/src/utils/paces.js @@ -1,21 +0,0 @@ -/** - * Calculate time from a distance and input pace - * @param {Number} d1 The input pace distance (in any unit) - * @param {Number} t1 The input pace time (in seconds) - * @param {Number} d2 The output distance (in the same unit as d1) - * @returns {Number} The output time (in seconds) - */ -export function calculateTime(d1, t1, d2) { - return (t1 / d1) * d2 -} - -/** - * Calculate distance from a time and input pace - * @param {Number} t1 The input pace time (in seconds) - * @param {Number} d1 The input pace distance (in any unit) - * @param {Number} t2 The output time (in seconds) - * @returns {Number} The output distance (in the same unit as d1) - */ -export function calculateDistance(t1, d1, t2) { - return (d1 / t1) * t2 -} diff --git a/src/utils/races.js b/src/utils/races.js @@ -1,461 +0,0 @@ -/** - * Estimate the point at which a function returns a target value using Newton's Method - * @param {Number} initialEstimate The initial estimate - * @param {Number} target The target function output - * @param {Function} method The function - * @param {Function} derivative The function derivative - * @param {Number} precision The acceptable precision - * @returns {Number} The refined estimate - */ -function NewtonsMethod(initialEstimate, target, method, derivative, precision) { - // Initialize estimate - let estimate = initialEstimate; - let estimateValue; - - for (let i = 0; i < 500; i += 1) { - // Evaluate function at estimate - estimateValue = method(estimate); - - // Check if estimate is close enough (usually occurs way before i = 500) - if (Math.abs(target - estimateValue) < precision) { - break; - } - - // Refine estimate - estimate -= (estimateValue - target) / derivative(estimate); - } - - // Return refined estimate - return estimate; -} - -/* - * Methods that implement the Purdy Points race prediction model - * https://www.cs.uml.edu/~phoffman/xcinfo3.html - */ -const PurdyPointsModel = { - /** - * Calculate the Purdy Point variables for a distance - * @param {Number} d The distance in meters - * @returns {Object} The Purdy Point variables - */ - getVariables(d) { - // Declare constants - const c1 = 11.15895; - const c2 = 4.304605; - const c3 = 0.5234627; - const c4 = 4.031560; - const c5 = 2.316157; - const r1 = 3.796158e-2; - const r2 = 1.646772e-3; - const r3 = 4.107670e-4; - const r4 = 7.068099e-6; - const r5 = 5.220990e-9; - - // Calculate world record velocity from running curve - const v = (-c1 * Math.exp(-r1 * d)) - + (c2 * Math.exp(-r2 * d)) - + (c3 * Math.exp(-r3 * d)) - + (c4 * Math.exp(-r4 * d)) - + (c5 * Math.exp(-r5 * d)); - - // Calculate world record time - const twsec = d / v; - - // Calculate constants - const k = 0.0654 - (0.00258 * v); - const a = 85 / k; - const b = 1 - (1035 / a); - - // Return Purdy Point variables - return { - twsec, - a, - b, - }; - }, - - /** - * Get the Purdy Points for a race - * @param {Number} d The distance of the race in meters - * @param {Number} t The finish time of the race in seconds - * @returns {Number} The Purdy Points for the race - */ - getPurdyPoints(d, t) { - // Get variables - const variables = PurdyPointsModel.getVariables(d); - - // Calculate Purdy Points - const points = variables.a * ((variables.twsec / t) - variables.b); - - // Return Purdy Points - return points; - }, - - /** - * Predict a race time using the Purdy Points Model - * @param {Number} d1 The distance of the input race in meters - * @param {Number} t1 The finish time of the input race in seconds - * @param {Number} d2 The distance of the output race in meters - * @returns {Number} The predicted time for the output race in seconds - */ - predictTime(d1, t1, d2) { - // Calculate Purdy Points for distance 1 - const points = PurdyPointsModel.getPurdyPoints(d1, t1); - - // Calculate time for distance 2 - const variables = PurdyPointsModel.getVariables(d2); - const seconds = (variables.a * variables.twsec) / (points + (variables.a * variables.b)); - - // Return predicted time - return seconds; - }, - - /** - * Calculate the derivative with respect to distance of the Purdy Points curve at a specific point - * @param {Number} d1 The distance of the input race in meters - * @param {Number} t1 The finish time of the input race in seconds - * @param {Number} d2 The distance of the output race in meters - * @return {Number} The derivative with respect to distance - */ - derivative(d1, t1, d2) { - const result = (85 * d2) / (((2316157 * Math.exp(-(522099 * d2) / 100000000000000)) / 1000000 - + (100789 * Math.exp(-(7068099 * d2) / 1000000000000)) / 25000 + (5234627 * Math.exp(-(410767 - * d2) / 1000000000)) / 10000000 + (860921 * Math.exp(-(411693 * d2) / 250000000)) / 200000 - - (223179 * Math.exp(-(1898079 * d2) / 50000000)) / 20000) * (327 / 5000 - (129 * ((2316157 - * Math.exp(-(522099 * d2) / 100000000000000)) / 1000000 + (100789 * Math.exp(-(7068099 * d2) - / 1000000000000)) / 25000 + (5234627 * Math.exp(-(410767 * d2) / 1000000000)) / 10000000 - + (860921 * Math.exp(-(411693 * d2) / 250000000)) / 200000 - (223179 * Math.exp(-(1898079 - * d2) / 50000000)) / 20000)) / 50000) * ((85 * (1 - (207 * (327 / 5000 - (129 * ((2316157 - * Math.exp(-(522099 * d2) / 100000000000000)) / 1000000 + (100789 * Math.exp(-(7068099 * d2) - / 1000000000000)) / 25000 + (5234627 * Math.exp(-(410767 * d2) / 1000000000)) / 10000000 - + (860921 * Math.exp(-(411693 * d2) / 250000000)) / 200000 - (223179 * Math.exp(-(1898079 - * d2) / 50000000)) / 20000)) / 50000)) / 17)) / (327 / 5000 - (129 * ((2316157 - * Math.exp(-(522099 * d2) / 100000000000000)) / 1000000 + (100789 * Math.exp(-(7068099 * d2) - / 1000000000000)) / 25000 + (5234627 * Math.exp(-(410767 * d2) / 1000000000)) / 10000000 - + (860921 * Math.exp(-(411693 * d2) / 250000000)) / 200000 - (223179 * Math.exp(-(1898079 - * d2) / 50000000)) / 20000)) / 50000) + (85 * (d1 / (((2316157 * Math.exp(-(522099 * d1) - / 100000000000000)) / 1000000 + (100789 * Math.exp(-(7068099 * d1) / 1000000000000)) / 25000 - + (5234627 * Math.exp(-(410767 * d1) / 1000000000)) / 10000000 + (860921 * Math.exp(-(411693 - * d1) / 250000000)) / 200000 - (223179 * Math.exp(-(1898079 * d1) / 50000000)) / 20000) * t1) - + (207 * (327 / 5000 - (129 * ((2316157 * Math.exp(-(522099 * d1) / 100000000000000)) - / 1000000 + (100789 * Math.exp(-(7068099 * d1) / 1000000000000)) / 25000 + (5234627 - * Math.exp(-(410767 * d1) / 1000000000)) / 10000000 + (860921 * Math.exp(-(411693 * d1) - / 250000000)) / 200000 - (223179 * Math.exp(-(1898079 * d1) / 50000000)) / 20000)) / 50000)) - / 17 - 1)) / (327 / 5000 - (129 * ((2316157 * Math.exp(-(522099 * d1) / 100000000000000)) - / 1000000 + (100789 * Math.exp(-(7068099 * d1) / 1000000000000)) / 25000 + (5234627 - * Math.exp(-(410767 * d1) / 1000000000)) / 10000000 + (860921 * Math.exp(-(411693 * d1) - / 250000000)) / 200000 - (223179 * Math.exp(-(1898079 * d1) / 50000000)) / 20000)) / 50000))); - return result; - }, - - /** - * Predict a race distance using the Purdy Points Model - * @param {Number} t1 The finish time of the input race in seconds - * @param {Number} d1 The distance of the input race in meters - * @param {Number} t2 The finish time of the output race in seconds - * @returns {Number} The predicted distance for the output race in meters - */ - predictDistance(t1, d1, t2) { - // Initialize estimate - let estimate = (d1 * t2) / t1; - - // Refine estimate (derivative on its own is too slow) - const method = (x) => PurdyPointsModel.predictTime(d1, t1, x); - const derivative = (x) => PurdyPointsModel.derivative(d1, t1, x) / 500; - estimate = NewtonsMethod(estimate, t2, method, derivative, 0.01); - - // Return estimate - return estimate; - }, -}; - -/* - * Methods that implement the VO2 Max race prediction model - * http://run-down.com/statistics/calcs_explained.php - * https://vdoto2.com/Calculator - */ -const VO2MaxModel = { - /** - * Calculate the VO2 of a runner during a race - * @param {Number} d The race distance in meters - * @param {Number} t The finish time in seconds - * @returns {Number} The VO2 - */ - getVO2(d, t) { - const minutes = t / 60; - const v = d / minutes; - const result = -4.6 + (0.182258 * v) + (0.000104 * (v ** 2)); - return result; - }, - - /** - * Calculate the percentage of VO2 max a runner is at during a race - * @param {Number} t The race time in seconds - * @returns {Number} The percentage of VO2 max - */ - getVO2Percentage(t) { - const minutes = t / 60; - const result = 0.8 + (0.189439 * Math.exp(-0.012778 * minutes)) + (0.298956 * Math.exp(-0.193261 - * minutes)); - return result; - }, - - /** - * Calculate a runner's VO2 max from a race result - * @param {Number} d The race distance in meters - * @param {Number} t The finish time in seconds - * @returns {Number} The runner's VO2 max - */ - getVO2Max(d, t) { - const result = VO2MaxModel.getVO2(d, t) / VO2MaxModel.getVO2Percentage(t); - return result; - }, - - /** - * Calculate the derivative with respect to time of the VO2 max curve at a specific point - * @param {Number} d The race distance in meters - * @param {Number} t The finish time in seconds - * @return {Number} The derivative with respect to time - */ - VO2MaxTimeDerivative(d, t) { - const result = (-(273 * d) / (25 * (t ** 2)) - (468 * (d ** 2)) / (625 * (t ** 3))) / ((189 - * Math.exp(-(2 * t) / 9375)) / 1000 + (299 * Math.exp(-(193 * t) / 60000)) / 1000 + 4 / 5) - - (((273 * d) / (25 * t) + (234 * (d ** 2)) / (625 * (t ** 2)) - 23 / 5) * (-(63 - * Math.exp(-(2 * t) / 9375)) / 1562500 - (57707 * Math.exp(-(193 * t) / 60000)) / 60000000)) - / (((189 * Math.exp(-(2 * t) / 9375)) / 1000 + (299 * Math.exp(-(193 * t) / 60000)) / 1000 - + 4 / 5) ** 2); - return result; - }, - - /** - * Predict a race time using the VO2 Max Model - * @param {Number} d1 The distance of the input race in meters - * @param {Number} t1 The finish time of the input race in seconds - * @param {Number} d2 The distance of the output race in meters - * @returns {Number} The predicted time for the output race in seconds - */ - predictTime(d1, t1, d2) { - // Calculate input VO2 max - const inputVO2Max = VO2MaxModel.getVO2Max(d1, t1); - - // Initialize estimate - let estimate = (t1 * d2) / d1; - - // Refine estimate - const method = (x) => VO2MaxModel.getVO2Max(d2, x); - const derivative = (x) => VO2MaxModel.VO2MaxTimeDerivative(d2, x); - estimate = NewtonsMethod(estimate, inputVO2Max, method, derivative, 0.0001); - - // Return estimate - return estimate; - }, - - /** - * Calculate the derivative with respect to distance of the VO2 max curve at a specific point - * @param {Number} d The race distance in meters - * @param {Number} t The finish time in seconds - * @return {Number} The derivative with respect to distance - */ - VO2MaxDistanceDerivative(d, t) { - const result = ((468 * d) / (625 * (t ** 2)) + 273 / (25 * t)) / ((189 * Math.exp(-(2 * t) - / 9375)) / 1000 + (299 * Math.exp(-(193 * t) / 60000)) / 1000 + 4 / 5); - return result; - }, - - /** - * Predict a race distance using the VO2 Max Model - * @param {Number} t1 The finish time of the input race in seconds - * @param {Number} d1 The distance of the input race in meters - * @param {Number} t2 The finish time of the output race in seconds - * @returns {Number} The predicted distance for the output race in meters - */ - predictDistance(t1, d1, t2) { - // Calculate input VO2 max - const inputVO2 = VO2MaxModel.getVO2Max(d1, t1); - - // Initialize estimate - let estimate = (d1 * t2) / t1; - - // Refine estimate - const method = (x) => VO2MaxModel.getVO2Max(x, t2); - const derivative = (x) => VO2MaxModel.VO2MaxDistanceDerivative(x, t2); - estimate = NewtonsMethod(estimate, inputVO2, method, derivative, 0.0001); - - // Return estimate - return estimate; - }, -}; - -/* - * Methods that implement Dave Cameron's race prediction model - * https://www.cs.uml.edu/~phoffman/cammod.html - */ -const CameronModel = { - /** - * Predict a race time using Dave Cameron's Model - * @param {Number} d1 The distance of the input race in meters - * @param {Number} t1 The finish time of the input race in seconds - * @param {Number} d2 The distance of the output race in meters - * @returns {Number} The predicted time for the output race in seconds - */ - predictTime(d1, t1, d2) { - const a = 13.49681 - (0.000030363 * d1) + (835.7114 / (d1 ** 0.7905)); - const b = 13.49681 - (0.000030363 * d2) + (835.7114 / (d2 ** 0.7905)); - return (t1 / d1) * (a / b) * d2; - }, - - /** - * Calculate the derivative with respect to distance of the Cameron curve at a specific point - * @param {Number} d1 The distance of the input race in meters - * @param {Number} t1 The finish time of the input race in seconds - * @param {Number} d2 The distance of the output race in meters - * @return {Number} The derivative with respect to distance - */ - derivative(d1, t1, d2) { - const result = -(100 * (30363 * (d1 ** (3581 / 2000)) - 13496810000 * (d1 ** (1581 / 2000)) - - 835711400000) * t1 * (134968100 * (d2 ** (3581 / 2000)) + 14963412617 * d2)) / ((d1 ** (3581 - / 2000)) * (d2 ** (419 / 2000)) * ((30363 * (d2 ** (3581 / 2000)) - 13496810000 * (d2 ** (1581 - / 2000)) - 835711400000) ** 2)); - return result; - }, - - /** - * Predict a race distance using Dave Cameron's Model - * @param {Number} t1 The finish time of the input race in seconds - * @param {Number} d1 The distance of the input race in meters - * @param {Number} t2 The finish time of the output race in seconds - * @returns {Number} The predicted distance for the output race in meters - */ - predictDistance(t1, d1, t2) { - // Initialize estimate - let estimate = (d1 * t2) / t1; - - // Refine estimate - const method = (x) => CameronModel.predictTime(d1, t1, x); - const derivative = (x) => CameronModel.derivative(d1, t1, x); - estimate = NewtonsMethod(estimate, t2, method, derivative, 0.01); - - // Return estimate - return estimate; - }, -}; - -/* - * Methods that implement Pete Riegel's race prediction model - * https://en.wikipedia.org/wiki/Peter_Riegel - */ -const RiegelModel = { - /** - * Predict a race time using Pete Riegel's Model - * @param {Number} d1 The distance of the input race in meters - * @param {Number} t1 The finish time of the input race in seconds - * @param {Number} d2 The distance of the output race in meters - * @param {Number} c The value of the exponent in the equation - * @returns {Number} The predicted time for the output race in seconds - */ - predictTime(d1, t1, d2, c = 1.06) { - return t1 * ((d2 / d1) ** c); - }, - - /** - * Predict a race distance using Pete Riegel's Model - * @param {Number} t1 The finish time of the input race in seconds - * @param {Number} d1 The distance of the input race in meters - * @param {Number} t2 The finish time of the output race in seconds - * @param {Number} c The value of the exponent in the equation - * @returns {Number} The predicted distance for the output race in meters - */ - predictDistance(t1, d1, t2, c = 1.06) { - return d1 * ((t2 / t1) ** (1 / c)); - }, -}; - -/* - * Methods that average the results of different race prediction models - */ -const AverageModel = { - /** - * Predict a race time by averaging the results of different models - * @param {Number} d1 The distance of the input race in meters - * @param {Number} t1 The finish time of the input race in seconds - * @param {Number} d2 The distance of the output race in meters - * @param {Number} c The value of the exponent in Pete Riegel's Model - * @returns {Number} The predicted time for the output race in seconds - */ - predictTime(d1, t1, d2, c = 1.06) { - const purdy = PurdyPointsModel.predictTime(d1, t1, d2); - const vo2max = VO2MaxModel.predictTime(d1, t1, d2); - const cameron = CameronModel.predictTime(d1, t1, d2); - const riegel = RiegelModel.predictTime(d1, t1, d2, c); - return (purdy + vo2max + cameron + riegel) / 4; - }, - - /** - * Predict a race distance by averaging the results of different models - * @param {Number} t1 The finish time of the input race in seconds - * @param {Number} d1 The distance of the input race in meters - * @param {Number} t2 The finish time of the output race in seconds - * @param {Number} c The value of the exponent in Pete Riegel's Model - * @returns {Number} The predicted distance for the output race in meters - */ - predictDistance(t1, d1, t2, c = 1.06) { - const purdy = PurdyPointsModel.predictDistance(t1, d1, t2); - const vo2max = VO2MaxModel.predictDistance(t1, d1, t2); - const cameron = CameronModel.predictDistance(t1, d1, t2); - const riegel = RiegelModel.predictDistance(t1, d1, t2, c); - return (purdy + vo2max + cameron + riegel) / 4; - }, -}; - -/** - * Predict a race time - * @param {Number} d1 The distance of the input race in meters - * @param {Number} t1 The finish time of the input race in seconds - * @param {Number} d2 The distance of the output race in meters - * @param {String} model The race prediction model to use - * @param {Number} c The value of the exponent in Pete Riegel's Model - */ -export function predictTime(d1, t1, d2, model='AverageModel', c=1.06) { - switch (model) { - case 'AverageModel': - return AverageModel.predictTime(d1, t1, d2, c); - case 'PurdyPointsModel': - return PurdyPointsModel.predictTime(d1, t1, d2); - case 'VO2MaxModel': - return VO2MaxModel.predictTime(d1, t1, d2); - case 'RiegelModel': - return RiegelModel.predictTime(d1, t1, d2, c); - case 'CameronModel': - return CameronModel.predictTime(d1, t1, d2); - } -} - -/** - * Predict a race distance - * @param {Number} t1 The finish time of the input race in seconds - * @param {Number} d1 The distance of the input race in meters - * @param {Number} t2 The finish time of the output race in seconds - * @param {String} model The race prediction model to use - * @param {Number} c The value of the exponent in Pete Riegel's Model - */ -export function predictDistance(t1, d1, t2, model='AverageModel', c=1.06) { - switch (model) { - default: - case 'AverageModel': - return AverageModel.predictDistance(t1, d1, t2, c); - case 'PurdyPointsModel': - return PurdyPointsModel.predictDistance(t1, d1, t2); - case 'VO2MaxModel': - return VO2MaxModel.predictDistance(t1, d1, t2); - case 'RiegelModel': - return RiegelModel.predictDistance(t1, d1, t2, c); - case 'CameronModel': - return CameronModel.predictDistance(t1, d1, t2); - } -} - -export const getPurdyPoints = PurdyPointsModel.getPurdyPoints; -export const getVO2 = VO2MaxModel.getVO2; -export const getVO2Percentage = VO2MaxModel.getVO2Percentage; -export const getVO2Max = VO2MaxModel.getVO2Max; diff --git a/src/utils/targets.js b/src/utils/targets.js @@ -1,111 +0,0 @@ -import { convertDistance } from '@/utils/units'; - -/** - * Sort an array of targets - * @param {Array} targets The array of targets - * @returns {Array} The sorted targets - */ -export function sort(targets) { - return [ - ...targets.filter((item) => item.type === 'distance') - .sort((a, b) => convertDistance(a.distanceValue, a.distanceUnit, 'meters') - - convertDistance(b.distanceValue, b.distanceUnit, 'meters')), - - ...targets.filter((item) => item.type === 'time') - .sort((a, b) => a.time - b.time), - ]; -} - -export const defaultTargetSets = { - '_pace_targets': { - name: 'Common Pace Targets', - targets: sort([ - { type: 'distance', distanceValue: 100, distanceUnit: 'meters' }, - { type: 'distance', distanceValue: 200, distanceUnit: 'meters' }, - { type: 'distance', distanceValue: 300, distanceUnit: 'meters' }, - { type: 'distance', distanceValue: 400, distanceUnit: 'meters' }, - { type: 'distance', distanceValue: 600, distanceUnit: 'meters' }, - { type: 'distance', distanceValue: 800, distanceUnit: 'meters' }, - { type: 'distance', distanceValue: 1000, distanceUnit: 'meters' }, - { type: 'distance', distanceValue: 1200, distanceUnit: 'meters' }, - { type: 'distance', distanceValue: 1500, distanceUnit: 'meters' }, - { type: 'distance', distanceValue: 1600, distanceUnit: 'meters' }, - { type: 'distance', distanceValue: 3200, distanceUnit: 'meters' }, - - { type: 'distance', distanceValue: 2, distanceUnit: 'kilometers' }, - { type: 'distance', distanceValue: 3, distanceUnit: 'kilometers' }, - { type: 'distance', distanceValue: 4, distanceUnit: 'kilometers' }, - { type: 'distance', distanceValue: 5, distanceUnit: 'kilometers' }, - { type: 'distance', distanceValue: 6, distanceUnit: 'kilometers' }, - { type: 'distance', distanceValue: 8, distanceUnit: 'kilometers' }, - { type: 'distance', distanceValue: 10, distanceUnit: 'kilometers' }, - - { type: 'distance', distanceValue: 1, distanceUnit: 'miles' }, - { type: 'distance', distanceValue: 2, distanceUnit: 'miles' }, - { type: 'distance', distanceValue: 3, distanceUnit: 'miles' }, - { type: 'distance', distanceValue: 5, distanceUnit: 'miles' }, - { type: 'distance', distanceValue: 6, distanceUnit: 'miles' }, - { type: 'distance', distanceValue: 8, distanceUnit: 'miles' }, - { type: 'distance', distanceValue: 10, distanceUnit: 'miles' }, - - { type: 'distance', distanceValue: 0.5, distanceUnit: 'marathons' }, - { type: 'distance', distanceValue: 1, distanceUnit: 'marathons' }, - - { type: 'time', time: 600 }, - { type: 'time', time: 1800 }, - { type: 'time', time: 3600 }, - ]), - }, - '_race_targets': { - name: 'Common Race Targets', - targets: sort([ - { type: 'distance', distanceValue: 400, distanceUnit: 'meters' }, - { type: 'distance', distanceValue: 800, distanceUnit: 'meters' }, - { type: 'distance', distanceValue: 1500, distanceUnit: 'meters' }, - { type: 'distance', distanceValue: 1600, distanceUnit: 'meters' }, - { type: 'distance', distanceValue: 1, distanceUnit: 'miles' }, - { type: 'distance', distanceValue: 3000, distanceUnit: 'meters' }, - { type: 'distance', distanceValue: 3200, distanceUnit: 'meters' }, - { type: 'distance', distanceValue: 2, distanceUnit: 'miles' }, - - { type: 'distance', distanceValue: 3, distanceUnit: 'miles' }, - { type: 'distance', distanceValue: 5, distanceUnit: 'kilometers' }, - { type: 'distance', distanceValue: 6, distanceUnit: 'kilometers' }, - { type: 'distance', distanceValue: 8, distanceUnit: 'kilometers' }, - { type: 'distance', distanceValue: 10, distanceUnit: 'kilometers' }, - { type: 'distance', distanceValue: 15, distanceUnit: 'kilometers' }, - - { type: 'distance', distanceValue: 0.5, distanceUnit: 'marathons' }, - { type: 'distance', distanceValue: 1, distanceUnit: 'marathons' }, - ]), - }, - '_split_targets': { - name: '5K Mile Splits', - targets: [ - { type: 'distance', distanceValue: 1, distanceUnit: 'miles' }, - { type: 'distance', distanceValue: 2, distanceUnit: 'miles' }, - { type: 'distance', distanceValue: 5, distanceUnit: 'kilometers' }, - ], - }, - '_workout_targets': { - name: 'Common Workout Targets', - targets: [ - { - splitValue: 400, splitUnit: 'meters', - type: 'distance', distanceValue: 1, distanceUnit: 'miles', - }, - { - splitValue: 800, splitUnit: 'meters', - type: 'distance', distanceValue: 5, distanceUnit: 'kilometers', - }, - { - splitValue: 1600, splitUnit: 'meters', - type: 'time', time: 3600, - }, - { - splitValue: 1, splitUnit: 'miles', - type: 'distance', distanceValue: 1, distanceUnit: 'marathons', - }, - ], - }, -}; diff --git a/src/utils/units.js b/src/utils/units.js @@ -1,199 +0,0 @@ -/** - * The time units - */ -export const TIME_UNITS = { - seconds: { - name: 'Seconds', - symbol: 's', - value: 1, - }, - minutes: { - name: 'Minutes', - symbol: 'min', - value: 60, - }, - hours: { - name: 'Hours', - symbol: 'hr', - value: 3600, - }, -}; - -/** - * The distance units - */ -export const DISTANCE_UNITS = { - meters: { - name: 'Meters', - symbol: 'm', - value: 1, - }, - yards: { - name: 'Yards', - symbol: 'yd', - value: 0.9144, - }, - kilometers: { - name: 'Kilometers', - symbol: 'km', - value: 1000, - }, - miles: { - name: 'Miles', - symbol: 'mi', - value: 1609.3499, - }, - marathons: { - name: 'Marathons', - symbol: 'marathons', - value: 42195, - }, -}; - -/** - * The speed units - */ -export const SPEED_UNITS = { - meters_per_second: { - name: 'Meters per Second', - symbol: 'm/s', - value: 1, - }, - kilometers_per_hour: { - name: 'Kilometers per Hour', - symbol: 'kph', - value: DISTANCE_UNITS.kilometers.value / TIME_UNITS.hours.value, - }, - miles_per_hour: { - name: 'Miles per Hour', - symbol: 'mph', - value: DISTANCE_UNITS.miles.value / TIME_UNITS.hours.value, - }, -}; - -/** - * The value of each pace unit in seconds per meter - */ -export const PACE_UNITS = { - seconds_per_meter: { - name: 'Seconds per Meter', - symbol: 's/m', - value: 1, - }, - seconds_per_kilometer: { - name: 'Time per Kilometer', - symbol: '/ km', - value: TIME_UNITS.seconds.value / DISTANCE_UNITS.kilometers.value, - }, - seconds_per_mile: { - name: 'Time per Mile', - symbol: '/ mi', - value: TIME_UNITS.seconds.value / DISTANCE_UNITS.miles.value, - }, -}; - -/** - * Convert between time units - * @param {Number} inputValue The input value - * @param {String} inputUnit The unit of the input - * @param {String} outputUnit The unit of the output - * @returns {Number} The output - */ -export function convertTime(inputValue, inputUnit, outputUnit) { - return (inputValue * TIME_UNITS[inputUnit].value) / TIME_UNITS[outputUnit].value; -} - -/** - * Convert between distance units - * @param {Number} inputValue The input value - * @param {String} inputUnit The unit of the input - * @param {String} outputUnit The unit of the output - * @returns {Number} The output - */ -export function convertDistance(inputValue, inputUnit, outputUnit) { - return (inputValue * DISTANCE_UNITS[inputUnit].value) / DISTANCE_UNITS[outputUnit].value; -} - -/** - * Convert between speed units - * @param {Number} inputValue The input value - * @param {String} inputUnit The unit of the input - * @param {String} outputUnit The unit of the output - * @returns {Number} The output - */ -export function convertSpeed(inputValue, inputUnit, outputUnit) { - return (inputValue * SPEED_UNITS[inputUnit].value) / SPEED_UNITS[outputUnit].value; -} - -/** - * Convert between pace units - * @param {Number} inputValue The input value - * @param {String} inputUnit The unit of the input - * @param {String} outputUnit The unit of the output - * @returns {Number} The output - */ -export function convertPace(inputValue, inputUnit, outputUnit) { - return (inputValue * PACE_UNITS[inputUnit].value) / PACE_UNITS[outputUnit].value; -} - -/** - * Convert between speed and/or pace units - * @param {Number} inputValue The input value - * @param {String} inputUnit The unit of the input - * @param {String} outputUnit The unit of the output - * @returns {Number} The output - */ -export function convertSpeedPace(inputValue, inputUnit, outputUnit) { - // Calculate input speed - let speed; - if (inputUnit in PACE_UNITS) { - speed = 1 / (inputValue * PACE_UNITS[inputUnit].value); - } else { - speed = inputValue * SPEED_UNITS[inputUnit].value; - } - - // Calculate output - if (outputUnit in PACE_UNITS) { - return (1 / speed) / PACE_UNITS[outputUnit].value; - } - return speed / SPEED_UNITS[outputUnit].value; -} - -/** - * Detect the user's default unit system - * @returns {String} The default unit system - */ -export function detectDefaultUnitSystem() { - const language = (navigator.language || navigator.userLanguage).toLowerCase(); - if (language.endsWith('-us') || language.endsWith('-mm')) { - return 'imperial'; - } - return 'metric'; -} - -/** - * Get the default distance unit in a unit system - * @param {String} unitSystem The unit system - * @returns {String} The default distance unit - */ -export function getDefaultDistanceUnit(unitSystem) { - return unitSystem === 'metric' ? 'kilometers' : 'miles'; -} - -/** - * Get the default speed unit in a unit system - * @param {String} unitSystem The unit system - * @returns {String} The default speed unit - */ -export function getDefaultSpeedUnit(unitSystem) { - return unitSystem === 'metric' ? 'kilometers_per_hour' : 'miles_per_hour'; -} - -/** - * Get the default pace unit in a unit system - * @param {String} unitSystem The unit system - * @returns {String} The default pace unit - */ -export function getDefaultPaceUnit(unitSystem) { - return unitSystem === 'metric' ? 'seconds_per_kilometer' : 'seconds_per_mile'; -} diff --git a/src/views/AboutPage.vue b/src/views/AboutPage.vue @@ -1,25 +1,31 @@ <template> - <div class="about-page"> + <div class="text"> <p> - Running Tools is an open source collection of tools for runners and their coaches. + Running Tools is an <a :href="git_url">open source</a> collection of tools for runners + and their coaches. All calculations are performed locally on your device. - This is Running Tools version {{ version }}{{ development ? ' (dev)' : '' }}. + This is Running Tools version <router-link :to="{ name: 'changelog' }"> + {{ version }}</router-link>{{ development ? ' (dev)' : '' }}. </p> <h2>Installation</h2> <p> Running Tools can be installed as a progressive web app so it works offline: - <ul> - <li>On iOS devices, open Running Tools in Safari, press <span aria-label="Share"> - <vue-feather type="share" aria-hidden="true"/></span>, and select Add to Home Screen</li> - <li>On all other platforms, open Running Tools in Chrome and click Install - <img src="@/assets/chrome-install.png" height="24" class="chrome-install" alt=""/> - </li> - </ul> </p> + <ul> + <li>On iOS devices, open Running Tools in Safari, press <span aria-label="Share"> + <vue-feather type="share" aria-hidden="true"/></span>, and select Add to Home Screen</li> + <li>On all other platforms, open Running Tools in Chrome and click Install + <img src="@/assets/chrome-install.png" height="24" class="chrome-install" alt=""/> + </li> + </ul> <h2>The Calculators</h2> - <p>Running Tools contains six calculators:</p> + <p> + Running Tools consists of six calculators. + The Pace, Race, and Unit Calculators are the simplest to use, while the Batch, Split, and + Workout Calculators are designed for more advanced use cases. + </p> <h3>Batch Calculator</h3> <p> @@ -27,12 +33,14 @@ results for a range of input times using the Pace, Race, or Workout Calculators. Options such as the default unit system, selected target set, and race prediction model are synced from the active calculator. + If the Workout Calculator is being used with custom workout names enabled, a custom name may + also be specified for the batch column header. </p> <p> The Batch Calculator is useful for tasks such as: </p> <ul class="questions"> - <li>Generating a table of mile splits and the corresponding marathon finish times.</li> + <li>Generating a table of marathon finish times and the corresponding mile splits.</li> <li>Generating a table of equivalent race results for many distances and speeds.</li> <li>Generating a table of workout split times for an entire team.</li> </ul> @@ -70,7 +78,7 @@ <li>The Purdy Points Model</li> <li>The V&#775;O&#8322; Max Model</li> <li>Dave Cameron's Model</li> - <li>Pete Riegel's Model</li> + <li>Pete Riegel's Model (includes a configurable exponent)</li> <li>Average Model (averages the output of the other four models)</li> </ul> <p> @@ -85,8 +93,8 @@ <p> <strong>Note:</strong> Output race times and V&#775;O&#8322; / V&#775;O&#8322; Max values are just estimates. - Race predictions are most accurate for similar distances and assume identical conditions and - equal fitness. + Race predictions assume equivalent fitness and conditions to the input race, and are most + accurate for similar distances. </p> <h3>Split Calculator</h3> @@ -127,7 +135,8 @@ The selected target set controls which race distances and/or times the calculator calculates outputs for and the distances of the splits that are shown for these races. The Advanced Options section includes the option to switch between the same five prediction - models that are available in the Race Calculator. + models that are available in the Race Calculator, as well as the ability to enable support + for specifying custom workout names (e.g. "1 mile threshold" instead of "1 mi @ 1:00:00"). </p> <p> The Workout Calculator is useful for answering questions like: @@ -139,8 +148,8 @@ <p> <strong>Note:</strong> Results are just estimated race splits that are helpful for estimating target workout splits. - As with the Race Calculator, splits are most accurate for similar distances and assume equal - fitness. + As with the Race Calculator, splits assume equivalent fitness and conditions to the input + race, and are most accurate for similar paces. </p> <h2>Target Sets</h2> @@ -154,42 +163,22 @@ calculator. </p> <p> - <strong>Note:</strong> The split calculator only supports distance targets. The workout - calculator also includes a split distance field for each target. + <strong>Note:</strong> The Split Calculator only supports distance targets. The Workout + Calculator also includes a split distance field for each target. </p> </div> </template> -<script setup> -import { version } from '/package.json'; +<script setup lang="ts"> +import { repository, version } from '/package.json'; import VueFeather from 'vue-feather'; -const development = process.env.NODE_ENV === 'development'; +const development: boolean = process.env.NODE_ENV === 'development'; +const git_url: string = repository.url.slice(4); </script> <style scoped> -.about-page { - max-width: 800px; - margin: auto; - font-size: 1.1em; -} -h2 { - text-align: center; -} -h2, h3 { - margin-top: 1em; -} -p, blockquote, ul { - margin-bottom: 0.5em; -} -li { - margin-bottom: 0.2em; - margin-left: 1.5em; -} -p { - line-height: 1.3; -} .questions { font-style: italic; } diff --git a/src/views/BatchCalculator.vue b/src/views/BatchCalculator.vue @@ -2,23 +2,23 @@ <div class="calculator"> <h2>Batch Input</h2> <div class="input"> - <pace-input v-model="input" aria-label="Input"/> + <pace-input v-model="batchOptions.input" aria-label="Input"/> </div> <h2>Batch Options</h2> <div class="input"> <div> Increment: - <time-input v-model="options.increment" label="Duration increment" :show-hours="false"/> + <time-input v-model="batchOptions.increment" label="Duration increment" :show-hours="false"/> &times; - <integer-input v-model="options.rows" min="1" aria-label="Number of rows"/> + <integer-input v-model="batchOptions.rows" min="1" aria-label="Number of rows"/> </div> <div> Calculator: - <select aria-label="Calculator" v-model="options.calculator"> - <option value="pace">Pace Calculator</option> - <option value="race">Race Calculator</option> - <option value="workout">Workout Calculator</option> + <select aria-label="Calculator" v-model="batchOptions.calculator"> + <option :value="calculators.Calculators.Pace">Pace Calculator</option> + <option :value="calculators.Calculators.Race">Race Calculator</option> + <option :value="calculators.Calculators.Workout">Workout Calculator</option> </select> </div> </div> @@ -27,179 +27,191 @@ <summary> <h2>Advanced Options</h2> </summary> - <div> - Default units: - <select v-model="defaultUnitSystem" aria-label="Default units"> - <option value="imperial">Miles</option> - <option value="metric">Kilometers</option> - </select> - </div> - <div> - Target Set: - <target-set-selector v-model:selectedTargetSet="selectedTargetSet" - :setType="options.calculator === 'workout' ? 'workout' : 'standard'" - v-model:targetSets="targetSets" :default-unit-system="defaultUnitSystem"/> - </div> - <race-options v-if="options.calculator !== 'pace'" v-model="advancedOptions"/> + <advanced-options-input v-model:batch-options="batchOptions" + v-model:globalOptions="globalOptions" v-model:options="calcOptions" + v-model:targetSets="targetSets" :type="batchOptions.calculator"/> </details> <h2>Batch Results</h2> - <double-output-table class="output" :input-times="inputTimes" :input-distance="inputDistance" - :calculate-result="calculateResult" - :targets="targetSets[selectedTargetSet] ? targetSets[selectedTargetSet].targets : []"/> + <double-output-table class="output" :calculate-result="calculateResult" + :input-distance="inputDistance" :input-times="inputTimes" :label="batchColumnLabel" + :targets="targetSets[calcOptions.selectedTargetSet] ? + targetSets[calcOptions.selectedTargetSet].targets : []"/> </div> </template> -<script setup> +<script setup lang="ts"> import { computed } from 'vue'; -import * as calcUtils from '@/utils/calculators'; -import { defaultTargetSets } from '@/utils/targets'; -import { detectDefaultUnitSystem } from '@/utils/units'; +import * as calculators from '@/core/calculators'; +import type { BatchOptions, GlobalOptions, PaceOptions, RaceOptions, TargetResult, + WorkoutOptions } from '@/core/calculators'; +import * as targetUtils from '@/core/targets'; +import { formatDistance } from '@/core/units'; +import type { Distance, DistanceTime } from '@/core/units'; +import AdvancedOptionsInput from '@/components/AdvancedOptionsInput.vue'; import DoubleOutputTable from '@/components/DoubleOutputTable.vue'; import IntegerInput from '@/components/IntegerInput.vue'; import PaceInput from '@/components/PaceInput.vue'; -import RaceOptions from '@/components/RaceOptions.vue'; -import TargetSetSelector from '@/components/TargetSetSelector.vue'; import TimeInput from '@/components/TimeInput.vue'; import useStorage from '@/composables/useStorage'; -/** - * The input pace +/* + * The global options */ -const input = useStorage('batch-calculator-input', { - distanceValue: 5, - distanceUnit: 'kilometers', - time: 1200, -}); - -/** - * The batch input options - */ -const options = useStorage('batch-calculator-options', { - calculator: 'workout', - increment: 15, - rows: 20, -}); +const globalOptions = useStorage<GlobalOptions>('global-options', calculators.defaultGlobalOptions); -/** - * The default unit system +/* + * The batch calculator options */ -const defaultUnitSystem = useStorage('default-unit-system', detectDefaultUnitSystem()); +const batchOptions = useStorage<BatchOptions>('batch-calculator-options', + calculators.defaultBatchOptions); -/** - * The current selected target sets for each calculator +/* + * The options for each calculator */ -const selectedPaceTargetSet = useStorage('pace-calculator-target-set', '_pace_targets'); -const selectedRaceTargetSet = useStorage('race-calculator-target-set', '_race_targets'); -const selectedWorkoutTargetSet = useStorage('workout-calculator-target-set', '_workout_targets'); - -/** +const paceOptions = useStorage<PaceOptions>('pace-calculator-options', + calculators.defaultPaceOptions); +const raceOptions = useStorage<RaceOptions>('race-calculator-options', + calculators.defaultRaceOptions); +const workoutOptions = useStorage<WorkoutOptions>('workout-calculator-options', + calculators.defaultWorkoutOptions); + +/* * The target sets for each calculator */ -const paceTargetSets = useStorage('pace-calculator-target-sets', { - _pace_targets: defaultTargetSets._pace_targets -}); -const raceTargetSets = useStorage('race-calculator-target-sets', { - _race_targets: defaultTargetSets._race_targets -}); -const workoutTargetSets = useStorage('workout-calculator-target-sets', { - _workout_targets: defaultTargetSets._workout_targets -}); - -/** - * The advanced options for each calculator - */ -const raceOptions = useStorage('race-calculator-options', { - model: 'AverageModel', - riegelExponent: 1.06, -}); -const workoutOptions = useStorage('workout-calculator-options', { - model: 'AverageModel', - riegelExponent: 1.06, -}); - -/** +const paceTargetSets = useStorage<targetUtils.StandardTargetSets>('pace-calculator-target-sets', + targetUtils.defaultPaceTargetSets); +const raceTargetSets = useStorage<targetUtils.StandardTargetSets>('race-calculator-target-sets', + targetUtils.defaultRaceTargetSets); +const workoutTargetSets = useStorage<targetUtils.WorkoutTargetSets>( + 'workout-calculator-target-sets', targetUtils.defaultWorkoutTargetSets); + +/* * The input distance */ -const inputDistance = computed(() => ({ - distanceValue: input.value.distanceValue, - distanceUnit: input.value.distanceUnit, +const inputDistance = computed<Distance>(() => ({ + distanceValue: batchOptions.value.input.distanceValue, + distanceUnit: batchOptions.value.input.distanceUnit, })); -/** +/* * The set of input times */ -const inputTimes = computed(() => { - let results = []; - for (let i = 0; i < options.value.rows; i++) { - results.push(input.value.time + options.value.increment * i); +const inputTimes = computed<Array<number>>(() => { + const results = []; + for (let i = 0; i < batchOptions.value.rows; i++) { + results.push(batchOptions.value.input.time + batchOptions.value.increment * i); } return results; }); -/** - * The selected target set for the current calculator +/* + * The target sets for the current calculator */ -const selectedTargetSet = computed({ +const targetSets = computed<targetUtils.TargetSets>({ get: () => { - if (options.value.calculator === 'pace') { - return selectedPaceTargetSet.value; - } else if (options.value.calculator === 'race') { - return selectedRaceTargetSet.value; - } else { - return selectedWorkoutTargetSet.value; + switch (batchOptions.value.calculator) { + case (calculators.Calculators.Pace): { + return paceTargetSets.value; + } + case (calculators.Calculators.Race): { + return raceTargetSets.value; + } + default: + case (calculators.Calculators.Workout): { + return workoutTargetSets.value; + } } }, - set: (newValue) => { - if (options.value.calculator === 'pace') { - selectedPaceTargetSet.value = newValue; - } else if (options.value.calculator === 'race') { - selectedRaceTargetSet.value = newValue; - } else { - selectedWorkoutTargetSet.value = newValue; + set: (newValue: targetUtils.TargetSets) => { + switch (batchOptions.value.calculator) { + case (calculators.Calculators.Pace): { + paceTargetSets.value = newValue as targetUtils.StandardTargetSets; + break; + } + case (calculators.Calculators.Race): { + raceTargetSets.value = newValue as targetUtils.StandardTargetSets; + break; + } + default: + case (calculators.Calculators.Workout): { + workoutTargetSets.value = newValue as targetUtils.WorkoutTargetSets; + break; + } } }, }); -/** - * The target sets for the current calculator +/* + * The options for the current calculator */ -const targetSets = computed(() => { - if (options.value.calculator === 'pace') { - return paceTargetSets.value; - } else if (options.value.calculator === 'race') { - return raceTargetSets.value; - } else { - return workoutTargetSets.value; - } +const calcOptions = computed<PaceOptions | RaceOptions | WorkoutOptions>({ + get: () => { + switch (batchOptions.value.calculator) { + case (calculators.Calculators.Pace): { + return paceOptions.value; + } + case (calculators.Calculators.Race): { + return raceOptions.value; + } + default: + case (calculators.Calculators.Workout): { + return workoutOptions.value; + } + } + }, + set: (newValue: PaceOptions | RaceOptions | WorkoutOptions) => { + switch(batchOptions.value.calculator) { + case (calculators.Calculators.Pace): { + paceOptions.value = newValue as PaceOptions; + break; + } + case (calculators.Calculators.Race): { + raceOptions.value = newValue as RaceOptions; + break; + } + default: + case (calculators.Calculators.Workout): { + workoutOptions.value = newValue as WorkoutOptions; + break; + } + } + }, }); -/** - * The advanced options for the current calculator +/* + * The appropriate calculate_results function for the current calculator */ -const advancedOptions = computed(() => { - if (options.value.calculator === 'pace') { - return {}; - } else if (options.value.calculator === 'race') { - return raceOptions.value; - } else { - return workoutOptions.value; +const calculateResult = computed<(x: DistanceTime, y: targetUtils.Target) => TargetResult>(() => { + switch(batchOptions.value.calculator) { + case (calculators.Calculators.Pace): { + return (x,y) => calculators.calculatePaceResults(x, y, globalOptions.value.defaultUnitSystem, + false); + } + case (calculators.Calculators.Race): { + return (x,y) => calculators.calculateRaceResults(x, y, + globalOptions.value.racePredictionOptions, globalOptions.value.defaultUnitSystem, false); + } + default: + case (calculators.Calculators.Workout): { + return (x,y) => calculators.calculateWorkoutResults(x, y as targetUtils.WorkoutTarget, + globalOptions.value.racePredictionOptions, workoutOptions.value.customTargetNames, false); + } } }); -/** - * The appropriate calculate_results function for the current calculator +/* + * The label to render for the batch column */ -const calculateResult = computed(() => { - if (options.value.calculator === 'pace') { - return (x,y) => calcUtils.calculatePaceResults(x, y, defaultUnitSystem.value); - } else if (options.value.calculator === 'race') { - return (x,y) => calcUtils.calculateRaceResults(x, y, raceOptions.value, defaultUnitSystem.value); +const batchColumnLabel = computed<string>(() => { + if (batchOptions.value.calculator == calculators.Calculators.Workout && + (calcOptions.value as WorkoutOptions).customTargetNames && batchOptions.value.label) { + return batchOptions.value.label; } else { - return (x,y) => calcUtils.calculateWorkoutResults(x, y, workoutOptions.value); + return formatDistance(batchOptions.value.input, false); } }); </script> diff --git a/src/views/ChangeLog.vue b/src/views/ChangeLog.vue @@ -0,0 +1,10 @@ +<template> + <div class="text" v-html="content"/> +</template> + +<script setup lang="ts"> +const content: string = __CHANGELOG_HTML__; +</script> + +<style scoped> +</style> diff --git a/src/views/NotFoundPage.vue b/src/views/NotFoundPage.vue @@ -1,6 +1,6 @@ <template> <div class="not-found-page"> - <h1>404 Not Found</h1> + <h1>Page Not Found</h1> <p><router-link to="/home">Return home</router-link></p> </div> </template> diff --git a/src/views/PaceCalculator.vue b/src/views/PaceCalculator.vue @@ -2,70 +2,53 @@ <div class="calculator"> <h2>Input Pace</h2> <div class="input"> - <pace-input v-model="input"/> + <pace-input v-model="paceOptions.input"/> </div> <details> <summary> <h2>Advanced Options</h2> </summary> - <div> - Default units: - <select v-model="defaultUnitSystem" aria-label="Default units"> - <option value="imperial">Miles</option> - <option value="metric">Kilometers</option> - </select> - </div> - <div> - Target Set: - <target-set-selector v-model:selectedTargetSet="selectedTargetSet" - v-model:targetSets="targetSets" :default-unit-system="defaultUnitSystem"/> - </div> + <advanced-options-input v-model:globalOptions="globalOptions" + v-model:options="paceOptions" v-model:targetSets="targetSets" :type="Calculators.Pace"/> </details> <h2>Equivalent Paces</h2> <single-output-table class="output" :calculate-result="x => - calculatePaceResults(input, x, defaultUnitSystem)" - :targets="targetSets[selectedTargetSet] ? targetSets[selectedTargetSet].targets : []"/> + calculatePaceResults(paceOptions.input, x, globalOptions.defaultUnitSystem, true)" + :targets="targetSets[paceOptions.selectedTargetSet] ? + targetSets[paceOptions.selectedTargetSet].targets : []"/> </div> </template> -<script setup> -import { calculatePaceResults } from '@/utils/calculators'; -import { defaultTargetSets } from '@/utils/targets'; -import { detectDefaultUnitSystem } from '@/utils/units'; +<script setup lang="ts"> +import { Calculators, calculatePaceResults, defaultGlobalOptions, + defaultPaceOptions } from '@/core/calculators'; +import type { GlobalOptions, PaceOptions } from '@/core/calculators'; +import { defaultPaceTargetSets } from '@/core/targets'; +import type { StandardTargetSets } from '@/core/targets'; +import AdvancedOptionsInput from '@/components/AdvancedOptionsInput.vue'; import PaceInput from '@/components/PaceInput.vue'; import SingleOutputTable from '@/components/SingleOutputTable.vue'; -import TargetSetSelector from '@/components/TargetSetSelector.vue'; import useStorage from '@/composables/useStorage'; -/** - * The input pace +/* + * The global options */ -const input = useStorage('pace-calculator-input', { - distanceValue: 5, - distanceUnit: 'kilometers', - time: 1200, -}); +const globalOptions = useStorage<GlobalOptions>('global-options', defaultGlobalOptions); -/** - * The default unit system +/* + * The pace calculator options */ -const defaultUnitSystem = useStorage('default-unit-system', detectDefaultUnitSystem()); +const paceOptions = useStorage<PaceOptions>('pace-calculator-options', defaultPaceOptions); -/** - * The current selected target set - */ -const selectedTargetSet = useStorage('pace-calculator-target-set', '_pace_targets'); - -/** +/* * The target sets */ -const targetSets = useStorage('pace-calculator-target-sets', { - _pace_targets: defaultTargetSets._pace_targets -}); +const targetSets = useStorage<StandardTargetSets>('pace-calculator-target-sets', + defaultPaceTargetSets); </script> <style scoped> diff --git a/src/views/RaceCalculator.vue b/src/views/RaceCalculator.vue @@ -2,7 +2,7 @@ <div class="calculator"> <h2>Input Race Result</h2> <div class="input"> - <pace-input v-model="input" label="Input race"/> + <pace-input v-model="raceOptions.input" label="Input race"/> </div> <details> @@ -10,7 +10,7 @@ <h2>Race Statistics</h2> </summary> <div> - Purdy Points: <b>{{ formatNumber(raceStats.purdyPoints, 0, 1, true) }}</b> + Purdy points: <b>{{ formatNumber(raceStats.purdyPoints, 0, 1, true) }}</b> </div> <div> V&#775;O&#8322;: <b>{{ formatNumber(raceStats.vo2, 0, 1, true) }}</b> ml/kg/min @@ -26,81 +26,55 @@ <summary> <h2>Advanced Options</h2> </summary> - <div> - Default units: - <select v-model="defaultUnitSystem" aria-label="Default units"> - <option value="imperial">Miles</option> - <option value="metric">Kilometers</option> - </select> - </div> - <div> - Target Set: - <target-set-selector v-model:selectedTargetSet="selectedTargetSet" - v-model:targetSets="targetSets" :default-unit-system="defaultUnitSystem"/> - </div> - <race-options v-model="options"/> + <advanced-options-input v-model:globalOptions="globalOptions" + v-model:options="raceOptions" v-model:targetSets="targetSets" :type="Calculators.Race"/> </details> <h2>Equivalent Race Results</h2> - <single-output-table class="output" show-pace - :calculate-result="x => calculateRaceResults(input, x, options, defaultUnitSystem)" - :targets="targetSets[selectedTargetSet] ? targetSets[selectedTargetSet].targets : []"/> + <single-output-table class="output" show-pace :calculate-result="x => + calculateRaceResults(raceOptions.input, x, globalOptions.racePredictionOptions, + globalOptions.defaultUnitSystem, true)" + :targets="targetSets[raceOptions.selectedTargetSet] ? + targetSets[raceOptions.selectedTargetSet].targets : []"/> </div> </template> -<script setup> +<script setup lang="ts"> import { computed } from 'vue'; -import { calculateRaceResults, calculateRaceStats } from '@/utils/calculators'; -import { formatNumber } from '@/utils/format'; -import { defaultTargetSets } from '@/utils/targets'; -import { detectDefaultUnitSystem } from '@/utils/units'; +import { Calculators, calculateRaceResults, calculateRaceStats, defaultGlobalOptions, + defaultRaceOptions } from '@/core/calculators'; +import type { GlobalOptions, RaceOptions, RaceStats } from '@/core/calculators'; +import { defaultRaceTargetSets } from '@/core/targets'; +import type { StandardTargetSets } from '@/core/targets'; +import { formatNumber } from '@/core/units'; +import AdvancedOptionsInput from '@/components/AdvancedOptionsInput.vue'; import PaceInput from '@/components/PaceInput.vue'; -import RaceOptions from '@/components/RaceOptions.vue'; import SingleOutputTable from '@/components/SingleOutputTable.vue'; -import TargetSetSelector from '@/components/TargetSetSelector.vue'; import useStorage from '@/composables/useStorage'; -/** - * The input race +/* + * The global options */ -const input = useStorage('race-calculator-input', { - distanceValue: 5, - distanceUnit: 'kilometers', - time: 1200, -}); +const globalOptions = useStorage<GlobalOptions>('global-options', defaultGlobalOptions); -/** - * The default unit system - */ -const defaultUnitSystem = useStorage('default-unit-system', detectDefaultUnitSystem()); - -/** -* The race prediction options +/* +* The race calculator options */ -const options = useStorage('race-calculator-options', { - model: 'AverageModel', - riegelExponent: 1.06, -}); - -/** - * The current selected target set - */ -const selectedTargetSet = useStorage('race-calculator-target-set', '_race_targets'); +const raceOptions = useStorage<RaceOptions>('race-calculator-options', defaultRaceOptions); -/** - * The target sets +/* + * The race calculator target sets */ -let targetSets = useStorage('race-calculator-target-sets', { - _race_targets: defaultTargetSets._race_targets -}); +const targetSets = useStorage<StandardTargetSets>('race-calculator-target-sets', + defaultRaceTargetSets); -/** +/* * The statistics for the current input race */ -const raceStats = computed(() => calculateRaceStats(input.value)); +const raceStats = computed<RaceStats>(() => calculateRaceStats(raceOptions.value.input)); </script> <style scoped> diff --git a/src/views/SplitCalculator.vue b/src/views/SplitCalculator.vue @@ -1,69 +1,60 @@ <template> <div class="calculator"> <div class="input"> - <div class="default-units"> - Default units: - <select v-model="defaultUnitSystem" aria-label="Default units"> - <option value="imperial">Miles</option> - <option value="metric">Kilometers</option> - </select> - </div> - - <div class="target-set"> - Target Set: - <target-set-selector v-model:selectedTargetSet="selectedTargetSet" setType="split" - v-model:targetSets="targetSets" :default-unit-system="defaultUnitSystem"/> - </div> + <advanced-options-input v-model:globalOptions="globalOptions" + v-model:options="splitOptions" v-model:targetSets="targetSets" :type="Calculators.Split"/> </div> <div class="output"> - <split-output-table :default-unit-system="defaultUnitSystem" v-model="targetSet"/> + <split-output-table :default-unit-system="globalOptions.defaultUnitSystem" + v-model="targetSet"/> </div> </div> </template> -<script setup> +<script setup lang="ts"> import { computed } from 'vue'; -import { defaultTargetSets } from '@/utils/targets'; -import { detectDefaultUnitSystem } from '@/utils/units'; +import { Calculators, defaultGlobalOptions, defaultSplitOptions } from '@/core/calculators'; +import type { GlobalOptions, SplitOptions } from '@/core/calculators'; +import { defaultSplitTargetSets } from '@/core/targets'; +import type { SplitTargetSets } from '@/core/targets'; +import AdvancedOptionsInput from '@/components/AdvancedOptionsInput.vue'; import SplitOutputTable from '@/components/SplitOutputTable.vue'; -import TargetSetSelector from '@/components/TargetSetSelector.vue'; import useStorage from '@/composables/useStorage'; -/** - * The default unit system +/* + * The global options */ -const defaultUnitSystem = useStorage('default-unit-system', detectDefaultUnitSystem()); +const globalOptions = useStorage<GlobalOptions>('global-options', defaultGlobalOptions); -/** - * The current selected target set +/* + * The split calculator options */ -const selectedTargetSet = useStorage('split-calculator-target-set', '_split_targets'); +const splitOptions = useStorage<SplitOptions>('split-calculator-options', defaultSplitOptions); -/** - * The default output targets +/* + * The split calculator target sets */ -const targetSets = useStorage('split-calculator-target-sets', { - _split_targets: defaultTargetSets._split_targets -}); +const targetSets = useStorage<SplitTargetSets>('split-calculator-target-sets', + defaultSplitTargetSets); -/** +/* * The active target set */ const targetSet = computed({ get: () => { - if (targetSets.value[selectedTargetSet.value]) { - return targetSets.value[selectedTargetSet.value].targets + if (targetSets.value[splitOptions.value.selectedTargetSet]) { + return targetSets.value[splitOptions.value.selectedTargetSet].targets } else { return [] } }, set: (newValue) => { - if (targetSets.value[selectedTargetSet.value]) { - targetSets.value[selectedTargetSet.value].targets = newValue; + if (targetSets.value[splitOptions.value.selectedTargetSet]) { + targetSets.value[splitOptions.value.selectedTargetSet].targets = newValue; } }, }); diff --git a/src/views/UnitCalculator.vue b/src/views/UnitCalculator.vue @@ -6,157 +6,208 @@ <option value="speed_and_pace">Speed &amp; Pace</option> </select> - <time-input v-if="getUnitType(input.inputUnit) === 'time'" class="input-value" + <time-input v-if="isTimeUnit(input.inputUnit)" class="input-value" label="Input time" v-model="input.inputValue"/> <decimal-input v-else class="input-value" aria-label="Input value" v-model="input.inputValue" :min="0" :digits="2"/> <select v-model="input.inputUnit" class="input-units" aria-label="Input units"> - <option v-for="(value, key) in units" :key="key" :value="key"> - {{ value.name }} + <option v-for="(value, key) in categoryUnits" :key="key" :value="key"> + {{ value?.name }} </option> </select> <span class="equals"> = </span> - <span v-if="getUnitType(input.outputUnit) === 'time'" class="output-value" aria-label="Output value"> - {{ formatDuration(outputValue, 6, 3, true) }} + <span v-if="isTimeUnit(input.outputUnit)" class="output-value" aria-label="Output value"> + {{ units.formatDuration(outputValue, 6, 3, true) }} </span> <span v-else class="output-value" aria-label="Output value"> - {{ formatNumber(outputValue, 0, 3, true) }} + {{ units.formatNumber(outputValue, 0, 3, true) }} </span> <select v-model="input.outputUnit" class="output-units" aria-label="Output units"> - <option v-for="(value, key) in units" :key="key" :value="key"> - {{ value.name }} + <option v-for="(value, key) in categoryUnits" :key="key" :value="key"> + {{ value?.name }} </option> </select> </div> </template> -<script setup> -import { computed, ref } from 'vue'; +<script setup lang="ts"> +import { computed } from 'vue'; -import { formatDuration, formatNumber } from '@/utils/format'; -import { DISTANCE_UNITS, TIME_UNITS, SPEED_UNITS, PACE_UNITS, convertDistance, convertTime, -convertSpeedPace } from '@/utils/units'; +import * as units from '@/core/units'; import DecimalInput from '@/components/DecimalInput.vue'; import TimeInput from '@/components/TimeInput.vue'; import useStorage from '@/composables/useStorage'; -/** +/* + * The three categories of units + */ +enum UnitTypes { + Distance = 'distance', + Time = 'time', + SpeedPace = 'speed_and_pace', +} + +/* + * The supported time units: Hours, Minutes, Seconds, and 'hh:mm:ss' + */ +type ExtendedTimeUnits = units.TimeUnits | 'hh:mm:ss'; + +/* + * The support units from all categories + */ +type AllUnits = units.DistanceUnits | ExtendedTimeUnits | units.SpeedPaceUnits; + +/* + * The type of the calculator inputs + */ +interface UnitCalculatorInputs { + [UnitTypes.Distance]: { + inputValue: number, + inputUnit: units.DistanceUnits, + outputUnit: units.DistanceUnits, + }, + [UnitTypes.Time]: { + inputValue: number, + inputUnit: ExtendedTimeUnits, + outputUnit: ExtendedTimeUnits, + }, + [UnitTypes.SpeedPace]: { + inputValue: number, + inputUnit: units.SpeedPaceUnits, + outputUnit: units.SpeedPaceUnits, + }, +}; + +/* * The calculator inputs */ -const inputs = useStorage('unit-calculator-inputs', { - distance: { +const inputs = useStorage<UnitCalculatorInputs>('unit-calculator-inputs', { + [UnitTypes.Distance]: { inputValue: 1, - inputUnit: 'miles', - outputUnit: 'kilometers', + inputUnit: units.DistanceUnits.Miles, + outputUnit: units.DistanceUnits.Kilometers, }, - time: { + [UnitTypes.Time]: { inputValue: 1, - inputUnit: 'seconds', + inputUnit: units.TimeUnits.Seconds, outputUnit: 'hh:mm:ss', }, - speed_and_pace: { + [UnitTypes.SpeedPace]: { inputValue: 600, - inputUnit: 'seconds_per_mile', - outputUnit: 'miles_per_hour', + inputUnit: units.PaceUnits.TimePerMile, + outputUnit: units.SpeedUnits.MilesPerHour, }, }); -/** +/* * The unit category */ -const category = ref('distance'); +const category = useStorage<UnitTypes>('unit-calculator-category', UnitTypes.Distance); -/** +/* * The inputs for the current category */ -const input = computed({ +const input = computed<{ inputValue: number, inputUnit: AllUnits, outputUnit: AllUnits }>({ get() { return inputs.value[category.value]; }, set(newValue) { - inputs.value[category.value] = newValue; + switch (category.value) { + default: + case UnitTypes.Distance: { + inputs.value[category.value] = { + inputValue: newValue.inputValue, + inputUnit: newValue.inputUnit as units.DistanceUnits, + outputUnit: newValue.outputUnit as units.DistanceUnits, + }; + break; + } + case UnitTypes.Time: { + inputs.value[category.value] = { + inputValue: newValue.inputValue, + inputUnit: newValue.inputUnit as ExtendedTimeUnits, + outputUnit: newValue.outputUnit as ExtendedTimeUnits, + }; + break; + } + case UnitTypes.SpeedPace: { + inputs.value[category.value] = { + inputValue: newValue.inputValue, + inputUnit: newValue.inputUnit as units.SpeedPaceUnits, + outputUnit: newValue.outputUnit as units.SpeedPaceUnits, + }; + break; + } + } }, }); -/** - * The names of the units in the current category +/* + * The data for the units in the current category */ -const units = computed(() => { +const categoryUnits = computed<{ [key in AllUnits]?: units.UnitData }>(() => { switch (category.value) { - case 'distance': { - return DISTANCE_UNITS; + default: + case UnitTypes.Distance: { + return units.DistanceUnitData; } - case 'time': { + case UnitTypes.Time: { return { - ...TIME_UNITS, + ...units.TimeUnitData, 'hh:mm:ss': { name: 'hh:mm:ss', symbol: '', - value: null, + value: 1, }, }; } - case 'speed_and_pace': { - return { ...PACE_UNITS, ...SPEED_UNITS }; - } - default: { - return {}; + case UnitTypes.SpeedPace: { + return { ...units.PaceUnitData, ...units.SpeedUnitData }; } } }); -/** +/* * The output value */ -const outputValue = computed(() => { +const outputValue = computed<number>(() => { switch (category.value) { - case 'distance': { - return convertDistance(input.value.inputValue, input.value.inputUnit, - input.value.outputUnit); + default: + case UnitTypes.Distance: { + return units.convertDistance(input.value.inputValue, + input.value.inputUnit as units.DistanceUnits, + input.value.outputUnit as units.DistanceUnits); } - case 'time': { + case UnitTypes.Time: { // Correct input and output units for 'hh:mm:ss' unit const realInput = input.value.inputUnit === 'hh:mm:ss' ? 'seconds' : input.value.inputUnit; const realOutput = input.value.outputUnit === 'hh:mm:ss' ? 'seconds' : input.value.outputUnit; // Calculate conversion - return convertTime(input.value.inputValue, realInput, realOutput); - } - case 'speed_and_pace': { - return convertSpeedPace(input.value.inputValue, input.value.inputUnit, - input.value.outputUnit); + return units.convertTime(input.value.inputValue, realInput as units.TimeUnits, + realOutput as units.TimeUnits); } - default: { - return null; + case UnitTypes.SpeedPace: { + return units.convertSpeedPace(input.value.inputValue, + input.value.inputUnit as units.SpeedPaceUnits, + input.value.outputUnit as units.SpeedPaceUnits); } } }); /** - * Get the type of a unit - * @param {String} unit The unit - * @returns {String} The type ('decimal' or 'time') + * Determine whether a unit should be represented as a time + * @param {AllUnits} unit The unit + * @returns {boolean} Whether the unit should be represented as a time */ -function getUnitType(unit) { - if (unit in DISTANCE_UNITS) { - return 'decimal'; - } - if (unit in TIME_UNITS) { - return 'decimal'; - } - if (unit === 'hh:mm:ss') { - return 'time'; - } - if (['seconds_per_kilometer', 'seconds_per_mile'].includes(unit)) { - return 'time'; - } - return 'decimal'; +function isTimeUnit(unit: AllUnits): boolean { + return unit in units.PaceUnitData || unit === 'hh:mm:ss'; } </script> diff --git a/src/views/WorkoutCalculator.vue b/src/views/WorkoutCalculator.vue @@ -2,80 +2,54 @@ <div class="calculator"> <h2>Input Race Result</h2> <div class="input"> - <pace-input v-model="input" label="Input race"/> + <pace-input v-model="workoutOptions.input" label="Input race"/> </div> <details> <summary> <h2>Advanced Options</h2> </summary> - <div> - Default units: - <select v-model="defaultUnitSystem" aria-label="Default units"> - <option value="imperial">Miles</option> - <option value="metric">Kilometers</option> - </select> - </div> - <div> - Target Set: - <target-set-selector v-model:selectedTargetSet="selectedTargetSet" setType="workout" - v-model:targetSets="targetSets" :default-unit-system="defaultUnitSystem"/> - </div> - <race-options v-model="options"/> + <advanced-options-input v-model:globalOptions="globalOptions" + v-model:options="workoutOptions" v-model:targetSets="targetSets" :type="Calculators.Workout"/> </details> <h2>Workout Splits</h2> - <single-output-table class="output" - :calculate-result="x => calculateWorkoutResults(input, x, options)" - :targets="targetSets[selectedTargetSet] ? targetSets[selectedTargetSet].targets : []"/> + <single-output-table class="output" :calculate-result="x => + calculateWorkoutResults(workoutOptions.input, x as WorkoutTarget, + globalOptions.racePredictionOptions, workoutOptions.customTargetNames, true)" + :targets="targetSets[workoutOptions.selectedTargetSet] ? + targetSets[workoutOptions.selectedTargetSet].targets : []"/> </div> </template> -<script setup> -import { calculateWorkoutResults } from '@/utils/calculators'; -import { defaultTargetSets } from '@/utils/targets'; -import { detectDefaultUnitSystem } from '@/utils/units'; +<script setup lang="ts"> +import { Calculators, calculateWorkoutResults, defaultGlobalOptions, + defaultWorkoutOptions } from '@/core/calculators'; +import type { GlobalOptions, WorkoutOptions } from '@/core/calculators'; +import { defaultWorkoutTargetSets } from '@/core/targets'; +import type { WorkoutTarget, WorkoutTargetSets } from '@/core/targets'; +import AdvancedOptionsInput from '@/components/AdvancedOptionsInput.vue'; import PaceInput from '@/components/PaceInput.vue'; -import RaceOptions from '@/components/RaceOptions.vue'; import SingleOutputTable from '@/components/SingleOutputTable.vue'; -import TargetSetSelector from '@/components/TargetSetSelector.vue'; import useStorage from '@/composables/useStorage'; -/** - * The input race +/* + * The global options */ -const input = useStorage('workout-calculator-input', { - distanceValue: 5, - distanceUnit: 'kilometers', - time: 1200, -}); +const globalOptions = useStorage<GlobalOptions>('global-options', defaultGlobalOptions); -/** - * The default unit system - */ -const defaultUnitSystem = useStorage('default-unit-system', detectDefaultUnitSystem()); - -/** +/* * The race prediction options */ -const options = useStorage('workout-calculator-options', { - model: 'AverageModel', - riegelExponent: 1.06, -}); - -/** - * The current selected target set - */ -const selectedTargetSet = useStorage('workout-calculator-target-set', '_workout_targets'); +const workoutOptions = useStorage<WorkoutOptions>('workout-calculator-options', defaultWorkoutOptions); -/** +/* * The target sets */ -let targetSets = useStorage('workout-calculator-target-sets', { - _workout_targets: defaultTargetSets._workout_targets -}); +const targetSets = useStorage<WorkoutTargetSets>('workout-calculator-target-sets', + defaultWorkoutTargetSets); </script> <style scoped> diff --git a/tests/e2e/batch-calculator.spec.js b/tests/e2e/batch-calculator.spec.js @@ -1,173 +1,182 @@ import { test, expect } from '@playwright/test'; test('Batch calculator', async ({ page }) => { - // Structure: - // - Test workout batch results, including modified prediction model - // - Test pace batch results, including modified default units - // - Test race batch results, including modified Riegel exponent - // - Reload page - // - Assert race batch results are still the same - // - Assert pace batch results are still the same - // - Assert workout batch results are still the same - await page.goto('/'); - // Go to batch calculator - await page.getByRole('button', { name: 'Batch Calculator' }).click(); - await expect(page).toHaveTitle('Batch Calculator - Running Tools'); - - // Enter input pace (2 mi in 10:30) - await page.getByLabel('Input distance value').fill('2'); - await page.getByLabel('Input distance unit').selectOption('Miles'); - await page.getByLabel('Input duration hours').fill('0'); - await page.getByLabel('Input duration minutes').fill('10'); - await page.getByLabel('Input duration seconds').fill('30'); - - // Enter batch options (15 x 10s increments) - await page.getByLabel('Duration increment minutes').fill('0'); - await page.getByLabel('Duration increment seconds').fill('10'); - await page.getByLabel('Number of rows').fill('15'); - - // Assert workout results are correct - await expect(page.getByRole('row').nth(0).getByRole('cell').nth(0)).toHaveText('2 mi'); - await expect(page.getByRole('row').nth(0).getByRole('cell').nth(2)).toHaveText('800 m @ 5 km'); - await expect(page.getByRole('row').nth(0).getByRole('cell')).toHaveCount(5); - await expect(page.getByRole('row').nth(1).getByRole('cell').nth(0)).toHaveText('10:30'); - await expect(page.getByRole('row').nth(1).getByRole('cell').nth(2)).toHaveText('2:41.21'); - await expect(page.getByRole('row').nth(1).getByRole('cell')).toHaveCount(5); - await expect(page.getByRole('row').nth(15).getByRole('cell').nth(0)).toHaveText('12:50'); - await expect(page.getByRole('row').nth(15).getByRole('cell').nth(2)).toHaveText('3:16.90'); - await expect(page.getByRole('row').nth(15).getByRole('cell')).toHaveCount(5); - await expect(page.getByRole('row')).toHaveCount(16); - - // Change prediction model - await page.getByText('Advanced Options').click(); - await page.getByLabel('Prediction model').selectOption('Riegel\'s Model'); - - // Assert workout results are correct - await expect(page.getByRole('row').nth(0).getByRole('cell').nth(0)).toHaveText('2 mi'); - await expect(page.getByRole('row').nth(0).getByRole('cell').nth(2)).toHaveText('800 m @ 5 km'); - await expect(page.getByRole('row').nth(0).getByRole('cell')).toHaveCount(5); - await expect(page.getByRole('row').nth(1).getByRole('cell').nth(0)).toHaveText('10:30'); - await expect(page.getByRole('row').nth(1).getByRole('cell').nth(2)).toHaveText('2:40.78'); - await expect(page.getByRole('row').nth(1).getByRole('cell')).toHaveCount(5); - await expect(page.getByRole('row').nth(15).getByRole('cell').nth(0)).toHaveText('12:50'); - await expect(page.getByRole('row').nth(15).getByRole('cell').nth(2)).toHaveText('3:16.51'); - await expect(page.getByRole('row').nth(15).getByRole('cell')).toHaveCount(5); - await expect(page.getByRole('row')).toHaveCount(16); - - // Change calculator - await expect(page.getByLabel('Calculator')).toHaveValue('workout'); - await page.getByLabel('Calculator').selectOption('Pace Calculator'); - - // Assert pace results are correct - await expect(page.getByRole('row').nth(0).getByRole('cell').nth(0)).toHaveText('2 mi'); - await expect(page.getByRole('row').nth(0).getByRole('cell').nth(6)).toHaveText('800 m'); - await expect(page.getByRole('row').nth(0).getByRole('cell').nth(28)).toHaveText('10:00'); - await expect(page.getByRole('row').nth(0).getByRole('cell')).toHaveCount(31); - await expect(page.getByRole('row').nth(1).getByRole('cell').nth(0)).toHaveText('10:30'); - await expect(page.getByRole('row').nth(1).getByRole('cell').nth(6)).toHaveText('2:36.58'); - await expect(page.getByRole('row').nth(1).getByRole('cell').nth(28)).toHaveText('1.90 mi'); - await expect(page.getByRole('row').nth(1).getByRole('cell')).toHaveCount(31); - await expect(page.getByRole('row').nth(15).getByRole('cell').nth(0)).toHaveText('12:50'); - await expect(page.getByRole('row').nth(15).getByRole('cell').nth(6)).toHaveText('3:11.38'); - await expect(page.getByRole('row').nth(15).getByRole('cell').nth(28)).toHaveText('1.56 mi'); - await expect(page.getByRole('row').nth(15).getByRole('cell')).toHaveCount(31); - await expect(page.getByRole('row')).toHaveCount(16); - - // Assert prediction options are hidden - await expect(page.getByLabel('Prediction model')).toHaveCount(0); - - // Change default units - await page.getByLabel('Default units').selectOption('Kilometers'); - - // Assert pace results are correct - await expect(page.getByRole('row').nth(0).getByRole('cell').nth(0)).toHaveText('2 mi'); - await expect(page.getByRole('row').nth(0).getByRole('cell').nth(6)).toHaveText('800 m'); - await expect(page.getByRole('row').nth(0).getByRole('cell').nth(28)).toHaveText('10:00'); - await expect(page.getByRole('row').nth(0).getByRole('cell')).toHaveCount(31); - await expect(page.getByRole('row').nth(1).getByRole('cell').nth(0)).toHaveText('10:30'); - await expect(page.getByRole('row').nth(1).getByRole('cell').nth(6)).toHaveText('2:36.58'); - await expect(page.getByRole('row').nth(1).getByRole('cell').nth(28)).toHaveText('3.07 km'); - await expect(page.getByRole('row').nth(1).getByRole('cell')).toHaveCount(31); - await expect(page.getByRole('row').nth(15).getByRole('cell').nth(0)).toHaveText('12:50'); - await expect(page.getByRole('row').nth(15).getByRole('cell').nth(6)).toHaveText('3:11.38'); - await expect(page.getByRole('row').nth(15).getByRole('cell').nth(28)).toHaveText('2.51 km'); - await expect(page.getByRole('row').nth(15).getByRole('cell')).toHaveCount(31); - await expect(page.getByRole('row')).toHaveCount(16); - - // Change calculator - await page.getByLabel('Calculator').selectOption('Race Calculator'); - - // Assert race results are correct - await expect(page.getByRole('row').nth(0).getByRole('cell').nth(0)).toHaveText('2 mi'); - await expect(page.getByRole('row').nth(0).getByRole('cell').nth(2)).toHaveText('800 m'); - await expect(page.getByRole('row').nth(0).getByRole('cell')).toHaveCount(17); - await expect(page.getByRole('row').nth(1).getByRole('cell').nth(0)).toHaveText('10:30'); - await expect(page.getByRole('row').nth(1).getByRole('cell').nth(2)).toHaveText('2:14.60'); - await expect(page.getByRole('row').nth(1).getByRole('cell')).toHaveCount(17); - await expect(page.getByRole('row').nth(15).getByRole('cell').nth(0)).toHaveText('12:50'); - await expect(page.getByRole('row').nth(15).getByRole('cell').nth(2)).toHaveText('2:43.62'); - await expect(page.getByRole('row').nth(15).getByRole('cell')).toHaveCount(17); - await expect(page.getByRole('row')).toHaveCount(16); - - // Change Riegel exponent - await expect(page.getByLabel('Prediction model')).toHaveValue('AverageModel'); - await page.getByLabel('Riegel Exponent').fill('1.12'); - - // Assert race results are correct - await expect(page.getByRole('row').nth(0).getByRole('cell').nth(0)).toHaveText('2 mi'); - await expect(page.getByRole('row').nth(0).getByRole('cell').nth(2)).toHaveText('800 m'); - await expect(page.getByRole('row').nth(0).getByRole('cell')).toHaveCount(17); - await expect(page.getByRole('row').nth(1).getByRole('cell').nth(0)).toHaveText('10:30'); - await expect(page.getByRole('row').nth(1).getByRole('cell').nth(2)).toHaveText('2:11.72'); - await expect(page.getByRole('row').nth(1).getByRole('cell')).toHaveCount(17); - await expect(page.getByRole('row').nth(15).getByRole('cell').nth(0)).toHaveText('12:50'); - await expect(page.getByRole('row').nth(15).getByRole('cell').nth(2)).toHaveText('2:40.09'); - await expect(page.getByRole('row').nth(15).getByRole('cell')).toHaveCount(17); - await expect(page.getByRole('row')).toHaveCount(16); + // Test workout batch results, including modified prediction model and custom target names + { + // Go to batch calculator + await page.getByRole('button', { name: 'Batch Calculator' }).click(); + await expect(page).toHaveTitle('Batch Calculator - Running Tools'); + + // Enter input pace (2 mi in 10:30) + await page.getByLabel('Input distance value').fill('2'); + await page.getByLabel('Input distance unit').selectOption('Miles'); + await page.getByLabel('Input duration hours').fill('0'); + await page.getByLabel('Input duration minutes').fill('10'); + await page.getByLabel('Input duration seconds').fill('30'); + + // Enter batch options (15 x 10s increments) + await page.getByLabel('Duration increment minutes').fill('0'); + await page.getByLabel('Duration increment seconds').fill('10'); + await page.getByLabel('Number of rows').fill('15'); + + // Assert workout results are correct + await expect(page.getByRole('row').nth(0).getByRole('cell').nth(0)).toHaveText('2 mi'); + await expect(page.getByRole('row').nth(0).getByRole('cell').nth(2)).toHaveText('800 m @ 5 km'); + await expect(page.getByRole('row').nth(0).getByRole('cell')).toHaveCount(5); + await expect(page.getByRole('row').nth(1).getByRole('cell').nth(0)).toHaveText('10:30'); + await expect(page.getByRole('row').nth(1).getByRole('cell').nth(2)).toHaveText('2:41'); + await expect(page.getByRole('row').nth(1).getByRole('cell')).toHaveCount(5); + await expect(page.getByRole('row').nth(15).getByRole('cell').nth(0)).toHaveText('12:50'); + await expect(page.getByRole('row').nth(15).getByRole('cell').nth(2)).toHaveText('3:17'); + await expect(page.getByRole('row').nth(15).getByRole('cell')).toHaveCount(5); + await expect(page.getByRole('row')).toHaveCount(16); + + // Change prediction model, enable customized target names, and set custom batch column label + await page.getByText('Advanced Options').click(); + await page.getByLabel('Prediction model').selectOption('Riegel\'s Model'); + await page.getByLabel('Workout name customization').selectOption('Enabled'); + await page.getByLabel('Batch column label').fill('foo'); + + // Assert workout results are correct + await expect(page.getByRole('row').nth(0).getByRole('cell').nth(0)).toHaveText('foo'); + await expect(page.getByRole('row').nth(0).getByRole('cell').nth(2)).toHaveText('800 m @ 5 km'); + await expect(page.getByRole('row').nth(0).getByRole('cell')).toHaveCount(5); + await expect(page.getByRole('row').nth(1).getByRole('cell').nth(0)).toHaveText('10:30'); + await expect(page.getByRole('row').nth(1).getByRole('cell').nth(2)).toHaveText('2:41'); + await expect(page.getByRole('row').nth(1).getByRole('cell')).toHaveCount(5); + await expect(page.getByRole('row').nth(15).getByRole('cell').nth(0)).toHaveText('12:50'); + await expect(page.getByRole('row').nth(15).getByRole('cell').nth(2)).toHaveText('3:17'); + await expect(page.getByRole('row').nth(15).getByRole('cell')).toHaveCount(5); + await expect(page.getByRole('row')).toHaveCount(16); + } + + // Test pace batch results, including modified default units + { + // Change calculator + await expect(page.getByLabel('Calculator')).toHaveValue('workout'); + await page.getByLabel('Calculator').selectOption('Pace Calculator'); + + // Assert pace results are correct + await expect(page.getByRole('row').nth(0).getByRole('cell').nth(0)).toHaveText('2 mi'); + await expect(page.getByRole('row').nth(0).getByRole('cell').nth(6)).toHaveText('800 m'); + await expect(page.getByRole('row').nth(0).getByRole('cell').nth(28)).toHaveText('10:00'); + await expect(page.getByRole('row').nth(0).getByRole('cell')).toHaveCount(31); + await expect(page.getByRole('row').nth(1).getByRole('cell').nth(0)).toHaveText('10:30'); + await expect(page.getByRole('row').nth(1).getByRole('cell').nth(6)).toHaveText('2:37'); + await expect(page.getByRole('row').nth(1).getByRole('cell').nth(28)).toHaveText('1.90 mi'); + await expect(page.getByRole('row').nth(1).getByRole('cell')).toHaveCount(31); + await expect(page.getByRole('row').nth(15).getByRole('cell').nth(0)).toHaveText('12:50'); + await expect(page.getByRole('row').nth(15).getByRole('cell').nth(6)).toHaveText('3:11'); + await expect(page.getByRole('row').nth(15).getByRole('cell').nth(28)).toHaveText('1.56 mi'); + await expect(page.getByRole('row').nth(15).getByRole('cell')).toHaveCount(31); + await expect(page.getByRole('row')).toHaveCount(16); + + // Assert prediction options are hidden + await expect(page.getByLabel('Prediction model')).toHaveCount(0); + + // Change default units + await page.getByLabel('Default units').selectOption('Kilometers'); + + // Assert pace results are correct + await expect(page.getByRole('row').nth(0).getByRole('cell').nth(0)).toHaveText('2 mi'); + await expect(page.getByRole('row').nth(0).getByRole('cell').nth(6)).toHaveText('800 m'); + await expect(page.getByRole('row').nth(0).getByRole('cell').nth(28)).toHaveText('10:00'); + await expect(page.getByRole('row').nth(0).getByRole('cell')).toHaveCount(31); + await expect(page.getByRole('row').nth(1).getByRole('cell').nth(0)).toHaveText('10:30'); + await expect(page.getByRole('row').nth(1).getByRole('cell').nth(6)).toHaveText('2:37'); + await expect(page.getByRole('row').nth(1).getByRole('cell').nth(28)).toHaveText('3.07 km'); + await expect(page.getByRole('row').nth(1).getByRole('cell')).toHaveCount(31); + await expect(page.getByRole('row').nth(15).getByRole('cell').nth(0)).toHaveText('12:50'); + await expect(page.getByRole('row').nth(15).getByRole('cell').nth(6)).toHaveText('3:11'); + await expect(page.getByRole('row').nth(15).getByRole('cell').nth(28)).toHaveText('2.51 km'); + await expect(page.getByRole('row').nth(15).getByRole('cell')).toHaveCount(31); + await expect(page.getByRole('row')).toHaveCount(16); + } + + // Test race batch results, including modified Riegel exponent + { + // Change calculator + await page.getByLabel('Calculator').selectOption('Race Calculator'); + + // Assert race results are correct + await expect(page.getByRole('row').nth(0).getByRole('cell').nth(0)).toHaveText('2 mi'); + await expect(page.getByRole('row').nth(0).getByRole('cell').nth(2)).toHaveText('800 m'); + await expect(page.getByRole('row').nth(0).getByRole('cell')).toHaveCount(17); + await expect(page.getByRole('row').nth(1).getByRole('cell').nth(0)).toHaveText('10:30'); + await expect(page.getByRole('row').nth(1).getByRole('cell').nth(2)).toHaveText('2:24'); + await expect(page.getByRole('row').nth(1).getByRole('cell')).toHaveCount(17); + await expect(page.getByRole('row').nth(15).getByRole('cell').nth(0)).toHaveText('12:50'); + await expect(page.getByRole('row').nth(15).getByRole('cell').nth(2)).toHaveText('2:56'); + await expect(page.getByRole('row').nth(15).getByRole('cell')).toHaveCount(17); + await expect(page.getByRole('row')).toHaveCount(16); + + // Change Riegel exponent + await expect(page.getByLabel('Prediction model')).toHaveValue('RiegelModel'); + await page.getByLabel('Riegel Exponent').fill('1.12'); + + // Assert race results are correct + await expect(page.getByRole('row').nth(0).getByRole('cell').nth(0)).toHaveText('2 mi'); + await expect(page.getByRole('row').nth(0).getByRole('cell').nth(2)).toHaveText('800 m'); + await expect(page.getByRole('row').nth(0).getByRole('cell')).toHaveCount(17); + await expect(page.getByRole('row').nth(1).getByRole('cell').nth(0)).toHaveText('10:30'); + await expect(page.getByRole('row').nth(1).getByRole('cell').nth(2)).toHaveText('2:12'); + await expect(page.getByRole('row').nth(1).getByRole('cell')).toHaveCount(17); + await expect(page.getByRole('row').nth(15).getByRole('cell').nth(0)).toHaveText('12:50'); + await expect(page.getByRole('row').nth(15).getByRole('cell').nth(2)).toHaveText('2:42'); + await expect(page.getByRole('row').nth(15).getByRole('cell')).toHaveCount(17); + await expect(page.getByRole('row')).toHaveCount(16); + } // Reload page await page.reload(); // Assert race results are correct (inputs and options not reset) - await expect(page.getByRole('row').nth(0).getByRole('cell').nth(0)).toHaveText('2 mi'); - await expect(page.getByRole('row').nth(0).getByRole('cell').nth(2)).toHaveText('800 m'); - await expect(page.getByRole('row').nth(0).getByRole('cell')).toHaveCount(17); - await expect(page.getByRole('row').nth(1).getByRole('cell').nth(0)).toHaveText('10:30'); - await expect(page.getByRole('row').nth(1).getByRole('cell').nth(2)).toHaveText('2:11.72'); - await expect(page.getByRole('row').nth(1).getByRole('cell')).toHaveCount(17); - await expect(page.getByRole('row').nth(15).getByRole('cell').nth(0)).toHaveText('12:50'); - await expect(page.getByRole('row').nth(15).getByRole('cell').nth(2)).toHaveText('2:40.09'); - await expect(page.getByRole('row').nth(15).getByRole('cell')).toHaveCount(17); - await expect(page.getByRole('row')).toHaveCount(16); + { + await expect(page.getByRole('row').nth(0).getByRole('cell').nth(0)).toHaveText('2 mi'); + await expect(page.getByRole('row').nth(0).getByRole('cell').nth(2)).toHaveText('800 m'); + await expect(page.getByRole('row').nth(0).getByRole('cell')).toHaveCount(17); + await expect(page.getByRole('row').nth(1).getByRole('cell').nth(0)).toHaveText('10:30'); + await expect(page.getByRole('row').nth(1).getByRole('cell').nth(2)).toHaveText('2:12'); + await expect(page.getByRole('row').nth(1).getByRole('cell')).toHaveCount(17); + await expect(page.getByRole('row').nth(15).getByRole('cell').nth(0)).toHaveText('12:50'); + await expect(page.getByRole('row').nth(15).getByRole('cell').nth(2)).toHaveText('2:42'); + await expect(page.getByRole('row').nth(15).getByRole('cell')).toHaveCount(17); + await expect(page.getByRole('row')).toHaveCount(16); + } // Assert pace results are correct (inputs and options not reset) - await page.getByLabel('Calculator').selectOption('Pace Calculator'); - await expect(page.getByRole('row').nth(0).getByRole('cell').nth(0)).toHaveText('2 mi'); - await expect(page.getByRole('row').nth(0).getByRole('cell').nth(6)).toHaveText('800 m'); - await expect(page.getByRole('row').nth(0).getByRole('cell').nth(28)).toHaveText('10:00'); - await expect(page.getByRole('row').nth(0).getByRole('cell')).toHaveCount(31); - await expect(page.getByRole('row').nth(1).getByRole('cell').nth(0)).toHaveText('10:30'); - await expect(page.getByRole('row').nth(1).getByRole('cell').nth(6)).toHaveText('2:36.58'); - await expect(page.getByRole('row').nth(1).getByRole('cell').nth(28)).toHaveText('3.07 km'); - await expect(page.getByRole('row').nth(1).getByRole('cell')).toHaveCount(31); - await expect(page.getByRole('row').nth(15).getByRole('cell').nth(0)).toHaveText('12:50'); - await expect(page.getByRole('row').nth(15).getByRole('cell').nth(6)).toHaveText('3:11.38'); - await expect(page.getByRole('row').nth(15).getByRole('cell').nth(28)).toHaveText('2.51 km'); - await expect(page.getByRole('row').nth(15).getByRole('cell')).toHaveCount(31); - await expect(page.getByRole('row')).toHaveCount(16); - - // Assert workout results are correct (inputs and options not reset) - await page.getByLabel('Calculator').selectOption('Workout Calculator'); - await expect(page.getByRole('row').nth(0).getByRole('cell').nth(0)).toHaveText('2 mi'); - await expect(page.getByRole('row').nth(0).getByRole('cell').nth(2)).toHaveText('800 m @ 5 km'); - await expect(page.getByRole('row').nth(0).getByRole('cell')).toHaveCount(5); - await expect(page.getByRole('row').nth(1).getByRole('cell').nth(0)).toHaveText('10:30'); - await expect(page.getByRole('row').nth(1).getByRole('cell').nth(2)).toHaveText('2:40.78'); - await expect(page.getByRole('row').nth(1).getByRole('cell')).toHaveCount(5); - await expect(page.getByRole('row').nth(15).getByRole('cell').nth(0)).toHaveText('12:50'); - await expect(page.getByRole('row').nth(15).getByRole('cell').nth(2)).toHaveText('3:16.51'); - await expect(page.getByRole('row').nth(15).getByRole('cell')).toHaveCount(5); - await expect(page.getByRole('row')).toHaveCount(16); + { + await page.getByLabel('Calculator').selectOption('Pace Calculator'); + await expect(page.getByRole('row').nth(0).getByRole('cell').nth(0)).toHaveText('2 mi'); + await expect(page.getByRole('row').nth(0).getByRole('cell').nth(6)).toHaveText('800 m'); + await expect(page.getByRole('row').nth(0).getByRole('cell').nth(28)).toHaveText('10:00'); + await expect(page.getByRole('row').nth(0).getByRole('cell')).toHaveCount(31); + await expect(page.getByRole('row').nth(1).getByRole('cell').nth(0)).toHaveText('10:30'); + await expect(page.getByRole('row').nth(1).getByRole('cell').nth(6)).toHaveText('2:37'); + await expect(page.getByRole('row').nth(1).getByRole('cell').nth(28)).toHaveText('3.07 km'); + await expect(page.getByRole('row').nth(1).getByRole('cell')).toHaveCount(31); + await expect(page.getByRole('row').nth(15).getByRole('cell').nth(0)).toHaveText('12:50'); + await expect(page.getByRole('row').nth(15).getByRole('cell').nth(6)).toHaveText('3:11'); + await expect(page.getByRole('row').nth(15).getByRole('cell').nth(28)).toHaveText('2.51 km'); + await expect(page.getByRole('row').nth(15).getByRole('cell')).toHaveCount(31); + await expect(page.getByRole('row')).toHaveCount(16); + } + + // Assert workout results are correct (inputs not reset, but updated options are used) + { + await page.getByLabel('Calculator').selectOption('Workout Calculator'); + await expect(page.getByLabel('Workout name customization')).toHaveValue("true"); + await expect(page.getByRole('row').nth(0).getByRole('cell').nth(0)).toHaveText('foo'); + await expect(page.getByRole('row').nth(0).getByRole('cell').nth(2)).toHaveText('800 m @ 5 km'); + await expect(page.getByRole('row').nth(0).getByRole('cell')).toHaveCount(5); + await expect(page.getByRole('row').nth(1).getByRole('cell').nth(0)).toHaveText('10:30'); + await expect(page.getByRole('row').nth(1).getByRole('cell').nth(2)).toHaveText('2:45'); + await expect(page.getByRole('row').nth(1).getByRole('cell')).toHaveCount(5); + await expect(page.getByRole('row').nth(15).getByRole('cell').nth(0)).toHaveText('12:50'); + await expect(page.getByRole('row').nth(15).getByRole('cell').nth(2)).toHaveText('3:22'); + await expect(page.getByRole('row').nth(15).getByRole('cell')).toHaveCount(5); + await expect(page.getByRole('row')).toHaveCount(16); + } }); diff --git a/tests/e2e/cross-calculator.spec.js b/tests/e2e/cross-calculator.spec.js @@ -1,206 +1,1101 @@ import { test, expect } from '@playwright/test'; test('Cross-calculator', async ({ page }) => { - // Go to batch calculator await page.goto('/'); - await page.getByRole('button', { name: 'Batch Calculator' }).click(); - // Enter input pace (2 mi in 10:30) - await page.getByLabel('Input distance value').fill('2'); - await page.getByLabel('Input distance unit').selectOption('Miles'); - await page.getByLabel('Input duration hours').fill('0'); - await page.getByLabel('Input duration minutes').fill('10'); - await page.getByLabel('Input duration seconds').fill('30'); + // Set various options in the different calculators + { + // Go to batch calculator + await page.getByRole('button', { name: 'Batch Calculator' }).click(); - // Enter batch options (15 x 10s increments) - await page.getByLabel('Duration increment minutes').fill('0'); - await page.getByLabel('Duration increment seconds').fill('10'); - await page.getByLabel('Number of rows').fill('15'); + // Enter input pace (2 mi in 10:30) + await page.getByLabel('Input distance value').fill('2'); + await page.getByLabel('Input distance unit').selectOption('Miles'); + await page.getByLabel('Input duration hours').fill('0'); + await page.getByLabel('Input duration minutes').fill('10'); + await page.getByLabel('Input duration seconds').fill('30'); - // Change calculator - await page.getByLabel('Calculator').selectOption('Race Calculator'); + // Enter batch options (15 x 10s increments) + await page.getByLabel('Duration increment minutes').fill('0'); + await page.getByLabel('Duration increment seconds').fill('10'); + await page.getByLabel('Number of rows').fill('15'); - // Change prediction model - await page.getByText('Advanced Options').click(); - await page.getByLabel('Prediction model').selectOption('Riegel\'s Model'); + // Change calculator + await page.getByLabel('Calculator').selectOption('Race Calculator'); - // Go to pace calculator + // Change prediction model + await page.getByText('Advanced Options').click(); + await page.getByLabel('Prediction model').selectOption('Riegel\'s Model'); + + // Go to pace calculator + await page.goto('/'); + await page.getByRole('button', { name: 'Pace Calculator' }).click(); + + // Enter input pace (2 mi in 15:30) + await page.getByLabel('Input distance value').fill('2'); + await page.getByLabel('Input distance unit').selectOption('Miles'); + await page.getByLabel('Input duration hours').fill('0'); + await page.getByLabel('Input duration minutes').fill('15'); + await page.getByLabel('Input duration seconds').fill('30'); + + // Create custom target set + await page.getByText('Advanced Options').click(); + await page.getByLabel('Selected target set').selectOption('[ Create New Target Set ]'); + await expect(page.getByRole('row').nth(4)).toHaveText('There aren\'t any targets in this set yet.'); + await expect(page.getByRole('row')).toHaveCount(5); + + // Edit new target set + await expect(page.getByRole('row').nth(1)).toHaveText('There aren\'t any targets in this set yet.'); + await expect(page.getByLabel('Target set label')).toHaveValue('New target set'); + await page.getByLabel('Target set label').fill('800m Splits'); + await page.getByRole('button', { name: 'Add distance target' }).click(); + await page.getByLabel('Target distance value').nth(0).fill('0.4'); + await page.getByLabel('Target distance unit').nth(0).selectOption('Kilometers'); + await page.getByRole('button', { name: 'Add distance target' }).click(); + await page.getByLabel('Target distance value').nth(1).fill('800'); + await page.getByLabel('Target distance unit').nth(1).selectOption('Meters'); + await page.getByRole('button', { name: 'Add time target' }).click(); + await page.getByRole('button', { name: 'Close' }).click(); + + // Go to race calculator + await page.getByRole('link', { name: 'Back' }).click(); + await page.getByRole('button', { name: 'Race Calculator' }).click(); + + // Enter input race (2 mi in 10:30) + await page.getByLabel('Input race distance value').fill('2'); + await page.getByLabel('Input race distance unit').selectOption('Miles'); + await page.getByLabel('Input race duration hours').fill('0'); + await page.getByLabel('Input race duration minutes').fill('10'); + await page.getByLabel('Input race duration seconds').fill('30'); + + // Go to split calculator + await page.getByRole('link', { name: 'Back' }).click(); + await page.getByRole('button', { name: 'Split Calculator' }).click(); + + // Edit target set + await page.getByRole('button', { name: 'Edit target set' }).click(); + await page.getByLabel('Target set label').fill('5K 1600m Splits'); + await page.getByLabel('Target distance value').nth(0).fill('1.6'); + await page.getByLabel('Target distance unit').nth(0).selectOption('Kilometers'); + await page.getByLabel('Target distance value').nth(1).fill('3.2'); + await page.getByLabel('Target distance unit').nth(1).selectOption('Kilometers'); + await page.getByRole('button', { name: 'Close' }).click(); + + // Enter input 5K splits (7:00, 6:30, 6:30) + await page.getByLabel('Split duration minutes').nth(0).fill('7'); + await page.getByLabel('Split duration seconds').nth(0).fill('0'); + await page.getByLabel('Split duration minutes').nth(1).fill('6'); + await page.getByLabel('Split duration seconds').nth(1).fill('30'); + await page.getByLabel('Split duration minutes').nth(2).fill('6'); + await page.getByLabel('Split duration seconds').nth(2).fill('30'); + + // Go to unit calculator + await page.getByRole('link', { name: 'Back' }).click(); + await page.getByRole('button', { name: 'Unit Calculator' }).click(); + + // Convert speed and pace units (10 kph to time per mile) + await page.getByLabel('Selected unit category').selectOption('Speed & Pace'); + await page.getByLabel('Input units').selectOption('Kilometers per Hour'); + await page.getByLabel('Input value').fill('10'); + await page.getByLabel('Output units').selectOption('Time per Mile'); + + // Go to workout calculator + await page.getByRole('link', { name: 'Back' }).click(); + await page.getByRole('button', { name: 'Workout Calculator' }).click(); + + // Enter input race (1 mi in 5:01) + await page.getByLabel('Input race distance value').fill('1'); + await page.getByLabel('Input race distance unit').selectOption('Miles'); + await page.getByLabel('Input race duration hours').fill('0'); + await page.getByLabel('Input race duration minutes').fill('5'); + await page.getByLabel('Input race duration seconds').fill('1'); + + // Change riegel exponent and enable target name customization + await page.getByText('Advanced Options').click(); + await page.getByLabel('Riegel Exponent').fill('1.12'); + await page.getByLabel('Workout name customization').selectOption('Enabled'); + + // Change default units (should update on other calculators too) + await page.getByLabel('Default units').selectOption('Kilometers'); + } + + // Go back and assert the options are not reset + { + // Return to batch calculator + await page.getByRole('link', { name: 'Back' }).click(); + await page.getByRole('button', { name: 'Batch Calculator' }).click(); + + // Assert race results are correct (inputs and options not reset) + await expect(page.getByRole('row').nth(0).getByRole('cell').nth(0)).toHaveText('2 mi'); + await expect(page.getByRole('row').nth(0).getByRole('cell').nth(2)).toHaveText('800 m'); + await expect(page.getByRole('row').nth(0).getByRole('cell')).toHaveCount(17); + await expect(page.getByRole('row').nth(1).getByRole('cell').nth(0)).toHaveText('10:30'); + await expect(page.getByRole('row').nth(1).getByRole('cell').nth(2)).toHaveText('2:12'); + await expect(page.getByRole('row').nth(1).getByRole('cell')).toHaveCount(17); + await expect(page.getByRole('row').nth(15).getByRole('cell').nth(0)).toHaveText('12:50'); + await expect(page.getByRole('row').nth(15).getByRole('cell').nth(2)).toHaveText('2:42'); + await expect(page.getByRole('row').nth(15).getByRole('cell')).toHaveCount(17); + await expect(page.getByRole('row')).toHaveCount(16); + + // Assert pace results are correct (inputs and options not reset, new pace targets loaded) + await page.getByLabel('Calculator').selectOption('Pace Calculator'); + await expect(page.getByRole('row').nth(0).getByRole('cell').nth(0)).toHaveText('2 mi'); + await expect(page.getByRole('row').nth(0).getByRole('cell').nth(2)).toHaveText('800 m'); + await expect(page.getByRole('row').nth(0).getByRole('cell')).toHaveCount(4); + await expect(page.getByRole('row').nth(1).getByRole('cell').nth(0)).toHaveText('10:30'); + await expect(page.getByRole('row').nth(1).getByRole('cell').nth(2)).toHaveText('2:37'); + await expect(page.getByRole('row').nth(1).getByRole('cell')).toHaveCount(4); + await expect(page.getByRole('row').nth(15).getByRole('cell').nth(0)).toHaveText('12:50'); + await expect(page.getByRole('row').nth(15).getByRole('cell').nth(2)).toHaveText('3:11'); + await expect(page.getByRole('row').nth(15).getByRole('cell')).toHaveCount(4); + await expect(page.getByRole('row')).toHaveCount(16); + + // Assert workout results are correct (new workout options loaded) + await page.getByLabel('Calculator').selectOption('Workout Calculator'); + await expect(page.getByLabel('Workout name customization')).toHaveValue("true"); + await expect(page.getByRole('row').nth(0).getByRole('cell').nth(0)).toHaveText('2 mi'); + await expect(page.getByRole('row').nth(0).getByRole('cell').nth(2)).toHaveText('800 m @ 5 km'); + await expect(page.getByRole('row').nth(0).getByRole('cell')).toHaveCount(5); + await expect(page.getByRole('row').nth(1).getByRole('cell').nth(0)).toHaveText('10:30'); + await expect(page.getByRole('row').nth(1).getByRole('cell').nth(2)).toHaveText('2:45'); + await expect(page.getByRole('row').nth(1).getByRole('cell')).toHaveCount(5); + await expect(page.getByRole('row').nth(15).getByRole('cell').nth(0)).toHaveText('12:50'); + await expect(page.getByRole('row').nth(15).getByRole('cell').nth(2)).toHaveText('3:22'); + await expect(page.getByRole('row').nth(15).getByRole('cell')).toHaveCount(5); + await expect(page.getByRole('row')).toHaveCount(16); + + // Reset selected calculator + await page.getByLabel('Calculator').selectOption('Race Calculator'); + + // Return to pace calculator + await page.getByRole('link', { name: 'Back' }).click(); + await page.getByRole('button', { name: 'Pace Calculator' }).click(); + + // Assert paces are correct (input pace not reset) + await expect(page.getByRole('row').nth(1)).toHaveText('0.4 km' + '1:55.58'); + await expect(page.getByRole('row').nth(2)).toHaveText('800 m' + '3:51.15'); + await expect(page.getByRole('row').nth(3)).toHaveText('2.08 km' + '10:00'); + await expect(page.getByRole('row')).toHaveCount(4); + + // Return to race calculator + await page.getByRole('link', { name: 'Back' }).click(); + await page.getByRole('button', { name: 'Race Calculator' }).click(); + + // Assert race predictions are correct (input race not resset and new prediction model loaded) + await expect(page.getByRole('row').nth(5)).toHaveText('1 mi' + '4:49.86' + '3:00 / km'); + await expect(page.getByRole('row').nth(10)).toHaveText('5 km' + '17:11.78' + '3:26 / km'); + await expect(page.getByRole('row')).toHaveCount(17); + + // Return to split calculator + await page.getByRole('link', { name: 'Back' }).click(); + await page.getByRole('button', { name: 'Split Calculator' }).click(); + + // Assert times and paces are correct (split times not reset) + await expect(page.getByRole('row').nth(1).getByRole('cell').nth(1)).toHaveText('7:00.00'); + await expect(page.getByRole('row').nth(1).getByRole('cell').nth(3)).toHaveText('4:23 / km'); + await expect(page.getByRole('row').nth(2).getByRole('cell').nth(1)).toHaveText('13:30.00'); + await expect(page.getByRole('row').nth(2).getByRole('cell').nth(3)).toHaveText('4:04 / km'); + await expect(page.getByRole('row').nth(3).getByRole('cell').nth(1)).toHaveText('20:00.00'); + await expect(page.getByRole('row').nth(3).getByRole('cell').nth(3)).toHaveText('3:37 / km'); + await expect(page.getByRole('row')).toHaveCount(4); + + // Return to unit calculator + await page.getByRole('link', { name: 'Back' }).click(); + await page.getByRole('button', { name: 'Unit Calculator' }).click(); + + // Assert result is correct (state not reset) + await expect(page.getByLabel('Output value')).toHaveText('00:09:39.364'); + + // Return to workout calculator + await page.getByRole('link', { name: 'Back' }).click(); + await page.getByRole('button', { name: 'Workout Calculator' }).click(); + + // Assert workout splits are correct (input race and prediction model not reset) + await expect(page.getByRole('row').nth(1)).toHaveText('400 m @ 1 mi' + '1:14.81'); + await expect(page.getByRole('row').nth(3)).toHaveText('1600 m @ 1:00:00' + '6:30.40'); + await expect(page.getByRole('row')).toHaveCount(5); + } + + // Assert localStorage entries are correct + { + // Assert global localStorage entries are correct + expect(await page.evaluate(() => localStorage.length)).toEqual(12); + expect(await page.evaluate(() => localStorage.getItem('running-tools.global-options'))) + .toEqual(JSON.stringify({ + defaultUnitSystem: 'metric', + racePredictionOptions: { + model: 'RiegelModel', + riegelExponent: 1.12, + }, + })); + + // Assert localStorage entries for the batch calculator are correct + expect(await page.evaluate(() => + localStorage.getItem('running-tools.batch-calculator-options'))).toEqual(JSON.stringify({ + calculator: 'race', + increment: 10, + input: { + distanceValue: 2, + distanceUnit: 'miles', + time: 630, + }, + label: '', + rows: 15, + })); + + // Assert localStorage entries for the pace calculator are correct + const paceCalculatorKey = parseInt(JSON.parse(await page.evaluate(() => + localStorage.getItem('running-tools.pace-calculator-options'))).selectedTargetSet); + expect(paceCalculatorKey - parseInt(Date.now().toString())).toBeLessThan(100000); + expect(await page.evaluate(() => + localStorage.getItem('running-tools.pace-calculator-options'))).toEqual(JSON.stringify({ + input: { + distanceValue: 2, + distanceUnit: 'miles', + time: 930, + }, + selectedTargetSet: paceCalculatorKey.toString(), + })); + expect(await page.evaluate(() => + localStorage.getItem('running-tools.pace-calculator-target-sets'))).toEqual(JSON.stringify({ + _pace_targets: { + name: 'Common Pace Targets', + targets: [ + { type: 'distance', distanceValue: 100, distanceUnit: 'meters' }, + { type: 'distance', distanceValue: 200, distanceUnit: 'meters' }, + { type: 'distance', distanceValue: 300, distanceUnit: 'meters' }, + { type: 'distance', distanceValue: 400, distanceUnit: 'meters' }, + { type: 'distance', distanceValue: 600, distanceUnit: 'meters' }, + { type: 'distance', distanceValue: 800, distanceUnit: 'meters' }, + { type: 'distance', distanceValue: 1000, distanceUnit: 'meters' }, + { type: 'distance', distanceValue: 1200, distanceUnit: 'meters' }, + { type: 'distance', distanceValue: 1500, distanceUnit: 'meters' }, + { type: 'distance', distanceValue: 1600, distanceUnit: 'meters' }, + { type: 'distance', distanceValue: 1, distanceUnit: 'miles' }, + { type: 'distance', distanceValue: 2, distanceUnit: 'kilometers' }, + { type: 'distance', distanceValue: 3, distanceUnit: 'kilometers' }, + { type: 'distance', distanceValue: 3200, distanceUnit: 'meters' }, + { type: 'distance', distanceValue: 2, distanceUnit: 'miles' }, + { type: 'distance', distanceValue: 4, distanceUnit: 'kilometers' }, + { type: 'distance', distanceValue: 3, distanceUnit: 'miles' }, + { type: 'distance', distanceValue: 5, distanceUnit: 'kilometers' }, + { type: 'distance', distanceValue: 6, distanceUnit: 'kilometers' }, + { type: 'distance', distanceValue: 8, distanceUnit: 'kilometers' }, + { type: 'distance', distanceValue: 5, distanceUnit: 'miles' }, + { type: 'distance', distanceValue: 6, distanceUnit: 'miles' }, + { type: 'distance', distanceValue: 10, distanceUnit: 'kilometers' }, + { type: 'distance', distanceValue: 8, distanceUnit: 'miles' }, + { type: 'distance', distanceValue: 10, distanceUnit: 'miles' }, + { type: 'distance', distanceValue: 0.5, distanceUnit: 'marathons' }, + { type: 'distance', distanceValue: 1, distanceUnit: 'marathons' }, + { type: 'time', time: 600 }, + { type: 'time', time: 1800 }, + { type: 'time', time: 3600 }, + ], + }, + [paceCalculatorKey.toString()]: { + name: '800m Splits', + targets: [ + { type: 'distance', distanceValue: 0.4, distanceUnit: 'kilometers' }, + { type: 'distance', distanceValue: 800, distanceUnit: 'meters' }, + { type: 'time', time: 600 }, + ], + }, + })); + + // Assert localStorage entries for the race calculator are correct + expect(await page.evaluate(() => + localStorage.getItem('running-tools.race-calculator-options'))).toEqual(JSON.stringify({ + input: { + distanceValue: 2, + distanceUnit: 'miles', + time: 630, + }, + selectedTargetSet: '_race_targets', + })); + expect(await page.evaluate(() => + localStorage.getItem('running-tools.race-calculator-target-sets'))).toEqual(JSON.stringify({ + _race_targets: { + name: 'Common Race Targets', + targets: [ + { type: 'distance', distanceValue: 400, distanceUnit: 'meters' }, + { type: 'distance', distanceValue: 800, distanceUnit: 'meters' }, + { type: 'distance', distanceValue: 1500, distanceUnit: 'meters' }, + { type: 'distance', distanceValue: 1600, distanceUnit: 'meters' }, + { type: 'distance', distanceValue: 1, distanceUnit: 'miles' }, + { type: 'distance', distanceValue: 3000, distanceUnit: 'meters' }, + { type: 'distance', distanceValue: 3200, distanceUnit: 'meters' }, + { type: 'distance', distanceValue: 2, distanceUnit: 'miles' }, + { type: 'distance', distanceValue: 3, distanceUnit: 'miles' }, + { type: 'distance', distanceValue: 5, distanceUnit: 'kilometers' }, + { type: 'distance', distanceValue: 6, distanceUnit: 'kilometers' }, + { type: 'distance', distanceValue: 8, distanceUnit: 'kilometers' }, + { type: 'distance', distanceValue: 10, distanceUnit: 'kilometers' }, + { type: 'distance', distanceValue: 15, distanceUnit: 'kilometers' }, + { type: 'distance', distanceValue: 0.5, distanceUnit: 'marathons' }, + { type: 'distance', distanceValue: 1, distanceUnit: 'marathons' }, + ], + }, + })); + + // Assert localStorage entries for the split calculator are correct + expect(await page.evaluate(() => + localStorage.getItem('running-tools.split-calculator-options'))) + .toEqual(JSON.stringify({ + selectedTargetSet: '_split_targets', + })); + expect(await page.evaluate(() => + localStorage.getItem('running-tools.split-calculator-target-sets'))).toEqual(JSON.stringify({ + _split_targets: { + name: '5K 1600m Splits', + targets: [ + { type: 'distance', distanceValue: 1.6, distanceUnit: 'kilometers', splitTime: 420 }, + { type: 'distance', distanceValue: 3.2, distanceUnit: 'kilometers', splitTime: 390 }, + { type: 'distance', distanceValue: 5, distanceUnit: 'kilometers', splitTime: 390 }, + ], + }, + })); + + // Assert localStorage entries for the unit calculator are correct + expect(await page.evaluate(() => localStorage.getItem('running-tools.unit-calculator-category'))) + .toEqual(JSON.stringify('speed_and_pace')); + expect(await page.evaluate(() => + localStorage.getItem('running-tools.unit-calculator-inputs'))).toEqual(JSON.stringify({ + distance: { + inputValue: 1, + inputUnit: 'miles', + outputUnit: 'kilometers', + }, + time: { + inputValue: 1, + inputUnit: 'seconds', + outputUnit: 'hh:mm:ss', + }, + speed_and_pace: { + inputValue: 10, + inputUnit: 'kilometers_per_hour', + outputUnit: 'seconds_per_mile', + }, + })); + + // Assert localStorage entries for the workout calculator are correct + expect(await page.evaluate(() => + localStorage.getItem('running-tools.workout-calculator-options'))).toEqual(JSON.stringify({ + customTargetNames: true, + input: { + distanceValue: 1, + distanceUnit: 'miles', + time: 301, + }, + selectedTargetSet: '_workout_targets', + })); + expect(await page.evaluate(() => + localStorage.getItem('running-tools.workout-calculator-target-sets'))).toEqual(JSON.stringify({ + _workout_targets: { + name: 'Common Workout Targets', + targets: [ + { + splitValue: 400, splitUnit: 'meters', + type: 'distance', distanceValue: 1, distanceUnit: 'miles', + }, + { + splitValue: 800, splitUnit: 'meters', + type: 'distance', distanceValue: 5, distanceUnit: 'kilometers', + }, + { + splitValue: 1600, splitUnit: 'meters', + type: 'time', time: 3600, + }, + { + splitValue: 1, splitUnit: 'miles', + type: 'distance', distanceValue: 1, distanceUnit: 'marathons', + }, + ], + }, + })); + } + + // Reload app and assert the updated options are loaded + // Identical to the previous "go back and assert the options are not resset" section + { + // Return to batch calculator + await page.getByRole('link', { name: 'Back' }).click(); + await page.getByRole('button', { name: 'Batch Calculator' }).click(); + + // Assert race results are correct (inputs and options not reset) + await expect(page.getByRole('row').nth(0).getByRole('cell').nth(0)).toHaveText('2 mi'); + await expect(page.getByRole('row').nth(0).getByRole('cell').nth(2)).toHaveText('800 m'); + await expect(page.getByRole('row').nth(0).getByRole('cell')).toHaveCount(17); + await expect(page.getByRole('row').nth(1).getByRole('cell').nth(0)).toHaveText('10:30'); + await expect(page.getByRole('row').nth(1).getByRole('cell').nth(2)).toHaveText('2:12'); + await expect(page.getByRole('row').nth(1).getByRole('cell')).toHaveCount(17); + await expect(page.getByRole('row').nth(15).getByRole('cell').nth(0)).toHaveText('12:50'); + await expect(page.getByRole('row').nth(15).getByRole('cell').nth(2)).toHaveText('2:42'); + await expect(page.getByRole('row').nth(15).getByRole('cell')).toHaveCount(17); + await expect(page.getByRole('row')).toHaveCount(16); + + // Assert pace results are correct (inputs and options not reset, new pace targets loaded) + await page.getByLabel('Calculator').selectOption('Pace Calculator'); + await expect(page.getByRole('row').nth(0).getByRole('cell').nth(0)).toHaveText('2 mi'); + await expect(page.getByRole('row').nth(0).getByRole('cell').nth(2)).toHaveText('800 m'); + await expect(page.getByRole('row').nth(0).getByRole('cell')).toHaveCount(4); + await expect(page.getByRole('row').nth(1).getByRole('cell').nth(0)).toHaveText('10:30'); + await expect(page.getByRole('row').nth(1).getByRole('cell').nth(2)).toHaveText('2:37'); + await expect(page.getByRole('row').nth(1).getByRole('cell')).toHaveCount(4); + await expect(page.getByRole('row').nth(15).getByRole('cell').nth(0)).toHaveText('12:50'); + await expect(page.getByRole('row').nth(15).getByRole('cell').nth(2)).toHaveText('3:11'); + await expect(page.getByRole('row').nth(15).getByRole('cell')).toHaveCount(4); + await expect(page.getByRole('row')).toHaveCount(16); + + // Assert workout results are correct (new workout options loaded) + await page.getByLabel('Calculator').selectOption('Workout Calculator'); + await expect(page.getByLabel('Workout name customization')).toHaveValue("true"); + await expect(page.getByRole('row').nth(0).getByRole('cell').nth(0)).toHaveText('2 mi'); + await expect(page.getByRole('row').nth(0).getByRole('cell').nth(2)).toHaveText('800 m @ 5 km'); + await expect(page.getByRole('row').nth(0).getByRole('cell')).toHaveCount(5); + await expect(page.getByRole('row').nth(1).getByRole('cell').nth(0)).toHaveText('10:30'); + await expect(page.getByRole('row').nth(1).getByRole('cell').nth(2)).toHaveText('2:45'); + await expect(page.getByRole('row').nth(1).getByRole('cell')).toHaveCount(5); + await expect(page.getByRole('row').nth(15).getByRole('cell').nth(0)).toHaveText('12:50'); + await expect(page.getByRole('row').nth(15).getByRole('cell').nth(2)).toHaveText('3:22'); + await expect(page.getByRole('row').nth(15).getByRole('cell')).toHaveCount(5); + await expect(page.getByRole('row')).toHaveCount(16); + + // Reset selected calculator + await page.getByLabel('Calculator').selectOption('Race Calculator'); + + // Return to pace calculator + await page.getByRole('link', { name: 'Back' }).click(); + await page.getByRole('button', { name: 'Pace Calculator' }).click(); + + // Assert paces are correct (input pace not reset) + await expect(page.getByRole('row').nth(1)).toHaveText('0.4 km' + '1:55.58'); + await expect(page.getByRole('row').nth(2)).toHaveText('800 m' + '3:51.15'); + await expect(page.getByRole('row').nth(3)).toHaveText('2.08 km' + '10:00'); + await expect(page.getByRole('row')).toHaveCount(4); + + // Return to race calculator + await page.getByRole('link', { name: 'Back' }).click(); + await page.getByRole('button', { name: 'Race Calculator' }).click(); + + // Assert race predictions are correct (input race not resset and new prediction model loaded) + await expect(page.getByRole('row').nth(5)).toHaveText('1 mi' + '4:49.86' + '3:00 / km'); + await expect(page.getByRole('row').nth(10)).toHaveText('5 km' + '17:11.78' + '3:26 / km'); + await expect(page.getByRole('row')).toHaveCount(17); + + // Return to split calculator + await page.getByRole('link', { name: 'Back' }).click(); + await page.getByRole('button', { name: 'Split Calculator' }).click(); + + // Assert times and paces are correct (split times not reset) + await expect(page.getByRole('row').nth(1).getByRole('cell').nth(1)).toHaveText('7:00.00'); + await expect(page.getByRole('row').nth(1).getByRole('cell').nth(3)).toHaveText('4:23 / km'); + await expect(page.getByRole('row').nth(2).getByRole('cell').nth(1)).toHaveText('13:30.00'); + await expect(page.getByRole('row').nth(2).getByRole('cell').nth(3)).toHaveText('4:04 / km'); + await expect(page.getByRole('row').nth(3).getByRole('cell').nth(1)).toHaveText('20:00.00'); + await expect(page.getByRole('row').nth(3).getByRole('cell').nth(3)).toHaveText('3:37 / km'); + await expect(page.getByRole('row')).toHaveCount(4); + + // Return to unit calculator + await page.getByRole('link', { name: 'Back' }).click(); + await page.getByRole('button', { name: 'Unit Calculator' }).click(); + + // Assert result is correct (state not reset) + await expect(page.getByLabel('Output value')).toHaveText('00:09:39.364'); + + // Return to workout calculator + await page.getByRole('link', { name: 'Back' }).click(); + await page.getByRole('button', { name: 'Workout Calculator' }).click(); + + // Assert workout splits are correct (input race and prediction model not reset) + await expect(page.getByRole('row').nth(1)).toHaveText('400 m @ 1 mi' + '1:14.81'); + await expect(page.getByRole('row').nth(3)).toHaveText('1600 m @ 1:00:00' + '6:30.40'); + await expect(page.getByRole('row')).toHaveCount(5); + } +}); + +test('v1.4.1 Migration', async ({ page }) => { await page.goto('/'); - await page.getByRole('button', { name: 'Pace Calculator' }).click(); - - // Enter input pace (2 mi in 15:30) - await page.getByLabel('Input distance value').fill('2'); - await page.getByLabel('Input distance unit').selectOption('Miles'); - await page.getByLabel('Input duration hours').fill('0'); - await page.getByLabel('Input duration minutes').fill('15'); - await page.getByLabel('Input duration seconds').fill('30'); - - // Create custom target set - await page.getByText('Advanced Options').click(); - await page.getByLabel('Selected target set').selectOption('[ Create New Target Set ]'); - await expect(page.getByRole('row').nth(4)).toHaveText('There aren\'t any targets in this set yet.'); - await expect(page.getByRole('row')).toHaveCount(5); - - // Edit new target set - await expect(page.getByRole('row').nth(1)).toHaveText('There aren\'t any targets in this set yet.'); - await expect(page.getByLabel('Target set label')).toHaveValue('New target set'); - await page.getByLabel('Target set label').fill('800m Splits'); - await page.getByRole('button', { name: 'Add distance target' }).click(); - await page.getByLabel('Target distance value').nth(0).fill('0.4'); - await page.getByLabel('Target distance unit').nth(0).selectOption('Kilometers'); - await page.getByRole('button', { name: 'Add distance target' }).click(); - await page.getByLabel('Target distance value').nth(1).fill('800'); - await page.getByLabel('Target distance unit').nth(1).selectOption('Meters'); - await page.getByRole('button', { name: 'Add time target' }).click(); - await page.getByRole('button', { name: 'Close' }).click(); - - // Go to race calculator - await page.getByRole('link', { name: 'Back' }).click(); - await page.getByRole('button', { name: 'Race Calculator' }).click(); - - // Enter input race (2 mi in 10:30) - await page.getByLabel('Input race distance value').fill('2'); - await page.getByLabel('Input race distance unit').selectOption('Miles'); - await page.getByLabel('Input race duration hours').fill('0'); - await page.getByLabel('Input race duration minutes').fill('10'); - await page.getByLabel('Input race duration seconds').fill('30'); - - // Go to split calculator - await page.getByRole('link', { name: 'Back' }).click(); - await page.getByRole('button', { name: 'Split Calculator' }).click(); - - // Edit target set - await page.getByRole('button', { name: 'Edit target set' }).click(); - await page.getByLabel('Target set label').fill('5K 1600m Splits'); - await page.getByLabel('Target distance value').nth(0).fill('1.6'); - await page.getByLabel('Target distance unit').nth(0).selectOption('Kilometers'); - await page.getByLabel('Target distance value').nth(1).fill('3.2'); - await page.getByLabel('Target distance unit').nth(1).selectOption('Kilometers'); - await page.getByRole('button', { name: 'Close' }).click(); - - // Enter input 5K splits (7:00, 6:30, 6:30) - await page.getByLabel('Split duration minutes').nth(0).fill('7'); - await page.getByLabel('Split duration seconds').nth(0).fill('0'); - await page.getByLabel('Split duration minutes').nth(1).fill('6'); - await page.getByLabel('Split duration seconds').nth(1).fill('30'); - await page.getByLabel('Split duration minutes').nth(2).fill('6'); - await page.getByLabel('Split duration seconds').nth(2).fill('30'); - - // Go to unit calculator - await page.getByRole('link', { name: 'Back' }).click(); - await page.getByRole('button', { name: 'Unit Calculator' }).click(); - - // Convert speed and pace units (10 kph to time per mile) - await page.getByLabel('Selected unit category').selectOption('Speed & Pace'); - await page.getByLabel('Input units').selectOption('Kilometers per Hour'); - await page.getByLabel('Input value').fill('10'); - await page.getByLabel('Output units').selectOption('Time per Mile'); - - // Go to workout calculator - await page.getByRole('link', { name: 'Back' }).click(); - await page.getByRole('button', { name: 'Workout Calculator' }).click(); - - // Enter input race (1 mi in 5:01) - await page.getByLabel('Input race distance value').fill('1'); - await page.getByLabel('Input race distance unit').selectOption('Miles'); - await page.getByLabel('Input race duration hours').fill('0'); - await page.getByLabel('Input race duration minutes').fill('5'); - await page.getByLabel('Input race duration seconds').fill('1'); - - // Change prediction model - await page.getByText('Advanced Options').click(); - await page.getByLabel('Prediction model').selectOption('V̇O₂ Max Model'); - - // Change default units (should update on other calculators too) - await page.getByLabel('Default units').selectOption('Kilometers'); - - // Return to batch calculator - await page.getByRole('link', { name: 'Back' }).click(); - await page.getByRole('button', { name: 'Batch Calculator' }).click(); - - // Assert pace results are correct (inputs and options not reset) - await expect(page.getByRole('row').nth(0).getByRole('cell').nth(0)).toHaveText('2 mi'); - await expect(page.getByRole('row').nth(0).getByRole('cell').nth(2)).toHaveText('800 m'); - await expect(page.getByRole('row').nth(0).getByRole('cell')).toHaveCount(17); - await expect(page.getByRole('row').nth(1).getByRole('cell').nth(0)).toHaveText('10:30'); - await expect(page.getByRole('row').nth(1).getByRole('cell').nth(2)).toHaveText('2:24.04'); - await expect(page.getByRole('row').nth(1).getByRole('cell')).toHaveCount(17); - await expect(page.getByRole('row').nth(15).getByRole('cell').nth(0)).toHaveText('12:50'); - await expect(page.getByRole('row').nth(15).getByRole('cell').nth(2)).toHaveText('2:56.05'); - await expect(page.getByRole('row').nth(15).getByRole('cell')).toHaveCount(17); - await expect(page.getByRole('row')).toHaveCount(16); - - // Assert pace results are correct (inputs and options not reset, new pace targets loaded) - await page.getByLabel('Calculator').selectOption('Pace Calculator'); - await expect(page.getByRole('row').nth(0).getByRole('cell').nth(0)).toHaveText('2 mi'); - await expect(page.getByRole('row').nth(0).getByRole('cell').nth(2)).toHaveText('800 m'); - await expect(page.getByRole('row').nth(0).getByRole('cell')).toHaveCount(4); - await expect(page.getByRole('row').nth(1).getByRole('cell').nth(0)).toHaveText('10:30'); - await expect(page.getByRole('row').nth(1).getByRole('cell').nth(2)).toHaveText('2:36.58'); - await expect(page.getByRole('row').nth(1).getByRole('cell')).toHaveCount(4); - await expect(page.getByRole('row').nth(15).getByRole('cell').nth(0)).toHaveText('12:50'); - await expect(page.getByRole('row').nth(15).getByRole('cell').nth(2)).toHaveText('3:11.38'); - await expect(page.getByRole('row').nth(15).getByRole('cell')).toHaveCount(4); - await expect(page.getByRole('row')).toHaveCount(16); - - // Assert workout results are correct (new workout options loaded) - await page.getByLabel('Calculator').selectOption('Workout Calculator'); - await expect(page.getByRole('row').nth(0).getByRole('cell').nth(0)).toHaveText('2 mi'); - await expect(page.getByRole('row').nth(0).getByRole('cell').nth(2)).toHaveText('800 m @ 5 km'); - await expect(page.getByRole('row').nth(0).getByRole('cell')).toHaveCount(5); - await expect(page.getByRole('row').nth(1).getByRole('cell').nth(0)).toHaveText('10:30'); - await expect(page.getByRole('row').nth(1).getByRole('cell').nth(2)).toHaveText('2:41.92'); - await expect(page.getByRole('row').nth(1).getByRole('cell')).toHaveCount(5); - await expect(page.getByRole('row').nth(15).getByRole('cell').nth(0)).toHaveText('12:50'); - await expect(page.getByRole('row').nth(15).getByRole('cell').nth(2)).toHaveText('3:16.97'); - await expect(page.getByRole('row').nth(15).getByRole('cell')).toHaveCount(5); - await expect(page.getByRole('row')).toHaveCount(16); - - // Return to pace calculator - await page.getByRole('link', { name: 'Back' }).click(); - await page.getByRole('button', { name: 'Pace Calculator' }).click(); - - // Assert paces are correct (input pace not reset) - await expect(page.getByRole('row').nth(1)).toHaveText('0.4 km' + '1:55.57'); - await expect(page.getByRole('row').nth(2)).toHaveText('800 m' + '3:51.15'); - await expect(page.getByRole('row').nth(3)).toHaveText('2.08 km' + '10:00'); - await expect(page.getByRole('row')).toHaveCount(4); - - // Return to race calculator - await page.getByRole('link', { name: 'Back' }).click(); - await page.getByRole('button', { name: 'Race Calculator' }).click(); - - // Assert race predictions are correct (input race not resset and new prediction model loaded) - await expect(page.getByRole('row').nth(5)).toHaveText('1 mi' + '5:02.17' + '3:08 / km'); - await expect(page.getByRole('row').nth(10)).toHaveText('5 km' + '16:44.86' + '3:21 / km'); - await expect(page.getByRole('row')).toHaveCount(17); - - // Return to split calculator - await page.getByRole('link', { name: 'Back' }).click(); - await page.getByRole('button', { name: 'Split Calculator' }).click(); - - // Assert times and paces are correct (split times not reset) - await expect(page.getByRole('row').nth(1).getByRole('cell').nth(1)).toHaveText('7:00.00'); - await expect(page.getByRole('row').nth(1).getByRole('cell').nth(3)).toHaveText('4:23 / km'); - await expect(page.getByRole('row').nth(2).getByRole('cell').nth(1)).toHaveText('13:30.00'); - await expect(page.getByRole('row').nth(2).getByRole('cell').nth(3)).toHaveText('4:04 / km'); - await expect(page.getByRole('row').nth(3).getByRole('cell').nth(1)).toHaveText('20:00.00'); - await expect(page.getByRole('row').nth(3).getByRole('cell').nth(3)).toHaveText('3:37 / km'); - await expect(page.getByRole('row')).toHaveCount(4); - - // Return to unit calculator - await page.getByRole('link', { name: 'Back' }).click(); - await page.getByRole('button', { name: 'Unit Calculator' }).click(); - - // Assert result is correct (state not reset) - await expect(page.getByLabel('Output value')).toHaveText('00:09:39.366'); - - // Return to workout calculator - await page.getByRole('link', { name: 'Back' }).click(); - await page.getByRole('button', { name: 'Workout Calculator' }).click(); - - // Assert workout splits are correct (input race and prediction model not reset) - await expect(page.getByRole('row').nth(1)).toHaveText('400 m @ 1 mi' + '1:14.81'); - await expect(page.getByRole('row').nth(3)).toHaveText('1600 m @ 1:00:00' + '5:53.56'); - await expect(page.getByRole('row')).toHaveCount(5); + + // Set v1.4.1 localStorage entries + { + // Set general localStorage entries + await page.evaluate(() => localStorage.setItem('running-tools.default-unit-system', + JSON.stringify('metric'))); + + // Set batch calculator localStorage entries + await page.evaluate(() => localStorage.setItem('running-tools.batch-calculator-input', + JSON.stringify({ + distanceValue: 2, + distanceUnit: 'miles', + time: 630, + }) + )); + await page.evaluate(() => localStorage.setItem('running-tools.batch-calculator-options', + JSON.stringify({ + calculator: 'race', + increment: 10, + rows: 15, + }) + )); + + // Set pace calculator localStorage entries + await page.evaluate(() => localStorage.setItem('running-tools.pace-calculator-input', + JSON.stringify({ + distanceValue: 2, + distanceUnit: 'miles', + time: 930, + }) + )); + await page.evaluate(() => localStorage.setItem('running-tools.pace-calculator-target-set', + JSON.stringify('123456789'))); // Property moved after v1.4.1 + await page.evaluate(() => localStorage.setItem('running-tools.pace-calculator-target-sets', + JSON.stringify({ + _pace_targets: { + name: 'Common Pace Targets', + targets: [ + { type: 'distance', distanceValue: 100, distanceUnit: 'meters' }, + { type: 'distance', distanceValue: 200, distanceUnit: 'meters' }, + { type: 'distance', distanceValue: 300, distanceUnit: 'meters' }, + { type: 'distance', distanceValue: 400, distanceUnit: 'meters' }, + { type: 'distance', distanceValue: 600, distanceUnit: 'meters' }, + { type: 'distance', distanceValue: 800, distanceUnit: 'meters' }, + { type: 'distance', distanceValue: 1000, distanceUnit: 'meters' }, + { type: 'distance', distanceValue: 1200, distanceUnit: 'meters' }, + { type: 'distance', distanceValue: 1500, distanceUnit: 'meters' }, + { type: 'distance', distanceValue: 1600, distanceUnit: 'meters' }, + { type: 'distance', distanceValue: 1, distanceUnit: 'miles' }, + { type: 'distance', distanceValue: 2, distanceUnit: 'kilometers' }, + { type: 'distance', distanceValue: 3, distanceUnit: 'kilometers' }, + { type: 'distance', distanceValue: 3200, distanceUnit: 'meters' }, + { type: 'distance', distanceValue: 2, distanceUnit: 'miles' }, + { type: 'distance', distanceValue: 4, distanceUnit: 'kilometers' }, + { type: 'distance', distanceValue: 3, distanceUnit: 'miles' }, + { type: 'distance', distanceValue: 5, distanceUnit: 'kilometers' }, + { type: 'distance', distanceValue: 6, distanceUnit: 'kilometers' }, + { type: 'distance', distanceValue: 8, distanceUnit: 'kilometers' }, + { type: 'distance', distanceValue: 5, distanceUnit: 'miles' }, + { type: 'distance', distanceValue: 6, distanceUnit: 'miles' }, + { type: 'distance', distanceValue: 10, distanceUnit: 'kilometers' }, + { type: 'distance', distanceValue: 8, distanceUnit: 'miles' }, + { type: 'distance', distanceValue: 10, distanceUnit: 'miles' }, + { type: 'distance', distanceValue: 0.5, distanceUnit: 'marathons' }, + { type: 'distance', distanceValue: 1, distanceUnit: 'marathons' }, + { type: 'time', time: 600 }, + { type: 'time', time: 1800 }, + { type: 'time', time: 3600 }, + ], + }, + '123456789': { + name: '800m Splits', + targets: [ + { type: 'distance', distanceValue: 0.4, distanceUnit: 'kilometers' }, + { type: 'distance', distanceValue: 800, distanceUnit: 'meters' }, + { type: 'time', time: 600 }, + ], + }, + }) + )); + + // Set race calculator localStorage entries + await page.evaluate(() => localStorage.setItem('running-tools.race-calculator-input', + JSON.stringify({ + distanceValue: 2, + distanceUnit: 'miles', + time: 630, + }) + )); + await page.evaluate(() => localStorage.setItem('running-tools.race-calculator-options', + JSON.stringify({ + model: 'RiegelModel', + riegelExponent: 1.06, + }) + )); + await page.evaluate(() => localStorage.setItem('running-tools.race-calculator-target-set', + JSON.stringify('_race_targets'))); // Property moved after v1.4.1 + await page.evaluate(() => localStorage.setItem('running-tools.race-calculator-target-sets', + JSON.stringify({ + _race_targets: { + name: 'Common Race Targets', + targets: [ + { type: 'distance', distanceValue: 400, distanceUnit: 'meters' }, + { type: 'distance', distanceValue: 800, distanceUnit: 'meters' }, + { type: 'distance', distanceValue: 1500, distanceUnit: 'meters' }, + { type: 'distance', distanceValue: 1600, distanceUnit: 'meters' }, + { type: 'distance', distanceValue: 1, distanceUnit: 'miles' }, + { type: 'distance', distanceValue: 3000, distanceUnit: 'meters' }, + { type: 'distance', distanceValue: 3200, distanceUnit: 'meters' }, + { type: 'distance', distanceValue: 2, distanceUnit: 'miles' }, + { type: 'distance', distanceValue: 3, distanceUnit: 'miles' }, + { type: 'distance', distanceValue: 5, distanceUnit: 'kilometers' }, + { type: 'distance', distanceValue: 6, distanceUnit: 'kilometers' }, + { type: 'distance', distanceValue: 8, distanceUnit: 'kilometers' }, + { type: 'distance', distanceValue: 10, distanceUnit: 'kilometers' }, + { type: 'distance', distanceValue: 15, distanceUnit: 'kilometers' }, + { type: 'distance', distanceValue: 0.5, distanceUnit: 'marathons' }, + { type: 'distance', distanceValue: 1, distanceUnit: 'marathons' }, + ], + }, + }) + )); + + // Set split calculator localStorage entries + await page.evaluate(() => localStorage.setItem('running-tools.split-calculator-target-set', + JSON.stringify('_split_targets'))); // Property moved after v1.4.1 + await page.evaluate(() => localStorage.setItem('running-tools.split-calculator-target-sets', + JSON.stringify({ + _split_targets: { + name: '5K 1600m Splits', + targets: [ + { type: 'distance', distanceValue: 1.6, distanceUnit: 'kilometers', splitTime: 420 }, + { type: 'distance', distanceValue: 3.2, distanceUnit: 'kilometers', splitTime: 390 }, + { type: 'distance', distanceValue: 5, distanceUnit: 'kilometers', splitTime: 390 }, + ], + }, + }) + )); + + // Set unit calculator localStorage entries + await page.evaluate(() => localStorage.setItem('running-tools.unit-calculator-category', + JSON.stringify('speed_and_pace'))); + await page.evaluate(() => localStorage.setItem('running-tools.unit-calculator-inputs', + JSON.stringify({ + distance: { + inputValue: 1, + inputUnit: 'miles', + outputUnit: 'kilometers', + }, + time: { + inputValue: 1, + inputUnit: 'seconds', + outputUnit: 'hh:mm:ss', + }, + speed_and_pace: { + inputValue: 10, + inputUnit: 'kilometers_per_hour', + outputUnit: 'seconds_per_mile', + }, + }) + )); + + // Set workout calculator localStorage entries + await page.evaluate(() => localStorage.setItem('running-tools.workout-calculator-input', + JSON.stringify({ + distanceValue: 1, + distanceUnit: 'miles', + time: 301, + }) + )); + await page.evaluate(() => localStorage.setItem('running-tools.workout-calculator-options', + JSON.stringify({ + // customTargetNames property added after v1.4.1 + model: 'VO2MaxModel', + riegelExponent: 1.06, + }) + )); + await page.evaluate(() => localStorage.setItem('running-tools.workout-calculator-target-set', + JSON.stringify('_workout_targets'))); // Property moved after v1.4.1 + await page.evaluate(() => localStorage.setItem('running-tools.workout-calculator-target-sets', + JSON.stringify({ + _workout_targets: { + name: 'Common Workout Targets', + targets: [ + { + splitValue: 400, splitUnit: 'meters', + type: 'distance', distanceValue: 1, distanceUnit: 'miles', + }, + { + splitValue: 800, splitUnit: 'meters', + type: 'distance', distanceValue: 5, distanceUnit: 'kilometers', + }, + { + splitValue: 1600, splitUnit: 'meters', + type: 'time', time: 3600, + }, + { + splitValue: 1, splitUnit: 'miles', + type: 'distance', distanceValue: 1, distanceUnit: 'marathons', + }, + ], + }, + }) + )); + } + + // Reload app and assert the proper localStorage migrations were performed + { + // Reload the app and assert general localStorage entries are correct + await page.goto('/'); + expect(await page.evaluate(() => localStorage.length)).toEqual(12); + expect(await page.evaluate(() => + localStorage.getItem('running-tools.default-unit-system'))).toBeNull(); + expect(await page.evaluate(() => + localStorage.getItem('running-tools.global-options'))).toEqual(JSON.stringify({ + defaultUnitSystem: 'metric', + racePredictionOptions: { + model: 'RiegelModel', + riegelExponent: 1.06, + }, + })); + + // Assert localStorage entries for the batch calculator are correct + expect(await page.evaluate(() => + localStorage.getItem('running-tools.batch-calculator-input'))).toBeNull(); + expect(await page.evaluate(() => + localStorage.getItem('running-tools.batch-calculator-options'))).toEqual(JSON.stringify({ + calculator: 'race', + increment: 10, + rows: 15, + label: '', + input: { + distanceValue: 2, + distanceUnit: 'miles', + time: 630, + }, + })); + + // Assert localStorage entries for the pace calculator are correct + expect(await page.evaluate(() => + localStorage.getItem('running-tools.pace-calculator-input'))).toBeNull(); + expect(await page.evaluate(() => localStorage.getItem('running-tools.pace-calculator-options'))) + .toEqual(JSON.stringify({ + input: { + distanceValue: 2, + distanceUnit: 'miles', + time: 930, + }, + selectedTargetSet: '123456789', + })); + expect(await page.evaluate(() => + localStorage.getItem('running-tools.pace-calculator-target-set'))).toBeNull(); + expect(await page.evaluate(() => + localStorage.getItem('running-tools.pace-calculator-target-sets'))).toEqual(JSON.stringify({ + _pace_targets: { + name: 'Common Pace Targets', + targets: [ + { type: 'distance', distanceValue: 100, distanceUnit: 'meters' }, + { type: 'distance', distanceValue: 200, distanceUnit: 'meters' }, + { type: 'distance', distanceValue: 300, distanceUnit: 'meters' }, + { type: 'distance', distanceValue: 400, distanceUnit: 'meters' }, + { type: 'distance', distanceValue: 600, distanceUnit: 'meters' }, + { type: 'distance', distanceValue: 800, distanceUnit: 'meters' }, + { type: 'distance', distanceValue: 1000, distanceUnit: 'meters' }, + { type: 'distance', distanceValue: 1200, distanceUnit: 'meters' }, + { type: 'distance', distanceValue: 1500, distanceUnit: 'meters' }, + { type: 'distance', distanceValue: 1600, distanceUnit: 'meters' }, + { type: 'distance', distanceValue: 1, distanceUnit: 'miles' }, + { type: 'distance', distanceValue: 2, distanceUnit: 'kilometers' }, + { type: 'distance', distanceValue: 3, distanceUnit: 'kilometers' }, + { type: 'distance', distanceValue: 3200, distanceUnit: 'meters' }, + { type: 'distance', distanceValue: 2, distanceUnit: 'miles' }, + { type: 'distance', distanceValue: 4, distanceUnit: 'kilometers' }, + { type: 'distance', distanceValue: 3, distanceUnit: 'miles' }, + { type: 'distance', distanceValue: 5, distanceUnit: 'kilometers' }, + { type: 'distance', distanceValue: 6, distanceUnit: 'kilometers' }, + { type: 'distance', distanceValue: 8, distanceUnit: 'kilometers' }, + { type: 'distance', distanceValue: 5, distanceUnit: 'miles' }, + { type: 'distance', distanceValue: 6, distanceUnit: 'miles' }, + { type: 'distance', distanceValue: 10, distanceUnit: 'kilometers' }, + { type: 'distance', distanceValue: 8, distanceUnit: 'miles' }, + { type: 'distance', distanceValue: 10, distanceUnit: 'miles' }, + { type: 'distance', distanceValue: 0.5, distanceUnit: 'marathons' }, + { type: 'distance', distanceValue: 1, distanceUnit: 'marathons' }, + { type: 'time', time: 600 }, + { type: 'time', time: 1800 }, + { type: 'time', time: 3600 }, + ], + }, + '123456789': { + name: '800m Splits', + targets: [ + { type: 'distance', distanceValue: 0.4, distanceUnit: 'kilometers' }, + { type: 'distance', distanceValue: 800, distanceUnit: 'meters' }, + { type: 'time', time: 600 }, + ], + }, + })); + + // Assert localStorage entries for the race calculator are correct + expect(await page.evaluate(() => + localStorage.getItem('running-tools.race-calculator-input'))).toBeNull(); + expect(await page.evaluate(() => localStorage.getItem('running-tools.race-calculator-options'))) + .toEqual(JSON.stringify({ + input: { + distanceValue: 2, + distanceUnit: 'miles', + time: 630, + }, + selectedTargetSet: '_race_targets', + })); + expect(await page.evaluate(() => + localStorage.getItem('running-tools.race-calculator-target-set'))).toBeNull(); + expect(await page.evaluate(() => + localStorage.getItem('running-tools.race-calculator-target-sets'))).toEqual(JSON.stringify({ + _race_targets: { + name: 'Common Race Targets', + targets: [ + { type: 'distance', distanceValue: 400, distanceUnit: 'meters' }, + { type: 'distance', distanceValue: 800, distanceUnit: 'meters' }, + { type: 'distance', distanceValue: 1500, distanceUnit: 'meters' }, + { type: 'distance', distanceValue: 1600, distanceUnit: 'meters' }, + { type: 'distance', distanceValue: 1, distanceUnit: 'miles' }, + { type: 'distance', distanceValue: 3000, distanceUnit: 'meters' }, + { type: 'distance', distanceValue: 3200, distanceUnit: 'meters' }, + { type: 'distance', distanceValue: 2, distanceUnit: 'miles' }, + { type: 'distance', distanceValue: 3, distanceUnit: 'miles' }, + { type: 'distance', distanceValue: 5, distanceUnit: 'kilometers' }, + { type: 'distance', distanceValue: 6, distanceUnit: 'kilometers' }, + { type: 'distance', distanceValue: 8, distanceUnit: 'kilometers' }, + { type: 'distance', distanceValue: 10, distanceUnit: 'kilometers' }, + { type: 'distance', distanceValue: 15, distanceUnit: 'kilometers' }, + { type: 'distance', distanceValue: 0.5, distanceUnit: 'marathons' }, + { type: 'distance', distanceValue: 1, distanceUnit: 'marathons' }, + ], + }, + })); + + // Assert localStorage entries for the split calculator are correct + expect(await page.evaluate(() => localStorage.getItem('running-tools.split-calculator-options'))) + .toEqual(JSON.stringify({ + selectedTargetSet: '_split_targets', + })); + expect(await page.evaluate(() => + localStorage.getItem('running-tools.split-calculator-target-set'))).toBeNull(); + expect(await page.evaluate(() => + localStorage.getItem('running-tools.split-calculator-target-sets'))).toEqual(JSON.stringify({ + _split_targets: { + name: '5K 1600m Splits', + targets: [ + { type: 'distance', distanceValue: 1.6, distanceUnit: 'kilometers', splitTime: 420 }, + { type: 'distance', distanceValue: 3.2, distanceUnit: 'kilometers', splitTime: 390 }, + { type: 'distance', distanceValue: 5, distanceUnit: 'kilometers', splitTime: 390 }, + ], + }, + })); + + // Assert localStorage entries for the unit calculator are correct + expect(await page.evaluate(() => localStorage.getItem('running-tools.unit-calculator-category'))) + .toEqual(JSON.stringify('speed_and_pace')); + expect(await page.evaluate(() => + localStorage.getItem('running-tools.unit-calculator-inputs'))).toEqual(JSON.stringify({ + distance: { + inputValue: 1, + inputUnit: 'miles', + outputUnit: 'kilometers', + }, + time: { + inputValue: 1, + inputUnit: 'seconds', + outputUnit: 'hh:mm:ss', + }, + speed_and_pace: { + inputValue: 10, + inputUnit: 'kilometers_per_hour', + outputUnit: 'seconds_per_mile', + }, + })); + + // Assert localStorage entries for the workout calculator are correct + expect(await page.evaluate(() => + localStorage.getItem('running-tools.workout-calculator-input'))).toBeNull(); + expect(await page.evaluate(() => + localStorage.getItem('running-tools.workout-calculator-options'))).toEqual(JSON.stringify({ + customTargetNames: false, + input: { + distanceValue: 1, + distanceUnit: 'miles', + time: 301, + }, + selectedTargetSet: '_workout_targets', + })); + expect(await page.evaluate(() => + localStorage.getItem('running-tools.workout-calculator-target-set'))).toBeNull(); + expect(await page.evaluate(() => + localStorage.getItem('running-tools.workout-calculator-target-sets'))).toEqual(JSON.stringify({ + _workout_targets: { + name: 'Common Workout Targets', + targets: [ + { + splitValue: 400, splitUnit: 'meters', + type: 'distance', distanceValue: 1, distanceUnit: 'miles', + }, + { + splitValue: 800, splitUnit: 'meters', + type: 'distance', distanceValue: 5, distanceUnit: 'kilometers', + }, + { + splitValue: 1600, splitUnit: 'meters', + type: 'time', time: 3600, + }, + { + splitValue: 1, splitUnit: 'miles', + type: 'distance', distanceValue: 1, distanceUnit: 'marathons', + }, + ], + }, + })); + } + + // Assert UI options are up to date + // Very similar to the previous "go back and assert the options are not resset" section + { + // Assert batch options are correct + await page.getByRole('button', { name: 'Batch Calculator' }).click(); + await expect(page.getByLabel('Input distance value')).toHaveValue('2.00'); + await expect(page.getByLabel('Input distance unit')).toHaveValue('miles'); + await expect(page.getByLabel('Input duration hours')).toHaveValue('0'); + await expect(page.getByLabel('Input duration minutes')).toHaveValue('10'); + await expect(page.getByLabel('Input duration seconds')).toHaveValue('30.00'); + await expect(page.getByLabel('Duration increment minutes')).toHaveValue('00'); + await expect(page.getByLabel('Duration increment seconds')).toHaveValue('10.00'); + await expect(page.getByLabel('Number of rows')).toHaveValue('15'); + await expect(page.getByLabel('Calculator')).toHaveValue('race'); + + // Assert advanced options are correct for race calculator mode + await page.getByText('Advanced Options').click(); + await expect(page.getByLabel('Default units')).toHaveValue('metric'); + await expect(page.getByLabel('Selected target set')).toHaveValue('_race_targets'); + await expect(page.getByLabel('Prediction model')).toHaveValue('RiegelModel'); + await expect(page.getByLabel('Riegel Exponent')).toHaveValue('1.06'); + + // Assert race results are correct (inputs and options not reset) + await expect(page.getByRole('row').nth(0).getByRole('cell').nth(0)).toHaveText('2 mi'); + await expect(page.getByRole('row').nth(0).getByRole('cell').nth(2)).toHaveText('800 m'); + await expect(page.getByRole('row').nth(0).getByRole('cell')).toHaveCount(17); + await expect(page.getByRole('row').nth(1).getByRole('cell').nth(0)).toHaveText('10:30'); + await expect(page.getByRole('row').nth(1).getByRole('cell').nth(2)).toHaveText('2:24'); + await expect(page.getByRole('row').nth(1).getByRole('cell')).toHaveCount(17); + await expect(page.getByRole('row').nth(15).getByRole('cell').nth(0)).toHaveText('12:50'); + await expect(page.getByRole('row').nth(15).getByRole('cell').nth(2)).toHaveText('2:56'); + await expect(page.getByRole('row').nth(15).getByRole('cell')).toHaveCount(17); + await expect(page.getByRole('row')).toHaveCount(16); + + // Assert advanced options are correct for pace calculator mode + await page.getByLabel('Calculator').selectOption('Pace Calculator'); + await expect(page.getByLabel('Default units')).toHaveValue('metric'); + await expect(page.getByLabel('Selected target set')).toHaveValue('123456789'); + + // Assert pace results are correct (inputs and options not reset, new pace targets loaded) + await expect(page.getByRole('row').nth(0).getByRole('cell').nth(0)).toHaveText('2 mi'); + await expect(page.getByRole('row').nth(0).getByRole('cell').nth(2)).toHaveText('800 m'); + await expect(page.getByRole('row').nth(0).getByRole('cell')).toHaveCount(4); + await expect(page.getByRole('row').nth(1).getByRole('cell').nth(0)).toHaveText('10:30'); + await expect(page.getByRole('row').nth(1).getByRole('cell').nth(2)).toHaveText('2:37'); + await expect(page.getByRole('row').nth(1).getByRole('cell')).toHaveCount(4); + await expect(page.getByRole('row').nth(15).getByRole('cell').nth(0)).toHaveText('12:50'); + await expect(page.getByRole('row').nth(15).getByRole('cell').nth(2)).toHaveText('3:11'); + await expect(page.getByRole('row').nth(15).getByRole('cell')).toHaveCount(4); + await expect(page.getByRole('row')).toHaveCount(16); + + // Assert advanced options are correct for workout calculator mode + await page.getByLabel('Calculator').selectOption('Workout Calculator'); + await page.getByText('Advanced Options').click(); + await expect(page.getByLabel('Default units')).toHaveValue('metric'); + await expect(page.getByLabel('Selected target set')).toHaveValue('_workout_targets'); + await expect(page.getByLabel('Workout name customization')).toHaveValue('false'); + await expect(page.getByLabel('Prediction model')).toHaveValue('RiegelModel'); + await expect(page.getByLabel('Riegel Exponent')).toHaveValue('1.06'); + + // Assert workout results are correct (new workout options loaded) + await expect(page.getByRole('row').nth(0).getByRole('cell').nth(0)).toHaveText('2 mi'); + await expect(page.getByRole('row').nth(0).getByRole('cell').nth(2)).toHaveText('800 m @ 5 km'); + await expect(page.getByRole('row').nth(0).getByRole('cell')).toHaveCount(5); + await expect(page.getByRole('row').nth(1).getByRole('cell').nth(0)).toHaveText('10:30'); + await expect(page.getByRole('row').nth(1).getByRole('cell').nth(2)).toHaveText('2:41'); + await expect(page.getByRole('row').nth(1).getByRole('cell')).toHaveCount(5); + await expect(page.getByRole('row').nth(15).getByRole('cell').nth(0)).toHaveText('12:50'); + await expect(page.getByRole('row').nth(15).getByRole('cell').nth(2)).toHaveText('3:17'); + await expect(page.getByRole('row').nth(15).getByRole('cell')).toHaveCount(5); + await expect(page.getByRole('row')).toHaveCount(16); + + // Reset selected calculator + await page.getByLabel('Calculator').selectOption('Race Calculator'); + + // Return to pace calculator + await page.getByRole('link', { name: 'Back' }).click(); + await page.getByRole('button', { name: 'Pace Calculator' }).click(); + + // Assert pace calculator options are correct + await expect(page.getByLabel('Input distance value')).toHaveValue('2.00'); + await expect(page.getByLabel('Input distance unit')).toHaveValue('miles'); + await expect(page.getByLabel('Input duration hours')).toHaveValue('0'); + await expect(page.getByLabel('Input duration minutes')).toHaveValue('15'); + await expect(page.getByLabel('Input duration seconds')).toHaveValue('30.00'); + await page.getByText('Advanced Options').click(); + await expect(page.getByLabel('Default units')).toHaveValue('metric'); + await expect(page.getByLabel('Selected target set')).toHaveValue('123456789'); + + // Assert paces are correct (input pace not reset) + await expect(page.getByRole('row').nth(1)).toHaveText('0.4 km' + '1:55.58'); + await expect(page.getByRole('row').nth(2)).toHaveText('800 m' + '3:51.15'); + await expect(page.getByRole('row').nth(3)).toHaveText('2.08 km' + '10:00'); + await expect(page.getByRole('row')).toHaveCount(4); + + // Return to race calculator + await page.getByRole('link', { name: 'Back' }).click(); + await page.getByRole('button', { name: 'Race Calculator' }).click(); + + // Assert race calculator options are correct + await expect(page.getByLabel('Input race distance value')).toHaveValue('2.00'); + await expect(page.getByLabel('Input race distance unit')).toHaveValue('miles'); + await expect(page.getByLabel('Input race duration hours')).toHaveValue('0'); + await expect(page.getByLabel('Input race duration minutes')).toHaveValue('10'); + await expect(page.getByLabel('Input race duration seconds')).toHaveValue('30.00'); + await page.getByText('Advanced Options').click(); + await expect(page.getByLabel('Default units')).toHaveValue('metric'); + await expect(page.getByLabel('Selected target set')).toHaveValue('_race_targets'); + await expect(page.getByLabel('Prediction model')).toHaveValue('RiegelModel'); + await expect(page.getByLabel('Riegel Exponent')).toHaveValue('1.06'); + + // Assert race predictions are correct (input race not resset and new prediction model loaded) + await expect(page.getByRole('row').nth(5)).toHaveText('1 mi' + '5:02.17' + '3:08 / km'); + await expect(page.getByRole('row').nth(10)).toHaveText('5 km' + '16:44.87' + '3:21 / km'); + await expect(page.getByRole('row')).toHaveCount(17); + + // Return to split calculator + await page.getByRole('link', { name: 'Back' }).click(); + await page.getByRole('button', { name: 'Split Calculator' }).click(); + + // Assert split calculator options are correct + await expect(page.getByLabel('Default units')).toHaveValue('metric'); + await expect(page.getByLabel('Selected target set')).toHaveValue('_split_targets'); + + // Assert times and paces are correct (split times not reset) + await expect(page.getByRole('row').nth(1).getByRole('cell').nth(1)).toHaveText('7:00.00'); + await expect(page.getByRole('row').nth(1).getByRole('cell').nth(3)).toHaveText('4:23 / km'); + await expect(page.getByRole('row').nth(2).getByRole('cell').nth(1)).toHaveText('13:30.00'); + await expect(page.getByRole('row').nth(2).getByRole('cell').nth(3)).toHaveText('4:04 / km'); + await expect(page.getByRole('row').nth(3).getByRole('cell').nth(1)).toHaveText('20:00.00'); + await expect(page.getByRole('row').nth(3).getByRole('cell').nth(3)).toHaveText('3:37 / km'); + await expect(page.getByRole('row')).toHaveCount(4); + + // Return to unit calculator + await page.getByRole('link', { name: 'Back' }).click(); + await page.getByRole('button', { name: 'Unit Calculator' }).click(); + + // Assert result is correct (state not reset) + await expect(page.getByLabel('Output value')).toHaveText('00:09:39.364'); + + // Return to workout calculator + await page.getByRole('link', { name: 'Back' }).click(); + await page.getByRole('button', { name: 'Workout Calculator' }).click(); + + // Assert workout calculator options are correct + await expect(page.getByLabel('Input race distance value')).toHaveValue('1.00'); + await expect(page.getByLabel('Input race distance unit')).toHaveValue('miles'); + await expect(page.getByLabel('Input race duration hours')).toHaveValue('0'); + await expect(page.getByLabel('Input race duration minutes')).toHaveValue('05'); + await expect(page.getByLabel('Input race duration seconds')).toHaveValue('01.00'); + await page.getByText('Advanced Options').click(); + await expect(page.getByLabel('Default units')).toHaveValue('metric'); + await expect(page.getByLabel('Selected target set')).toHaveValue('_workout_targets'); + await expect(page.getByLabel('Workout name customization')).toHaveValue('false'); + await expect(page.getByLabel('Prediction model')).toHaveValue('RiegelModel'); + await expect(page.getByLabel('Riegel Exponent')).toHaveValue('1.06'); + + // Assert workout splits are correct (input race and prediction model not reset) + await expect(page.getByRole('row').nth(1)).toHaveText('400 m @ 1 mi' + '1:14.81'); + await expect(page.getByRole('row').nth(3)).toHaveText('1600 m @ 1:00:00' + '5:44.38'); + await expect(page.getByRole('row')).toHaveCount(5); + } }); diff --git a/tests/e2e/pace-calculator.spec.js b/tests/e2e/pace-calculator.spec.js @@ -1,128 +1,134 @@ import { test, expect } from '@playwright/test'; test('Pace Calculator', async ({ page }) => { - // Structure: - // - Test standard pace results - // - Test different default units - // - Test modified default target set - // - Test custom target set - // - Reload page - // - Assert outputs are still the same - // - Test target set deletion and reversion - // Go to pace calculator await page.goto('/'); await page.getByRole('button', { name: 'Pace Calculator' }).click(); await expect(page).toHaveTitle('Pace Calculator - Running Tools'); - // Enter input pace (2 mi in 15:30) - await page.getByLabel('Input distance value').fill('2'); - await page.getByLabel('Input distance unit').selectOption('Miles'); - await page.getByLabel('Input duration hours').fill('0'); - await page.getByLabel('Input duration minutes').fill('15'); - await page.getByLabel('Input duration seconds').fill('30'); - - // Assert paces are correct - await expect(page.getByRole('row').nth(11)).toHaveText('1 mi' + '7:45.00'); - await expect(page.getByRole('row').nth(13)).toHaveText('1.29 mi' + '10:00'); - await expect(page.getByRole('row')).toHaveCount(31); - - // Change default units - await page.getByText('Advanced Options').click(); - await page.getByLabel('Default units').selectOption('Kilometers'); - - // Assert paces are correct - await expect(page.getByRole('row').nth(11)).toHaveText('1 mi' + '7:45.00'); - await expect(page.getByRole('row').nth(13)).toHaveText('2.08 km' + '10:00'); - await expect(page.getByRole('row')).toHaveCount(31); - - // Edit default target set - await page.getByRole('button', { name: 'Edit target set' }).click(); - await page.getByLabel('Target set label').fill('Less-common Pace Targets'); - await page.getByLabel('Target distance value').nth(10).fill('1.01'); - await page.getByLabel('Target distance unit').nth(10).selectOption('Miles'); - await page.getByLabel('Target duration second').nth(0).fill('1'); - await page.getByRole('button', { name: 'Add distance target' }).click(); - await page.getByLabel('Target distance value').last().fill('1.5'); - await page.getByLabel('Target distance unit').last().selectOption('Miles'); - await page.getByRole('button', { name: 'Add time target' }).click(); - await page.getByLabel('Target duration minutes').last().fill('19'); - await page.getByLabel('Target duration seconds').last().fill('0'); - await page.getByRole('button', { name: 'Close' }).click(); - - // Assert paces are correct - await expect(page.getByRole('row').nth(11)).toHaveText('1.01 mi' + '7:49.65'); - await expect(page.getByRole('row').nth(13)).toHaveText('2.08 km' + '10:01'); - await expect(page.getByRole('row').nth(14)).toHaveText('1.5 mi' + '11:37.50'); - await expect(page.getByRole('row').nth(18)).toHaveText('3.95 km' + '19:00'); - await expect(page.getByRole('row')).toHaveCount(33); - - // Create custom target set - await page.getByLabel('Selected target set').selectOption('[ Create New Target Set ]'); - await expect(page.getByRole('row').nth(4)).toHaveText('There aren\'t any targets in this set yet.'); - await expect(page.getByRole('row')).toHaveCount(5); - - // Edit new target set - await expect(page.getByRole('row').nth(1)).toHaveText('There aren\'t any targets in this set yet.'); - await expect(page.getByLabel('Target set label')).toHaveValue('New target set'); - await page.getByLabel('Target set label').fill('800m Splits'); - await page.getByRole('button', { name: 'Add distance target' }).click(); - await page.getByLabel('Target distance value').nth(0).fill('0.4'); - await page.getByLabel('Target distance unit').nth(0).selectOption('Kilometers'); - await page.getByRole('button', { name: 'Add distance target' }).click(); - await page.getByLabel('Target distance value').nth(1).fill('800'); - await page.getByLabel('Target distance unit').nth(1).selectOption('Meters'); - await page.getByRole('button', { name: 'Close' }).click(); - - // Assert paces are correct - await expect(page.getByRole('row').nth(1)).toHaveText('0.4 km' + '1:55.57'); - await expect(page.getByRole('row').nth(2)).toHaveText('800 m' + '3:51.15'); - await expect(page.getByRole('row')).toHaveCount(3); + // Test standard pace results + { + // Enter input pace (2 mi in 15:30) + await page.getByLabel('Input distance value').fill('2'); + await page.getByLabel('Input distance unit').selectOption('Miles'); + await page.getByLabel('Input duration hours').fill('0'); + await page.getByLabel('Input duration minutes').fill('15'); + await page.getByLabel('Input duration seconds').fill('30'); + + // Assert paces are correct + await expect(page.getByRole('row').nth(11)).toHaveText('1 mi' + '7:45.00'); + await expect(page.getByRole('row').nth(13)).toHaveText('1.29 mi' + '10:00'); + await expect(page.getByRole('row')).toHaveCount(31); + + // Change default units + await page.getByText('Advanced Options').click(); + await page.getByLabel('Default units').selectOption('Kilometers'); + + // Assert paces are correct + await expect(page.getByRole('row').nth(11)).toHaveText('1 mi' + '7:45.00'); + await expect(page.getByRole('row').nth(13)).toHaveText('2.08 km' + '10:00'); + await expect(page.getByRole('row')).toHaveCount(31); + } + + // Test modified default target set + { + // Edit default target set + await page.getByRole('button', { name: 'Edit target set' }).click(); + await page.getByLabel('Target set label').fill('Less-common Pace Targets'); + await page.getByLabel('Target distance value').nth(10).fill('1.01'); + await page.getByLabel('Target distance unit').nth(10).selectOption('Miles'); + await page.getByLabel('Target duration second').nth(0).fill('1'); + await page.getByRole('button', { name: 'Add distance target' }).click(); + await page.getByLabel('Target distance value').last().fill('1.5'); + await page.getByLabel('Target distance unit').last().selectOption('Miles'); + await page.getByRole('button', { name: 'Add time target' }).click(); + await page.getByLabel('Target duration minutes').last().fill('19'); + await page.getByLabel('Target duration seconds').last().fill('0'); + await page.getByRole('button', { name: 'Close' }).click(); + + // Assert paces are correct + await expect(page.getByRole('row').nth(11)).toHaveText('1.01 mi' + '7:49.65'); + await expect(page.getByRole('row').nth(13)).toHaveText('2.08 km' + '10:01'); + await expect(page.getByRole('row').nth(14)).toHaveText('1.5 mi' + '11:37.50'); + await expect(page.getByRole('row').nth(18)).toHaveText('3.95 km' + '19:00'); + await expect(page.getByRole('row')).toHaveCount(33); + } + + // Test custom target set + { + // Create custom target set + await page.getByLabel('Selected target set').selectOption('[ Create New Target Set ]'); + await expect(page.getByRole('row').nth(4)).toHaveText('There aren\'t any targets in this set yet.'); + await expect(page.getByRole('row')).toHaveCount(5); + + // Edit new target set + await expect(page.getByRole('row').nth(1)).toHaveText('There aren\'t any targets in this set yet.'); + await expect(page.getByLabel('Target set label')).toHaveValue('New target set'); + await page.getByLabel('Target set label').fill('800m Splits'); + await page.getByRole('button', { name: 'Add distance target' }).click(); + await page.getByLabel('Target distance value').nth(0).fill('0.4'); + await page.getByLabel('Target distance unit').nth(0).selectOption('Kilometers'); + await page.getByRole('button', { name: 'Add distance target' }).click(); + await page.getByLabel('Target distance value').nth(1).fill('800'); + await page.getByLabel('Target distance unit').nth(1).selectOption('Meters'); + await page.getByRole('button', { name: 'Close' }).click(); + + // Assert paces are correct + await expect(page.getByRole('row').nth(1)).toHaveText('0.4 km' + '1:55.58'); + await expect(page.getByRole('row').nth(2)).toHaveText('800 m' + '3:51.15'); + await expect(page.getByRole('row')).toHaveCount(3); + } // Reload page await page.reload(); - // Assert paces are correct (custom targets and default units not reset) - await expect(page.getByRole('row').nth(1)).toHaveText('0.4 km' + '1:55.57'); - await expect(page.getByRole('row').nth(2)).toHaveText('800 m' + '3:51.15'); - await expect(page.getByRole('row')).toHaveCount(3); - - // Switch target set - await page.getByText('Advanced Options').click(); - await page.getByLabel('Selected target set').selectOption('Less-common Pace Targets'); - - // Assert paces are correct - await expect(page.getByRole('row').nth(11)).toHaveText('1.01 mi' + '7:49.65'); - await expect(page.getByRole('row').nth(13)).toHaveText('2.08 km' + '10:01'); - await expect(page.getByRole('row').nth(14)).toHaveText('1.5 mi' + '11:37.50'); - await expect(page.getByRole('row').nth(18)).toHaveText('3.95 km' + '19:00'); - await expect(page.getByRole('row')).toHaveCount(33); - - // Delete custom target set - await page.getByLabel('Selected target set').selectOption('800m Splits'); - await page.getByRole('button', { name: 'Edit target set' }).click(); - await expect(page.getByLabel('Target set label')).toHaveValue('800m Splits'); - await page.getByRole('button', { name: 'Delete target set' }).click(); - - // Assert paces are correct (back to default target set) - await expect(page.getByRole('row').nth(11)).toHaveText('1.01 mi' + '7:49.65'); - await expect(page.getByRole('row').nth(13)).toHaveText('2.08 km' + '10:01'); - await expect(page.getByRole('row').nth(14)).toHaveText('1.5 mi' + '11:37.50'); - await expect(page.getByRole('row').nth(18)).toHaveText('3.95 km' + '19:00'); - await expect(page.getByRole('row')).toHaveCount(33); - - // Revert target set - await page.getByRole('button', { name: 'Edit target set' }).click(); - await expect(page.getByLabel('Target set label')).toHaveValue('Less-common Pace Targets'); - await page.getByRole('button', { name: 'Revert target set' }).click(); - await page.getByRole('button', { name: 'Close' }).click(); - - // Assert paces are correct - await expect(page.getByRole('row').nth(11)).toHaveText('1 mi' + '7:45.00'); - await expect(page.getByRole('row').nth(13)).toHaveText('2.08 km' + '10:00'); - await expect(page.getByRole('row')).toHaveCount(31); - - // Assert title was reset - await page.getByRole('button', { name: 'Edit target set' }).click(); - await expect(page.getByLabel('Target set label')).toHaveValue('Common Pace Targets'); + // Assert outputs are still the same + { + // Assert paces are correct (custom targets and default units not reset) + await expect(page.getByRole('row').nth(1)).toHaveText('0.4 km' + '1:55.58'); + await expect(page.getByRole('row').nth(2)).toHaveText('800 m' + '3:51.15'); + await expect(page.getByRole('row')).toHaveCount(3); + + // Switch target set + await page.getByText('Advanced Options').click(); + await page.getByLabel('Selected target set').selectOption('Less-common Pace Targets'); + + // Assert paces are correct + await expect(page.getByRole('row').nth(11)).toHaveText('1.01 mi' + '7:49.65'); + await expect(page.getByRole('row').nth(13)).toHaveText('2.08 km' + '10:01'); + await expect(page.getByRole('row').nth(14)).toHaveText('1.5 mi' + '11:37.50'); + await expect(page.getByRole('row').nth(18)).toHaveText('3.95 km' + '19:00'); + await expect(page.getByRole('row')).toHaveCount(33); + } + + // Test target set deletion and reversion + { + // Delete custom target set + await page.getByLabel('Selected target set').selectOption('800m Splits'); + await page.getByRole('button', { name: 'Edit target set' }).click(); + await expect(page.getByLabel('Target set label')).toHaveValue('800m Splits'); + await page.getByRole('button', { name: 'Delete target set' }).click(); + + // Assert paces are correct (back to default target set) + await expect(page.getByRole('row').nth(11)).toHaveText('1.01 mi' + '7:49.65'); + await expect(page.getByRole('row').nth(13)).toHaveText('2.08 km' + '10:01'); + await expect(page.getByRole('row').nth(14)).toHaveText('1.5 mi' + '11:37.50'); + await expect(page.getByRole('row').nth(18)).toHaveText('3.95 km' + '19:00'); + await expect(page.getByRole('row')).toHaveCount(33); + + // Revert target set + await page.getByRole('button', { name: 'Edit target set' }).click(); + await expect(page.getByLabel('Target set label')).toHaveValue('Less-common Pace Targets'); + await page.getByRole('button', { name: 'Revert target set' }).click(); + await page.getByRole('button', { name: 'Close' }).click(); + + // Assert paces are correct + await expect(page.getByRole('row').nth(11)).toHaveText('1 mi' + '7:45.00'); + await expect(page.getByRole('row').nth(13)).toHaveText('2.08 km' + '10:00'); + await expect(page.getByRole('row')).toHaveCount(31); + + // Assert title was reset + await page.getByRole('button', { name: 'Edit target set' }).click(); + await expect(page.getByLabel('Target set label')).toHaveValue('Common Pace Targets'); + } }); diff --git a/tests/e2e/race-calculator.spec.js b/tests/e2e/race-calculator.spec.js @@ -1,146 +1,154 @@ import { test, expect } from '@playwright/test'; test('Race Calculator', async ({ page }) => { - // Structure: - // - Test standard race results - // - Test different default units - // - Test different prediction options - // - Test modified default target set - // - Test custom target set - // - Reload page - // - Assert outputs are still the same - // - Test target set deletion and reversion - // Go to race calculator await page.goto('/'); await page.getByRole('button', { name: 'Race Calculator' }).click(); await expect(page).toHaveTitle('Race Calculator - Running Tools'); - // Enter input race (2 mi in 10:30) - await page.getByLabel('Input race distance value').fill('2'); - await page.getByLabel('Input race distance unit').selectOption('Miles'); - await page.getByLabel('Input race duration hours').fill('0'); - await page.getByLabel('Input race duration minutes').fill('10'); - await page.getByLabel('Input race duration seconds').fill('30'); - - // Assert race predictions are correct - await expect(page.getByRole('row').nth(5)).toHaveText('1 mi' + '4:55.53' + '4:56 / mi'); - await expect(page.getByRole('row').nth(10)).toHaveText('5 km' + '16:47.57' + '5:24 / mi'); - await expect(page.getByRole('row')).toHaveCount(17); - - // Assert race statistics are correct - await page.getByText('Race Statistics').click(); - await expect(page.getByText('Purdy Points:')).toContainText(': 680.1'); - await expect(page.getByText('V̇O₂:')).toContainText(': 61.0 ml/kg/min (100.5% of max)'); - await expect(page.getByText('V̇O₂ Max:')).toContainText(': 60.7 ml/kg/min'); - - // Change default units - await page.getByText('Advanced Options').click(); - await page.getByLabel('Default units').selectOption('Kilometers'); - - // Assert race predictions are correct - await expect(page.getByRole('row').nth(5)).toHaveText('1 mi' + '4:55.53' + '3:04 / km'); - await expect(page.getByRole('row').nth(10)).toHaveText('5 km' + '16:47.57' + '3:22 / km'); - await expect(page.getByRole('row')).toHaveCount(17); - - // Change prediction model - await page.getByLabel('Prediction model').selectOption('Riegel\'s Model'); - - // Assert race predictions are correct - await expect(page.getByRole('row').nth(5)).toHaveText('1 mi' + '5:02.17' + '3:08 / km'); - await expect(page.getByRole('row').nth(10)).toHaveText('5 km' + '16:44.86' + '3:21 / km'); - await expect(page.getByRole('row')).toHaveCount(17); - - // Change Riegel exponent - await page.getByLabel('Riegel Exponent').fill('1.12'); - - // Assert race predictions are correct - await expect(page.getByRole('row').nth(5)).toHaveText('1 mi' + '4:49.86' + '3:00 / km'); - await expect(page.getByRole('row').nth(10)).toHaveText('5 km' + '17:11.77' + '3:26 / km'); - await expect(page.getByRole('row')).toHaveCount(17); - - // Edit default target set - await page.getByRole('button', { name: 'Edit target set' }).click(); - await page.getByLabel('Target set label').fill('Less-common Race Targets'); - await page.getByLabel('Target distance value').nth(4).fill('1.01'); - await page.getByRole('button', { name: 'Add distance target' }).click(); - await page.getByLabel('Target distance value').last().fill('1.5'); - await page.getByLabel('Target distance unit').last().selectOption('Miles'); - await page.getByRole('button', { name: 'Add time target' }).click(); - await page.getByLabel('Target duration minutes').last().fill('19'); - await page.getByLabel('Target duration seconds').last().fill('0'); - await page.getByRole('button', { name: 'Close' }).click(); - - // Assert race predictions are correct - await expect(page.getByRole('row').nth(5)).toHaveText('1.01 mi' + '4:53.11' + '3:00 / km'); - await expect(page.getByRole('row').nth(6)).toHaveText('1.5 mi' + '7:36.47' + '3:09 / km'); - await expect(page.getByRole('row').nth(12)).toHaveText('5.47 km' + '19:00' + '3:29 / km'); - await expect(page.getByRole('row')).toHaveCount(19); - - // Create custom target set - await page.getByLabel('Selected target set').selectOption('[ Create New Target Set ]'); - await expect(page.getByRole('row').nth(4)).toHaveText('There aren\'t any targets in this set yet.'); - await expect(page.getByRole('row')).toHaveCount(5); - - // Edit new target set - await expect(page.getByRole('row').nth(1)).toHaveText('There aren\'t any targets in this set yet.'); - await expect(page.getByLabel('Target set label')).toHaveValue('New target set'); - await page.getByLabel('Target set label').fill('XC Race Targets'); - await page.getByRole('button', { name: 'Add distance target' }).click(); - await page.getByLabel('Target distance value').nth(0).fill('5'); - await page.getByLabel('Target distance unit').nth(0).selectOption('Kilometers'); - await page.getByRole('button', { name: 'Add distance target' }).click(); - await page.getByLabel('Target distance value').nth(1).fill('10'); - await page.getByLabel('Target distance unit').nth(1).selectOption('Kilometers'); - await page.getByRole('button', { name: 'Close' }).click(); - - // Assert race predictions are correct - await expect(page.getByRole('row').nth(1)).toHaveText('5 km' + '17:11.77' + '3:26 / km'); - await expect(page.getByRole('row').nth(2)).toHaveText('10 km' + '37:22.53' + '3:44 / km'); - await expect(page.getByRole('row')).toHaveCount(3); + // Test standard race results + { + // Enter input race (2 mi in 10:30) + await page.getByLabel('Input race distance value').fill('2'); + await page.getByLabel('Input race distance unit').selectOption('Miles'); + await page.getByLabel('Input race duration hours').fill('0'); + await page.getByLabel('Input race duration minutes').fill('10'); + await page.getByLabel('Input race duration seconds').fill('30'); + + // Assert race predictions are correct + await expect(page.getByRole('row').nth(5)).toHaveText('1 mi' + '4:55.53' + '4:56 / mi'); + await expect(page.getByRole('row').nth(10)).toHaveText('5 km' + '16:47.57' + '5:24 / mi'); + await expect(page.getByRole('row')).toHaveCount(17); + + // Assert race statistics are correct + await page.getByText('Race Statistics').click(); + await expect(page.getByText('Purdy Points:')).toContainText(': 680.1'); + await expect(page.getByText('V̇O₂:')).toContainText(': 61.0 ml/kg/min (100.5% of max)'); + await expect(page.getByText('V̇O₂ Max:')).toContainText(': 60.7 ml/kg/min'); + } + + // Test different calculator options + { + // Change default units + await page.getByText('Advanced Options').click(); + await page.getByLabel('Default units').selectOption('Kilometers'); + + // Assert race predictions are correct + await expect(page.getByRole('row').nth(5)).toHaveText('1 mi' + '4:55.53' + '3:04 / km'); + await expect(page.getByRole('row').nth(10)).toHaveText('5 km' + '16:47.57' + '3:22 / km'); + await expect(page.getByRole('row')).toHaveCount(17); + + // Change prediction model + await page.getByLabel('Prediction model').selectOption('Riegel\'s Model'); + + // Assert race predictions are correct + await expect(page.getByRole('row').nth(5)).toHaveText('1 mi' + '5:02.17' + '3:08 / km'); + await expect(page.getByRole('row').nth(10)).toHaveText('5 km' + '16:44.87' + '3:21 / km'); + await expect(page.getByRole('row')).toHaveCount(17); + + // Change Riegel exponent + await page.getByLabel('Riegel Exponent').fill('1.12'); + + // Assert race predictions are correct + await expect(page.getByRole('row').nth(5)).toHaveText('1 mi' + '4:49.86' + '3:00 / km'); + await expect(page.getByRole('row').nth(10)).toHaveText('5 km' + '17:11.78' + '3:26 / km'); + await expect(page.getByRole('row')).toHaveCount(17); + } + + // Test modified default target set + { + // Edit default target set + await page.getByRole('button', { name: 'Edit target set' }).click(); + await page.getByLabel('Target set label').fill('Less-common Race Targets'); + await page.getByLabel('Target distance value').nth(4).fill('1.01'); + await page.getByRole('button', { name: 'Add distance target' }).click(); + await page.getByLabel('Target distance value').last().fill('1.5'); + await page.getByLabel('Target distance unit').last().selectOption('Miles'); + await page.getByRole('button', { name: 'Add time target' }).click(); + await page.getByLabel('Target duration minutes').last().fill('19'); + await page.getByLabel('Target duration seconds').last().fill('0'); + await page.getByRole('button', { name: 'Close' }).click(); + + // Assert race predictions are correct + await expect(page.getByRole('row').nth(5)).toHaveText('1.01 mi' + '4:53.11' + '3:00 / km'); + await expect(page.getByRole('row').nth(6)).toHaveText('1.5 mi' + '7:36.47' + '3:09 / km'); + await expect(page.getByRole('row').nth(12)).toHaveText('5.47 km' + '19:00' + '3:29 / km'); + await expect(page.getByRole('row')).toHaveCount(19); + } + + // Test custom target set + { + // Create custom target set + await page.getByLabel('Selected target set').selectOption('[ Create New Target Set ]'); + await expect(page.getByRole('row').nth(4)).toHaveText('There aren\'t any targets in this set yet.'); + await expect(page.getByRole('row')).toHaveCount(5); + + // Edit new target set + await expect(page.getByRole('row').nth(1)).toHaveText('There aren\'t any targets in this set yet.'); + await expect(page.getByLabel('Target set label')).toHaveValue('New target set'); + await page.getByLabel('Target set label').fill('XC Race Targets'); + await page.getByRole('button', { name: 'Add distance target' }).click(); + await page.getByLabel('Target distance value').nth(0).fill('5'); + await page.getByLabel('Target distance unit').nth(0).selectOption('Kilometers'); + await page.getByRole('button', { name: 'Add distance target' }).click(); + await page.getByLabel('Target distance value').nth(1).fill('10'); + await page.getByLabel('Target distance unit').nth(1).selectOption('Kilometers'); + await page.getByRole('button', { name: 'Close' }).click(); + + // Assert race predictions are correct + await expect(page.getByRole('row').nth(1)).toHaveText('5 km' + '17:11.78' + '3:26 / km'); + await expect(page.getByRole('row').nth(2)).toHaveText('10 km' + '37:22.54' + '3:44 / km'); + await expect(page.getByRole('row')).toHaveCount(3); + } // Reload page await page.reload(); - // Assert race predictions are correct (custom targets, default units, and model settings not reset) - await expect(page.getByRole('row').nth(1)).toHaveText('5 km' + '17:11.77' + '3:26 / km'); - await expect(page.getByRole('row').nth(2)).toHaveText('10 km' + '37:22.53' + '3:44 / km'); - await expect(page.getByRole('row')).toHaveCount(3); - - // Switch target set - await page.getByText('Advanced Options').click(); - await page.getByLabel('Selected target set').selectOption('Less-common Race Targets'); - - // Assert race predictions are correct - await expect(page.getByRole('row').nth(5)).toHaveText('1.01 mi' + '4:53.11' + '3:00 / km'); - await expect(page.getByRole('row').nth(6)).toHaveText('1.5 mi' + '7:36.47' + '3:09 / km'); - await expect(page.getByRole('row').nth(12)).toHaveText('5.47 km' + '19:00' + '3:29 / km'); - await expect(page.getByRole('row')).toHaveCount(19); - - // Delete custom target set - await page.getByLabel('Selected target set').selectOption('XC Race Targets'); - await page.getByRole('button', { name: 'Edit target set' }).click(); - await expect(page.getByLabel('Target set label')).toHaveValue('XC Race Targets'); - await page.getByRole('button', { name: 'Delete target set' }).click(); - - // Assert race predictions are correct (back to default target set) - await expect(page.getByRole('row').nth(5)).toHaveText('1.01 mi' + '4:53.11' + '3:00 / km'); - await expect(page.getByRole('row').nth(6)).toHaveText('1.5 mi' + '7:36.47' + '3:09 / km'); - await expect(page.getByRole('row').nth(12)).toHaveText('5.47 km' + '19:00' + '3:29 / km'); - await expect(page.getByRole('row')).toHaveCount(19); - - // Revert target set - await page.getByRole('button', { name: 'Edit target set' }).click(); - await expect(page.getByLabel('Target set label')).toHaveValue('Less-common Race Targets'); - await page.getByRole('button', { name: 'Revert target set' }).click(); - await page.getByRole('button', { name: 'Close' }).click(); - - // Assert paces are correct - await expect(page.getByRole('row').nth(5)).toHaveText('1 mi' + '4:49.86' + '3:00 / km'); - await expect(page.getByRole('row').nth(10)).toHaveText('5 km' + '17:11.77' + '3:26 / km'); - await expect(page.getByRole('row')).toHaveCount(17); - - // Assert title was reset - await page.getByRole('button', { name: 'Edit target set' }).click(); - await expect(page.getByLabel('Target set label')).toHaveValue('Common Race Targets'); + // Assert outputs are still the same + { + // Assert race predictions are correct (custom targets, default units, and model settings not reset) + await expect(page.getByRole('row').nth(1)).toHaveText('5 km' + '17:11.78' + '3:26 / km'); + await expect(page.getByRole('row').nth(2)).toHaveText('10 km' + '37:22.54' + '3:44 / km'); + await expect(page.getByRole('row')).toHaveCount(3); + + // Switch target set + await page.getByText('Advanced Options').click(); + await page.getByLabel('Selected target set').selectOption('Less-common Race Targets'); + + // Assert race predictions are correct + await expect(page.getByRole('row').nth(5)).toHaveText('1.01 mi' + '4:53.11' + '3:00 / km'); + await expect(page.getByRole('row').nth(6)).toHaveText('1.5 mi' + '7:36.47' + '3:09 / km'); + await expect(page.getByRole('row').nth(12)).toHaveText('5.47 km' + '19:00' + '3:29 / km'); + await expect(page.getByRole('row')).toHaveCount(19); + } + + // Test target set deletion and reversion + { + // Delete custom target set + await page.getByLabel('Selected target set').selectOption('XC Race Targets'); + await page.getByRole('button', { name: 'Edit target set' }).click(); + await expect(page.getByLabel('Target set label')).toHaveValue('XC Race Targets'); + await page.getByRole('button', { name: 'Delete target set' }).click(); + + // Assert race predictions are correct (back to default target set) + await expect(page.getByRole('row').nth(5)).toHaveText('1.01 mi' + '4:53.11' + '3:00 / km'); + await expect(page.getByRole('row').nth(6)).toHaveText('1.5 mi' + '7:36.47' + '3:09 / km'); + await expect(page.getByRole('row').nth(12)).toHaveText('5.47 km' + '19:00' + '3:29 / km'); + await expect(page.getByRole('row')).toHaveCount(19); + + // Revert target set + await page.getByRole('button', { name: 'Edit target set' }).click(); + await expect(page.getByLabel('Target set label')).toHaveValue('Less-common Race Targets'); + await page.getByRole('button', { name: 'Revert target set' }).click(); + await page.getByRole('button', { name: 'Close' }).click(); + + // Assert paces are correct + await expect(page.getByRole('row').nth(5)).toHaveText('1 mi' + '4:49.86' + '3:00 / km'); + await expect(page.getByRole('row').nth(10)).toHaveText('5 km' + '17:11.78' + '3:26 / km'); + await expect(page.getByRole('row')).toHaveCount(17); + + // Assert title was reset + await page.getByRole('button', { name: 'Edit target set' }).click(); + await expect(page.getByLabel('Target set label')).toHaveValue('Common Race Targets'); + } }); diff --git a/tests/e2e/split-calculator.spec.js b/tests/e2e/split-calculator.spec.js @@ -1,191 +1,200 @@ import { test, expect } from '@playwright/test'; test('Split Calculator', async ({ page }) => { - // Structure: - // - Test standard split results - // - Test different default units - // - Test modified default target set - // - Test custom target set - // - Reload page - // - Assert outputs are still the same - // - Test target set deletion and reversion - // Go to split calculator await page.goto('/'); await page.getByRole('button', { name: 'Split Calculator' }).click(); await expect(page).toHaveTitle('Split Calculator - Running Tools'); - // Enter input 5K splits (7:00, 6:30, 6:30) - await page.getByLabel('Split duration minutes').nth(0).fill('7'); - await page.getByLabel('Split duration seconds').nth(0).fill('0'); - await page.getByLabel('Split duration minutes').nth(1).fill('6'); - await page.getByLabel('Split duration seconds').nth(1).fill('30'); - await page.getByLabel('Split duration minutes').nth(2).fill('6'); - await page.getByLabel('Split duration seconds').nth(2).fill('30'); - - // Assert times and paces are correct - await expect(page.getByRole('row').nth(1).getByRole('cell').nth(1)).toHaveText('7:00.00'); - await expect(page.getByRole('row').nth(1).getByRole('cell').nth(3)).toHaveText('7:00 / mi'); - await expect(page.getByRole('row').nth(2).getByRole('cell').nth(1)).toHaveText('13:30.00'); - await expect(page.getByRole('row').nth(2).getByRole('cell').nth(3)).toHaveText('6:30 / mi'); - await expect(page.getByRole('row').nth(3).getByRole('cell').nth(1)).toHaveText('20:00.00'); - await expect(page.getByRole('row').nth(3).getByRole('cell').nth(3)).toHaveText('5:52 / mi'); - await expect(page.getByRole('row')).toHaveCount(4); - - // Change default units - await page.getByLabel('Default units').selectOption('Kilometers'); - - // Assert times and paces are correct - await expect(page.getByRole('row').nth(1).getByRole('cell').nth(1)).toHaveText('7:00.00'); - await expect(page.getByRole('row').nth(1).getByRole('cell').nth(3)).toHaveText('4:21 / km'); - await expect(page.getByRole('row').nth(2).getByRole('cell').nth(1)).toHaveText('13:30.00'); - await expect(page.getByRole('row').nth(2).getByRole('cell').nth(3)).toHaveText('4:02 / km'); - await expect(page.getByRole('row').nth(3).getByRole('cell').nth(1)).toHaveText('20:00.00'); - await expect(page.getByRole('row').nth(3).getByRole('cell').nth(3)).toHaveText('3:39 / km'); - await expect(page.getByRole('row')).toHaveCount(4); - - // Edit target set - await page.getByRole('button', { name: 'Edit target set' }).click(); - await page.getByLabel('Target set label').fill('5K 1600m Splits'); - await page.getByLabel('Target distance value').nth(0).fill('1.6'); - await page.getByLabel('Target distance unit').nth(0).selectOption('Kilometers'); - await page.getByLabel('Target distance value').nth(1).fill('3.2'); - await page.getByLabel('Target distance unit').nth(1).selectOption('Kilometers'); - await page.getByRole('button', { name: 'Add distance target' }).click(); - await page.getByLabel('Target distance value').nth(3).fill('4.8'); - await page.getByLabel('Target distance unit').nth(3).selectOption('Kilometers'); - await page.getByRole('button', { name: 'Close' }).click(); - - // Assert times and paces are correct (new distances are processed) - await expect(page.getByRole('row').nth(1).getByRole('cell').nth(1)).toHaveText('7:00.00'); - await expect(page.getByRole('row').nth(1).getByRole('cell').nth(3)).toHaveText('4:23 / km'); - await expect(page.getByRole('row').nth(2).getByRole('cell').nth(1)).toHaveText('13:30.00'); - await expect(page.getByRole('row').nth(2).getByRole('cell').nth(3)).toHaveText('4:04 / km'); - await expect(page.getByRole('row').nth(3).getByRole('cell').nth(1)).toHaveText('13:30.00'); - await expect(page.getByRole('row').nth(3).getByRole('cell').nth(3)).toHaveText('0:00 / km'); - await expect(page.getByRole('row').nth(4).getByRole('cell').nth(1)).toHaveText('20:00.00'); - await expect(page.getByRole('row').nth(4).getByRole('cell').nth(3)).toHaveText('32:30 / km'); - await expect(page.getByRole('row')).toHaveCount(5); - - // Update third and fourth splits - await page.getByLabel('Split duration minutes').nth(2).fill('6'); - await page.getByLabel('Split duration seconds').nth(2).fill('0'); - await page.getByLabel('Split duration minutes').nth(3).fill('0'); - await page.getByLabel('Split duration seconds').nth(3).fill('30'); - await page.getByLabel('Split duration seconds').nth(3).blur(); - - // Assert times and paces are correct (new input splits are processed) - await expect(page.getByRole('row').nth(1).getByRole('cell').nth(1)).toHaveText('7:00.00'); - await expect(page.getByRole('row').nth(1).getByRole('cell').nth(3)).toHaveText('4:23 / km'); - await expect(page.getByRole('row').nth(2).getByRole('cell').nth(1)).toHaveText('13:30.00'); - await expect(page.getByRole('row').nth(2).getByRole('cell').nth(3)).toHaveText('4:04 / km'); - await expect(page.getByRole('row').nth(3).getByRole('cell').nth(1)).toHaveText('19:30.00'); - await expect(page.getByRole('row').nth(3).getByRole('cell').nth(3)).toHaveText('3:45 / km'); - await expect(page.getByRole('row').nth(4).getByRole('cell').nth(1)).toHaveText('20:00.00'); - await expect(page.getByRole('row').nth(4).getByRole('cell').nth(3)).toHaveText('2:30 / km'); - await expect(page.getByRole('row')).toHaveCount(5); - - // Create custom target set - await page.getByLabel('Selected target set').selectOption('[ Create New Target Set ]'); - await expect(page.getByRole('row').nth(4)).toHaveText('There aren\'t any targets in this set yet.'); - await expect(page.getByRole('row')).toHaveCount(5); - - // Edit new target set - await expect(page.getByRole('row').nth(1)).toHaveText('There aren\'t any targets in this set yet.'); - await expect(page.getByLabel('Target set label')).toHaveValue('New target set'); - await page.getByLabel('Target set label').fill('800m Splits'); - await page.getByRole('button', { name: 'Add distance target' }).click(); - await page.getByLabel('Target distance value').nth(0).fill('0.4'); - await page.getByLabel('Target distance unit').nth(0).selectOption('Kilometers'); - await page.getByRole('button', { name: 'Add distance target' }).click(); - await page.getByLabel('Target distance value').nth(1).fill('800'); - await page.getByLabel('Target distance unit').nth(1).selectOption('Meters'); - await page.getByRole('button', { name: 'Close' }).click(); - - // Assert times and paces are correct (input splits initialized to zero) - await expect(page.getByRole('row').nth(1).getByRole('cell').nth(0)).toHaveText('0.4 km'); - await expect(page.getByRole('row').nth(1).getByRole('cell').nth(1)).toHaveText('0:00.00'); - await expect(page.getByRole('row').nth(1).getByRole('cell').nth(3)).toHaveText('0:00 / km'); - await expect(page.getByRole('row').nth(2).getByRole('cell').nth(0)).toHaveText('800 m'); - await expect(page.getByRole('row').nth(2).getByRole('cell').nth(1)).toHaveText('0:00.00'); - await expect(page.getByRole('row').nth(2).getByRole('cell').nth(3)).toHaveText('0:00 / km'); - await expect(page.getByRole('row')).toHaveCount(3); - - // Enter input 800m splits (0:55, 1:05) - await page.getByLabel('Split duration minutes').nth(0).fill('0'); - await page.getByLabel('Split duration seconds').nth(0).fill('55'); - await page.getByLabel('Split duration minutes').nth(1).fill('1'); - await page.getByLabel('Split duration seconds').nth(1).fill('5'); - - // Assert times and paces are correct - await expect(page.getByRole('row').nth(1).getByRole('cell').nth(1)).toHaveText('0:55.00'); - await expect(page.getByRole('row').nth(1).getByRole('cell').nth(3)).toHaveText('2:18 / km'); - await expect(page.getByRole('row').nth(2).getByRole('cell').nth(1)).toHaveText('2:00.00'); - await expect(page.getByRole('row').nth(2).getByRole('cell').nth(3)).toHaveText('2:43 / km'); - await expect(page.getByRole('row')).toHaveCount(3); + // Test standard split results + { + // Enter input 5K splits (7:00, 6:30, 6:30) + await page.getByLabel('Split duration minutes').nth(0).fill('7'); + await page.getByLabel('Split duration seconds').nth(0).fill('0'); + await page.getByLabel('Split duration minutes').nth(1).fill('6'); + await page.getByLabel('Split duration seconds').nth(1).fill('30'); + await page.getByLabel('Split duration minutes').nth(2).fill('6'); + await page.getByLabel('Split duration seconds').nth(2).fill('30'); + + // Assert times and paces are correct + await expect(page.getByRole('row').nth(1).getByRole('cell').nth(1)).toHaveText('7:00.00'); + await expect(page.getByRole('row').nth(1).getByRole('cell').nth(3)).toHaveText('7:00 / mi'); + await expect(page.getByRole('row').nth(2).getByRole('cell').nth(1)).toHaveText('13:30.00'); + await expect(page.getByRole('row').nth(2).getByRole('cell').nth(3)).toHaveText('6:30 / mi'); + await expect(page.getByRole('row').nth(3).getByRole('cell').nth(1)).toHaveText('20:00.00'); + await expect(page.getByRole('row').nth(3).getByRole('cell').nth(3)).toHaveText('5:52 / mi'); + await expect(page.getByRole('row')).toHaveCount(4); + } + + // Test different calculator options + { + // Change default units + await page.getByLabel('Default units').selectOption('Kilometers'); + + // Assert times and paces are correct + await expect(page.getByRole('row').nth(1).getByRole('cell').nth(1)).toHaveText('7:00.00'); + await expect(page.getByRole('row').nth(1).getByRole('cell').nth(3)).toHaveText('4:21 / km'); + await expect(page.getByRole('row').nth(2).getByRole('cell').nth(1)).toHaveText('13:30.00'); + await expect(page.getByRole('row').nth(2).getByRole('cell').nth(3)).toHaveText('4:02 / km'); + await expect(page.getByRole('row').nth(3).getByRole('cell').nth(1)).toHaveText('20:00.00'); + await expect(page.getByRole('row').nth(3).getByRole('cell').nth(3)).toHaveText('3:39 / km'); + await expect(page.getByRole('row')).toHaveCount(4); + } + + // Test modified default target set + { + // Edit target set + await page.getByRole('button', { name: 'Edit target set' }).click(); + await page.getByLabel('Target set label').fill('5K 1600m Splits'); + await page.getByLabel('Target distance value').nth(0).fill('1.6'); + await page.getByLabel('Target distance unit').nth(0).selectOption('Kilometers'); + await page.getByLabel('Target distance value').nth(1).fill('3.2'); + await page.getByLabel('Target distance unit').nth(1).selectOption('Kilometers'); + await page.getByRole('button', { name: 'Add distance target' }).click(); + await page.getByLabel('Target distance value').nth(3).fill('4.8'); + await page.getByLabel('Target distance unit').nth(3).selectOption('Kilometers'); + await page.getByRole('button', { name: 'Close' }).click(); + + // Assert times and paces are correct (new distances are processed) + await expect(page.getByRole('row').nth(1).getByRole('cell').nth(1)).toHaveText('7:00.00'); + await expect(page.getByRole('row').nth(1).getByRole('cell').nth(3)).toHaveText('4:23 / km'); + await expect(page.getByRole('row').nth(2).getByRole('cell').nth(1)).toHaveText('13:30.00'); + await expect(page.getByRole('row').nth(2).getByRole('cell').nth(3)).toHaveText('4:04 / km'); + await expect(page.getByRole('row').nth(3).getByRole('cell').nth(1)).toHaveText('13:30.00'); + await expect(page.getByRole('row').nth(3).getByRole('cell').nth(3)).toHaveText('0:00 / km'); + await expect(page.getByRole('row').nth(4).getByRole('cell').nth(1)).toHaveText('20:00.00'); + await expect(page.getByRole('row').nth(4).getByRole('cell').nth(3)).toHaveText('32:30 / km'); + await expect(page.getByRole('row')).toHaveCount(5); + + // Update third and fourth splits + await page.getByLabel('Split duration minutes').nth(2).fill('6'); + await page.getByLabel('Split duration seconds').nth(2).fill('0'); + await page.getByLabel('Split duration minutes').nth(3).fill('0'); + await page.getByLabel('Split duration seconds').nth(3).fill('30'); + await page.getByLabel('Split duration seconds').nth(3).blur(); + + // Assert times and paces are correct (new input splits are processed) + await expect(page.getByRole('row').nth(1).getByRole('cell').nth(1)).toHaveText('7:00.00'); + await expect(page.getByRole('row').nth(1).getByRole('cell').nth(3)).toHaveText('4:23 / km'); + await expect(page.getByRole('row').nth(2).getByRole('cell').nth(1)).toHaveText('13:30.00'); + await expect(page.getByRole('row').nth(2).getByRole('cell').nth(3)).toHaveText('4:04 / km'); + await expect(page.getByRole('row').nth(3).getByRole('cell').nth(1)).toHaveText('19:30.00'); + await expect(page.getByRole('row').nth(3).getByRole('cell').nth(3)).toHaveText('3:45 / km'); + await expect(page.getByRole('row').nth(4).getByRole('cell').nth(1)).toHaveText('20:00.00'); + await expect(page.getByRole('row').nth(4).getByRole('cell').nth(3)).toHaveText('2:30 / km'); + await expect(page.getByRole('row')).toHaveCount(5); + } + + // Test custom target set + { + // Create custom target set + await page.getByLabel('Selected target set').selectOption('[ Create New Target Set ]'); + await expect(page.getByRole('row').nth(4)).toHaveText('There aren\'t any targets in this set yet.'); + await expect(page.getByRole('row')).toHaveCount(5); + + // Edit new target set + await expect(page.getByRole('row').nth(1)).toHaveText('There aren\'t any targets in this set yet.'); + await expect(page.getByLabel('Target set label')).toHaveValue('New target set'); + await page.getByLabel('Target set label').fill('800m Splits'); + await page.getByRole('button', { name: 'Add distance target' }).click(); + await page.getByLabel('Target distance value').nth(0).fill('0.4'); + await page.getByLabel('Target distance unit').nth(0).selectOption('Kilometers'); + await page.getByRole('button', { name: 'Add distance target' }).click(); + await page.getByLabel('Target distance value').nth(1).fill('800'); + await page.getByLabel('Target distance unit').nth(1).selectOption('Meters'); + await page.getByRole('button', { name: 'Close' }).click(); + + // Assert times and paces are correct (input splits initialized to zero) + await expect(page.getByRole('row').nth(1).getByRole('cell').nth(0)).toHaveText('0.4 km'); + await expect(page.getByRole('row').nth(1).getByRole('cell').nth(1)).toHaveText('0:00.00'); + await expect(page.getByRole('row').nth(1).getByRole('cell').nth(3)).toHaveText('0:00 / km'); + await expect(page.getByRole('row').nth(2).getByRole('cell').nth(0)).toHaveText('800 m'); + await expect(page.getByRole('row').nth(2).getByRole('cell').nth(1)).toHaveText('0:00.00'); + await expect(page.getByRole('row').nth(2).getByRole('cell').nth(3)).toHaveText('0:00 / km'); + await expect(page.getByRole('row')).toHaveCount(3); + + // Enter input 800m splits (0:55, 1:05) + await page.getByLabel('Split duration minutes').nth(0).fill('0'); + await page.getByLabel('Split duration seconds').nth(0).fill('55'); + await page.getByLabel('Split duration minutes').nth(1).fill('1'); + await page.getByLabel('Split duration seconds').nth(1).fill('5'); + + // Assert times and paces are correct + await expect(page.getByRole('row').nth(1).getByRole('cell').nth(1)).toHaveText('0:55.00'); + await expect(page.getByRole('row').nth(1).getByRole('cell').nth(3)).toHaveText('2:18 / km'); + await expect(page.getByRole('row').nth(2).getByRole('cell').nth(1)).toHaveText('2:00.00'); + await expect(page.getByRole('row').nth(2).getByRole('cell').nth(3)).toHaveText('2:43 / km'); + await expect(page.getByRole('row')).toHaveCount(3); + } // Reload page await page.reload(); - // Assert times and paces are correct (custom targets, split times, and default units not reset) - await expect(page.getByRole('row').nth(1).getByRole('cell').nth(0)).toHaveText('0.4 km'); - await expect(page.getByRole('row').nth(1).getByRole('cell').nth(1)).toHaveText('0:55.00'); - await expect(page.getByRole('row').nth(1).getByRole('cell').nth(3)).toHaveText('2:18 / km'); - await expect(page.getByRole('row').nth(2).getByRole('cell').nth(0)).toHaveText('800 m'); - await expect(page.getByRole('row').nth(2).getByRole('cell').nth(1)).toHaveText('2:00.00'); - await expect(page.getByRole('row').nth(2).getByRole('cell').nth(3)).toHaveText('2:43 / km'); - await expect(page.getByRole('row')).toHaveCount(3); - - // Switch target set - await page.getByLabel('Selected target set').selectOption('5K 1600m Splits'); - - // Assert times and paces are correct (input splits are not reset) - await expect(page.getByRole('row').nth(1).getByRole('cell').nth(1)).toHaveText('7:00.00'); - await expect(page.getByRole('row').nth(1).getByRole('cell').nth(3)).toHaveText('4:23 / km'); - await expect(page.getByRole('row').nth(2).getByRole('cell').nth(1)).toHaveText('13:30.00'); - await expect(page.getByRole('row').nth(2).getByRole('cell').nth(3)).toHaveText('4:04 / km'); - await expect(page.getByRole('row').nth(3).getByRole('cell').nth(1)).toHaveText('19:30.00'); - await expect(page.getByRole('row').nth(3).getByRole('cell').nth(3)).toHaveText('3:45 / km'); - await expect(page.getByRole('row').nth(4).getByRole('cell').nth(1)).toHaveText('20:00.00'); - await expect(page.getByRole('row').nth(4).getByRole('cell').nth(3)).toHaveText('2:30 / km'); - await expect(page.getByRole('row')).toHaveCount(5); - - // Delete custom target set - await page.getByLabel('Selected target set').selectOption('800m Splits'); - await page.getByRole('button', { name: 'Edit target set' }).click(); - await expect(page.getByLabel('Target set label')).toHaveValue('800m Splits'); - await page.getByRole('button', { name: 'Delete target set' }).click(); - - // Assert times and paces are correct (back to default target set) - await expect(page.getByRole('row').nth(1).getByRole('cell').nth(1)).toHaveText('7:00.00'); - await expect(page.getByRole('row').nth(1).getByRole('cell').nth(3)).toHaveText('4:23 / km'); - await expect(page.getByRole('row').nth(2).getByRole('cell').nth(1)).toHaveText('13:30.00'); - await expect(page.getByRole('row').nth(2).getByRole('cell').nth(3)).toHaveText('4:04 / km'); - await expect(page.getByRole('row').nth(3).getByRole('cell').nth(1)).toHaveText('19:30.00'); - await expect(page.getByRole('row').nth(3).getByRole('cell').nth(3)).toHaveText('3:45 / km'); - await expect(page.getByRole('row').nth(4).getByRole('cell').nth(1)).toHaveText('20:00.00'); - await expect(page.getByRole('row').nth(4).getByRole('cell').nth(3)).toHaveText('2:30 / km'); - await expect(page.getByRole('row')).toHaveCount(5); - - // Revert target set - await page.getByRole('button', { name: 'Edit target set' }).click(); - await expect(page.getByLabel('Target set label')).toHaveValue('5K 1600m Splits'); - await page.getByRole('button', { name: 'Revert target set' }).click(); - await page.getByRole('button', { name: 'Close' }).click(); - - // Assert times and paces are correct (split times are reverted) - await expect(page.getByRole('row').nth(1).getByRole('cell').nth(0)).toHaveText('1 mi'); - await expect(page.getByRole('row').nth(1).getByRole('cell').nth(1)).toHaveText('0:00.00'); - await expect(page.getByRole('row').nth(1).getByRole('cell').nth(3)).toHaveText('0:00 / km'); - await expect(page.getByRole('row').nth(2).getByRole('cell').nth(0)).toHaveText('2 mi'); - await expect(page.getByRole('row').nth(2).getByRole('cell').nth(1)).toHaveText('0:00.00'); - await expect(page.getByRole('row').nth(2).getByRole('cell').nth(3)).toHaveText('0:00 / km'); - await expect(page.getByRole('row').nth(3).getByRole('cell').nth(0)).toHaveText('5 km'); - await expect(page.getByRole('row').nth(3).getByRole('cell').nth(1)).toHaveText('0:00.00'); - await expect(page.getByRole('row').nth(3).getByRole('cell').nth(3)).toHaveText('0:00 / km'); - await expect(page.getByRole('row')).toHaveCount(4); - - // Assert title was reset - await page.getByRole('button', { name: 'Edit target set' }).click(); - await expect(page.getByLabel('Target set label')).toHaveValue('5K Mile Splits'); + // Assert outputs are still the same + { + // Assert times and paces are correct (custom targets, split times, and default units not reset) + await expect(page.getByRole('row').nth(1).getByRole('cell').nth(0)).toHaveText('0.4 km'); + await expect(page.getByRole('row').nth(1).getByRole('cell').nth(1)).toHaveText('0:55.00'); + await expect(page.getByRole('row').nth(1).getByRole('cell').nth(3)).toHaveText('2:18 / km'); + await expect(page.getByRole('row').nth(2).getByRole('cell').nth(0)).toHaveText('800 m'); + await expect(page.getByRole('row').nth(2).getByRole('cell').nth(1)).toHaveText('2:00.00'); + await expect(page.getByRole('row').nth(2).getByRole('cell').nth(3)).toHaveText('2:43 / km'); + await expect(page.getByRole('row')).toHaveCount(3); + + // Switch target set + await page.getByLabel('Selected target set').selectOption('5K 1600m Splits'); + + // Assert times and paces are correct (input splits are not reset) + await expect(page.getByRole('row').nth(1).getByRole('cell').nth(1)).toHaveText('7:00.00'); + await expect(page.getByRole('row').nth(1).getByRole('cell').nth(3)).toHaveText('4:23 / km'); + await expect(page.getByRole('row').nth(2).getByRole('cell').nth(1)).toHaveText('13:30.00'); + await expect(page.getByRole('row').nth(2).getByRole('cell').nth(3)).toHaveText('4:04 / km'); + await expect(page.getByRole('row').nth(3).getByRole('cell').nth(1)).toHaveText('19:30.00'); + await expect(page.getByRole('row').nth(3).getByRole('cell').nth(3)).toHaveText('3:45 / km'); + await expect(page.getByRole('row').nth(4).getByRole('cell').nth(1)).toHaveText('20:00.00'); + await expect(page.getByRole('row').nth(4).getByRole('cell').nth(3)).toHaveText('2:30 / km'); + await expect(page.getByRole('row')).toHaveCount(5); + } + + // Test target set deletion and reversion + { + // Delete custom target set + await page.getByLabel('Selected target set').selectOption('800m Splits'); + await page.getByRole('button', { name: 'Edit target set' }).click(); + await expect(page.getByLabel('Target set label')).toHaveValue('800m Splits'); + await page.getByRole('button', { name: 'Delete target set' }).click(); + + // Assert times and paces are correct (back to default target set) + await expect(page.getByRole('row').nth(1).getByRole('cell').nth(1)).toHaveText('7:00.00'); + await expect(page.getByRole('row').nth(1).getByRole('cell').nth(3)).toHaveText('4:23 / km'); + await expect(page.getByRole('row').nth(2).getByRole('cell').nth(1)).toHaveText('13:30.00'); + await expect(page.getByRole('row').nth(2).getByRole('cell').nth(3)).toHaveText('4:04 / km'); + await expect(page.getByRole('row').nth(3).getByRole('cell').nth(1)).toHaveText('19:30.00'); + await expect(page.getByRole('row').nth(3).getByRole('cell').nth(3)).toHaveText('3:45 / km'); + await expect(page.getByRole('row').nth(4).getByRole('cell').nth(1)).toHaveText('20:00.00'); + await expect(page.getByRole('row').nth(4).getByRole('cell').nth(3)).toHaveText('2:30 / km'); + await expect(page.getByRole('row')).toHaveCount(5); + + // Revert target set + await page.getByRole('button', { name: 'Edit target set' }).click(); + await expect(page.getByLabel('Target set label')).toHaveValue('5K 1600m Splits'); + await page.getByRole('button', { name: 'Revert target set' }).click(); + await page.getByRole('button', { name: 'Close' }).click(); + + // Assert times and paces are correct (split times are reverted) + await expect(page.getByRole('row').nth(1).getByRole('cell').nth(0)).toHaveText('1 mi'); + await expect(page.getByRole('row').nth(1).getByRole('cell').nth(1)).toHaveText('0:00.00'); + await expect(page.getByRole('row').nth(1).getByRole('cell').nth(3)).toHaveText('0:00 / km'); + await expect(page.getByRole('row').nth(2).getByRole('cell').nth(0)).toHaveText('2 mi'); + await expect(page.getByRole('row').nth(2).getByRole('cell').nth(1)).toHaveText('0:00.00'); + await expect(page.getByRole('row').nth(2).getByRole('cell').nth(3)).toHaveText('0:00 / km'); + await expect(page.getByRole('row').nth(3).getByRole('cell').nth(0)).toHaveText('5 km'); + await expect(page.getByRole('row').nth(3).getByRole('cell').nth(1)).toHaveText('0:00.00'); + await expect(page.getByRole('row').nth(3).getByRole('cell').nth(3)).toHaveText('0:00 / km'); + await expect(page.getByRole('row')).toHaveCount(4); + + // Assert title was reset + await page.getByRole('button', { name: 'Edit target set' }).click(); + await expect(page.getByLabel('Target set label')).toHaveValue('5K Mile Splits'); + } }); diff --git a/tests/e2e/unit-calculator.spec.js b/tests/e2e/unit-calculator.spec.js @@ -1,68 +1,72 @@ import { test, expect } from '@playwright/test'; test('Unit Calculator', async ({ page }) => { - // Structure: - // - Test distance unit conversion - // - Test speed and pace unit conversion - // - Test time unit conversion - // - Reload page - // - Assert distance inputs are still loaded - // - Assert time inputs are still loaded - // - Assert speed and pace inputs are still loaded - // Go to unit calculator await page.goto('/'); await page.getByRole('button', { name: 'Unit Calculator' }).click(); await expect(page).toHaveTitle('Unit Calculator - Running Tools'); - // Convert distance units (5000m to mi) - await page.getByLabel('Input units').selectOption('Meters'); - await page.getByLabel('Input value').fill('5000'); - await page.getByLabel('Output units').selectOption('Miles'); - await expect(page.getByLabel('Output value')).toHaveText('3.107'); + // Test distance unit conversion + { + // Convert distance units (5000m to mi) + await page.getByLabel('Input units').selectOption('Meters'); + await page.getByLabel('Input value').fill('5000'); + await page.getByLabel('Output units').selectOption('Miles'); + await expect(page.getByLabel('Output value')).toHaveText('3.107'); + } - // Convert speed and pace units (0:04:32/km to mph) - await page.getByLabel('Selected unit category').selectOption('Speed & Pace'); - await page.getByLabel('Input units').selectOption('Time per Kilometer'); - await page.getByLabel('Input time hours').fill('0'); - await page.getByLabel('Input time minutes').fill('4'); - await page.getByLabel('Input time seconds').fill('32'); - await page.getByLabel('Output units').selectOption('Miles per Hour'); - await expect(page.getByLabel('Output value')).toHaveText('8.224'); + // Test speed and pace unit conversion + { + // Convert speed and pace units (0:04:32/km to mph) + await page.getByLabel('Selected unit category').selectOption('Speed & Pace'); + await page.getByLabel('Input units').selectOption('Time per Kilometer'); + await page.getByLabel('Input time hours').fill('0'); + await page.getByLabel('Input time minutes').fill('4'); + await page.getByLabel('Input time seconds').fill('32'); + await page.getByLabel('Output units').selectOption('Miles per Hour'); + await expect(page.getByLabel('Output value')).toHaveText('8.224'); - // Convert speed and pace units (10 kph to time per mile) - await page.getByLabel('Input units').selectOption('Kilometers per Hour'); - await page.getByLabel('Input value').fill('10'); - await page.getByLabel('Output units').selectOption('Time per Mile'); - await expect(page.getByLabel('Output value')).toHaveText('00:09:39.366'); + // Convert speed and pace units (10 kph to time per mile) + await page.getByLabel('Input units').selectOption('Kilometers per Hour'); + await page.getByLabel('Input value').fill('10'); + await page.getByLabel('Output units').selectOption('Time per Mile'); + await expect(page.getByLabel('Output value')).toHaveText('00:09:39.364'); + } - // Convert time units (83.76 min to hh:mm:ss) - await page.getByLabel('Selected unit category').selectOption('Time'); - await page.getByLabel('Input units').selectOption('Minutes'); - await page.getByLabel('Input value').fill('83.76'); - await page.getByLabel('Output units').selectOption('hh:mm:ss'); - await expect(page.getByLabel('Output value')).toHaveText('01:23:45.600'); + // Test time unit conversion + { + // Convert time units (83.76 min to hh:mm:ss) + await page.getByLabel('Selected unit category').selectOption('Time'); + await page.getByLabel('Input units').selectOption('Minutes'); + await page.getByLabel('Input value').fill('83.76'); + await page.getByLabel('Output units').selectOption('hh:mm:ss'); + await expect(page.getByLabel('Output value')).toHaveText('01:23:45.600'); - // Convert time units (6:54:32.100 to seconds) - await page.getByLabel('Selected unit category').selectOption('Time'); - await page.getByLabel('Input units').selectOption('hh:mm:ss'); - await page.getByLabel('Input time hours').fill('6'); - await page.getByLabel('Input time minutes').fill('54'); - await page.getByLabel('Input time seconds').fill('32.1'); - await page.getByLabel('Output units').selectOption('seconds'); - await expect(page.getByLabel('Output value')).toHaveText('24872.100'); + // Convert time units (6:54:32.100 to seconds) + await page.getByLabel('Selected unit category').selectOption('Time'); + await page.getByLabel('Input units').selectOption('hh:mm:ss'); + await page.getByLabel('Input time hours').fill('6'); + await page.getByLabel('Input time minutes').fill('54'); + await page.getByLabel('Input time seconds').fill('32.1'); + await page.getByLabel('Output units').selectOption('seconds'); + await expect(page.getByLabel('Output value')).toHaveText('24872.100'); + } // Reload page await page.reload(); - // Assert distance result is correct (state not reset) - await expect(page.getByLabel('Output value')).toHaveText('3.107'); + // Assert inputs are still loaded + { + // Assert time result is correct (state not reset) + await expect(page.getByLabel('Selected unit category')).toHaveValue('time'); + await expect(page.getByLabel('Output value')).toHaveText('24872.100'); - // Assert time result is correct (state not reset) - await page.getByLabel('Selected unit category').selectOption('Time'); - await expect(page.getByLabel('Output value')).toHaveText('24872.100'); + // Assert distance result is correct (state not reset) + await page.getByLabel('Selected unit category').selectOption('Distance'); + await expect(page.getByLabel('Output value')).toHaveText('3.107'); - // Assert speed & pace result is correct (state not reset) - await page.getByLabel('Selected unit category').selectOption('Speed & Pace'); - await expect(page.getByLabel('Output value')).toHaveText('00:09:39.366'); + // Assert speed & pace result is correct (state not reset) + await page.getByLabel('Selected unit category').selectOption('Speed & Pace'); + await expect(page.getByLabel('Output value')).toHaveText('00:09:39.364'); + } }); diff --git a/tests/e2e/workout-calculator.spec.js b/tests/e2e/workout-calculator.spec.js @@ -1,143 +1,165 @@ import { test, expect } from '@playwright/test'; test('Workout Calculator', async ({ page }) => { - // Structure: - // - Test standard workout results - // - Test different prediction options - // - Test modified default target set - // - Test custom target set - // - Reload page - // - Assert outputs are still the same - // - Test target set deletion and reversion - // Go to workout calculator await page.goto('/'); await page.getByRole('button', { name: 'Workout Calculator' }).click(); await expect(page).toHaveTitle('Workout Calculator - Running Tools'); - // Enter input race (2 mi in 10:30) - await page.getByLabel('Input race distance value').fill('2'); - await page.getByLabel('Input race distance unit').selectOption('Miles'); - await page.getByLabel('Input race duration hours').fill('0'); - await page.getByLabel('Input race duration minutes').fill('10'); - await page.getByLabel('Input race duration seconds').fill('30'); - - // Assert workout splits are correct - await expect(page.getByRole('row').nth(1)).toHaveText('400 m @ 1 mi' + '1:13.45'); - await expect(page.getByRole('row').nth(3)).toHaveText('1600 m @ 1:00:00' + '5:45.43'); - await expect(page.getByRole('row')).toHaveCount(5); - - // Change prediction model - await page.getByText('Advanced Options').click(); - await page.getByLabel('Prediction model').selectOption('Riegel\'s Model'); - - // Assert workout splits are correct - await expect(page.getByRole('row').nth(1)).toHaveText('400 m @ 1 mi' + '1:15.10'); - await expect(page.getByRole('row').nth(3)).toHaveText('1600 m @ 1:00:00' + '5:45.64'); - await expect(page.getByRole('row')).toHaveCount(5); - - // Change Riegel exponent - await page.getByLabel('Riegel Exponent').fill('1.12'); - - // Assert workout splits are correct - await expect(page.getByRole('row').nth(1)).toHaveText('400 m @ 1 mi' + '1:12.04'); - await expect(page.getByRole('row').nth(3)).toHaveText('1600 m @ 1:00:00' + '6:17.47'); - await expect(page.getByRole('row')).toHaveCount(5); - - // Edit default target set - await page.getByRole('button', { name: 'Edit target set' }).click(); - await page.getByLabel('Target set label').fill('Less-common Workout Targets'); - await page.getByLabel('Split distance value').nth(0).fill('401'); - await page.getByLabel('Target distance value').nth(0).fill('2'); - await page.getByRole('button', { name: 'Add distance target' }).click(); - await page.getByLabel('Split distance value').last().fill('1'); - await page.getByLabel('Split distance unit').last().selectOption('Miles'); - await page.getByLabel('Target distance value').last().fill('10'); - await page.getByLabel('Target distance unit').last().selectOption('Kilometers'); - await page.getByRole('button', { name: 'Add time target' }).click(); - await page.getByLabel('Split distance value').last().fill('600'); - await page.getByLabel('Split distance unit').last().selectOption('Meters'); - await page.getByLabel('Target duration minutes').last().fill('19'); - await page.getByLabel('Target duration seconds').last().fill('0'); - await page.getByRole('button', { name: 'Close' }).click(); - - // Assert workout splits are correct - await expect(page.getByRole('row').nth(1)).toHaveText('401 m @ 2 mi' + '1:18.49'); - await expect(page.getByRole('row').nth(2)).toHaveText('600 m @ 19:00' + '2:05.14'); - await expect(page.getByRole('row').nth(4)).toHaveText('1 mi @ 10 km' + '6:00.90'); - await expect(page.getByRole('row')).toHaveCount(7); - - // Create custom target set - await page.getByLabel('Selected target set').selectOption('[ Create New Target Set ]'); - await expect(page.getByRole('row').nth(4)).toHaveText('There aren\'t any targets in this set yet.'); - await expect(page.getByRole('row')).toHaveCount(5); - - // Edit new target set - await expect(page.getByRole('row').nth(1)).toHaveText('There aren\'t any targets in this set yet.'); - await expect(page.getByLabel('Target set label')).toHaveValue('New target set'); - await page.getByLabel('Target set label').fill('Workout Target Set #2'); - await page.getByRole('button', { name: 'Add distance target' }).click(); - await page.getByLabel('Split distance value').last().fill('800'); - await page.getByLabel('Split distance unit').last().selectOption('Meters'); - await page.getByLabel('Target distance value').last().fill('5'); - await page.getByLabel('Target distance unit').last().selectOption('Kilometers'); - await page.getByRole('button', { name: 'Add distance target' }).click(); - await page.getByLabel('Split distance value').last().fill('1600'); - await page.getByLabel('Split distance unit').last().selectOption('Meters'); - await page.getByLabel('Target distance value').last().fill('10'); - await page.getByLabel('Target distance unit').last().selectOption('Kilometers'); - await page.getByRole('button', { name: 'Close' }).click(); - - // Assert workout splits are correct - await expect(page.getByRole('row').nth(1)).toHaveText('800 m @ 5 km' + '2:45.08'); - await expect(page.getByRole('row').nth(2)).toHaveText('1600 m @ 10 km' + '5:58.80'); - await expect(page.getByRole('row')).toHaveCount(3); + // Test standard workout results + { + // Enter input race (2 mi in 10:30) + await page.getByLabel('Input race distance value').fill('2'); + await page.getByLabel('Input race distance unit').selectOption('Miles'); + await page.getByLabel('Input race duration hours').fill('0'); + await page.getByLabel('Input race duration minutes').fill('10'); + await page.getByLabel('Input race duration seconds').fill('30'); + + // Assert workout splits are correct + await expect(page.getByRole('row').nth(1)).toHaveText('400 m @ 1 mi' + '1:13.45'); + await expect(page.getByRole('row').nth(3)).toHaveText('1600 m @ 1:00:00' + '5:45.44'); + await expect(page.getByRole('row')).toHaveCount(5); + } + + // Test different calculator options options + { + // Change prediction model + await page.getByText('Advanced Options').click(); + await page.getByLabel('Prediction model').selectOption('Riegel\'s Model'); + + // Assert workout splits are correct + await expect(page.getByRole('row').nth(1)).toHaveText('400 m @ 1 mi' + '1:15.10'); + await expect(page.getByRole('row').nth(3)).toHaveText('1600 m @ 1:00:00' + '5:45.64'); + await expect(page.getByRole('row')).toHaveCount(5); + + // Change Riegel exponent + await page.getByLabel('Riegel Exponent').fill('1.12'); + + // Assert workout splits are correct + await expect(page.getByRole('row').nth(1)).toHaveText('400 m @ 1 mi' + '1:12.04'); + await expect(page.getByRole('row').nth(3)).toHaveText('1600 m @ 1:00:00' + '6:17.47'); + await expect(page.getByRole('row')).toHaveCount(5); + } + + // Test modified default target set + { + // Edit default target set + await page.getByRole('button', { name: 'Edit target set' }).click(); + await page.getByLabel('Target set label').fill('Less-common Workout Targets'); + await expect(page.getByLabel('Custom target name')).toHaveCount(0); + await page.getByLabel('Split distance value').nth(0).fill('401'); + await page.getByLabel('Target distance value').nth(0).fill('2'); + await page.getByRole('button', { name: 'Add distance target' }).click(); + await page.getByLabel('Split distance value').last().fill('1'); + await page.getByLabel('Split distance unit').last().selectOption('Miles'); + await page.getByLabel('Target distance value').last().fill('10'); + await page.getByLabel('Target distance unit').last().selectOption('Kilometers'); + await page.getByRole('button', { name: 'Add time target' }).click(); + await page.getByLabel('Split distance value').last().fill('600'); + await page.getByLabel('Split distance unit').last().selectOption('Meters'); + await page.getByLabel('Target duration minutes').last().fill('19'); + await page.getByLabel('Target duration seconds').last().fill('0'); + await page.getByRole('button', { name: 'Add distance target' }).click(); + await page.getByLabel('Split distance value').last().fill('2'); + await page.getByLabel('Split distance unit').last().selectOption('Miles'); + await page.getByLabel('Target distance value').last().fill('2'); + await page.getByLabel('Target distance unit').last().selectOption('Miles'); + await page.getByRole('button', { name: 'Close' }).click(); + + // Assert workout splits are correct + await expect(page.getByRole('row').nth(1)).toHaveText('401 m @ 2 mi' + '1:18.49'); + await expect(page.getByRole('row').nth(2)).toHaveText('600 m @ 19:00' + '2:05.14'); + await expect(page.getByRole('row').nth(4)).toHaveText('1 mi @ 10 km' + '6:00.90'); + await expect(page.getByRole('row').nth(7)).toHaveText('2 mi' + '10:30.00'); + await expect(page.getByRole('row')).toHaveCount(8); + } + + // Test custom target set (with custom target names) + { + // Enable target name customization + await page.getByLabel('Workout name customization').selectOption('Enabled'); + + // Create custom target set + await page.getByLabel('Selected target set').selectOption('[ Create New Target Set ]'); + await expect(page.getByRole('row').nth(4)).toHaveText('There aren\'t any targets in this set yet.'); + await expect(page.getByRole('row')).toHaveCount(5); + + // Edit new target set + await expect(page.getByRole('row').nth(1)).toHaveText('There aren\'t any targets in this set yet.'); + await expect(page.getByLabel('Target set label')).toHaveValue('New target set'); + await page.getByLabel('Target set label').fill('Workout Target Set #2'); + await page.getByRole('button', { name: 'Add distance target' }).click(); + await page.getByLabel('Custom target name').last().fill('800m Interval'); + await page.getByLabel('Split distance value').last().fill('800'); + await page.getByLabel('Split distance unit').last().selectOption('Meters'); + await page.getByLabel('Target distance value').last().fill('5'); + await page.getByLabel('Target distance unit').last().selectOption('Kilometers'); + await page.getByRole('button', { name: 'Add distance target' }).click(); + await page.getByLabel('Split distance value').last().fill('1600'); + await page.getByLabel('Split distance unit').last().selectOption('Meters'); + await page.getByLabel('Target distance value').last().fill('10'); + await page.getByLabel('Target distance unit').last().selectOption('Kilometers'); + await page.getByRole('button', { name: 'Close' }).click(); + + // Assert workout splits are correct + await expect(page.getByRole('row').nth(1)).toHaveText('800m Interval' + '2:45.08'); + await expect(page.getByRole('row').nth(2)).toHaveText('1600 m @ 10 km' + '5:58.81'); + await expect(page.getByRole('row')).toHaveCount(3); + } // Reload page await page.reload(); - // Assert workout splits are correct (custom targets and model settings not reset) - await expect(page.getByRole('row').nth(1)).toHaveText('800 m @ 5 km' + '2:45.08'); - await expect(page.getByRole('row').nth(2)).toHaveText('1600 m @ 10 km' + '5:58.80'); - await expect(page.getByRole('row')).toHaveCount(3); - - // Switch target set - await page.getByText('Advanced Options').click(); - await page.getByLabel('Selected target set').selectOption('Less-common Workout Targets'); - - // Assert workout splits are correct - await expect(page.getByRole('row').nth(1)).toHaveText('401 m @ 2 mi' + '1:18.49'); - await expect(page.getByRole('row').nth(2)).toHaveText('600 m @ 19:00' + '2:05.14'); - await expect(page.getByRole('row').nth(4)).toHaveText('1 mi @ 10 km' + '6:00.90'); - await expect(page.getByRole('row')).toHaveCount(7); - - // Delete custom target set - await page.getByLabel('Selected target set').selectOption('Workout Target Set #2'); - await page.getByRole('button', { name: 'Edit target set' }).click(); - await expect(page.getByLabel('Target set label')).toHaveValue('Workout Target Set #2'); - await page.getByRole('button', { name: 'Delete target set' }).click(); - - // Switch to default target set - await page.getByLabel('Selected target set').selectOption('Less-common Workout Targets'); - - // Assert workout splits are correct (back to default target set) - await expect(page.getByRole('row').nth(1)).toHaveText('401 m @ 2 mi' + '1:18.49'); - await expect(page.getByRole('row').nth(2)).toHaveText('600 m @ 19:00' + '2:05.14'); - await expect(page.getByRole('row').nth(4)).toHaveText('1 mi @ 10 km' + '6:00.90'); - await expect(page.getByRole('row')).toHaveCount(7); - - // Revert target set - await page.getByRole('button', { name: 'Edit target set' }).click(); - await expect(page.getByLabel('Target set label')).toHaveValue('Less-common Workout Targets'); - await page.getByRole('button', { name: 'Revert target set' }).click(); - await page.getByRole('button', { name: 'Close' }).click(); - - // Assert paces are correct - await expect(page.getByRole('row').nth(1)).toHaveText('400 m @ 1 mi' + '1:12.04'); - await expect(page.getByRole('row').nth(3)).toHaveText('1600 m @ 1:00:00' + '6:17.47'); - await expect(page.getByRole('row')).toHaveCount(5); - - // Assert title was reset - await page.getByRole('button', { name: 'Edit target set' }).click(); - await expect(page.getByLabel('Target set label')).toHaveValue('Common Workout Targets'); + // Assert outputs are still the same + { + // Assert workout splits are correct (custom targets and model settings not reset) + await expect(page.getByRole('row').nth(1)).toHaveText('800m Interval' + '2:45.08'); + await expect(page.getByRole('row').nth(2)).toHaveText('1600 m @ 10 km' + '5:58.81'); + await expect(page.getByRole('row')).toHaveCount(3); + + // Switch target set + await page.getByText('Advanced Options').click(); + await page.getByLabel('Selected target set').selectOption('Less-common Workout Targets'); + + // Assert workout splits are correct + await expect(page.getByRole('row').nth(1)).toHaveText('401 m @ 2 mi' + '1:18.49'); + await expect(page.getByRole('row').nth(2)).toHaveText('600 m @ 19:00' + '2:05.14'); + await expect(page.getByRole('row').nth(4)).toHaveText('1 mi @ 10 km' + '6:00.90'); + await expect(page.getByRole('row').nth(7)).toHaveText('2 mi' + '10:30.00'); + await expect(page.getByRole('row')).toHaveCount(8); + } + + // Test target set deletion and reversion + { + // Delete custom target set + await page.getByLabel('Selected target set').selectOption('Workout Target Set #2'); + await page.getByRole('button', { name: 'Edit target set' }).click(); + await expect(page.getByLabel('Target set label')).toHaveValue('Workout Target Set #2'); + await page.getByRole('button', { name: 'Delete target set' }).click(); + + // Switch to default target set + await page.getByLabel('Selected target set').selectOption('Less-common Workout Targets'); + + // Assert workout splits are correct (back to default target set) + await expect(page.getByRole('row').nth(1)).toHaveText('401 m @ 2 mi' + '1:18.49'); + await expect(page.getByRole('row').nth(2)).toHaveText('600 m @ 19:00' + '2:05.14'); + await expect(page.getByRole('row').nth(4)).toHaveText('1 mi @ 10 km' + '6:00.90'); + await expect(page.getByRole('row').nth(7)).toHaveText('2 mi' + '10:30.00'); + await expect(page.getByRole('row')).toHaveCount(8); + + // Revert target set + await page.getByRole('button', { name: 'Edit target set' }).click(); + await expect(page.getByLabel('Target set label')).toHaveValue('Less-common Workout Targets'); + await page.getByRole('button', { name: 'Revert target set' }).click(); + await page.getByRole('button', { name: 'Close' }).click(); + + // Assert paces are correct + await expect(page.getByRole('row').nth(1)).toHaveText('400 m @ 1 mi' + '1:12.04'); + await expect(page.getByRole('row').nth(3)).toHaveText('1600 m @ 1:00:00' + '6:17.47'); + await expect(page.getByRole('row')).toHaveCount(5); + + // Assert title was reset + await page.getByRole('button', { name: 'Edit target set' }).click(); + await expect(page.getByLabel('Target set label')).toHaveValue('Common Workout Targets'); + } }); diff --git a/tests/unit/components/AdvancedOptionsInput.spec.js b/tests/unit/components/AdvancedOptionsInput.spec.js @@ -0,0 +1,556 @@ +import { test, expect } from 'vitest'; +import { shallowMount } from '@vue/test-utils'; +import AdvancedOptionsInput from '@/components/AdvancedOptionsInput.vue'; + +test('should be correctly render pace options according to props', () => { + // Initialize component + const wrapper = shallowMount(AdvancedOptionsInput, { + propsData: { + globalOptions: { + defaultUnitSystem: 'metric', + racePredictionOptions: { + model: 'CameronModel', + riegelExponent: 1.30, + }, + }, + options: { + input: { + distanceValue: 5, + distanceUnit: 'kilometers', + time: 1200, + }, + selectedTargetSet: 'B', + }, + targetSets: { + 'A': { + name: '1st target set', + targets: [ + { type: 'distance', distanceValue: 1, distanceUnit: 'miles' }, + { type: 'distance', distanceValue: 2, distanceUnit: 'miles' }, + { type: 'distance', distanceValue: 3, distanceUnit: 'miles' }, + ], + }, + 'B': { + name: '2nd target set', + targets: [ + { type: 'distance', distanceValue: 1, distanceUnit: 'kilometers' }, + { type: 'distance', distanceValue: 5, distanceUnit: 'kilometers' }, + { type: 'distance', distanceValue: 10, distanceUnit: 'kilometers' }, + ], + }, + }, + type: 'pace', + }, + }); + + // Assert all input fields are correct + expect(wrapper.find('select[aria-label="Default units"]').element.value).to.equal('metric'); + expect(wrapper.findComponent({ name: 'target-set-selector' }).vm.selectedTargetSet).to + .equal('B'); + expect(wrapper.findComponent({ name: 'target-set-selector' }).vm.targetSets).to.deep.equal({ + 'A': { + name: '1st target set', + targets: [ + { type: 'distance', distanceValue: 1, distanceUnit: 'miles' }, + { type: 'distance', distanceValue: 2, distanceUnit: 'miles' }, + { type: 'distance', distanceValue: 3, distanceUnit: 'miles' }, + ], + }, + 'B': { + name: '2nd target set', + targets: [ + { type: 'distance', distanceValue: 1, distanceUnit: 'kilometers' }, + { type: 'distance', distanceValue: 5, distanceUnit: 'kilometers' }, + { type: 'distance', distanceValue: 10, distanceUnit: 'kilometers' }, + ], + }, + }); + expect(wrapper.findAll('select[aria-label="Workout name customization"]')).to.have + .length(0); + expect(wrapper.findAll('input[aria-label="Batch column label"]')).to.have + .length(0); + expect(wrapper.findAll('select[aria-label="Prediction model"]')).to.have.length(0); + expect(wrapper.findAllComponents({ name: 'decimal-input' })).to.have.length(0); +}); + +test('should be correctly render race options according to props', () => { + // Initialize component + const wrapper = shallowMount(AdvancedOptionsInput, { + propsData: { + globalOptions: { + defaultUnitSystem: 'metric', + racePredictionOptions: { + model: 'PurdyPointsModel', + riegelExponent: 1.2, + }, + }, + options: { + input: { + distanceValue: 5, + distanceUnit: 'kilometers', + time: 1200, + }, + selectedTargetSet: '_new', + }, + type: 'race', + targetSets: {}, + }, + }); + + // Assert input fields are correct + expect(wrapper.find('select[aria-label="Default units"]').element.value).to + .equal('metric'); + expect(wrapper.findComponent({ name: 'target-set-selector' }).vm.selectedTargetSet).to + .equal('_new'); + expect(wrapper.findAll('select[aria-label="Workout name customization"]')).to.have + .length(0); + expect(wrapper.findAll('input[aria-label="Batch column label"]')).to.have + .length(0); + expect(wrapper.find('select[aria-label="Prediction model"]').element.value).to + .equal('PurdyPointsModel'); + expect(wrapper.findComponent({ name: 'decimal-input' }).vm.modelValue).to.equal(1.2); + expect(wrapper.findComponent({ name: 'decimal-input' }).isVisible()).to.equal(false); +}); + +test('should render riegel exponent field only for supported race prediction models', async () => { + // Initialize component + const wrapper = shallowMount(AdvancedOptionsInput, { + propsData: { + globalOptions: { + defaultUnitSystem: 'metric', + racePredictionOptions: { + model: 'AverageModel', + riegelExponent: 1.2, + }, + }, + options: { + input: { + distanceValue: 5, + distanceUnit: 'kilometers', + time: 1200, + }, + selectedTargetSet: '_new', + }, + type: 'race', + targetSets: {}, + }, + attachTo: document.body, + }); + + // Assert field is visible for Average model + expect(wrapper.findComponent({ name: 'decimal-input' }).isVisible()).to.equal(true); + + // Assert field is not visible for Purdy Points model + await wrapper.find('select[aria-label="Prediction model"]').setValue('PurdyPointsModel'); + expect(wrapper.findComponent({ name: 'decimal-input' }).isVisible()).to.equal(false); + + // Assert field is not visible for VO2 Max model + await wrapper.find('select[aria-label="Prediction model"]').setValue('VO2MaxModel'); + expect(wrapper.findComponent({ name: 'decimal-input' }).isVisible()).to.equal(false); + + // Assert field is not visible for Cameron model + await wrapper.find('select[aria-label="Prediction model"]').setValue('CameronModel'); + expect(wrapper.findComponent({ name: 'decimal-input' }).isVisible()).to.equal(false); + + // Assert field is not visible for Riegel model + await wrapper.find('select[aria-label="Prediction model"]').setValue('RiegelModel'); + expect(wrapper.findComponent({ name: 'decimal-input' }).isVisible()).to.equal(true); +}); + +test('should be correctly render split options according to props', () => { + // Initialize component + const wrapper = shallowMount(AdvancedOptionsInput, { + propsData: { + globalOptions: { + defaultUnitSystem: 'metric', + racePredictionOptions: { + model: 'CameronModel', + riegelExponent: 1.30, + }, + }, + options: { + selectedTargetSet: '_new', + }, + targetSets: {}, + type: 'split', + }, + }); + + // Assert input fields are correct + expect(wrapper.find('select[aria-label="Default units"]').element.value).to + .equal('metric'); + expect(wrapper.findComponent({ name: 'target-set-selector' }).vm.selectedTargetSet).to + .equal('_new'); + expect(wrapper.findAll('select[aria-label="Workout name customization"]')).to.have + .length(0); + expect(wrapper.findAll('input[aria-label="Batch column label"]')).to.have + .length(0); + expect(wrapper.findAll('select[aria-label="Prediction model"]')).to.have.length(0); + expect(wrapper.findAllComponents({ name: 'decimal-input' })).to.have.length(0); +}); + +test('should be correctly render workout options according to props', () => { + // Initialize component + const wrapper = shallowMount(AdvancedOptionsInput, { + propsData: { + globalOptions: { + defaultUnitSystem: 'metric', + racePredictionOptions: { + model: 'PurdyPointsModel', + riegelExponent: 1.2, + }, + }, + options: { + customTargetNames: true, + input: { + distanceValue: 5, + distanceUnit: 'kilometers', + time: 1200, + }, + selectedTargetSet: '_new', + }, + targetSets: {}, + type: 'workout', + }, + }); + + // Assert input fields are correct + expect(wrapper.find('select[aria-label="Default units"]').element.value).to.equal('metric'); + expect(wrapper.findComponent({ name: 'target-set-selector' }).vm.selectedTargetSet).to + .equal('_new'); + expect(wrapper.find('select[aria-label="Workout name customization"]').element.value).to + .equal('true'); + expect(wrapper.findAll('input[aria-label="Batch column label"]')).to.have + .length(0); + expect(wrapper.find('select[aria-label="Prediction model"]').element.value).to + .equal('PurdyPointsModel'); + expect(wrapper.findComponent({ name: 'decimal-input' }).vm.modelValue).to.equal(1.2); +}); + +test('should only show batch column label field when applicable', async () => { + // Initialize component with workout target name customization enabled + const wrapper = shallowMount(AdvancedOptionsInput, { + propsData: { + globalOptions: { + defaultUnitSystem: 'metric', + racePredictionOptions: { + model: 'CameronModel', + riegelExponent: 1.30, + }, + }, + options: { + customTargetNames: true, + input: { + distanceValue: 5, + distanceUnit: 'kilometers', + time: 1200, + }, + selectedTargetSet: '_new', + }, + targetSets: {}, + type: 'workout', + }, + attachTo: document.body, + }); + + // Assert batch column label field is hidden + expect(wrapper.findAll('input[aria-label="Batch column label"]')).to.have + .length(0); + + // Add batchOptions but disable workout target name customization + await wrapper.setProps({ + batchOptions: { // added + calculator: 'workout', + increment: 32, + input: { + distanceValue: 2, + distanceUnit: 'miles', + time: 600, + }, + label: 'foo', + rows: 15, + }, + globalOptions: { + defaultUnitSystem: 'metric', + racePredictionOptions: { + model: 'CameronModel', + riegelExponent: 1.30, + }, + }, + options: { + customTargetNames: false, // disabled + input: { + distanceValue: 5, + distanceUnit: 'kilometers', + time: 1200, + }, + selectedTargetSet: '_new', + }, + targetSets: {}, + type: 'workout', + }); + + // Assert batch column label field is still hidden + expect(wrapper.find('input[aria-label="Batch column label"]').isVisible()).to.equal(false); + + // Enable workout target name customization + await wrapper.setProps({ + batchOptions: { + calculator: 'workout', + increment: 32, + input: { + distanceValue: 2, + distanceUnit: 'miles', + time: 600, + }, + label: 'foo', + rows: 15, + }, + globalOptions: { + defaultUnitSystem: 'metric', + racePredictionOptions: { + model: 'CameronModel', + riegelExponent: 1.30, + }, + }, + options: { + customTargetNames: true, // enabled + input: { + distanceValue: 5, + distanceUnit: 'kilometers', + time: 1200, + }, + selectedTargetSet: '_new', + }, + targetSets: {}, + type: 'workout', + }); + + // Assert batch column label field is now visible + expect(wrapper.find('input[aria-label="Batch column label"]').isVisible()).to.equal(true); + expect(wrapper.find('input[aria-label="Batch column label"]').element.placeholder).to.equal('2 mi') + expect(wrapper.find('input[aria-label="Batch column label"]').element.value).to.equal('foo') + + // Switch to race calculator + await wrapper.setProps({ + batchOptions: { + calculator: 'workout', + increment: 32, + input: { + distanceValue: 2, + distanceUnit: 'miles', + time: 600, + }, + label: 'foo', + rows: 15, + }, + globalOptions: { + defaultUnitSystem: 'metric', + racePredictionOptions: { + model: 'CameronModel', + riegelExponent: 1.30, + }, + }, + options: { + input: { + distanceValue: 5, + distanceUnit: 'kilometers', + time: 1200, + }, + selectedTargetSet: '_new', + }, + targetSets: {}, + type: 'race', // changed + }); + + // Assert batch column label field is hidden again + expect(wrapper.findAll('input[aria-label="Batch column label"]')).to.have + .length(0); +}); + +test('should pass correct props to TargetSetSelector', async () => { + // Initialize component + const wrapper = shallowMount(AdvancedOptionsInput, { + propsData: { + globalOptions: { + defaultUnitSystem: 'metric', + racePredictionOptions: { + model: 'CameronModel', + riegelExponent: 1.30, + }, + }, + options: { + customTargetNames: false, + input: { + distanceValue: 5, + distanceUnit: 'kilometers', + time: 1200, + }, + selectedTargetSet: 'B', + }, + targetSets: { + 'A': { + name: '1st target set v2', + targets: [ + { type: 'distance', distanceValue: 1, distanceUnit: 'miles' }, + { type: 'distance', distanceValue: 2, distanceUnit: 'miles' }, + { type: 'distance', distanceValue: 3, distanceUnit: 'miles' }, + ], + }, + 'B': { + name: '2nd target set', + targets: [ + { type: 'distance', distanceValue: 1, distanceUnit: 'kilometers' }, + { type: 'distance', distanceValue: 5, distanceUnit: 'kilometers' }, + { type: 'distance', distanceValue: 10, distanceUnit: 'kilometers' }, + ], + }, + }, + type: 'workout', + }, + }); + + // Assert props are correct + expect(wrapper.findComponent({ name: 'target-set-selector' }).vm.selectedTargetSet).to.equal('B'); + expect(wrapper.findComponent({ name: 'target-set-selector' }).vm.targetSets).to.deep.equal({ + 'A': { + name: '1st target set v2', + targets: [ + { type: 'distance', distanceValue: 1, distanceUnit: 'miles' }, + { type: 'distance', distanceValue: 2, distanceUnit: 'miles' }, + { type: 'distance', distanceValue: 3, distanceUnit: 'miles' }, + ], + }, + 'B': { + name: '2nd target set', + targets: [ + { type: 'distance', distanceValue: 1, distanceUnit: 'kilometers' }, + { type: 'distance', distanceValue: 5, distanceUnit: 'kilometers' }, + { type: 'distance', distanceValue: 10, distanceUnit: 'kilometers' }, + ], + }, + }); + expect(wrapper.findComponent({ name: 'target-set-selector' }).vm.customWorkoutNames) + .to.equal(false); + + // Update options + await wrapper.find('select[aria-label="Workout name customization"]').setValue('true'); + + // Assert props are updated + expect(wrapper.findComponent({ name: 'target-set-selector' }).vm.customWorkoutNames) + .to.equal(true); +}); + +test('should emit input events when options are modified', async () => { + // Initialize component + const wrapper = shallowMount(AdvancedOptionsInput, { + propsData: { + globalOptions: { + defaultUnitSystem: 'metric', + racePredictionOptions: { + model: 'AverageModel', + riegelExponent: 1.06, + }, + }, + options: { + customTargetNames: false, + input: { + distanceValue: 5, + distanceUnit: 'kilometers', + time: 1200, + }, + selectedTargetSet: '_new', + }, + targetSets: {}, + type: 'workout', + }, + }); + + // Update options + await wrapper.find('select[aria-label="Default units"]').setValue('imperial'); + await wrapper.findComponent({ name: 'target-set-selector' }).setValue({ + 'A': { + name: '1st target set v2', + targets: [ + { type: 'distance', distanceValue: 1, distanceUnit: 'miles' }, + { type: 'distance', distanceValue: 2, distanceUnit: 'miles' }, + { type: 'distance', distanceValue: 3, distanceUnit: 'miles' }, + ], + }, + 'B': { + name: '2nd target set', + targets: [ + { type: 'distance', distanceValue: 1, distanceUnit: 'kilometers' }, + { type: 'distance', distanceValue: 5, distanceUnit: 'kilometers' }, + { type: 'distance', distanceValue: 10, distanceUnit: 'kilometers' }, + ], + }, + }, 'targetSets'); + await wrapper.findComponent({ name: 'target-set-selector' }).setValue('B', 'selectedTargetSet'); + await wrapper.find('select[aria-label="Workout name customization"]').setValue('true'); + await wrapper.find('select[aria-label="Prediction model"]').setValue('CameronModel'); + await wrapper.findComponent({ name: 'decimal-input' }).setValue(1.3); + + // Assert correct update events emitted + expect(wrapper.emitted()['update:globalOptions']).to.deep.equal([ + [{ + defaultUnitSystem: 'imperial', + racePredictionOptions: { + model: 'AverageModel', + riegelExponent: 1.06, + }, + }], + [{ + defaultUnitSystem: 'imperial', + racePredictionOptions: { + model: 'CameronModel', + riegelExponent: 1.06, + }, + }], + [{ + defaultUnitSystem: 'imperial', + racePredictionOptions: { + model: 'CameronModel', + riegelExponent: 1.3, + }, + }], + ]); + expect(wrapper.emitted()['update:targetSets']).to.deep.equal([[{ + 'A': { + name: '1st target set v2', + targets: [ + { type: 'distance', distanceValue: 1, distanceUnit: 'miles' }, + { type: 'distance', distanceValue: 2, distanceUnit: 'miles' }, + { type: 'distance', distanceValue: 3, distanceUnit: 'miles' }, + ], + }, + 'B': { + name: '2nd target set', + targets: [ + { type: 'distance', distanceValue: 1, distanceUnit: 'kilometers' }, + { type: 'distance', distanceValue: 5, distanceUnit: 'kilometers' }, + { type: 'distance', distanceValue: 10, distanceUnit: 'kilometers' }, + ], + }, + }]]); + expect(wrapper.emitted()['update:options']).to.deep.equal([ + [{ + customTargetNames: false, + input: { + distanceValue: 5, + distanceUnit: 'kilometers', + time: 1200, + }, + selectedTargetSet: 'B', + }], + [{ + customTargetNames: true, + input: { + distanceValue: 5, + distanceUnit: 'kilometers', + time: 1200, + }, + selectedTargetSet: 'B', + }], + ]); +}); diff --git a/tests/unit/components/DoubleOutputTable.spec.js b/tests/unit/components/DoubleOutputTable.spec.js @@ -34,12 +34,13 @@ test('should correctly render table body rows and headers', () => { distanceUnit: 'miles', distanceValue: 2, }, + label: 'foo', }, }); // Assert headers are correctly generated from first row of results const headers = wrapper.findAll('th'); - expect(headers[0].element.textContent).to.equal('2 mi'); + expect(headers[0].element.textContent).to.equal('foo'); expect(headers[1].element.textContent).to.equal('key1'); expect(headers[2].element.textContent).to.equal('key2'); expect(headers[3].element.textContent).to.equal('key3'); @@ -80,12 +81,13 @@ test('Should display message when inputs are empty', () => { distanceUnit: 'miles', distanceValue: 2, }, + label: 'foo', }, }); // Assert headers are correctly generated const headers = wrapper.findAll('th'); - expect(headers[0].element.textContent).to.equal('2 mi'); + expect(headers[0].element.textContent).to.equal('foo'); expect(headers.length).to.equal(1); // Assert results are correctly rendered diff --git a/tests/unit/components/PaceInput.spec.js b/tests/unit/components/PaceInput.spec.js @@ -1,7 +1,26 @@ import { test, expect } from 'vitest'; -import { shallowMount } from '@vue/test-utils'; +import { mount, shallowMount } from '@vue/test-utils'; import PaceInput from '@/components/PaceInput.vue'; +test('should correctly render input label', () => { + // Initialize component + const wrapper = mount(PaceInput, { + propsData: { + modelValue: { + distanceValue: 3, + distanceUnit: 'miles', + time: 1000, + }, + label: 'My input', + }, + }); + + // Assert input fields are correct + expect(wrapper.findAll('input')[0].element.ariaLabel).to.equal('My input distance value'); + expect(wrapper.find('select').element.ariaLabel).to.equal('My input distance unit'); + expect(wrapper.findComponent({ name: 'time-input' }).vm.label).to.equal('My input duration'); +}); + test('should be initialized to modelValue', () => { // Initialize component const wrapper = shallowMount(PaceInput, { @@ -20,31 +39,60 @@ test('should be initialized to modelValue', () => { expect(wrapper.findComponent({ name: 'time-input' }).vm.modelValue).to.equal(1000); }); -test('should update modelValue when inputs are modified', async () => { +test('should emit event when inputs are modified', async () => { // Initialize component - const wrapper = shallowMount(PaceInput); + const wrapper = shallowMount(PaceInput, { + propsData: { + modelValue: { + distanceValue: 5, + distanceUnit: 'kilometers', + time: 1200, + }, + }, + }); // Update distance value await wrapper.findComponent({ name: 'decimal-input' }).setValue(3); - expect(wrapper.vm.modelValue).to.deep.equal({ - distanceValue: 3, - distanceUnit: 'kilometers', - time: 1200, - }); + expect(wrapper.emitted()['update:modelValue']).to.deep.equal([ + [{ + distanceValue: 3, + distanceUnit: 'kilometers', + time: 1200, + }], + ]); // Update distance unit await wrapper.find('select').setValue('miles'); - expect(wrapper.vm.modelValue).to.deep.equal({ - distanceValue: 3, - distanceUnit: 'miles', - time: 1200, - }); + expect(wrapper.emitted()['update:modelValue']).to.deep.equal([ + [{ + distanceValue: 3, + distanceUnit: 'kilometers', + time: 1200, + }], + [{ + distanceValue: 3, + distanceUnit: 'miles', + time: 1200, + }], + ]); // Update time await wrapper.findComponent({ name: 'time-input' }).setValue(1000); - expect(wrapper.vm.modelValue).to.deep.equal({ - distanceValue: 3, - distanceUnit: 'miles', - time: 1000, - }); + expect(wrapper.emitted()['update:modelValue']).to.deep.equal([ + [{ + distanceValue: 3, + distanceUnit: 'kilometers', + time: 1200, + }], + [{ + distanceValue: 3, + distanceUnit: 'miles', + time: 1200, + }], + [{ + distanceValue: 3, + distanceUnit: 'miles', + time: 1000, + }], + ]); }); diff --git a/tests/unit/components/RaceOptions.spec.js b/tests/unit/components/RaceOptions.spec.js @@ -1,38 +0,0 @@ -import { test, expect } from 'vitest'; -import { shallowMount } from '@vue/test-utils'; -import RaceOptions from '@/components/RaceOptions.vue'; - -test('should be initialized to modelValue', () => { - // Initialize component - const wrapper = shallowMount(RaceOptions, { - propsData: { - modelValue: { - model: 'PurdyPointsModel', - riegelExponent: 1.2, - } - }, - }); - - // Assert input fields are correct - expect(wrapper.find('select').element.value).to.equal('PurdyPointsModel'); - expect(wrapper.findComponent({ name: 'decimal-input' }).vm.modelValue).to.equal(1.2); -}); - -test('should update modelValue when inputs are modified', async () => { - // Initialize component - const wrapper = shallowMount(RaceOptions); - - // Update model - await wrapper.find('select').setValue('CameronModel'); - expect(wrapper.vm.modelValue).to.deep.equal({ - model: 'CameronModel', - riegelExponent: 1.06, - }); - - // Update Riegel exponent - await wrapper.findComponent({ name: 'decimal-input' }).setValue(1.3); - expect(wrapper.vm.modelValue).to.deep.equal({ - model: 'CameronModel', - riegelExponent: 1.3, - }); -}); diff --git a/tests/unit/components/SplitOutputTable.spec.js b/tests/unit/components/SplitOutputTable.spec.js @@ -103,7 +103,7 @@ test('should correctly calculate paces and cumulative times from entered split t expect(rows.length).to.equal(3); }); -test('should correctly update modelValue with split times', async () => { +test('should emit update event when split times are changed', async () => { // Initialize component const wrapper = shallowMount(SplitOutputTable, { propsData: { @@ -119,11 +119,18 @@ test('should correctly update modelValue with split times', async () => { await wrapper.findAllComponents({ name: 'time-input' })[1].setValue(190); await wrapper.findAllComponents({ name: 'time-input' })[2].setValue(200); - // Assert modelValue correctly updated - expect(wrapper.vm.modelValue).to.deep.equal([ + // Assert update events correctly emitted + expect(wrapper.emitted()['update:modelValue']).to.deep.equal([ + [[ + { result: 'time', distanceValue: 1, distanceUnit: 'kilometers', splitTime: 180 }, + { result: 'time', distanceValue: 2, distanceUnit: 'kilometers', splitTime: 190 }, + { result: 'time', distanceValue: 3000, distanceUnit: 'meters', splitTime: 180 }, + ]], + [[ { result: 'time', distanceValue: 1, distanceUnit: 'kilometers', splitTime: 180 }, { result: 'time', distanceValue: 2, distanceUnit: 'kilometers', splitTime: 190 }, { result: 'time', distanceValue: 3000, distanceUnit: 'meters', splitTime: 200 }, + ]], ]); }); diff --git a/tests/unit/components/TargetEditor.spec.js b/tests/unit/components/TargetEditor.spec.js @@ -15,16 +15,20 @@ test('should correctly render standard target set', async () => { ], }, setType: 'standard', + customWorkoutNames: true, // name input should not be rendered }, }); // Assert target set correctly rendered expect(wrapper.find('input').element.value).to.equal('My target set'); const rows = wrapper.findAll('tbody tr'); + expect(rows[0].findAll('input').length).to.equal(0); expect(rows[0].findComponent({ name: 'decimal-input' }).vm.modelValue).to.equal(1.61); expect(rows[0].find('select').element.value).to.equal('kilometers'); + expect(rows[1].findAll('input').length).to.equal(0); expect(rows[1].findComponent({ name: 'decimal-input' }).vm.modelValue).to.equal(3.11); expect(rows[1].find('select').element.value).to.equal('miles'); + expect(rows[2].findAll('input').length).to.equal(0); expect(rows[2].findComponent({ name: 'time-input' }).vm.modelValue).to.equal(600); expect(rows.length).to.equal(3); }); @@ -47,14 +51,16 @@ test('should correctly render split target set', async () => { // Assert target set correctly rendered expect(wrapper.find('input').element.value).to.equal('My target set'); const rows = wrapper.findAll('tbody tr'); + expect(rows[0].findAll('input').length).to.equal(0); expect(rows[0].findComponent({ name: 'decimal-input' }).vm.modelValue).to.equal(1.61); expect(rows[0].find('select').element.value).to.equal('kilometers'); + expect(rows[1].findAll('input').length).to.equal(0); expect(rows[1].findComponent({ name: 'decimal-input' }).vm.modelValue).to.equal(3.11); expect(rows[1].find('select').element.value).to.equal('miles'); expect(rows.length).to.equal(2); }); -test('should correctly render workout target set', async () => { +test('should correctly render workout target set without custom names', async () => { // Initialize component const wrapper = shallowMount(TargetEditor, { propsData: { @@ -85,13 +91,71 @@ test('should correctly render workout target set', async () => { // Assert target set correctly rendered expect(wrapper.find('input').element.value).to.equal('My target set'); const rows = wrapper.findAll('tbody tr'); + expect(rows[0].findAll('input').length).to.equal(0); expect(rows[0].findAllComponents({ name: 'decimal-input' })[0].vm.modelValue).to.equal(400); expect(rows[0].findAll('select')[0].element.value).to.equal('meters'); expect(rows[0].findAllComponents({ name: 'decimal-input' })[1].vm.modelValue).to.equal(2); expect(rows[0].findAll('select')[1].element.value).to.equal('miles'); + expect(rows[1].findAll('input').length).to.equal(0); expect(rows[1].findComponent({ name: 'decimal-input' }).vm.modelValue).to.equal(2); expect(rows[1].find('select').element.value).to.equal('kilometers'); expect(rows[1].findComponent({ name: 'time-input' }).vm.modelValue).to.equal(6000); + expect(rows[2].findAll('input').length).to.equal(0); + expect(rows[2].findAllComponents({ name: 'decimal-input' })[0].vm.modelValue).to.equal(1); + expect(rows[2].findAll('select')[0].element.value).to.equal('miles'); + expect(rows[2].findAllComponents({ name: 'decimal-input' })[1].vm.modelValue).to.equal(5); + expect(rows[2].findAll('select')[1].element.value).to.equal('kilometers'); + expect(rows.length).to.equal(3); +}); + +test('should correctly render workout target set with custom names', async () => { + // Initialize component + const wrapper = shallowMount(TargetEditor, { + propsData: { + modelValue: { + name: 'My target set', + targets: [ + { + // customName is undefined + distanceUnit: 'miles', distanceValue: 2, + splitUnit: 'meters', splitValue: 400, + type: 'distance', + }, + { + customName: '', + time: 6000, + splitUnit: 'kilometers', splitValue: 2, + type: 'time', + }, + { + customName: 'my custom name', + distanceUnit: 'kilometers', distanceValue: 5, + splitUnit: 'miles', splitValue: 1, + type: 'distance' + }, + ], + }, + setType: 'workout', + customWorkoutNames: true, + }, + }); + + // Assert target set correctly rendered + expect(wrapper.find('input').element.value).to.equal('My target set'); + const rows = wrapper.findAll('tbody tr'); + expect(rows[0].find('input').element.value).to.equal(''); + expect(rows[0].find('input').element.placeholder).to.equal('400 m @ 2 mi'); + expect(rows[0].findAllComponents({ name: 'decimal-input' })[0].vm.modelValue).to.equal(400); + expect(rows[0].findAll('select')[0].element.value).to.equal('meters'); + expect(rows[0].findAllComponents({ name: 'decimal-input' })[1].vm.modelValue).to.equal(2); + expect(rows[0].findAll('select')[1].element.value).to.equal('miles'); + expect(rows[1].find('input').element.value).to.equal(''); + expect(rows[1].find('input').element.placeholder).to.equal('2 km @ 1:40:00'); + expect(rows[1].findComponent({ name: 'decimal-input' }).vm.modelValue).to.equal(2); + expect(rows[1].find('select').element.value).to.equal('kilometers'); + expect(rows[1].findComponent({ name: 'time-input' }).vm.modelValue).to.equal(6000); + expect(rows[2].find('input').element.value).to.equal('my custom name'); + expect(rows[2].find('input').element.placeholder).to.equal('1 mi @ 5 km'); expect(rows[2].findAllComponents({ name: 'decimal-input' })[0].vm.modelValue).to.equal(1); expect(rows[2].findAll('select')[0].element.value).to.equal('miles'); expect(rows[2].findAllComponents({ name: 'decimal-input' })[1].vm.modelValue).to.equal(5); @@ -101,7 +165,14 @@ test('should correctly render workout target set', async () => { test('revert button should emit revert event', async () => { // Initialize component - const wrapper = shallowMount(TargetEditor); + const wrapper = shallowMount(TargetEditor, { + propsData: { + modelValue: { + name: 'My target set', + targets: [], + }, + }, + }); // Click revert button await wrapper.find('button[title="Revert target set"]').trigger('click'); @@ -114,8 +185,12 @@ test('delete button should emit revert event', async () => { // Initialize component const wrapper = shallowMount(TargetEditor, { propsData: { + modelValue: { + name: 'My target set', + targets: [], + }, isCustomSet: true, - } + }, }); // Click delete button @@ -127,7 +202,14 @@ test('delete button should emit revert event', async () => { test('close button should emit close event', async () => { // Initialize component - const wrapper = shallowMount(TargetEditor); + const wrapper = shallowMount(TargetEditor, { + propsData: { + modelValue: { + name: 'My target set', + targets: [], + }, + }, + }); // Call close method await wrapper.find('button[title="Close"]').trigger('click'); @@ -148,7 +230,7 @@ test('add distance target button should correctly add standard imperial distance ], }, setType: 'standard', - defaultUnitSystem: 'imperial' + defaultUnitSystem: 'imperial', }, }); @@ -180,7 +262,7 @@ test('add distance target button should correctly add standard metric distance t ], }, setType: 'standard', - defaultUnitSystem: 'metric' + defaultUnitSystem: 'metric', }, }); @@ -211,7 +293,7 @@ test('add distance target button should correctly add split imperial distance ta ], }, setType: 'split', - defaultUnitSystem: 'imperial' + defaultUnitSystem: 'imperial', }, }); @@ -241,7 +323,7 @@ test('add distance target button should correctly add split metric distance targ ], }, setType: 'split', - defaultUnitSystem: 'metric' + defaultUnitSystem: 'metric', }, }); @@ -280,7 +362,7 @@ test('add distance target button should correctly add workout imperial distance ], }, setType: 'workout', - defaultUnitSystem: 'imperial' + defaultUnitSystem: 'imperial', }, }); @@ -332,7 +414,7 @@ test('add distance target button should correctly add workout metric distance ta ], }, setType: 'workout', - defaultUnitSystem: 'metric' + defaultUnitSystem: 'metric', }, }); @@ -433,7 +515,7 @@ test('add time target button should correctly add workout imperial time target', ], }, setType: 'workout', - defaultUnitSystem: 'imperial' + defaultUnitSystem: 'imperial', }, }); @@ -485,7 +567,7 @@ test('add time target button should correctly add workout metric time target', a ], }, setType: 'workout', - defaultUnitSystem: 'metric' + defaultUnitSystem: 'metric', }, }); diff --git a/tests/unit/components/TargetSetSelector.spec.js b/tests/unit/components/TargetSetSelector.spec.js @@ -86,12 +86,16 @@ test('Create New Target Set option should correctly add target set', async () => expect(options[3].element.value).to.equal('_new'); expect(options.length).to.equal(4); - // Assert target sets were correctly updated - targetSets[key1] = { - name: 'New target set', - targets: [], - }; - expect(wrapper.vm.targetSets).to.deep.equal(targetSets); + // Assert update event emitted with correct target sets + expect(wrapper.emitted()['update:targetSets']).to.deep.equal([ + [{ + [key1]: { + name: 'New target set', + targets: [], + }, + ...targetSets, + }], + ]); // Add another target set await wrapper.find('select').setValue('_new'); @@ -115,12 +119,27 @@ test('Create New Target Set option should correctly add target set', async () => expect(options[4].element.value).to.equal('_new'); expect(options.length).to.equal(5); - // Assert target sets were correctly updated - targetSets[key2] = { - name: 'New target set', - targets: [], - }; - expect(wrapper.vm.targetSets).to.deep.equal(targetSets); + // Assert update event emitted with correct target sets + expect(wrapper.emitted()['update:targetSets']).to.deep.equal([ + [{ + [key1]: { + name: 'New target set', + targets: [], + }, + ...targetSets, + }], + [{ + [key1]: { + name: 'New target set', + targets: [], + }, + ...targetSets, + [key2]: { + name: 'New target set', + targets: [], + }, + }], + ]); }); test('Revert event should correctly reset a default target set', async () => { @@ -163,14 +182,27 @@ test('Revert event should correctly reset a default target set', async () => { expect(options[2].element.value).to.equal('_new'); expect(options.length).to.equal(3); - // Assert target sets were correctly updated - targetSets._split_targets.name = '5K Mile Splits'; - targetSets._split_targets.targets[2] = { - type: 'distance', - distanceValue: 5, - distanceUnit: 'kilometers', - }; - expect(wrapper.vm.targetSets).to.deep.equal(targetSets); + // Assert update event emitted with correct target sets + expect(wrapper.emitted()['update:targetSets']).to.deep.equal([ + [{ + '_split_targets': { + name: '5K Mile Splits', + targets: [ + { type: 'distance', distanceValue: 1, distanceUnit: 'miles' }, + { type: 'distance', distanceValue: 2, distanceUnit: 'miles' }, + { type: 'distance', distanceValue: 5, distanceUnit: 'kilometers' }, + ], + }, + '1234567890123': { + name: '2nd target set', + targets: [ + { type: 'distance', distanceValue: 1, distanceUnit: 'kilometers' }, + { type: 'distance', distanceValue: 5, distanceUnit: 'kilometers' }, + { type: 'distance', distanceValue: 10, distanceUnit: 'kilometers' }, + ], + }, + }], + ]); }); test('Revert event should correctly delete a custom target set', async () => { @@ -211,9 +243,19 @@ test('Revert event should correctly delete a custom target set', async () => { expect(options[1].element.value).to.equal('_new'); expect(options.length).to.equal(2); - // Assert target sets were correctly updated - delete targetSets['1234567890123']; - expect(wrapper.vm.targetSets).to.deep.equal(targetSets); + // Assert update event emitted with correct target sets + expect(wrapper.emitted()['update:targetSets']).to.deep.equal([ + [{ + '_split_targets': { + name: '1st target set', + targets: [ + { type: 'distance', distanceValue: 1, distanceUnit: 'miles' }, + { type: 'distance', distanceValue: 2, distanceUnit: 'miles' }, + { type: 'distance', distanceValue: 3, distanceUnit: 'miles' }, + ], + }, + }], + ]); }); test('edit button should open target editor with the correct props for default set', async () => { @@ -313,26 +355,63 @@ test('should sort target set after target editor is closed', async () => { }); await wrapper.findComponent({ name: 'target-editor' }).vm.$emit('close'); - // Assert target set was sorted - expect(wrapper.findComponent({ name: 'target-editor' }).vm.modelValue).to.deep.equal({ - name: '5K Mile Splits', - targets: [ - { type: 'distance', distanceValue: 1, distanceUnit: 'miles' }, - { type: 'distance', distanceValue: 2, distanceUnit: 'miles' }, - { type: 'distance', distanceValue: 3, distanceUnit: 'miles' }, - { type: 'distance', distanceValue: 5, distanceUnit: 'kilometers' }, - { type: 'time', timeValue: 60 }, - ], - }); + // Assert update events were emitted correctly + expect(wrapper.emitted()['update:targetSets']).to.deep.equal([ + [{ + _split_targets: { + name: '5K Mile Splits', + targets: [ + { type: 'time', timeValue: 60 }, + { type: 'distance', distanceValue: 1, distanceUnit: 'miles' }, + { type: 'distance', distanceValue: 2, distanceUnit: 'miles' }, + { type: 'distance', distanceValue: 5, distanceUnit: 'kilometers' }, + { type: 'distance', distanceValue: 3, distanceUnit: 'miles' }, + ], + }, + }], + [{ + _split_targets: { + name: '5K Mile Splits', + targets: [ + { type: 'distance', distanceValue: 1, distanceUnit: 'miles' }, + { type: 'distance', distanceValue: 2, distanceUnit: 'miles' }, + { type: 'distance', distanceValue: 3, distanceUnit: 'miles' }, + { type: 'distance', distanceValue: 5, distanceUnit: 'kilometers' }, + { type: 'time', timeValue: 60 }, + ], + }, + }], + ]); }); test('should correctly pass setType prop to TargetEditor', async () => { const wrapper = shallowMount(TargetSetSelector, { propsData: { + selectedTargetSet: '_new', + targetSets: {}, setType: 'foo' - } + }, }); // Assert target editor props are correct expect(wrapper.findComponent({ name: 'target-editor' }).vm.setType).to.equal('foo'); }); + +test('should correctly pass customWorkoutNames prop to TargetEditor', async () => { + const wrapper = shallowMount(TargetSetSelector, { + propsData: { + selectedTargetSet: '_new', + targetSets: {}, + customWorkoutNames: false, + } + }); + + // Assert target editor props are correct + expect(wrapper.findComponent({ name: 'target-editor' }).vm.customWorkoutNames).to.equal(false); + + // Update customWorkoutNames prop + await wrapper.setProps({ customWorkoutNames: true }); + + // Assert target editor props are correct + expect(wrapper.findComponent({ name: 'target-editor' }).vm.customWorkoutNames).to.equal(true); +}); diff --git a/tests/unit/components/TimeInput.spec.js b/tests/unit/components/TimeInput.spec.js @@ -1,7 +1,22 @@ import { test, expect } from 'vitest'; -import { shallowMount, mount } from '@vue/test-utils'; +import { mount, shallowMount } from '@vue/test-utils'; import TimeInput from '@/components/TimeInput.vue'; +test('should correctly render input label', () => { + // Initialize component + const wrapper = mount(TimeInput, { + propsData: { + modelValue: 0, + label: 'My input', + }, + }); + + // Assert input fields are correct + expect(wrapper.findAll('input')[0].element.ariaLabel).to.equal('My input hours'); + expect(wrapper.findAll('input')[1].element.ariaLabel).to.equal('My input minutes'); + expect(wrapper.findAll('input')[2].element.ariaLabel).to.equal('My input seconds'); +}); + test('value should be 0:00:0.00 by default', () => { // Initialize component const wrapper = shallowMount(TimeInput); diff --git a/tests/unit/core/calculators.spec.js b/tests/unit/core/calculators.spec.js @@ -0,0 +1,257 @@ +import { describe, test, expect } from 'vitest'; +import * as calculators from '@/core/calculators'; + +describe('calculatePaceResults method', () => { + test('should correctly calculate pace times', () => { + const input = { + distanceValue: 1, + distanceUnit: 'kilometers', + time: 100, + }; + const target = { + distanceValue: 20, + distanceUnit: 'meters', + type: 'distance', + }; + + const result = calculators.calculatePaceResults(input, target, 'metric', true); + + expect(result).to.deep.equal({ + key: '20 m', + value: '0:02.00', + pace: '1:40 / km', + result: 'value', + sort: 2, + }); + }); + + test('should correctly calculate pace distances according to default units setting', () => { + const input = { + distanceValue: 2, + distanceUnit: 'miles', + time: 1200, + }; + const target = { + time: 600, + type: 'time', + }; + + const result1 = calculators.calculatePaceResults(input, target, 'metric', true); + const result2 = calculators.calculatePaceResults(input, target, 'imperial', true); + + expect(result1.key).to.equal('1.61 km'); + expect(result1.value).to.equal('10:00'); + expect(result1.pace).to.equal('6:13 / km'); + expect(result1.result).to.equal('key'); + expect(result1.sort).to.be.closeTo(600, 0.01); + + expect(result2.key).to.equal('1.00 mi'); + expect(result2.value).to.equal('10:00'); + expect(result2.pace).to.equal('10:00 / mi'); + expect(result2.result).to.equal('key'); + expect(result2.sort).to.be.closeTo(600, 0.01); + }); +}); + +describe('calculateRaceResults method', () => { + test('should correctly predict race times', () => { + const input = { + distanceValue: 5, + distanceUnit: 'kilometers', + time: 1200, + }; + const target = { + distanceValue: 10, + distanceUnit: 'kilometers', + type: 'distance', + }; + const racePredictionOptions = { + model: 'AverageModel', + riegelExponent: 1.06, + } + + const result = calculators.calculateRaceResults(input, target, racePredictionOptions, + 'imperial', true); + + expect(result.key).to.equal('10 km'); + expect(result.value).to.equal('41:34.80'); + expect(result.pace).to.equal('6:41 / mi'); + expect(result.result).to.equal('value'); + expect(result.sort).to.be.closeTo(2494.80, 0.01); + }); + + test('should correctly calculate race distances according to default units setting', () => { + const input = { + distanceValue: 5, + distanceUnit: 'kilometers', + time: 1200, + }; + const target = { + time: 2495, + type: 'time', + }; + const racePredictionOptions = { + model: 'AverageModel', + riegelExponent: 1.06, + }; + + const result1 = calculators.calculateRaceResults(input, target, racePredictionOptions, + 'metric', true); + const result2 = calculators.calculateRaceResults(input, target, racePredictionOptions, + 'imperial', true); + + expect(result1.key).to.equal('10.00 km'); + expect(result1.value).to.equal('41:35'); + expect(result1.pace).to.equal('4:09 / km'); + expect(result1.result).to.equal('key'); + expect(result1.sort).to.equal(2495); + + expect(result2.key).to.equal('6.21 mi'); + expect(result2.value).to.equal('41:35'); + expect(result2.pace).to.equal('6:41 / mi'); + expect(result2.result).to.equal('key'); + expect(result2.sort).to.equal(2495); + }); + + test('should correctly predict race times according to race options', () => { + const input = { + distanceValue: 2, + distanceUnit: 'miles', + time: 630, + }; + const target = { + distanceValue: 5, + distanceUnit: 'kilometers', + type: 'distance', + }; + const racePredictionOptions = { + model: 'RiegelModel', + riegelExponent: 1.12, + } + + const result = calculators.calculateRaceResults(input, target, racePredictionOptions, + 'imperial', true); + + expect(result.key).to.equal('5 km'); + expect(result.value).to.equal('17:11.78'); + expect(result.pace).to.equal('5:32 / mi'); + expect(result.result).to.equal('value'); + expect(result.sort).to.be.closeTo(1031.77, 0.01); + }); +}); + +describe('calculateRaceStats method', () => { + test('should correctly calculate race statistics', () => { + const input = { + distanceValue: 5, + distanceUnit: 'kilometers', + time: 1200, + }; + + const results = calculators.calculateRaceStats(input); + + expect(results.purdyPoints).to.be.closeTo(454.5, 0.1); + expect(results.vo2).to.be.closeTo(47.4, 0.1); + expect(results.vo2MaxPercentage).to.be.closeTo(95.3, 0.1); + expect(results.vo2Max).to.be.closeTo(49.8, 0.1); + }); +}); + +describe('calculateWorkoutResults method', () => { + test('should correctly calculate distance-based workouts according to race options', () => { + const input = { + distanceValue: 2, + distanceUnit: 'miles', + time: 630, + }; + const target = { + distanceValue: 5, + distanceUnit: 'kilometers', // 5k split is ~17:11.77 + splitValue: 1000, + splitUnit: 'meters', + type: 'distance', + }; + const racePredictionOptions = { + model: 'RiegelModel', + riegelExponent: 1.12, + } + + const result = calculators.calculateWorkoutResults(input, target, racePredictionOptions, + false, true); + + expect(result.key).to.equal('1000 m @ 5 km'); + expect(result.value).to.equal('3:26.36'); + expect(result.pace).to.equal(''); + expect(result.result).to.equal('value'); + expect(result.sort).to.be.closeTo(206.35, 0.01); + }); + + test('should correctly calculate distance-based workouts according to custom names', () => { + const input = { + distanceValue: 2, + distanceUnit: 'miles', + time: 630, + }; + const target_1 = { + distanceValue: 5, + distanceUnit: 'kilometers', // 5k split is ~17:11.77 + splitValue: 1000, + splitUnit: 'meters', + type: 'distance', + // no custom name + }; + const target_2 = { + distanceValue: 5, + distanceUnit: 'kilometers', // 5k split is ~17:11.77 + splitValue: 1000, + splitUnit: 'meters', + type: 'distance', + customName: 'my custom name', + }; + const racePredictionOptions = { + model: 'RiegelModel', + riegelExponent: 1.12, + }; + + const result1a = calculators.calculateWorkoutResults(input, target_1, racePredictionOptions, + false, true); + const result1b = calculators.calculateWorkoutResults(input, target_1, racePredictionOptions, + true, true); + const result2a = calculators.calculateWorkoutResults(input, target_2, racePredictionOptions, + false, true); + const result2b = calculators.calculateWorkoutResults(input, target_2, racePredictionOptions, + true, true); + + expect(result1a.key).to.equal('1000 m @ 5 km'); + expect(result1b.key).to.equal('1000 m @ 5 km'); + expect(result2a.key).to.equal('1000 m @ 5 km'); + expect(result2b.key).to.equal('my custom name'); + }); + + test('should correctly calculate time-based workouts', () => { + const input = { + distanceValue: 5, + distanceUnit: 'kilometers', + time: 1200, + }; + const target = { + time: 2495, // ~10k split is 41:35 + splitValue: 1, + splitUnit: 'miles', + type: 'time', + }; + const racePredictionOptions = { + model: 'AverageModel', + riegelExponent: 1.06, + } + + const result = calculators.calculateWorkoutResults(input, target, racePredictionOptions, false, + true); + + expect(result.key).to.equal('1 mi @ 41:35'); + expect(result.value).to.equal('6:41.50'); + expect(result.pace).to.equal(''); + expect(result.result).to.equal('value'); + expect(result.sort).to.be.closeTo(401.50, 0.01); + }); +}); diff --git a/tests/unit/core/migration.spec.js b/tests/unit/core/migration.spec.js @@ -0,0 +1,150 @@ +import { beforeEach, describe, test, expect } from 'vitest'; +import { migrateLocalStorage } from '@/core/migrations'; +import { detectDefaultUnitSystem } from '@/core/units'; + +beforeEach(() => { + localStorage.clear(); +}); + +describe('migrateLocalStorage method', () => { + test('should correctly migrate <=1.4.1 calculator options', async () => { + // Initialize localStorage + localStorage.setItem('running-tools.default-unit-system', '"imperial"'); + + localStorage.setItem('running-tools.batch-calculator-input', + '{"distanceValue":100,"distanceUnit":"meters","time":10}'); + localStorage.setItem('running-tools.batch-calculator-options', + '{"calculator":"race","increment":32,"rows":15}'); + + localStorage.setItem('running-tools.pace-calculator-input', + '{"distanceValue":110,"distanceUnit":"meters","time":11}'); + localStorage.setItem('running-tools.pace-calculator-target-set', '"A"'); + + localStorage.setItem('running-tools.race-calculator-input', + '{"distanceValue":120,"distanceUnit":"meters","time":12}'); + localStorage.setItem('running-tools.race-calculator-options', + '{"model":"RiegelModel","riegelExponent":1.07}'); + localStorage.setItem('running-tools.race-calculator-target-set', '"B"'); + + localStorage.setItem('running-tools.split-calculator-target-set', '"C"'); + + localStorage.setItem('running-tools.workout-calculator-input', + '{"distanceValue":130,"distanceUnit":"meters","time":13}'); + localStorage.setItem('running-tools.workout-calculator-options', + '{"model":"RiegelModel","riegelExponent":1.08}'); + localStorage.setItem('running-tools.workout-calculator-target-set', '"D"'); + + // Run migrations + migrateLocalStorage(); + + // Assert localStorage entries correctly migrated + expect(localStorage.getItem('running-tools.default-unit-system')).to.equal(null); + expect(localStorage.getItem('running-tools.global-options')).to.equal( + '{"defaultUnitSystem":"imperial","racePredictionOptions":{"model":"RiegelModel",' + + '"riegelExponent":1.07}}'); + + expect(localStorage.getItem('running-tools.batch-calculator-input')).to.equal(null); + expect(localStorage.getItem('running-tools.batch-calculator-options')).to.equal( + '{"calculator":"race","increment":32,"rows":15,"label":"",' + + '"input":{"distanceValue":100,"distanceUnit":"meters","time":10}}'); + + expect(localStorage.getItem('running-tools.pace-calculator-input')).to.equal(null); + expect(localStorage.getItem('running-tools.pace-calculator-options')).to.equal( + '{"input":{"distanceValue":110,"distanceUnit":"meters","time":11},"selectedTargetSet":"A"}'); + expect(localStorage.getItem('running-tools.pace-calculator-target-set')).to.equal(null); + + expect(localStorage.getItem('running-tools.race-calculator-input')).to.equal(null); + expect(localStorage.getItem('running-tools.race-calculator-options')).to.equal( + '{"input":{"distanceValue":120,"distanceUnit":"meters","time":12},"selectedTargetSet":"B"}'); + expect(localStorage.getItem('running-tools.race-calculator-target-set')).to.equal(null); + + expect(localStorage.getItem('running-tools.split-calculator-options')).to.equal( + '{"selectedTargetSet":"C"}'); + expect(localStorage.getItem('running-tools.split-calculator-target-set')).to.equal(null); + + expect(localStorage.getItem('running-tools.workout-calculator-input')).to.equal(null); + expect(localStorage.getItem('running-tools.workout-calculator-options')).to.equal( + '{"customTargetNames":false,"input":{"distanceValue":130,"distanceUnit":"meters",' + + '"time":13},"selectedTargetSet":"D"}'); + expect(localStorage.getItem('running-tools.workout-calculator-target-set')).to.equal(null); + }); + + test('should correctly migrate partial <=1.4.1 calculator options using default values', async () => { + // Initialize localStorage + // default-unit-system, *-calculator-input, and *-calculator-target-set left undefined + localStorage.setItem('running-tools.batch-calculator-options', + '{"calculator":"race","increment":32,"rows":15}'); + localStorage.setItem('running-tools.race-calculator-options', + '{"model":"RiegelModel","riegelExponent":1.07}'); + localStorage.setItem('running-tools.workout-calculator-options', + '{"model":"RiegelModel","riegelExponent":1.08}'); + + // Run migrations + migrateLocalStorage(); + + // Assert localStorage entries correctly migrated + const defaultUnitSystem = detectDefaultUnitSystem(); + expect(localStorage.getItem('running-tools.global-options')).to.equal( + `{"defaultUnitSystem":"${defaultUnitSystem}",` + + '"racePredictionOptions":{"model":"RiegelModel","riegelExponent":1.07}}'); + expect(localStorage.getItem('running-tools.batch-calculator-options')).to.equal( + '{"calculator":"race","increment":32,"rows":15,"label":"",' + + '"input":{"distanceValue":5,"distanceUnit":"kilometers","time":1200}}'); + expect(localStorage.getItem('running-tools.race-calculator-options')).to.equal( + '{"input":{"distanceValue":5,"distanceUnit":"kilometers","time":1200},' + + '"selectedTargetSet":"_race_targets"}'); + expect(localStorage.getItem('running-tools.workout-calculator-options')).to.equal( + '{"customTargetNames":false,"input":{"distanceValue":5,"distanceUnit":"kilometers",' + + '"time":1200},"selectedTargetSet":"_workout_targets"}'); + }); + + test('should not modify >1.4.1 calculator options', async () => { + // Initialize localStorage + localStorage.setItem('running-tools.global-calculator-options', + '{"model":"RiegelModel","riegelExponent":1.07}'); + localStorage.setItem('running-tools.batch-calculator-options', + '{"calculator":"race","increment":32,"input":{"distanceValue":100,"distanceUnit":"meters",' + + '"time":10},"label":"foo","rows":15}'); + localStorage.setItem('running-tools.pace-calculator-options', + '{"input":{"distanceValue":110,"distanceUnit":"meters","time":11},"selectedTargetSet":"A"}'); + localStorage.setItem('running-tools.race-calculator-options', + '{"input":{"distanceValue":120,"distanceUnit":"meters","time":12},"selectedTargetSet":"B"}'); + localStorage.setItem('running-tools.split-calculator-options', + '{"selectedTargetSet":"C"}'); + localStorage.setItem('running-tools.workout-calculator-options', + '{"customTargetNames":true,"input":{"distanceValue":120,"distanceUnit":"meters","time":12},' + + '"selectedTargetSet":"D"}'); + + // Run migrations + migrateLocalStorage(); + + // Assert localStorage entries not modified + expect(localStorage.getItem('running-tools.global-calculator-options')).to.equal( + '{"model":"RiegelModel","riegelExponent":1.07}'); + expect(localStorage.getItem('running-tools.batch-calculator-options')).to.equal( + '{"calculator":"race","increment":32,"input":{"distanceValue":100,"distanceUnit":"meters",' + + '"time":10},"label":"foo","rows":15}'); + expect(localStorage.getItem('running-tools.pace-calculator-options')).to.equal( + '{"input":{"distanceValue":110,"distanceUnit":"meters","time":11},"selectedTargetSet":"A"}'); + expect(localStorage.getItem('running-tools.race-calculator-options')).to.equal( + '{"input":{"distanceValue":120,"distanceUnit":"meters","time":12},"selectedTargetSet":"B"}'); + expect(localStorage.getItem('running-tools.split-calculator-options')).to.equal( + '{"selectedTargetSet":"C"}'); + expect(localStorage.getItem('running-tools.workout-calculator-options')).to.equal( + '{"customTargetNames":true,"input":{"distanceValue":120,"distanceUnit":"meters","time":12},' + + '"selectedTargetSet":"D"}'); + }); + + test('should not modify missing calculator options', async () => { + // Run migrations + migrateLocalStorage(); + + // Assert localStorage entries not modified + expect(localStorage.getItem('running-tools.global-options')).to.equal(null); + expect(localStorage.getItem('running-tools.batch-calculator-options')).to.equal(null); + expect(localStorage.getItem('running-tools.pace-calculator-options')).to.equal(null); + expect(localStorage.getItem('running-tools.race-calculator-options')).to.equal(null); + expect(localStorage.getItem('running-tools.split-calculator-options')).to.equal(null); + expect(localStorage.getItem('running-tools.workout-calculator-options')).to.equal(null); + }); +}); diff --git a/tests/unit/core/racePrediction.spec.js b/tests/unit/core/racePrediction.spec.js @@ -0,0 +1,250 @@ +import { describe, test, expect } from 'vitest'; +import * as racePrediction from '@/core/racePrediction'; + +describe('predictTime method', () => { + describe('PredictTime method', () => { + test('Average Model', () => { + const riegel = racePrediction.predictTime(5000, 1200, 10000, { + model: 'RiegelModel', + riegelExponent: 1.06, + }); + const cameron = racePrediction.predictTime(5000, 1200, 10000, { + model: 'CameronModel', + riegelExponent: 1.06, + }); + const purdyPoints = racePrediction.predictTime(5000, 1200, 10000, { + model: 'PurdyPointsModel', + riegelExponent: 1.06, + }); + const vo2Max = racePrediction.predictTime(5000, 1200, 10000, { + model: 'VO2MaxModel', + riegelExponent: 1.06, + }); + const expected = (riegel + cameron + purdyPoints + vo2Max) / 4; + + const result = racePrediction.predictTime(5000, 1200, 10000, { + model: 'AverageModel', + riegelExponent: 1.06, + }); + expect(result).to.equal(expected); + }); + + test('Should predict identical times for itentical distances', () => { + const result = racePrediction.predictTime(5000, 1200, 5000, { + model: 'AverageModel', + riegelExponent: 1.06, + }); + expect(result).to.be.closeTo(1200, 0.001); + }); + }); + + describe('Purdy Points Model', () => { + test('Predictions should be approximately correct', () => { + const result = racePrediction.predictTime(5000, 1200, 10000, { + model: 'PurdyPointsModel', + riegelExponent: 1.06, + }); + expect(result).to.be.closeTo(2490, 1); + }); + + test('Should predict identical times for itentical distances', () => { + const result = racePrediction.predictTime(5000, 1200, 5000, { + model: 'PurdyPointsModel', + riegelExponent: 1.06, + }); + expect(result).to.be.closeTo(1200, 0.001); + }); + }); + + describe('VO2 Max Model', () => { + test('Predictions should be approximately correct', () => { + const result = racePrediction.predictTime(5000, 1200, 10000, { + model: 'VO2MaxModel', + riegelExponent: 1.06, + }); + expect(result).to.be.closeTo(2488, 1); + }); + + test('Should predict identical times for itentical distances', () => { + const result = racePrediction.predictTime(5000, 1200, 5000, { + model: 'VO2MaxModel', + riegelExponent: 1.06, + }); + expect(result).to.be.closeTo(1200, 0.001); + }); + }); + + describe('Cameron Model', () => { + test('Predictions should be approximately correct', () => { + const result = racePrediction.predictTime(5000, 1200, 10000, { + model: 'CameronModel', + riegelExponent: 1.06, + }); + expect(result).to.be.closeTo(2500, 1); + }); + + test('Should predict identical times for itentical distances', () => { + const result = racePrediction.predictTime(5000, 1200, 5000, { + model: 'CameronModel', + riegelExponent: 1.06, + }); + expect(result).to.be.closeTo(1200, 0.001); + }); + }); + + describe('Riegel Model', () => { + test('Predictions should be approximately correct', () => { + const result = racePrediction.predictTime(5000, 1200, 10000, { + model: 'RiegelModel', + RiegelModel: 1.06, + }); + expect(result).to.be.closeTo(2502, 1); + }); + + test('Should predict identical times for itentical distances', () => { + const result = racePrediction.predictTime(5000, 1200, 5000, { + model: 'RiegelModel', + RiegelModel: 1.06, + }); + expect(result).to.be.closeTo(1200, 0.001); + }); + }); +}); + +describe('predictDistance method', () => { + describe('Average Model', () => { + test('Predictions should be correct', () => { + const riegel = racePrediction.predictTime(5000, 1200, 10000, { + model: 'RiegelModel', + riegel: 1.06, + }); + const cameron = racePrediction.predictTime(5000, 1200, 10000, { + model: 'CameronModel', + riegel: 1.06, + }); + const purdyPoints = racePrediction.predictTime(5000, 1200, 10000, { + model: 'PurdyPointsModel', + riegel: 1.06, + }); + const vo2Max = racePrediction.predictTime(5000, 1200, 10000, { + model: 'VO2MaxModel', + riegel: 1.06, + }); + const expected = (riegel + cameron + purdyPoints + vo2Max) / 4; + + const result = racePrediction.predictDistance(1200, 5000, expected, { + model: 'AverageModel', + riegelExponent: 1.06, + }); + expect(result).to.be.closeTo(10000, 10); + }); + + test('Should predict identical times for itentical distances', () => { + const result = racePrediction.predictDistance(1200, 5000, 1200, { + model: 'AverageModel', + riegel: 1.06, + }); + expect(result).to.be.closeTo(5000, 0.001); + }); + }); + + describe('Purdy Points Model', () => { + test('Predictions should be approximately correct', () => { + const result = racePrediction.predictDistance(1200, 5000, 2490, { + model: 'PurdyPointsModel', + riegel: 1.06, + }); + expect(result).to.be.closeTo(10000, 10); + }); + + test('Should predict identical times for itentical distances', () => { + const result = racePrediction.predictDistance(1200, 5000, 1200, { + model: 'PurdyPointsModel', + riegel: 1.06, + }); + expect(result).to.be.closeTo(5000, 0.001); + }); + }); + + describe('VO2 Max Model', () => { + test('Predictions should be approximately correct', () => { + const result = racePrediction.predictDistance(1200, 5000, 2488, { + model: 'VO2MaxModel', + riegel: 1.06, + }); + expect(result).to.be.closeTo(10000, 10); + }); + + test('Should predict identical times for itentical distances', () => { + const result = racePrediction.predictDistance(1200, 5000, 1200, { + model: 'VO2MaxModel', + riegel: 1.06, + }); + expect(result).to.be.closeTo(5000, 0.001); + }); + }); + + describe('Cameron Model', () => { + test('Predictions should be approximately correct', () => { + const result = racePrediction.predictDistance(1200, 5000, 2500, { + model: 'CameronModel', + riegel: 1.06, + }); + expect(result).to.be.closeTo(10000, 10); + }); + + test('Should predict identical times for itentical distances', () => { + const result = racePrediction.predictDistance(1200, 5000, 1200, { + model: 'CameronModel', + riegel: 1.06, + }); + expect(result).to.be.closeTo(5000, 0.001); + }); + }); + + describe('Riegel Model', () => { + test('Predictions should be approximately correct', () => { + const result = racePrediction.predictDistance(1200, 5000, 2502, { + model: 'RiegelModel', + RiegelModel: 1.06, + }); + expect(result).to.be.closeTo(10000, 10); + }); + + test('Should predict identical times for itentical distances', () => { + const result = racePrediction.predictDistance(1200, 5000, 1200, { + model: 'RiegelModel', + RiegelModel: 1.06, + }); + expect(result).to.be.closeTo(5000, 0.001); + }); + }); +}); + +describe('getVO2 method', () => { + test('Result should be approximately correct', () => { + const result = racePrediction.getVO2(5000, 1200); + expect(result).to.be.closeTo(47.4, 0.1); + }); +}); + +describe('getVO2Percentage method', () => { + test('Result should be approximately correct', () => { + const result = racePrediction.getVO2Percentage(660); + expect(result).to.be.closeTo(1, 0.001); + }); +}); + +describe('getVO2Max method', () => { + test('Result should be approximately correct', () => { + const result = racePrediction.getVO2Max(5000, 1200); + expect(result).to.be.closeTo(49.8, 0.1); + }); +}); + +describe('getPurdyPoints method', () => { + test('Result should be approximately correct', () => { + const result = racePrediction.getPurdyPoints(5000, 1200); + expect(result).to.be.closeTo(454, 1); + }); +}); diff --git a/tests/unit/core/targets.spec.js b/tests/unit/core/targets.spec.js @@ -0,0 +1,59 @@ +import { describe, test, expect } from 'vitest'; +import * as targets from '@/core/targets'; + +describe('sort method', () => { + test('should correctly sort targets', () => { + // Initialize unsorted and sorted targets + const input = [ + { time: 60, type: 'time' }, + { distanceUnit: 'kilometers', distanceValue: 5, type: 'distance' }, + { distanceUnit: 'miles', distanceValue: 3, type: 'distance' }, + ]; + const expected = [ + { distanceUnit: 'miles', distanceValue: 3, type: 'distance' }, + { distanceUnit: 'kilometers', distanceValue: 5, type: 'distance' }, + { time: 60, type: 'time' }, + ]; + + // Assert sort method sorts targets correctly + expect(targets.sort(input)).to.deep.equal(expected); + }); +}); + +describe('workoutTargetToString method', () => { + test('should correctly stringify time target', () => { + // Initialize original and stringified target + const input = { + splitValue: 1600, splitUnit: 'meters', + type: 'time', time: 3600, + }; + const expected = '1600 m @ 1:00:00'; + + // Assert sort method sorts targets correctly + expect(targets.workoutTargetToString(input)).to.deep.equal(expected); + }); + + test('should correctly stringify distance target', () => { + // Initialize original and stringified target + const input = { + splitValue: 800, splitUnit: 'meters', + type: 'distance', distanceValue: 5, distanceUnit: 'kilometers', + }; + const expected = '800 m @ 5 km'; + + // Assert sort method sorts targets correctly + expect(targets.workoutTargetToString(input)).to.deep.equal(expected); + }); + + test('should correctly stringify race target', () => { + // Initialize original and stringified target + const input = { + splitValue: 5, splitUnit: 'kilometers', + type: 'distance', distanceValue: 5, distanceUnit: 'kilometers', + }; + const expected = '5 km'; + + // Assert sort method sorts targets correctly + expect(targets.workoutTargetToString(input)).to.deep.equal(expected); + }); +}); diff --git a/tests/unit/core/units.spec.js b/tests/unit/core/units.spec.js @@ -0,0 +1,439 @@ +import { describe, test, expect } from 'vitest'; +import * as units from '@/core/units'; + +describe('convertTime method', () => { + test('90 seconds should equal 1.5 minutes', () => { + const result = units.convertTime(90, 'seconds', 'minutes'); + expect(result).to.equal(1.5); + }); + + test('1.5 minutes should equal 95 seconds', () => { + const result = units.convertTime(1.5, 'minutes', 'seconds'); + expect(result).to.equal(90); + }); +}); + +describe('convertDistance method', () => { + test('100 meters should equal 0.1 kilometers', () => { + const result = units.convertDistance(100, 'meters', 'kilometers'); + expect(result).to.equal(0.1); + }); + + test('0.1 kilometers should equal 100 meters', () => { + const result = units.convertDistance(0.1, 'kilometers', 'meters'); + expect(result).to.equal(100); + }); +}); + +describe('convertSpeed method', () => { + test('1000 meters per seconds should equal 3600 kilometers per hour', () => { + const result = units.convertSpeed(1000, 'meters_per_second', 'kilometers_per_hour'); + expect(result).to.equal(3600); + }); + + test('3600 kilometers per hour should equal 1000 meters per second', () => { + const result = units.convertSpeed(3600, 'kilometers_per_hour', 'meters_per_second'); + expect(result).to.equal(1000); + }); +}); + +describe('convertPace method', () => { + test('1 second per meter should equal 1000 seconds per kilometer', () => { + const result = units.convertPace(1, 'seconds_per_meter', 'seconds_per_kilometer'); + expect(result).to.equal(1000); + }); + + test('1000 seconds per kilometer should equal 1 second per meter', () => { + const result = units.convertPace(1000, 'seconds_per_kilometer', 'seconds_per_meter'); + expect(result).to.equal(1); + }); +}); + +describe('convertSpeedPace method', () => { + test('3600 kilometers per hour should equal 1 second per kilometer', () => { + const result = units.convertSpeedPace(3600, 'kilometers_per_hour', 'seconds_per_kilometer'); + expect(result).to.equal(1); + }); + + test('1 second per kilometer should equal 3600 kilometers per hour', () => { + const result = units.convertSpeedPace(3600, 'seconds_per_kilometer', 'kilometers_per_hour'); + expect(result).to.equal(1); + }); +}); + +describe('formatNumber method', () => { + test('should correctly format number when padding is not 0', () => { + let result = units.formatNumber(12.3, 3, 0); + expect(result).to.equal('012'); + + result = units.formatNumber(12.3, 3, 2); + expect(result).to.equal('012.30'); + + result = units.formatNumber(123, 2, 0); + expect(result).to.equal('123'); + + result = units.formatNumber(-12, 3, 0); + expect(result).to.equal('-012'); + }); + + test('should correctly format number when extraDigits is true', () => { + let result = units.formatNumber(1234, 0, 2); + expect(result).to.equal('1234.00'); + + result = units.formatNumber(1234.5, 0, 2); + expect(result).to.equal('1234.50'); + + result = units.formatNumber(1234.56, 0, 2); + expect(result).to.equal('1234.56'); + + result = units.formatNumber(1234.567, 0, 2); + expect(result).to.equal('1234.57'); + + result = units.formatNumber(1234.56, 0, 0); + expect(result).to.equal('1235'); + }); + + test('should correctly format number when extraDigits is false', () => { + let result = units.formatNumber(1234, 0, 2, false); + expect(result).to.equal('1234'); + + result = units.formatNumber(1234.5, 0, 2, false); + expect(result).to.equal('1234.5'); + + result = units.formatNumber(1234.56, 0, 2, false); + expect(result).to.equal('1234.56'); + + result = units.formatNumber(1234.567, 0, 2, false); + expect(result).to.equal('1234.57'); + + result = units.formatNumber(1234.56, 0, 0, false); + expect(result).to.equal('1235'); + }); + + test('should correctly format undefined', () => { + let result = units.formatNumber(undefined, 0, 2); + expect(result).to.equal('NaN'); + + result = units.formatNumber(undefined, 0, 2, false); + expect(result).to.equal('NaN'); + + result = units.formatNumber(undefined, 5, 2); + expect(result).to.equal('NaN'); + }); + + test('should correctly format NaN', () => { + let result = units.formatNumber(NaN, 0, 0); + expect(result).to.equal('NaN'); + + result = units.formatNumber(NaN, 0, 2, false); + expect(result).to.equal('NaN'); + + result = units.formatNumber(NaN, 5, 2); + expect(result).to.equal('NaN'); + }); + + test('should correctly format +/- Infinity', () => { + let result = units.formatNumber(Infinity); + expect(result).to.equal('Infinity'); + + result = units.formatNumber(Infinity, 10, 2); + expect(result).to.equal('Infinity'); + + result = units.formatNumber(-Infinity); + expect(result).to.equal('-Infinity'); + }); + + test('should correctly format numbers smaller than 1', () => { + let result = units.formatNumber(0.123, 0, 0); + expect(result).to.equal('0'); + + result = units.formatNumber(0.123, 0, 2); + expect(result).to.equal('0.12'); + }); + + test('should correctly format negative numbers', () => { + let result = units.formatNumber(-12, 0, 2, false); + expect(result).to.equal('-12'); + + result = units.formatNumber(-12, 0, 2); + expect(result).to.equal('-12.00'); + + result = units.formatNumber(-12.34, 0, 2); + expect(result).to.equal('-12.34'); + + result = units.formatNumber(-12.34, 3, 2); + expect(result).to.equal('-012.34'); + + result = units.formatNumber(-0.12, 0, 2); + expect(result).to.equal('-0.12'); + }); +}); + +describe('formatDistance method', () => { + test('should correctly format distances with a variety of units', () => { + let result = units.formatDistance({ + distanceValue: 1, + distanceUnit: units.DistanceUnits.Yards, + }, false); + expect(result).to.equal('1 yd'); + + result = units.formatDistance({ + distanceValue: 2, + distanceUnit: units.DistanceUnits.Meters, + }, false); + expect(result).to.equal('2 m'); + + result = units.formatDistance({ + distanceValue: 3, + distanceUnit: units.DistanceUnits.Kilometers, + }, false); + expect(result).to.equal('3 km'); + + result = units.formatDistance({ + distanceValue: 4, + distanceUnit: units.DistanceUnits.Miles, + }, false); + expect(result).to.equal('4 mi'); + + result = units.formatDistance({ + distanceValue: 5, + distanceUnit: units.DistanceUnits.Marathons, + }, false); + expect(result).to.equal('5 Mar'); + }); + + test('should correctly format distance when extraDigits is true', () => { + let result = units.formatDistance({ + distanceValue: 1234, + distanceUnit: units.DistanceUnits.Miles, + }, true); + expect(result).to.equal('1234.00 mi'); + + result = units.formatDistance({ + distanceValue: 1234.5, + distanceUnit: units.DistanceUnits.Miles, + }, true); + expect(result).to.equal('1234.50 mi'); + + result = units.formatDistance({ + distanceValue: 1234.56, + distanceUnit: units.DistanceUnits.Miles, + }, true); + expect(result).to.equal('1234.56 mi'); + + result = units.formatDistance({ + distanceValue: 1234.567, + distanceUnit: units.DistanceUnits.Miles, + }, true); + expect(result).to.equal('1234.57 mi'); + }); + + test('should correctly format distance when extraDigits is false', () => { + let result = units.formatDistance({ + distanceValue: 1234, + distanceUnit: units.DistanceUnits.Miles, + }, false); + expect(result).to.equal('1234 mi'); + + result = units.formatDistance({ + distanceValue: 1234.5, + distanceUnit: units.DistanceUnits.Miles, + }, false); + expect(result).to.equal('1234.5 mi'); + + result = units.formatDistance({ + distanceValue: 1234.56, + distanceUnit: units.DistanceUnits.Miles, + }, false); + expect(result).to.equal('1234.56 mi'); + + result = units.formatDistance({ + distanceValue: 1234.567, + distanceUnit: units.DistanceUnits.Miles, + }, false); + expect(result).to.equal('1234.57 mi'); + }); + + test('should correctly format distances smaller than 1', () => { + let result = units.formatDistance({ + distanceValue: 0, + distanceUnit: units.DistanceUnits.Miles, + }, false); + expect(result).to.equal('0 mi'); + + result = units.formatDistance({ + distanceValue: 0, + distanceUnit: units.DistanceUnits.Miles, + }, true); + expect(result).to.equal('0.00 mi'); + + result = units.formatDistance({ + distanceValue: 0.1, + distanceUnit: units.DistanceUnits.Miles, + }, false); + expect(result).to.equal('0.1 mi'); + + result = units.formatDistance({ + distanceValue: 0.1, + distanceUnit: units.DistanceUnits.Miles, + }, true); + expect(result).to.equal('0.10 mi'); + + result = units.formatDistance({ + distanceValue: 0.12, + distanceUnit: units.DistanceUnits.Miles, + }, false); + expect(result).to.equal('0.12 mi'); + }); + + test('should correctly format negative distances', () => { + let result = units.formatDistance({ + distanceValue: -1234, + distanceUnit: units.DistanceUnits.Miles, + }, false); + expect(result).to.equal('-1234 mi'); + + result = units.formatDistance({ + distanceValue: -1234, + distanceUnit: units.DistanceUnits.Miles, + }, true); + expect(result).to.equal('-1234.00 mi'); + + result = units.formatDistance({ + distanceValue: -1234.56, + distanceUnit: units.DistanceUnits.Miles, + }, false); + expect(result).to.equal('-1234.56 mi'); + }); +}); + +describe('formatDuration method', () => { + test('should correctly divide durations into parts', () => { + const result = units.formatDuration(3600 + 120 + 3 + 0.4); + expect(result).to.equal('01:02:03.40'); + }); + + test('should correctly format duration when padding is 7', () => { + const result = units.formatDuration(3600 + 120 + 3 + 0.4, 7); + expect(result).to.equal('01:02:03.40'); + }); + + test('should correctly format duration when padding is 3', () => { + let result = units.formatDuration(3600 + 120 + 3 + 0.4, 3); + expect(result).to.equal('1:02:03.40'); + + result = units.formatDuration(120 + 3 + 0.4, 3); + expect(result).to.equal('2:03.40'); + + result = units.formatDuration(3 + 0.4, 3); + expect(result).to.equal('0:03.40'); + }); + + test('should correctly format duration when padding is 0', () => { + const result = units.formatDuration(0.4, 0); + expect(result).to.equal('0.40'); + }); + + test('should correctly format duration when digits is 3', () => { + const result = units.formatDuration(3600 + 120 + 3 + 0.4567, 0, 3); + expect(result).to.equal('1:02:03.457'); + }); + + test('should correctly format duration when digits is 0', () => { + const result = units.formatDuration(3600 + 120 + 3 + 0.456, 0, 0); + expect(result).to.equal('1:02:03'); + }); + + test('should correctly format NaN', () => { + const result = units.formatDuration(NaN); + expect(result).to.equal('NaN'); + }); + + test('should correctly format +/- Infinity', () => { + let result = units.formatDuration(Infinity); + expect(result).to.equal('Infinity'); + + result = units.formatDuration(-Infinity); + expect(result).to.equal('-Infinity'); + }); + + test('should correctly format 0 when padding is 0', () => { + const result = units.formatDuration(0, 0); + expect(result).to.equal('0.00'); + }); + + test('should correctly format negative durations', () => { + const result = units.formatDuration(-3600 - 120 - 3 - 0.4); + expect(result).to.equal('-01:02:03.40'); + }); + + test('should correctly format 59.9999', () => { + const result = units.formatDuration(59.9999); + expect(result).to.equal('00:01:00.00'); + }); + + test('should correctly format duration when extraDigits is false', () => { + let result = units.formatDuration(83, 0, 2, false); + expect(result).to.equal('1:23'); + + result = units.formatDuration(83.4, 0, 2, false); + expect(result).to.equal('1:23.4'); + + result = units.formatDuration(83.45, 0, 2, false); + expect(result).to.equal('1:23.45'); + + result = units.formatDuration(83.456, 0, 2, false); + expect(result).to.equal('1:23.46'); + + result = units.formatDuration(83.45, 0, 0, false); + expect(result).to.equal('1:23'); + }); +}); + +describe('formatPace method', () => { + test('should correctly format paces in a variety of units', () => { + let result = units.formatPace({ + distanceValue: 1, + distanceUnit: units.DistanceUnits.Meters, + time: 600, + }, units.PaceUnits.SecondsPerMeter); + expect(result).to.equal('10:00 s/m'); + + result = units.formatPace({ + distanceValue: 2, + distanceUnit: units.DistanceUnits.Kilometers, + time: 600, + }, units.PaceUnits.TimePerKilometer); + expect(result).to.equal('5:00 / km'); + + result = units.formatPace({ + distanceValue: 3, + distanceUnit: units.DistanceUnits.Miles, + time: 600, + }, units.PaceUnits.TimePerMile); + expect(result).to.equal('3:20 / mi'); + }); + + test('should correctly format paces that require distance conversion', () => { + let result = units.formatPace({ + distanceValue: 100, + distanceUnit: units.DistanceUnits.Meters, + time: 600, + }, units.PaceUnits.TimePerKilometer); + expect(result).to.equal('1:40:00 / km'); + + result = units.formatPace({ + distanceValue: 2, + distanceUnit: units.DistanceUnits.Kilometers, + time: 600, + }, units.PaceUnits.TimePerMile); + expect(result).to.equal('8:03 / mi'); + + result = units.formatPace({ + distanceValue: 0.03, + distanceUnit: units.DistanceUnits.Miles, + time: 600, + }, units.PaceUnits.SecondsPerMeter); + expect(result).to.equal('0:12 s/m'); + }); +}); diff --git a/tests/unit/core/utils.spec.js b/tests/unit/core/utils.spec.js @@ -0,0 +1,206 @@ +import { beforeEach, describe, test, expect } from 'vitest'; +import * as utils from '@/core/utils'; + +beforeEach(() => { + localStorage.clear(); +}); + +describe('deepCopy method', () => { + test('should deeply clone an object', () => { + let input = { + foo: 123, + bar: ['a', 'b', 'c'], + baz: { + baz: { + baz: { + baz: 'baz' + } + } + } + }; + let output = utils.deepCopy(input); + + // Output should equal input + expect(output).to.deep.equal(input); + + // Modifying input should not modify output + input.foo = 1234; + input.baz.baz.baz.baz = 'baz2'; + expect(output).to.deep.equal({ + foo: 123, + bar: ['a', 'b', 'c'], + baz: { + baz: { + baz: { + baz: 'baz' + } + } + } + }); + + // Modifying output should not modify input + output.foo = 12345; + output.baz.baz.baz.baz = 'baz3'; + expect(input).to.deep.equal({ + foo: 1234, + bar: ['a', 'b', 'c'], + baz: { + baz: { + baz: { + baz: 'baz2' + } + } + } + }); + }); +}); + +describe('deepEqual method', () => { + test('should correctly compare an object with itself', () => { + let obj1 = { + foo: 123, + bar: ['a', 'b', 'c'], + baz: { + baz: { + baz: { + baz: 'baz' + } + } + } + }; + + // obj1 should equal obj1 + expect(utils.deepEqual(obj1, obj1)).to.equal(true); + }); + + test('should correctly compare identical objects', () => { + let obj1 = { + foo: 123, + bar: ['a', 'b', 'c'], + baz: { + baz: { + baz: { + baz: 'baz' + } + } + } + }; + let obj2 = { + foo: 123, + bar: ['a', 'b', 'c'], + baz: { + baz: { + baz: { + baz: 'baz' + } + } + } + }; + + // obj1 should equal obj2 + expect(utils.deepEqual(obj1, obj2)).to.equal(true); + }); + + test('should correctly compare unequal objects', () => { + let obj1 = { + foo: 123, + bar: ['a', 'b', 'c'], + baz: { + baz: { + baz: { + baz: 'baz' + } + } + } + }; + let obj2 = { + foo: 123, + bar: ['a', 'b', 'c'], + baz: { + baz: { + baz: { + baz: 'baz2' + } + } + } + }; + + // obj1 should not equal obj2 + expect(utils.deepEqual(obj1, obj2)).to.equal(false); + }); +}); + +describe('getLocalStorage method', () => { + test('should correctly parse correct localStorage item', async () => { + // Initialize localStorage + localStorage.setItem('running-tools.foo', '{"bar":123}'); + + // Assert result is correct + expect(utils.getLocalStorage('foo')).to.deep.equal({ bar: 123 }); + }); + + test('should return null for corrupt localStorage item', async () => { + // Initialize localStorage + localStorage.setItem('running-tools.foo', 'invalid json'); + + // Assert result is correct + expect(utils.getLocalStorage('foo')).to.equal(null); + }); + + test('should return null for missing localStorage item', async () => { + // Initialize localStorage + localStorage.setItem('running-tools.foo', '{"bar":123}'); + + // Assert result is correct + expect(utils.getLocalStorage('baz')).to.equal(null); + }); +}); + +describe('setLocalStorage method', () => { + test('should correctly set new localStorage item', async () => { + // Set localStorage item + utils.setLocalStorage('foo', { baz: 456 }); + + // Assert result is correct + expect(localStorage.getItem('running-tools.foo')).to.equal('{"baz":456}'); + }); + + test('should correctly override existing localStorage item', async () => { + // Initialize localStorage + localStorage.setItem('running-tools.foo', '{"bar":123}'); + + // Set localStorage item + utils.setLocalStorage('foo', { baz: 456 }); + + // Assert result is correct + expect(localStorage.getItem('running-tools.foo')).to.equal('{"baz":456}'); + }); +}); + +describe('unsetLocalStorage method', () => { + test('should correctly remove existing localStorage item', async () => { + // Set localStorage item + localStorage.setItem('running-tools.foo', '1'); + localStorage.setItem('running-tools.bar', '2'); + + // Remove localStorage item + utils.unsetLocalStorage('bar'); + + // Assert localStorage updated correctly + expect(localStorage.getItem('running-tools.foo')).to.equal('1'); + expect(localStorage.getItem('running-tools.bar')).to.equal(null); + expect(localStorage.length).to.equal(1); + }); + + test('should remove non-existant localStorage item without error', async () => { + // Set localStorage item + localStorage.setItem('running-tools.foo', '1'); + + // Remove localStorage item + utils.unsetLocalStorage('missing'); + + // Assert localStorage updated correctly + expect(localStorage.length).to.equal(1); + expect(localStorage.getItem('running-tools.foo')).to.equal('1'); + }); +}); diff --git a/tests/unit/utils/calculators.spec.js b/tests/unit/utils/calculators.spec.js @@ -1,201 +0,0 @@ -import { test, expect } from 'vitest'; -import * as calculatorUtils from '@/utils/calculators'; - -test('should correctly calculate pace times', () => { - const input = { - distanceValue: 1, - distanceUnit: 'kilometers', - time: 100, - }; - const target = { - distanceValue: 20, - distanceUnit: 'meters', - type: 'distance', - }; - - const result = calculatorUtils.calculatePaceResults(input, target, 'metric'); - - expect(result).to.deep.equal({ - key: '20 m', - value: '0:02.00', - pace: '1:40 / km', - result: 'value', - sort: 2, - }); -}); - -test('should correctly calculate pace distances according to default units setting', () => { - const input = { - distanceValue: 2, - distanceUnit: 'miles', - time: 1200, - }; - const target = { - time: 600, - type: 'time', - }; - - const result1 = calculatorUtils.calculatePaceResults(input, target, 'metric'); - const result2 = calculatorUtils.calculatePaceResults(input, target, 'imperial'); - - expect(result1.key).to.equal('1.61 km'); - expect(result1.value).to.equal('10:00'); - expect(result1.pace).to.equal('6:13 / km'); - expect(result1.result).to.equal('key'); - expect(result1.sort).to.be.closeTo(600, 0.01); - - expect(result2.key).to.equal('1.00 mi'); - expect(result2.value).to.equal('10:00'); - expect(result2.pace).to.equal('10:00 / mi'); - expect(result2.result).to.equal('key'); - expect(result2.sort).to.be.closeTo(600, 0.01); -}); - -test('should correctly predict race times', () => { - const input = { - distanceValue: 5, - distanceUnit: 'kilometers', - time: 1200, - }; - const target = { - distanceValue: 10, - distanceUnit: 'kilometers', - type: 'distance', - }; - const options = { - model: 'AverageModel', - riegelExponent: 1.06, - } - - const result = calculatorUtils.calculateRaceResults(input, target, options, 'imperial'); - - expect(result.key).to.equal('10 km'); - expect(result.value).to.equal('41:34.80'); - expect(result.pace).to.equal('6:42 / mi'); - expect(result.result).to.equal('value'); - expect(result.sort).to.be.closeTo(2494.80, 0.01); -}); - -test('should correctly calculate race distances according to default units setting', () => { - const input = { - distanceValue: 5, - distanceUnit: 'kilometers', - time: 1200, - }; - const target = { - time: 2495, - type: 'time', - }; - const options = { - model: 'AverageModel', - riegelExponent: 1.06, - } - - const result1 = calculatorUtils.calculateRaceResults(input, target, options, 'metric'); - const result2 = calculatorUtils.calculateRaceResults(input, target, options, 'imperial'); - - expect(result1.key).to.equal('10.00 km'); - expect(result1.value).to.equal('41:35'); - expect(result1.pace).to.equal('4:09 / km'); - expect(result1.result).to.equal('key'); - expect(result1.sort).to.equal(2495); - - expect(result2.key).to.equal('6.21 mi'); - expect(result2.value).to.equal('41:35'); - expect(result2.pace).to.equal('6:41 / mi'); - expect(result2.result).to.equal('key'); - expect(result2.sort).to.equal(2495); -}); - -test('should correctly predict race times according to race options', () => { - const input = { - distanceValue: 2, - distanceUnit: 'miles', - time: 630, - }; - const target = { - distanceValue: 5, - distanceUnit: 'kilometers', - type: 'distance', - }; - const options = { - model: 'RiegelModel', - riegelExponent: 1.12, - } - - const result = calculatorUtils.calculateRaceResults(input, target, options, 'imperial'); - - expect(result.key).to.equal('5 km'); - expect(result.value).to.equal('17:11.77'); - expect(result.pace).to.equal('5:32 / mi'); - expect(result.result).to.equal('value'); - expect(result.sort).to.be.closeTo(1031.77, 0.01); -}); - -test('should correctly calculate race statistics', () => { - const input = { - distanceValue: 5, - distanceUnit: 'kilometers', - time: 1200, - }; - - const results = calculatorUtils.calculateRaceStats(input); - - expect(results.purdyPoints).to.be.closeTo(454.5, 0.1); - expect(results.vo2).to.be.closeTo(47.4, 0.1); - expect(results.vo2MaxPercentage).to.be.closeTo(95.3, 0.1); - expect(results.vo2Max).to.be.closeTo(49.8, 0.1); -}); - -test('should correctly calculate distance-based workouts according to race options', () => { - const input = { - distanceValue: 2, - distanceUnit: 'miles', - time: 630, - }; - const target = { - distanceValue: 5, - distanceUnit: 'kilometers', // 5k split is ~17:11.77 - splitValue: 1000, - splitUnit: 'meters', - type: 'distance', - }; - const options = { - model: 'RiegelModel', - riegelExponent: 1.12, - } - - const result = calculatorUtils.calculateWorkoutResults(input, target, options); - - expect(result.key).to.equal('1000 m @ 5 km'); - expect(result.value).to.equal('3:26.35'); - expect(result.pace).to.equal(''); - expect(result.result).to.equal('value'); - expect(result.sort).to.be.closeTo(206.35, 0.01); -}); - -test('should correctly calculate time-based workouts', () => { - const input = { - distanceValue: 5, - distanceUnit: 'kilometers', - time: 1200, - }; - const target = { - time: 2495, // ~10k split is 41:35 - splitValue: 1, - splitUnit: 'miles', - type: 'time', - }; - const options = { - model: 'AverageModel', - riegelExponent: 1.06, - } - - const result = calculatorUtils.calculateWorkoutResults(input, target, options); - - expect(result.key).to.equal('1 mi @ 41:35'); - expect(result.value).to.equal('6:41.50'); - expect(result.pace).to.equal(''); - expect(result.result).to.equal('value'); - expect(result.sort).to.be.closeTo(401.50, 0.01); -}); diff --git a/tests/unit/utils/format.spec.js b/tests/unit/utils/format.spec.js @@ -1,193 +0,0 @@ -import { describe, test, expect } from 'vitest'; -import * as formatUtils from '@/utils/format'; - -describe('formatNumber method', () => { - test('should correctly format number when padding is not 0', () => { - let result = formatUtils.formatNumber(12.3, 3, 0); - expect(result).to.equal('012'); - - result = formatUtils.formatNumber(12.3, 3, 2); - expect(result).to.equal('012.30'); - - result = formatUtils.formatNumber(123, 2, 0); - expect(result).to.equal('123'); - - result = formatUtils.formatNumber(-12, 3, 0); - expect(result).to.equal('-012'); - }); - - test('should correctly format number when extraDigits is true', () => { - let result = formatUtils.formatNumber(1234, 0, 2); - expect(result).to.equal('1234.00'); - - result = formatUtils.formatNumber(1234.5, 0, 2); - expect(result).to.equal('1234.50'); - - result = formatUtils.formatNumber(1234.56, 0, 2); - expect(result).to.equal('1234.56'); - - result = formatUtils.formatNumber(1234.567, 0, 2); - expect(result).to.equal('1234.57'); - - result = formatUtils.formatNumber(1234.56, 0, 0); - expect(result).to.equal('1235'); - }); - - test('should correctly format number when extraDigits is false', () => { - let result = formatUtils.formatNumber(1234, 0, 2, false); - expect(result).to.equal('1234'); - - result = formatUtils.formatNumber(1234.5, 0, 2, false); - expect(result).to.equal('1234.5'); - - result = formatUtils.formatNumber(1234.56, 0, 2, false); - expect(result).to.equal('1234.56'); - - result = formatUtils.formatNumber(1234.567, 0, 2, false); - expect(result).to.equal('1234.57'); - - result = formatUtils.formatNumber(1234.56, 0, 0, false); - expect(result).to.equal('1235'); - }); - - test('should correctly format undefined', () => { - let result = formatUtils.formatNumber(undefined, 0, 2); - expect(result).to.equal('NaN'); - - result = formatUtils.formatNumber(undefined, 0, 2, false); - expect(result).to.equal('NaN'); - - result = formatUtils.formatNumber(undefined, 5, 2); - expect(result).to.equal('NaN'); - }); - - test('should correctly format NaN', () => { - let result = formatUtils.formatNumber(NaN, 0, 0); - expect(result).to.equal('NaN'); - - result = formatUtils.formatNumber(NaN, 0, 2, false); - expect(result).to.equal('NaN'); - - result = formatUtils.formatNumber(NaN, 5, 2); - expect(result).to.equal('NaN'); - }); - - test('should correctly format +/- Infinity', () => { - let result = formatUtils.formatNumber(Infinity); - expect(result).to.equal('Infinity'); - - result = formatUtils.formatNumber(Infinity, 10, 2); - expect(result).to.equal('Infinity'); - - result = formatUtils.formatNumber(-Infinity); - expect(result).to.equal('-Infinity'); - }); - - test('should correctly format numbers less than 1', () => { - let result = formatUtils.formatNumber(0.123, 0, 0); - expect(result).to.equal('0'); - - result = formatUtils.formatNumber(0.123, 0, 2); - expect(result).to.equal('0.12'); - }); - - test('should correctly format negative numbers', () => { - let result = formatUtils.formatNumber(-12, 0, 2, false); - expect(result).to.equal('-12'); - - result = formatUtils.formatNumber(-12, 0, 2); - expect(result).to.equal('-12.00'); - - result = formatUtils.formatNumber(-12.34, 0, 2); - expect(result).to.equal('-12.34'); - - result = formatUtils.formatNumber(-12.34, 3, 2); - expect(result).to.equal('-012.34'); - - result = formatUtils.formatNumber(-0.12, 0, 2); - expect(result).to.equal('-0.12'); - }); -}); - -describe('formatDuration method', () => { - test('should correctly divide durations into parts', () => { - const result = formatUtils.formatDuration(3600 + 120 + 3 + 0.4); - expect(result).to.equal('01:02:03.40'); - }); - - test('should correctly format duration when padding is 7', () => { - const result = formatUtils.formatDuration(3600 + 120 + 3 + 0.4, 7); - expect(result).to.equal('01:02:03.40'); - }); - - test('should correctly format duration when padding is 3', () => { - let result = formatUtils.formatDuration(3600 + 120 + 3 + 0.4, 3); - expect(result).to.equal('1:02:03.40'); - - result = formatUtils.formatDuration(120 + 3 + 0.4, 3); - expect(result).to.equal('2:03.40'); - - result = formatUtils.formatDuration(3 + 0.4, 3); - expect(result).to.equal('0:03.40'); - }); - - test('should correctly format duration when padding is 0', () => { - const result = formatUtils.formatDuration(0.4, 0); - expect(result).to.equal('0.40'); - }); - - test('should correctly format duration when digits is 3', () => { - const result = formatUtils.formatDuration(3600 + 120 + 3 + 0.4567, 0, 3); - expect(result).to.equal('1:02:03.457'); - }); - - test('should correctly format duration when digits is 0', () => { - const result = formatUtils.formatDuration(3600 + 120 + 3 + 0.456, 0, 0); - expect(result).to.equal('1:02:03'); - }); - - test('should correctly format NaN', () => { - const result = formatUtils.formatDuration(NaN); - expect(result).to.equal('NaN'); - }); - - test('should correctly format +/- Infinity', () => { - let result = formatUtils.formatDuration(Infinity); - expect(result).to.equal('Infinity'); - - result = formatUtils.formatDuration(-Infinity); - expect(result).to.equal('-Infinity'); - }); - - test('should correctly format 0 when padding is 0', () => { - const result = formatUtils.formatDuration(0, 0); - expect(result).to.equal('0.00'); - }); - - test('should correctly format negative durations', () => { - const result = formatUtils.formatDuration(-3600 - 120 - 3 - 0.4); - expect(result).to.equal('-01:02:03.40'); - }); - - test('should correctly format 59.9999', () => { - const result = formatUtils.formatDuration(59.9999); - expect(result).to.equal('00:01:00.00'); - }); - - test('should correctly format duration when extraDigits is false', () => { - let result = formatUtils.formatDuration(83, 0, 2, false); - expect(result).to.equal('1:23'); - - result = formatUtils.formatDuration(83.4, 0, 2, false); - expect(result).to.equal('1:23.4'); - - result = formatUtils.formatDuration(83.45, 0, 2, false); - expect(result).to.equal('1:23.45'); - - result = formatUtils.formatDuration(83.456, 0, 2, false); - expect(result).to.equal('1:23.46'); - - result = formatUtils.formatDuration(83.45, 0, 0, false); - expect(result).to.equal('1:23'); - }); -}); diff --git a/tests/unit/utils/paces.spec.js b/tests/unit/utils/paces.spec.js @@ -1,14 +0,0 @@ -import { describe, test, expect } from 'vitest'; -import * as paces from '@/utils/paces'; - -describe('calculateTime method', () => { - test('1 meters in 3 seconds should equal 2 meters in 6 seconds', () => { - expect(paces.calculateTime(1, 3, 2)).to.equal(6); - }); -}); - -describe('calculateDistance method', () => { - test('1 meter in 3 seconds should equal 2 meters in 6 seconds', () => { - expect(paces.calculateDistance(3, 1, 6)).to.equal(2); - }); -}); diff --git a/tests/unit/utils/races.spec.js b/tests/unit/utils/races.spec.js @@ -1,166 +0,0 @@ -import { describe, test, expect } from 'vitest'; -import * as raceUtils from '@/utils/races'; - -describe('predictTime method', () => { - describe('PredictTime method', () => { - test('Average Model', () => { - const riegel = raceUtils.predictTime(5000, 1200, 10000, 'AverageModel'); - const cameron = raceUtils.predictTime(5000, 1200, 10000, 'AverageModel'); - const purdyPoints = raceUtils.predictTime(5000, 1200, 10000, 'AverageModel'); - const vo2Max = raceUtils.predictTime(5000, 1200, 10000, 'AverageModel'); - const expected = (riegel + cameron + purdyPoints + vo2Max) / 4; - - const result = raceUtils.predictTime(5000, 1200, 10000, 'AverageModel'); - expect(result).to.equal(expected); - }); - - test('Should predict identical times for itentical distances', () => { - const result = raceUtils.predictTime(5000, 1200, 5000, 'AverageModel'); - expect(result).to.be.closeTo(1200, 0.001); - }); - }); - - describe('Purdy Points Model', () => { - test('Predictions should be approximately correct', () => { - const result = raceUtils.predictTime(5000, 1200, 10000, 'PurdyPointsModel'); - expect(result).to.be.closeTo(2490, 1); - }); - - test('Should predict identical times for itentical distances', () => { - const result = raceUtils.predictTime(5000, 1200, 5000, 'PurdyPointsModel'); - expect(result).to.be.closeTo(1200, 0.001); - }); - }); - - describe('VO2 Max Model', () => { - test('Predictions should be approximately correct', () => { - const result = raceUtils.predictTime(5000, 1200, 10000, 'VO2MaxModel'); - expect(result).to.be.closeTo(2488, 1); - }); - - test('Should predict identical times for itentical distances', () => { - const result = raceUtils.predictTime(5000, 1200, 5000, 'VO2MaxModel'); - expect(result).to.be.closeTo(1200, 0.001); - }); - }); - - describe('Cameron Model', () => { - test('Predictions should be approximately correct', () => { - const result = raceUtils.predictTime(5000, 1200, 10000, 'CameronModel'); - expect(result).to.be.closeTo(2500, 1); - }); - - test('Should predict identical times for itentical distances', () => { - const result = raceUtils.predictTime(5000, 1200, 5000, 'CameronModel'); - expect(result).to.be.closeTo(1200, 0.001); - }); - }); - - describe('Riegel Model', () => { - test('Predictions should be approximately correct', () => { - const result = raceUtils.predictTime(5000, 1200, 10000, 'RiegelModel'); - expect(result).to.be.closeTo(2502, 1); - }); - - test('Should predict identical times for itentical distances', () => { - const result = raceUtils.predictTime(5000, 1200, 5000, 'RiegelModel'); - expect(result).to.be.closeTo(1200, 0.001); - }); - }); -}); - -describe('predictDistance method', () => { - describe('Average Model', () => { - test('Predictions should be correct', () => { - const riegel = raceUtils.predictTime(5000, 1200, 10000, 'AverageModel'); - const cameron = raceUtils.predictTime(5000, 1200, 10000, 'AverageModel'); - const purdyPoints = raceUtils.predictTime(5000, 1200, 10000, 'AverageModel'); - const vo2Max = raceUtils.predictTime(5000, 1200, 10000, 'AverageModel'); - const expected = (riegel + cameron + purdyPoints + vo2Max) / 4; - - const result = raceUtils.predictDistance(1200, 5000, expected); - expect(result).to.be.closeTo(10000, 10); - }); - - test('Should predict identical times for itentical distances', () => { - const result = raceUtils.predictDistance(1200, 5000, 1200, 'AverageModel'); - expect(result).to.be.closeTo(5000, 0.001); - }); - }); - - describe('Purdy Points Model', () => { - test('Predictions should be approximately correct', () => { - const result = raceUtils.predictDistance(1200, 5000, 2490, 'PurdyPointsModel'); - expect(result).to.be.closeTo(10000, 10); - }); - - test('Should predict identical times for itentical distances', () => { - const result = raceUtils.predictDistance(1200, 5000, 1200, 'PurdyPointsModel'); - expect(result).to.be.closeTo(5000, 0.001); - }); - }); - - describe('VO2 Max Model', () => { - test('Predictions should be approximately correct', () => { - const result = raceUtils.predictDistance(1200, 5000, 2488, 'VO2MaxModel'); - expect(result).to.be.closeTo(10000, 10); - }); - - test('Should predict identical times for itentical distances', () => { - const result = raceUtils.predictDistance(1200, 5000, 1200, 'VO2MaxModel'); - expect(result).to.be.closeTo(5000, 0.001); - }); - }); - - describe('Cameron Model', () => { - test('Predictions should be approximately correct', () => { - const result = raceUtils.predictDistance(1200, 5000, 2500, 'CameronModel'); - expect(result).to.be.closeTo(10000, 10); - }); - - test('Should predict identical times for itentical distances', () => { - const result = raceUtils.predictDistance(1200, 5000, 1200, 'CameronModel'); - expect(result).to.be.closeTo(5000, 0.001); - }); - }); - - describe('Riegel Model', () => { - test('Predictions should be approximately correct', () => { - const result = raceUtils.predictDistance(1200, 5000, 2502, 'RiegelModel'); - expect(result).to.be.closeTo(10000, 10); - }); - - test('Should predict identical times for itentical distances', () => { - const result = raceUtils.predictDistance(1200, 5000, 1200, 'RiegelModel'); - expect(result).to.be.closeTo(5000, 0.001); - }); - }); -}); - -describe('getVO2 method', () => { - test('Result should be approximately correct', () => { - const result = raceUtils.getVO2(5000, 1200); - expect(result).to.be.closeTo(47.4, 0.1); - }); -}); - -describe('getVO2Percentage method', () => { - test('Result should be approximately correct', () => { - const result = raceUtils.getVO2Percentage(660); - expect(result).to.be.closeTo(1, 0.001); - }); -}); - -describe('getVO2Max method', () => { - test('Result should be approximately correct', () => { - const result = raceUtils.getVO2Max(5000, 1200); - expect(result).to.be.closeTo(49.8, 0.1); - }); -}); - -describe('getPurdyPoints method', () => { - test('Result should be approximately correct', () => { - const result = raceUtils.getPurdyPoints(5000, 1200); - expect(result).to.be.closeTo(454, 1); - }); -}); diff --git a/tests/unit/utils/targets.spec.js b/tests/unit/utils/targets.spec.js @@ -1,21 +0,0 @@ -import { describe, test, expect } from 'vitest'; -import * as targets from '@/utils/targets'; - -describe('sort method', () => { - test('should correctly sort targets', () => { - // Initialize unsorted and sorted targets - const input = [ - { time: 60, type: 'time' }, - { distanceUnit: 'kilometers', distanceValue: 5, type: 'distance' }, - { distanceUnit: 'miles', distanceValue: 3, type: 'distance' }, - ]; - const expected = [ - { distanceUnit: 'miles', distanceValue: 3, type: 'distance' }, - { distanceUnit: 'kilometers', distanceValue: 5, type: 'distance' }, - { time: 60, type: 'time' }, - ]; - - // Assert sort method sorts targets correctly - expect(targets.sort(input)).to.deep.equal(expected); - }); -}); diff --git a/tests/unit/utils/units.spec.js b/tests/unit/utils/units.spec.js @@ -1,62 +0,0 @@ -import { describe, test, expect } from 'vitest'; -import * as units from '@/utils/units'; - -describe('convertTime method', () => { - test('90 seconds should equal 1.5 minutes', () => { - const result = units.convertTime(90, 'seconds', 'minutes'); - expect(result).to.equal(1.5); - }); - - test('1.5 minutes should equal 95 seconds', () => { - const result = units.convertTime(1.5, 'minutes', 'seconds'); - expect(result).to.equal(90); - }); -}); - -describe('convertDistance method', () => { - test('100 meters should equal 0.1 kilometers', () => { - const result = units.convertDistance(100, 'meters', 'kilometers'); - expect(result).to.equal(0.1); - }); - - test('0.1 kilometers should equal 100 meters', () => { - const result = units.convertDistance(0.1, 'kilometers', 'meters'); - expect(result).to.equal(100); - }); -}); - -describe('convertSpeed method', () => { - test('1000 meters per seconds should equal 3600 kilometers per hour', () => { - const result = units.convertSpeed(1000, 'meters_per_second', 'kilometers_per_hour'); - expect(result).to.equal(3600); - }); - - test('3600 kilometers per hour should equal 1000 meters per second', () => { - const result = units.convertSpeed(3600, 'kilometers_per_hour', 'meters_per_second'); - expect(result).to.equal(1000); - }); -}); - -describe('convertPace method', () => { - test('1 second per meter should equal 1000 seconds per kilometer', () => { - const result = units.convertPace(1, 'seconds_per_meter', 'seconds_per_kilometer'); - expect(result).to.equal(1000); - }); - - test('1000 seconds per kilometer should equal 1 second per meter', () => { - const result = units.convertPace(1000, 'seconds_per_kilometer', 'seconds_per_meter'); - expect(result).to.equal(1); - }); -}); - -describe('convertSpeedPace method', () => { - test('3600 kilometers per hour should equal 1 second per kilometer', () => { - const result = units.convertSpeedPace(3600, 'kilometers_per_hour', 'seconds_per_kilometer'); - expect(result).to.equal(1); - }); - - test('1 second per kilometer should equal 3600 kilometers per hour', () => { - const result = units.convertSpeedPace(3600, 'seconds_per_kilometer', 'kilometers_per_hour'); - expect(result).to.equal(1); - }); -}); diff --git a/tests/unit/views/BatchCalculator.spec.js b/tests/unit/views/BatchCalculator.spec.js @@ -1,129 +1,147 @@ import { beforeEach, test, expect } from 'vitest'; import { shallowMount } from '@vue/test-utils'; import BatchCalculator from '@/views/BatchCalculator.vue'; +import { defaultTargetSets } from '@/core/targets'; +import { detectDefaultUnitSystem } from '@/core/units'; beforeEach(() => { localStorage.clear(); -}) - -test('should load input from localStorage', async () => { - // Initialize localStorage - localStorage.setItem('running-tools.batch-calculator-input', JSON.stringify({ - distanceValue: 2, - distanceUnit: 'miles', - time: 600, - })); +}); +test('should initialize regular options to default values', async () => { // Initialize component const wrapper = shallowMount(BatchCalculator); - // Assert options loaded + // Assert regular options are initialized expect(wrapper.findComponent({ name: 'pace-input' }).vm.modelValue).to.deep.equal({ - distanceValue: 2, - distanceUnit: 'miles', - time: 600, + distanceValue: 5, + distanceUnit: 'kilometers', + time: 1200, + }); + expect(wrapper.findComponent({ name: 'time-input' }).vm.modelValue).to.equal(15); + expect(wrapper.findComponent({ name: 'integer-input' }).vm.modelValue).to.equal(20); + expect(wrapper.find('select[aria-label="Calculator"]').element.value).to.equal('workout'); + expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.globalOptions).to.deep.equal({ + defaultUnitSystem: detectDefaultUnitSystem(), + racePredictionOptions: { + model: 'AverageModel', + riegelExponent: 1.06, + }, + }); + expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.batchOptions).to.deep.equal({ + calculator: 'workout', + input: { + distanceValue: 5, + distanceUnit: 'kilometers', + time: 1200, + }, + increment: 15, + label: '', + rows: 20, }); }); -test('should save input to localStorage when modified', async () => { +test('should initialize calculator options to default values', async () => { // Initialize component const wrapper = shallowMount(BatchCalculator); - // Update input pace - await wrapper.findComponent({ name: 'pace-input' }).setValue({ - distanceValue: 2, - distanceUnit: 'miles', - time: 600, + // Assert pace calculator options are initialized + await wrapper.find('select[aria-label="Calculator"]').setValue('pace'); + expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.options).to.deep.equal({ + input: { + distanceValue: 5, + distanceUnit: 'kilometers', + time: 1200, + }, + selectedTargetSet: '_pace_targets', + }); + expect(wrapper.findComponent({ name: 'double-output-table' }).vm.targets) + .to.deep.equal(defaultTargetSets._pace_targets.targets); + + // Assert race calculator options are loaded + await wrapper.find('select[aria-label="Calculator"]').setValue('race'); + expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.options).to.deep.equal({ + input: { + distanceValue: 5, + distanceUnit: 'kilometers', + time: 1200, + }, + selectedTargetSet: '_race_targets', }); + expect(wrapper.findComponent({ name: 'double-output-table' }).vm.targets) + .to.deep.equal(defaultTargetSets._race_targets.targets); - // Assert input saved - expect(localStorage.getItem('running-tools.batch-calculator-input')).to.equal(JSON.stringify({ - distanceValue: 2, - distanceUnit: 'miles', - time: 600, - })); + // Assert workout calculator options are loaded + await wrapper.find('select[aria-label="Calculator"]').setValue('workout'); + expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.options).to.deep.equal({ + customTargetNames: false, + input: { + distanceValue: 5, + distanceUnit: 'kilometers', + time: 1200, + }, + selectedTargetSet: '_workout_targets', + }); + expect(wrapper.findComponent({ name: 'double-output-table' }).vm.targets) + .to.deep.equal(defaultTargetSets._workout_targets.targets); }); -test('should load batch options from localStorage', async () => { +test('should load regular options from localStorage', async () => { // Initialize localStorage + localStorage.setItem('running-tools.global-options', JSON.stringify({ + defaultUnitSystem: 'imperial', + racePredictionOptions: { + model: 'PurdyPointsModel', + riegelExponent: 1.2, + }, + })); localStorage.setItem('running-tools.batch-calculator-options', JSON.stringify({ calculator: 'race', increment: 32, + input: { + distanceValue: 2, + distanceUnit: 'miles', + time: 600, + }, + label: 'foo', rows: 15, })); // Initialize component const wrapper = shallowMount(BatchCalculator); + // Assert data loaded + expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.globalOptions).to.deep.equal({ + defaultUnitSystem: 'imperial', + racePredictionOptions: { + model: 'PurdyPointsModel', + riegelExponent: 1.2, + }, + }); + // Assert options loaded expect(wrapper.find('select[aria-label="Calculator"]').element.value).to.equal('race'); + expect(wrapper.findComponent({ name: 'pace-input' }).vm.modelValue).to.deep.equal({ + distanceValue: 2, + distanceUnit: 'miles', + time: 600, + }); expect(wrapper.findComponent({ name: 'time-input' }).vm.modelValue).to.equal(32); expect(wrapper.findComponent({ name: 'integer-input' }).vm.modelValue).to.equal(15); -}); - -test('should save batch options to localStorage when modified', async () => { - // Initialize component - const wrapper = shallowMount(BatchCalculator); - - // Update active calculator - await wrapper.find('select[aria-label="Calculator"]').setValue('race'); - - // Assert options saved - expect(localStorage.getItem('running-tools.batch-calculator-options')).to.equal(JSON.stringify({ - calculator: 'race', - increment: 15, - rows: 20, - })); - - // Update increment value - await wrapper.findComponent({ name: 'time-input' }).setValue(32); - - // Assert options saved - expect(localStorage.getItem('running-tools.batch-calculator-options')).to.equal(JSON.stringify({ - calculator: 'race', - increment: 32, - rows: 20, - })); - - // Update number of rows - await wrapper.findComponent({ name: 'integer-input' }).setValue(15); - - // Assert options saved - expect(localStorage.getItem('running-tools.batch-calculator-options')).to.equal(JSON.stringify({ + expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.batchOptions).to.deep.equal({ calculator: 'race', + input: { + distanceValue: 2, + distanceUnit: 'miles', + time: 600, + }, increment: 32, + label: 'foo', rows: 15, - })); -}); - -test('should load default units setting from localStorage', async () => { - // Initialize localStorage - localStorage.setItem('running-tools.default-unit-system', '"metric"'); - - // Initialize component - const wrapper = shallowMount(BatchCalculator); - - // Assert default units setting loaded - expect(wrapper.find('select[aria-label="Default units"]').element.value).to.equal('metric'); - expect(wrapper.findComponent({ name: 'target-set-selector' }).vm.defaultUnitSystem) - .to.equal('metric'); -}); - -test('should save default units setting from localStorage when modified', async () => { - // Initialize localStorage - localStorage.setItem('running-tools.default-unit-system', '"metric"'); - - // Initialize component - const wrapper = shallowMount(BatchCalculator); - - // Change default units setting - await wrapper.find('select[aria-label="Default units"]').setValue('imperial'); - - // New default units should be saved to localStorage - expect(localStorage.getItem('running-tools.default-unit-system')).to.equal('"imperial"'); + }); }); -test('should load selected target set from localStorage', async () => { +test('should load calculator options from localStorage', async () => { // Initialize localStorage const selectedTargetSets = [ { @@ -157,7 +175,6 @@ test('should load selected target set from localStorage', async () => { ], } })); - localStorage.setItem('running-tools.pace-calculator-target-set', '"A"'); localStorage.setItem('running-tools.race-calculator-target-sets', JSON.stringify({ 'C': selectedTargetSets[1], 'D': { @@ -167,7 +184,6 @@ test('should load selected target set from localStorage', async () => { ], } })); - localStorage.setItem('running-tools.race-calculator-target-set', '"C"'); localStorage.setItem('running-tools.workout-calculator-target-sets', JSON.stringify({ 'E': selectedTargetSets[2], 'F': { @@ -177,31 +193,197 @@ test('should load selected target set from localStorage', async () => { ], } })); - localStorage.setItem('running-tools.workout-calculator-target-set', '"E"'); + localStorage.setItem('running-tools.pace-calculator-options', JSON.stringify({ + input: { + distanceValue: 1, + distanceUnit: 'miles', + time: 300, + }, + selectedTargetSet: 'A', + })); + localStorage.setItem('running-tools.race-calculator-options', JSON.stringify({ + input: { + distanceValue: 1.1, + distanceUnit: 'miles', + time: 310, + }, + selectedTargetSet: 'C', + })); + localStorage.setItem('running-tools.workout-calculator-options', JSON.stringify({ + customTargetNames: true, + input: { + distanceValue: 1.2, + distanceUnit: 'miles', + time: 320, + }, + selectedTargetSet: 'E', + })); // Initialize component const wrapper = shallowMount(BatchCalculator); - // Assert selected pace target set is loaded + // Assert pace calculator options are loaded await wrapper.find('select[aria-label="Calculator"]').setValue('pace'); - expect(wrapper.findComponent({ name: 'target-set-selector' }).vm.selectedTargetSet).to.equal('A'); + expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.options).to.deep.equal({ + input: { + distanceValue: 1.0, + distanceUnit: 'miles', + time: 300, + }, + selectedTargetSet: 'A', + }); expect(wrapper.findComponent({ name: 'double-output-table' }).vm.targets) .to.deep.equal(selectedTargetSets[0].targets); - // Assert selected race target set is loaded + // Assert race calculator options are loaded await wrapper.find('select[aria-label="Calculator"]').setValue('race'); - expect(wrapper.findComponent({ name: 'target-set-selector' }).vm.selectedTargetSet).to.equal('C'); + expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.options).to.deep.equal({ + input: { + distanceValue: 1.1, + distanceUnit: 'miles', + time: 310, + }, + selectedTargetSet: 'C', + }); expect(wrapper.findComponent({ name: 'double-output-table' }).vm.targets) .to.deep.equal(selectedTargetSets[1].targets); - // Assert selected workout target set is loaded + // Assert workout calculator options are loaded await wrapper.find('select[aria-label="Calculator"]').setValue('workout'); - expect(wrapper.findComponent({ name: 'target-set-selector' }).vm.selectedTargetSet).to.equal('E'); + expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.options).to.deep.equal({ + customTargetNames: true, + input: { + distanceValue: 1.2, + distanceUnit: 'miles', + time: 320, + }, + selectedTargetSet: 'E', + }); expect(wrapper.findComponent({ name: 'double-output-table' }).vm.targets) .to.deep.equal(selectedTargetSets[2].targets); }); -test('should save selected target set to localStorage when modified', async () => { +test('should save regular options to localStorage when modified', async () => { + // Initialize localStorage + localStorage.setItem('running-tools.default-unit-system', '"metric"'); + + // Initialize component + const wrapper = shallowMount(BatchCalculator); + + // Change default units setting + await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({ + defaultUnitSystem: 'imperial', + racePredictionOptions: { + model: 'CameronModel', + riegelExponent: 1.30, + }, + }, 'globalOptions'); + + // New default units should be saved to localStorage + expect(localStorage.getItem('running-tools.global-options')).to.equal(JSON.stringify({ + defaultUnitSystem: 'imperial', + racePredictionOptions: { + model: 'CameronModel', + riegelExponent: 1.30, + }, + })); + + // Update input pace + await wrapper.findComponent({ name: 'pace-input' }).setValue({ + distanceValue: 2, + distanceUnit: 'miles', + time: 600, + }); + + // Assert options saved + expect(localStorage.getItem('running-tools.batch-calculator-options')).to.equal(JSON.stringify({ + calculator: 'workout', + increment: 15, + input: { + distanceValue: 2, + distanceUnit: 'miles', + time: 600, + }, + label: '', + rows: 20, + })); + + // Update increment value + await wrapper.findComponent({ name: 'time-input' }).setValue(32); + + // Assert options saved + expect(localStorage.getItem('running-tools.batch-calculator-options')).to.equal(JSON.stringify({ + calculator: 'workout', + increment: 32, + input: { + distanceValue: 2, + distanceUnit: 'miles', + time: 600, + }, + label: '', + rows: 20, + })); + + // Update number of rows + await wrapper.findComponent({ name: 'integer-input' }).setValue(15); + + // Assert options saved + expect(localStorage.getItem('running-tools.batch-calculator-options')).to.equal(JSON.stringify({ + calculator: 'workout', + increment: 32, + input: { + distanceValue: 2, + distanceUnit: 'miles', + time: 600, + }, + label: '', + rows: 15, + })); + + // Update batch column label + await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({ + calculator: 'workout', + increment: 32, + input: { + distanceValue: 2, + distanceUnit: 'miles', + time: 600, + }, + label: 'foo', + rows: 15, + }, 'batch-options'); + + // Assert options saved + expect(localStorage.getItem('running-tools.batch-calculator-options')).to.equal(JSON.stringify({ + calculator: 'workout', + increment: 32, + input: { + distanceValue: 2, + distanceUnit: 'miles', + time: 600, + }, + label: 'foo', + rows: 15, + })); + + // Update active calculator + await wrapper.find('select[aria-label="Calculator"]').setValue('race'); + + // Assert options saved + expect(localStorage.getItem('running-tools.batch-calculator-options')).to.equal(JSON.stringify({ + calculator: 'race', + increment: 32, + input: { + distanceValue: 2, + distanceUnit: 'miles', + time: 600, + }, + label: 'foo', + rows: 15, + })); +}); + +test('should save calculator options to localStorage when modified', async () => { // Initialize localStorage const selectedTargetSets = [ { @@ -235,7 +417,14 @@ test('should save selected target set to localStorage when modified', async () = ], } })); - localStorage.setItem('running-tools.pace-calculator-target-set', '"B"'); + localStorage.setItem('running-tools.pace-calculator-options', JSON.stringify({ + input: { + distanceValue: 1, + distanceUnit: 'miles', + time: 300, + }, + selectedTargetSet: 'B', + })); localStorage.setItem('running-tools.race-calculator-target-sets', JSON.stringify({ 'C': selectedTargetSets[1], 'D': { @@ -245,7 +434,14 @@ test('should save selected target set to localStorage when modified', async () = ], } })); - localStorage.setItem('running-tools.race-calculator-target-set', '"D"'); + localStorage.setItem('running-tools.race-calculator-options', JSON.stringify({ + input: { + distanceValue: 1.1, + distanceUnit: 'miles', + time: 310, + }, + selectedTargetSet: 'D', + })); localStorage.setItem('running-tools.workout-calculator-target-sets', JSON.stringify({ 'E': selectedTargetSets[2], 'F': { @@ -255,60 +451,85 @@ test('should save selected target set to localStorage when modified', async () = ], } })); - localStorage.setItem('running-tools.workout-calculator-target-set', '"F"'); + localStorage.setItem('running-tools.workout-calculator-options', JSON.stringify({ + customWorkoutNames: false, + input: { + distanceValue: 1.2, + distanceUnit: 'miles', + time: 320, + }, + selectedTargetSet: 'F', + })); // Initialize component const wrapper = shallowMount(BatchCalculator); - // Update selected pace target set + // Update pace calculator options X await wrapper.find('select[aria-label="Calculator"]').setValue('pace'); - await wrapper.findComponent({ name: 'target-set-selector' }).setValue('A', 'selectedTargetSet'); + await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({ + input: { + distanceValue: 1.0, + distanceUnit: 'miles', + time: 300, + }, + selectedTargetSet: 'A', + }, 'options'); expect(wrapper.findComponent({ name: 'double-output-table' }).vm.targets) .to.deep.equal(selectedTargetSets[0].targets); - expect(localStorage.getItem('running-tools.pace-calculator-target-set')).to.equal('"A"'); - // Assert selected race target set is loaded + // Update race calculator options await wrapper.find('select[aria-label="Calculator"]').setValue('race'); - await wrapper.findComponent({ name: 'target-set-selector' }).setValue('C', 'selectedTargetSet'); + await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({ + input: { + distanceValue: 1.1, + distanceUnit: 'miles', + time: 310, + }, + selectedTargetSet: 'C', + }, 'options'); expect(wrapper.findComponent({ name: 'double-output-table' }).vm.targets) .to.deep.equal(selectedTargetSets[1].targets); - expect(localStorage.getItem('running-tools.race-calculator-target-set')).to.equal('"C"'); - // Assert selected workout target set is loaded + // Update workout calculator options await wrapper.find('select[aria-label="Calculator"]').setValue('workout'); - await wrapper.findComponent({ name: 'target-set-selector' }).setValue('E', 'selectedTargetSet'); + await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({ + customTargetNames: true, + input: { + distanceValue: 1.2, + distanceUnit: 'miles', + time: 320, + }, + selectedTargetSet: 'E', + }, 'options'); expect(wrapper.findComponent({ name: 'double-output-table' }).vm.targets) .to.deep.equal(selectedTargetSets[2].targets); - expect(localStorage.getItem('running-tools.workout-calculator-target-set')).to.equal('"E"'); -}); -test('should load advanced model options from localStorage', async () => { - // Initialize localStorage - localStorage.setItem('running-tools.race-calculator-options', JSON.stringify({ - model: 'PurdyPointsModel', - riegelExponent: 1.2, + // Assert options saved to localStorage + expect(localStorage.getItem('running-tools.pace-calculator-options')).to.equal(JSON.stringify({ + input: { + distanceValue: 1.0, + distanceUnit: 'miles', + time: 300, + }, + selectedTargetSet: 'A', })); - localStorage.setItem('running-tools.workout-calculator-options', JSON.stringify({ - model: 'RiegelModel', - riegelExponent: 1.1, + expect(localStorage.getItem('running-tools.race-calculator-options')).to.equal(JSON.stringify({ + input: { + distanceValue: 1.1, + distanceUnit: 'miles', + time: 310, + }, + selectedTargetSet: 'C', + })); + expect(localStorage.getItem('running-tools.workout-calculator-options')).to.equal(JSON.stringify({ + customTargetNames: true, + input: { + distanceValue: 1.2, + distanceUnit: 'miles', + time: 320, + }, + selectedTargetSet: 'E', })); - - // Initialize component - const wrapper = shallowMount(BatchCalculator); - - // Assert race prediction options are loaded - await wrapper.find('select[aria-label="Calculator"]').setValue('race'); - expect(wrapper.findComponent({ name: 'RaceOptions' }).vm.modelValue).to.deep.equal({ - model: 'PurdyPointsModel', - riegelExponent: 1.2, - }); - - // Assert workout prediction options are loaded - await wrapper.find('select[aria-label="Calculator"]').setValue('workout'); - expect(wrapper.findComponent({ name: 'RaceOptions' }).vm.modelValue).to.deep.equal({ - model: 'RiegelModel', - riegelExponent: 1.1, - }); }); test('should pass correct input props to DoubleOutputTable', async () => { @@ -324,6 +545,7 @@ test('should pass correct input props to DoubleOutputTable', async () => { 1200, 1215, 1230, 1245, 1260, 1275, 1290, 1305, 1320, 1335, 1350, 1365, 1380, 1395, 1410, 1425, 1440, 1455, 1470, 1485, ]); + expect(wrapper.findComponent({ name: 'double-output-table' }).vm.label).to.equal('5 km'); // Change input pace await wrapper.findComponent({ name: 'pace-input' }).setValue({ @@ -341,6 +563,7 @@ test('should pass correct input props to DoubleOutputTable', async () => { 600, 615, 630, 645, 660, 675, 690, 705, 720, 735, 750, 765, 780, 795, 810, 825, 840, 855, 870, 885, ]); + expect(wrapper.findComponent({ name: 'double-output-table' }).vm.label).to.equal('2 mi'); // Change increment value await wrapper.findComponent({ name: 'time-input' }).setValue(10); @@ -354,6 +577,7 @@ test('should pass correct input props to DoubleOutputTable', async () => { 600, 610, 620, 630, 640, 650, 660, 670, 680, 690, 700, 710, 720, 730, 740, 750, 760, 770, 780, 790, ]); + expect(wrapper.findComponent({ name: 'double-output-table' }).vm.label).to.equal('2 mi'); // Change number of rows await wrapper.findComponent({ name: 'integer-input' }).setValue(15); @@ -366,19 +590,177 @@ test('should pass correct input props to DoubleOutputTable', async () => { expect(wrapper.findComponent({ name: 'double-output-table' }).vm.inputTimes).to.deep.equal([ 600, 610, 620, 630, 640, 650, 660, 670, 680, 690, 700, 710, 720, 730, 740, ]); + expect(wrapper.findComponent({ name: 'double-output-table' }).vm.label).to.equal('2 mi'); + + // Enter custom batch column label + await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({ + calculator: 'workout', + increment: 10, + input: { + distanceValue: 2, + distanceUnit: 'miles', + time: 600, + }, + label: 'foo', + rows: 15, + }, 'batchOptions'); + + // Assert that the props are updated + expect(wrapper.findComponent({ name: 'double-output-table' }).vm.inputDistance).to.deep.equal({ + distanceValue: 2, + distanceUnit: 'miles', + }); + expect(wrapper.findComponent({ name: 'double-output-table' }).vm.inputTimes).to.deep.equal([ + 600, 610, 620, 630, 640, 650, 660, 670, 680, 690, 700, 710, 720, 730, 740, + ]); + expect(wrapper.findComponent({ name: 'double-output-table' }).vm.label).to.equal('2 mi'); + + // Enable target name customization + await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({ + customTargetNames: true, + input: { + distanceValue: 5, + distanceUnit: 'kilometers', + time: 1200, + }, + selectedTargetSet: '_workout_targets', + }, 'options'); + + // Assert that the props are updated + expect(wrapper.findComponent({ name: 'double-output-table' }).vm.inputDistance).to.deep.equal({ + distanceValue: 2, + distanceUnit: 'miles', + }); + expect(wrapper.findComponent({ name: 'double-output-table' }).vm.inputTimes).to.deep.equal([ + 600, 610, 620, 630, 640, 650, 660, 670, 680, 690, 700, 710, 720, 730, 740, + ]); + expect(wrapper.findComponent({ name: 'double-output-table' }).vm.label).to.equal('foo'); + + // Switch calculators + await wrapper.find('select[aria-label="Calculator"]').setValue('race'); + + // Assert that the props are updated + expect(wrapper.findComponent({ name: 'double-output-table' }).vm.inputDistance).to.deep.equal({ + distanceValue: 2, + distanceUnit: 'miles', + }); + expect(wrapper.findComponent({ name: 'double-output-table' }).vm.inputTimes).to.deep.equal([ + 600, 610, 620, 630, 640, 650, 660, 670, 680, 690, 700, 710, 720, 730, 740, + ]); + expect(wrapper.findComponent({ name: 'double-output-table' }).vm.label).to.equal('2 mi'); +}); + +test('should correctly set AdvancedOptionsInput props', async () => { + // Initialize component + const wrapper = shallowMount(BatchCalculator); + + // Set input pace and batch options + await wrapper.findComponent({ name: 'pace-input' }).setValue({ + distanceValue: 2, + distanceUnit: 'miles', + time: 600, + }); + await wrapper.findComponent({ name: 'time-input' }).setValue(32); + await wrapper.findComponent({ name: 'integer-input' }).setValue(15); + + // Assert batch props are correct + expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.batchOptions).to.deep.equal({ + calculator: 'workout', + increment: 32, + input: { + distanceValue: 2, + distanceUnit: 'miles', + time: 600, + }, + label: '', + rows: 15, + }); + + // Assert props are correct for pace calculator + await wrapper.find('select[aria-label="Calculator"]').setValue('pace'); + expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.type).to.equal('pace'); + expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.globalOptions).to.deep.equal({ + defaultUnitSystem: 'imperial', + racePredictionOptions: { + model: 'AverageModel', + riegelExponent: 1.06, + }, + }); + expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.options).to.deep.equal({ + input: { + distanceValue: 5, + distanceUnit: 'kilometers', + time: 1200, + }, + selectedTargetSet: '_pace_targets', + }); + + // Assert props are correct for race calculator + await wrapper.find('select[aria-label="Calculator"]').setValue('race'); + expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.type).to.equal('race'); + expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.globalOptions).to.deep.equal({ + defaultUnitSystem: 'imperial', + racePredictionOptions: { + model: 'AverageModel', + riegelExponent: 1.06, + }, + }); + expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.options).to.deep.equal({ + input: { + distanceValue: 5, + distanceUnit: 'kilometers', + time: 1200, + }, + selectedTargetSet: '_race_targets', + }); + + // Assert props are correct for workout calculator + await wrapper.find('select[aria-label="Calculator"]').setValue('workout'); + expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.type).to.equal('workout'); + expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.globalOptions).to.deep.equal({ + defaultUnitSystem: 'imperial', + racePredictionOptions: { + model: 'AverageModel', + riegelExponent: 1.06, + }, + }); + expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.options).to.deep.equal({ + customTargetNames: false, + input: { + distanceValue: 5, + distanceUnit: 'kilometers', + time: 1200, + }, + selectedTargetSet: '_workout_targets', + }); }); test('should correctly calculate outputs', async () => { // Initialize localStorage + localStorage.setItem('running-tools.global-options', JSON.stringify({ + defaultUnitSystem: 'imperial', + racePredictionOptions: { + model: 'PurdyPointsModel', + riegelExponent: 1.2, + }, + })); localStorage.setItem('running-tools.race-calculator-options', JSON.stringify({ - model: 'PurdyPointsModel', - riegelExponent: 1.2, + input: { + distanceValue: 1.1, + distanceUnit: 'miles', + time: 310, + }, + selectedTargetSet: '_race_targets', })); localStorage.setItem('running-tools.workout-calculator-options', JSON.stringify({ - model: 'RiegelModel', - riegelExponent: 1.1, + customTargetNames: false, + input: { + distanceValue: 1.2, + distanceUnit: 'miles', + time: 320, + }, + selectedTargetSet: '_workout_targets', })); - localStorage.setItem('running-tools.default-unit-system', '"imperial"'); // Initialize component const wrapper = shallowMount(BatchCalculator); @@ -412,8 +794,8 @@ test('should correctly calculate outputs', async () => { const workoutTarget = { type: 'time', time: 3600, splitValue: 1, splitUnit: 'miles' }; const result = calculate(input, workoutTarget); expect(result.key).to.equal('1 mi @ 1:00:00'); - expect(result.value).to.equal('5:53.07'); + expect(result.value).to.equal('5:29'); expect(result.pace).to.equal(''); - expect(result.sort).to.be.closeTo(353.07, 0.01); + expect(result.sort).to.be.closeTo(329.48, 0.01); expect(result.result).to.equal('value'); }); diff --git a/tests/unit/views/PaceCalculator.spec.js b/tests/unit/views/PaceCalculator.spec.js @@ -1,11 +1,184 @@ import { beforeEach, test, expect } from 'vitest'; import { shallowMount } from '@vue/test-utils'; import PaceCalculator from '@/views/PaceCalculator.vue'; -import { defaultTargetSets } from '@/utils/targets'; +import { defaultTargetSets } from '@/core/targets'; +import { detectDefaultUnitSystem } from '@/core/units'; beforeEach(() => { localStorage.clear(); -}) +}); + +test('should initialize options to default values', async () => { + // Initialize component + const wrapper = shallowMount(PaceCalculator); + + // Assert options are initialized + expect(wrapper.findComponent({ name: 'pace-input' }).vm.modelValue).to.deep.equal({ + distanceValue: 5, + distanceUnit: 'kilometers', + time: 1200, + }); + expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.globalOptions + .defaultUnitSystem).to.equal(detectDefaultUnitSystem()); + expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.options).to.deep.equal({ + input: { + distanceValue: 5, + distanceUnit: 'kilometers', + time: 1200, + }, + selectedTargetSet: '_pace_targets', + }); + expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.targetSets) + .to.deep.equal({ _pace_targets: defaultTargetSets._pace_targets }); + expect(wrapper.findComponent({ name: 'single-output-table' }).vm.targets) + .to.deep.equal(defaultTargetSets._pace_targets.targets); +}); + +test('should load options from localStorage', async () => { + const targetSets = { + '_pace_targets': { + name: 'Pace targets #1', + targets: [ + { type: 'distance', distanceValue: 400, distanceUnit: 'meters' }, + { type: 'distance', distanceValue: 800, distanceUnit: 'meters' }, + { type: 'distance', distanceValue: 1600, distanceUnit: 'meters' }, + ], + }, + 'B': { + name: 'Pace targets #2', + targets: [ + { type: 'distance', distanceValue: 1, distanceUnit: 'miles' }, + { type: 'distance', distanceValue: 2, distanceUnit: 'miles' }, + { type: 'distance', distanceValue: 5, distanceUnit: 'kilometers' }, + ], + }, + }; + + // Initialize localStorage + localStorage.setItem('running-tools.global-options', JSON.stringify({ + defaultUnitSystem: 'imperial', + racePredictionOptions: { + model: 'PurdyPointsModel', + riegelExponent: 1.2, + }, + })); + localStorage.setItem('running-tools.pace-calculator-target-sets', JSON.stringify(targetSets)); + localStorage.setItem('running-tools.pace-calculator-options', JSON.stringify({ + input: { + distanceValue: 1, + distanceUnit: 'miles', + time: 600, + }, + selectedTargetSet: 'B', + })); + + // Initialize component + const wrapper = shallowMount(PaceCalculator); + + // Assert options are loaded + expect(wrapper.findComponent({ name: 'pace-input' }).vm.modelValue).to.deep.equal({ + distanceValue: 1, + distanceUnit: 'miles', + time: 600, + }); + expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.globalOptions + .defaultUnitSystem).to.equal('imperial'); + expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.options).to.deep.equal({ + input: { + distanceValue: 1, + distanceUnit: 'miles', + time: 600, + }, + selectedTargetSet: 'B', + }); + expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.targetSets) + .to.deep.equal(targetSets); + expect(wrapper.findComponent({ name: 'single-output-table' }).vm.targets) + .to.deep.equal(targetSets.B.targets); +}); + +test('should save options to localStorage when modified', async () => { + const targetSets = { + '_pace_targets': { + name: 'Pace targets #1', + targets: [ + { type: 'distance', distanceValue: 400, distanceUnit: 'meters' }, + { type: 'distance', distanceValue: 800, distanceUnit: 'meters' }, + { type: 'distance', distanceValue: 1600, distanceUnit: 'meters' }, + ], + }, + 'B': { + name: 'Pace targets #2', + targets: [ + { type: 'distance', distanceValue: 1, distanceUnit: 'miles' }, + { type: 'distance', distanceValue: 2, distanceUnit: 'miles' }, + { type: 'distance', distanceValue: 5, distanceUnit: 'kilometers' }, + ], + }, + }; + + // Initialize component + const wrapper = shallowMount(PaceCalculator); + + // Change default units + await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({ + defaultUnitSystem: 'imperial', + racePredictionOptions: { + model: 'AverageModel', + riegelExponent: 1.06, + }, + }, 'globalOptions'); + + // New default units should be saved to localStorage + expect(localStorage.getItem('running-tools.global-options')).to.equal(JSON.stringify({ + defaultUnitSystem: 'imperial', + racePredictionOptions: { + model: 'AverageModel', + riegelExponent: 1.06, + }, + })); + + // Update input pace + await wrapper.findComponent({ name: 'pace-input' }).setValue({ + distanceValue: 1, + distanceUnit: 'miles', + time: 600, + }); + + // New input pace should be saved to localStorage + expect(localStorage.getItem('running-tools.pace-calculator-options')).to.equal(JSON.stringify({ + input: { + distanceValue: 1, + distanceUnit: 'miles', + time: 600, + }, + selectedTargetSet: '_pace_targets', + })); + + // Update target sets and selected target set + await wrapper.findComponent({ name: 'advanced-options-input' }).setValue(targetSets, + 'targetSets'); + await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({ + input: { + distanceValue: 1, + distanceUnit: 'miles', + time: 600, + }, + selectedTargetSet: 'B', + }, 'options'); + + // New selected target set should be saved to localStorage + expect(localStorage.getItem('running-tools.pace-calculator-target-sets')) + .to.equal(JSON.stringify(targetSets)); + expect(localStorage.getItem('running-tools.pace-calculator-options')).to.equal(JSON.stringify({ + input: { + distanceValue: 1, + distanceUnit: 'miles', + time: 600, + }, + selectedTargetSet: 'B', + })); +}); test('should correctly calculate time results', async () => { // Initialize component @@ -36,7 +209,7 @@ test('should correctly calculate time results', async () => { }); }); -test('should correctly calculate distance results according to default units setting', async () => { +test('should correctly calculate distance results according to global options', async () => { // Initialize component const wrapper = shallowMount(PaceCalculator); @@ -48,7 +221,13 @@ test('should correctly calculate distance results according to default units set }); // Set default units - await wrapper.find('select[aria-label="Default units"]').setValue('metric'); + await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({ + defaultUnitSystem: 'metric', + racePredictionOptions: { + model: 'AverageModel', + riegelExponent: 1.06, + }, + }, 'globalOptions'); // Get calculate result function const calculateResult = wrapper.findComponent({ name: 'single-output-table' }).vm.calculateResult; @@ -58,7 +237,13 @@ test('should correctly calculate distance results according to default units set expect(result.key).to.equal('1.61 km'); // Change default units - await wrapper.find('select[aria-label="Default units"]').setValue('imperial'); + await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({ + defaultUnitSystem: 'imperial', + racePredictionOptions: { + model: 'AverageModel', + riegelExponent: 1.06, + }, + }, 'globalOptions'); // Assert result is correct result = calculateResult({ type: 'time', time: 600 }); @@ -78,15 +263,27 @@ test('should correctly handle null target set', async () => { const wrapper = shallowMount(PaceCalculator); // Switch to invalid target set - await wrapper.findComponent({ name: 'target-set-selector' }) - .setValue('does_not_exist', 'selectedTargetSet'); + await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({ + input: { + distanceValue: 5, + distanceUnit: 'kilometers', + time: 1200, + }, + selectedTargetSet: 'does_not_exist' + }, 'options'); // Assert empty array passed to SingleOutputTable component expect(wrapper.findComponent({ name: 'single-output-table' }).vm.targets).to.deep.equal([]); // Switch to valid target set - await wrapper.findComponent({ name: 'target-set-selector' }) - .setValue('_pace_targets', 'selectedTargetSet'); + await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({ + input: { + distanceValue: 5, + distanceUnit: 'kilometers', + time: 1200, + }, + selectedTargetSet: '_pace_targets' + }, 'options'); // Assert valid targets passed to SingleOutputTable component const paceTargets = defaultTargetSets._pace_targets.targets; @@ -94,98 +291,10 @@ test('should correctly handle null target set', async () => { .to.deep.equal(paceTargets); }); -test('should load input pace from localStorage', async () => { - // Initialize localStorage - localStorage.setItem('running-tools.pace-calculator-input', JSON.stringify({ - distanceValue: 1, - distanceUnit: 'miles', - time: 600, - })); - - // Initialize component - const wrapper = shallowMount(PaceCalculator); - - // Assert data loaded - expect(wrapper.findComponent({ name: 'pace-input' }).vm.modelValue).to.deep.equal({ - distanceValue: 1, - distanceUnit: 'miles', - time: 600, - }); -}); - -test('should save input pace to localStorage', async () => { - // Initialize component - const wrapper = shallowMount(PaceCalculator); - - // Enter input pace data - await wrapper.findComponent({ name: 'pace-input' }).setValue({ - distanceValue: 1, - distanceUnit: 'miles', - time: 600, - }); - - // Assert data saved to localStorage - expect(localStorage.getItem('running-tools.pace-calculator-input')).to.equal(JSON.stringify({ - distanceValue: 1, - distanceUnit: 'miles', - time: 600, - })); -}); - -test('should load selected target set from localStorage', async () => { - // Initialize localStorage - const targetSet2 = { - name: 'Pace targets #2', - targets: [ - { type: 'distance', distanceValue: 1, distanceUnit: 'miles' }, - { type: 'distance', distanceValue: 2, distanceUnit: 'miles' }, - { type: 'distance', distanceValue: 5, distanceUnit: 'kilometers' }, - ], - }; - localStorage.setItem('running-tools.pace-calculator-target-sets', JSON.stringify({ - '_pace_targets': { - name: 'Pace targets #1', - targets: [ - { type: 'distance', distanceValue: 400, distanceUnit: 'meters' }, - { type: 'distance', distanceValue: 800, distanceUnit: 'meters' }, - { type: 'distance', distanceValue: 1600, distanceUnit: 'meters' }, - ], - }, - 'B': targetSet2, - })); - localStorage.setItem('running-tools.pace-calculator-target-set', '"B"'); - - // Initialize component - const wrapper = shallowMount(PaceCalculator); - - // Assert selection is loaded - expect(wrapper.findComponent({ name: 'target-set-selector' }).vm.selectedTargetSet) - .to.equal('B'); - expect(wrapper.findComponent({ name: 'single-output-table' }).vm.targets) - .to.deep.equal(targetSet2.targets); -}); - -test('should save selected target set to localStorage when modified', async () => { - // Initialize component - const wrapper = shallowMount(PaceCalculator); - - // Select a new target set - await wrapper.findComponent({ name: 'target-set-selector' }) - .setValue('B', 'selectedTargetSet'); - - // New selected target set should be saved to localStorage - expect(localStorage.getItem('running-tools.pace-calculator-target-set')) - .to.equal('"B"'); -}); - -test('should save default units setting to localStorage when modified', async () => { +test('should correctly set AdvancedOptionsInput type prop', async () => { // Initialize component const wrapper = shallowMount(PaceCalculator); - // Change default units - await wrapper.find('select[aria-label="Default units"]').setValue('metric'); - await wrapper.find('select[aria-label="Default units"]').setValue('imperial'); - - // New default units should be saved to localStorage - expect(localStorage.getItem('running-tools.default-unit-system')).to.equal('"imperial"'); + // Assert type prop is correctly set + expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.type).to.equal('pace'); }); diff --git a/tests/unit/views/RaceCalculator.spec.js b/tests/unit/views/RaceCalculator.spec.js @@ -1,40 +1,196 @@ import { beforeEach, test, expect } from 'vitest'; import { shallowMount } from '@vue/test-utils'; import RaceCalculator from '@/views/RaceCalculator.vue'; -import { defaultTargetSets } from '@/utils/targets'; +import { defaultTargetSets } from '@/core/targets'; +import { detectDefaultUnitSystem } from '@/core/units'; beforeEach(() => { localStorage.clear(); -}) +}); -test('should correctly predict race times', async () => { +test('should initialize options to default values', async () => { // Initialize component const wrapper = shallowMount(RaceCalculator); - // Enter input race data - await wrapper.findComponent({ name: 'pace-input' }).setValue({ + // Assert options are initialized + expect(wrapper.findComponent({ name: 'pace-input' }).vm.modelValue).to.deep.equal({ distanceValue: 5, distanceUnit: 'kilometers', time: 1200, }); + expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.globalOptions).to.deep.equal({ + defaultUnitSystem: detectDefaultUnitSystem(), + racePredictionOptions: { + model: 'AverageModel', + riegelExponent: 1.06, + }, + }); + expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.options).to.deep.equal({ + input: { + distanceValue: 5, + distanceUnit: 'kilometers', + time: 1200, + }, + selectedTargetSet: '_race_targets', + }); + expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.targetSets) + .to.deep.equal({ _race_targets: defaultTargetSets._race_targets }); + expect(wrapper.findComponent({ name: 'single-output-table' }).vm.targets) + .to.deep.equal(defaultTargetSets._race_targets.targets); +}); - // Calculate result - const calculateResult = wrapper.findComponent({ name: 'single-output-table' }).vm.calculateResult; - const result = calculateResult({ - distanceValue: 10, - distanceUnit: 'kilometers', - type: 'distance', +test('should load options from localStorage', async () => { + const targetSets = { + '_race_targets': { + name: 'Race targets #1', + targets: [ + { type: 'distance', distanceValue: 400, distanceUnit: 'meters' }, + { type: 'distance', distanceValue: 800, distanceUnit: 'meters' }, + { type: 'distance', distanceValue: 1600, distanceUnit: 'meters' }, + ], + }, + 'B': { + name: 'Race targets #2', + targets: [ + { type: 'distance', distanceValue: 1, distanceUnit: 'miles' }, + { type: 'distance', distanceValue: 2, distanceUnit: 'miles' }, + { type: 'distance', distanceValue: 5, distanceUnit: 'kilometers' }, + ], + }, + }; + + // Initialize localStorage + localStorage.setItem('running-tools.global-options', JSON.stringify({ + defaultUnitSystem: 'imperial', + racePredictionOptions: { + model: 'PurdyPointsModel', + riegelExponent: 1.2, + }, + })); + localStorage.setItem('running-tools.race-calculator-target-sets', JSON.stringify(targetSets)); + localStorage.setItem('running-tools.race-calculator-options', JSON.stringify({ + input: { + distanceValue: 1, + distanceUnit: 'miles', + time: 600, + }, + selectedTargetSet: 'B', + })); + + // Initialize component + const wrapper = shallowMount(RaceCalculator); + + // Assert options are loaded + expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.globalOptions).to.deep.equal({ + defaultUnitSystem: 'imperial', + racePredictionOptions: { + model: 'PurdyPointsModel', + riegelExponent: 1.2, + }, }); + expect(wrapper.findComponent({ name: 'pace-input' }).vm.modelValue).to.deep.equal({ + distanceValue: 1, + distanceUnit: 'miles', + time: 600, + }); + expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.options).to.deep.equal({ + input: { + distanceValue: 1, + distanceUnit: 'miles', + time: 600, + }, + selectedTargetSet: 'B', + }); + expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.targetSets) + .to.deep.equal(targetSets); + expect(wrapper.findComponent({ name: 'single-output-table' }).vm.targets) + .to.deep.equal(targetSets.B.targets); +}); - // Assert result is correct - expect(result.key).to.equal('10 km'); - expect(result.value).to.equal('41:34.80'); - expect(result.pace).to.equal('6:42 / mi'); - expect(result.result).to.equal('value'); - expect(result.sort).to.be.closeTo(2494.80, 0.01); +test('should save options to localStorage when modified', async () => { + const targetSets = { + '_race_targets': { + name: 'Race targets #1', + targets: [ + { type: 'distance', distanceValue: 400, distanceUnit: 'meters' }, + { type: 'distance', distanceValue: 800, distanceUnit: 'meters' }, + { type: 'distance', distanceValue: 1600, distanceUnit: 'meters' }, + ], + }, + 'B': { + name: 'Race targets #2', + targets: [ + { type: 'distance', distanceValue: 1, distanceUnit: 'miles' }, + { type: 'distance', distanceValue: 2, distanceUnit: 'miles' }, + { type: 'distance', distanceValue: 5, distanceUnit: 'kilometers' }, + ], + }, + }; + + // Initialize component + const wrapper = shallowMount(RaceCalculator); + + // Update options + await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({ + defaultUnitSystem: 'imperial', + racePredictionOptions: { + model: 'PurdyPointsModel', + riegelExponent: 1.2, + }, + }, 'globalOptions'); + + // New global options should be saved to localStorage + expect(localStorage.getItem('running-tools.global-options')).to.equal(JSON.stringify({ + defaultUnitSystem: 'imperial', + racePredictionOptions: { + model: 'PurdyPointsModel', + riegelExponent: 1.2, + }, + })); + + // Update input race + await wrapper.findComponent({ name: 'pace-input' }).setValue({ + distanceValue: 1, + distanceUnit: 'miles', + time: 600, + }); + + // Assert data saved to localStorage + expect(localStorage.getItem('running-tools.race-calculator-options')).to.equal(JSON.stringify({ + input: { + distanceValue: 1, + distanceUnit: 'miles', + time: 600, + }, + selectedTargetSet: '_race_targets', + })); + + // Update target sets and selected target set + await wrapper.findComponent({ name: 'advanced-options-input' }).setValue(targetSets, + 'targetSets'); + await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({ + input: { + distanceValue: 1, + distanceUnit: 'miles', + time: 600, + }, + selectedTargetSet: 'B', + }, 'options'); + + // Assert data saved to localStorage + expect(localStorage.getItem('running-tools.race-calculator-target-sets')) + .to.equal(JSON.stringify(targetSets)); + expect(localStorage.getItem('running-tools.race-calculator-options')).to.equal(JSON.stringify({ + input: { + distanceValue: 1, + distanceUnit: 'miles', + time: 600, + }, + selectedTargetSet: 'B', + })); }); -test('should correctly calculate distance results according to default units setting', async () => { +test('should correctly predict race times', async () => { // Initialize component const wrapper = shallowMount(RaceCalculator); @@ -45,30 +201,20 @@ test('should correctly calculate distance results according to default units set time: 1200, }); - // Set default units - await wrapper.find('select[aria-label="Default units"]').setValue('metric'); - - // Get calculate result function + // Calculate result const calculateResult = wrapper.findComponent({ name: 'single-output-table' }).vm.calculateResult; + const result = calculateResult({ + distanceValue: 10, + distanceUnit: 'kilometers', + type: 'distance', + }); // Assert result is correct - let result = calculateResult({ type: 'time', time: 2495 }); - expect(result.key).to.equal('10.00 km'); - expect(result.value).to.equal('41:35'); - expect(result.pace).to.equal('4:09 / km'); - expect(result.result).to.equal('key'); - expect(result.sort).to.equal(2495); - - // Change default units - await wrapper.find('select[aria-label="Default units"]').setValue('imperial'); - - // Assert result is correct - result = calculateResult({ type: 'time', time: 2495 }); - expect(result.key).to.equal('6.21 mi'); - expect(result.value).to.equal('41:35'); + expect(result.key).to.equal('10 km'); + expect(result.value).to.equal('41:34.80'); expect(result.pace).to.equal('6:41 / mi'); - expect(result.result).to.equal('key'); - expect(result.sort).to.equal(2495); + expect(result.result).to.equal('value'); + expect(result.sort).to.be.closeTo(2494.80, 0.01); }); test('should show paces in results table', async () => { @@ -84,15 +230,27 @@ test('should correctly handle null target set', async () => { const wrapper = shallowMount(RaceCalculator); // Switch to invalid target set - await wrapper.findComponent({ name: 'target-set-selector' }) - .setValue('does_not_exist', 'selectedTargetSet'); + await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({ + input: { + distanceValue: 5, + distanceUnit: 'kilometers', + time: 1200, + }, + selectedTargetSet: 'does_not_exist', + }, 'options'); // Assert empty array passed to SingleOutputTable component expect(wrapper.findComponent({ name: 'single-output-table' }).vm.targets).to.deep.equal([]); // Switch to valid target set - await wrapper.findComponent({ name: 'target-set-selector' }) - .setValue('_race_targets', 'selectedTargetSet'); + await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({ + input: { + distanceValue: 5, + distanceUnit: 'kilometers', + time: 1200, + }, + selectedTargetSet: '_race_targets', + }, 'options'); // Assert valid targets passed to SingleOutputTable component const raceTargets = defaultTargetSets._race_targets.targets; @@ -118,12 +276,12 @@ test('should correctly calculate race statistics', async () => { const vo2Max = raceStats.findAll('div')[2].element.textContent.trim(); // Assert race statistics are correct - expect(purdyPoints).to.equal('Purdy Points: 454.5'); + expect(purdyPoints).to.equal('Purdy points: 454.5'); expect(vo2).to.equal('V̇O₂: 47.5 ml/kg/min (95.3% of max)') expect(vo2Max).to.equal('V̇O₂ Max: 49.8 ml/kg/min') }); -test('should correctly calculate results according to advanced model options', async () => { +test('should correctly calculate results according to options', async () => { // Initialize component const wrapper = shallowMount(RaceCalculator); @@ -134,15 +292,54 @@ test('should correctly calculate results according to advanced model options', a time: 1200, }); + // Set default units + await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({ + defaultUnitSystem: 'metric', + racePredictionOptions: { + model: 'AverageModel', + riegelExponent: 1.06, + }, + }, 'globalOptions'); + + // Get calculate result function + const calculateResult = wrapper.findComponent({ name: 'single-output-table' }).vm.calculateResult; + + // Assert result is correct + let result = calculateResult({ type: 'time', time: 2495 }); + expect(result.key).to.equal('10.00 km'); + expect(result.value).to.equal('41:35'); + expect(result.pace).to.equal('4:09 / km'); + expect(result.result).to.equal('key'); + expect(result.sort).to.equal(2495); + + // Change default units + await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({ + defaultUnitSystem: 'imperial', // changed from metric + racePredictionOptions: { + model: 'AverageModel', + riegelExponent: 1.06, + }, + }, 'globalOptions'); + + // Assert result is correct + result = calculateResult({ type: 'time', time: 2495 }); + expect(result.key).to.equal('6.21 mi'); + expect(result.value).to.equal('41:35'); + expect(result.pace).to.equal('6:41 / mi'); + expect(result.result).to.equal('key'); + expect(result.sort).to.equal(2495); + // Switch model - await wrapper.findComponent({ name: 'RaceOptions' }).setValue({ - model: 'RiegelModel', - riegelExponent: 1.06, // default value - }); + await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({ + defaultUnitSystem: 'imperial', + racePredictionOptions: { + model: 'RiegelModel', // changed from the Average Model + riegelExponent: 1.06, + }, + }, 'globalOptions'); // Calculate result - const calculateResult = wrapper.findComponent({ name: 'single-output-table' }).vm.calculateResult; - let result = calculateResult({ + result = calculateResult({ distanceValue: 10, distanceUnit: 'kilometers', type: 'distance', @@ -152,10 +349,13 @@ test('should correctly calculate results according to advanced model options', a expect(result.value).to.equal('41:41.92'); // Update Riegel Exponent - await wrapper.findComponent({ name: 'RaceOptions' }).setValue({ - model: 'RiegelModel', // existing value - riegelExponent: 1, - }); + await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({ + defaultUnitSystem: 'imperial', + racePredictionOptions: { + model: 'RiegelModel', + riegelExponent: 1, // changed from 1.06 + }, + }, 'globalOptions'); // Calculate result result = calculateResult({ @@ -168,133 +368,10 @@ test('should correctly calculate results according to advanced model options', a expect(result.value).to.equal('40:00.00'); }); -test('should load input race from localStorage', async () => { - // Initialize localStorage - localStorage.setItem('running-tools.race-calculator-input', JSON.stringify({ - distanceValue: 1, - distanceUnit: 'miles', - time: 600, - })); - +test('should correctly set AdvancedOptionsInput type prop', async () => { // Initialize component const wrapper = shallowMount(RaceCalculator); - // Assert data loaded - expect(wrapper.findComponent({ name: 'pace-input' }).vm.modelValue).to.deep.equal({ - distanceValue: 1, - distanceUnit: 'miles', - time: 600, - }); + // Assert type prop is correctly set + expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.type).to.equal('race'); }); - -test('should save input race to localStorage', async () => { - // Initialize component - const wrapper = shallowMount(RaceCalculator); - - // Enter input race data - await wrapper.findComponent({ name: 'pace-input' }).setValue({ - distanceValue: 1, - distanceUnit: 'miles', - time: 600, - }); - - // Assert data saved to localStorage - expect(localStorage.getItem('running-tools.race-calculator-input')).to.equal(JSON.stringify({ - distanceValue: 1, - distanceUnit: 'miles', - time: 600, - })); -}); - -test('should load selected target set from localStorage', async () => { - // Initialize localStorage - const targetSet2 = { - name: 'Race targets #2', - targets: [ - { type: 'distance', distanceValue: 1, distanceUnit: 'miles' }, - { type: 'distance', distanceValue: 2, distanceUnit: 'miles' }, - { type: 'distance', distanceValue: 5, distanceUnit: 'kilometers' }, - ], - }; - localStorage.setItem('running-tools.race-calculator-target-sets', JSON.stringify({ - '_race_targets': { - name: 'Race targets #1', - targets: [ - { type: 'distance', distanceValue: 400, distanceUnit: 'meters' }, - { type: 'distance', distanceValue: 800, distanceUnit: 'meters' }, - { type: 'distance', distanceValue: 1600, distanceUnit: 'meters' }, - ], - }, - 'B': targetSet2, - })); - localStorage.setItem('running-tools.race-calculator-target-set', '"B"'); - - // Initialize component - const wrapper = shallowMount(RaceCalculator); - - // Assert selection is loaded - expect(wrapper.findComponent({ name: 'target-set-selector' }).vm.selectedTargetSet) - .to.equal('B'); - expect(wrapper.findComponent({ name: 'single-output-table' }).vm.targets) - .to.deep.equal(targetSet2.targets); -}); - -test('should save selected target set to localStorage when modified', async () => { - // Initialize component - const wrapper = shallowMount(RaceCalculator); - - // Select a new target set - await wrapper.findComponent({ name: 'target-set-selector' }) - .setValue('B', 'selectedTargetSet'); - - // New selected target set should be saved to localStorage - expect(localStorage.getItem('running-tools.race-calculator-target-set')) - .to.equal('"B"'); -}); - -test('should save default units setting to localStorage when modified', async () => { - // Initialize component - const wrapper = shallowMount(RaceCalculator); - - // Change default units - await wrapper.find('select[aria-label="Default units"]').setValue('metric'); - await wrapper.find('select[aria-label="Default units"]').setValue('imperial'); - - // New default units should be saved to localStorage - expect(localStorage.getItem('running-tools.default-unit-system')).to.equal('"imperial"'); -}); - -test('should load advanced model options from localStorage', async () => { - // Initialize localStorage - localStorage.setItem('running-tools.race-calculator-options', JSON.stringify({ - model: 'PurdyPointsModel', - riegelExponent: 1.2, - })); - - // Initialize component - const wrapper = shallowMount(RaceCalculator); - - // Assert data loaded - expect(wrapper.findComponent({ name: 'RaceOptions' }).vm.modelValue).to.deep.equal({ - model: 'PurdyPointsModel', - riegelExponent: 1.2, - }); -}); - -test('should save advanced model options to localStorage when modified', async () => { - // Initialize component - const wrapper = shallowMount(RaceCalculator); - - // Update advanced model options - await wrapper.findComponent({ name: 'RaceOptions' }).setValue({ - model: 'CameronModel', - riegelExponent: 1.30, - }); - - // Assert data saved to localStorage - expect(localStorage.getItem('running-tools.race-calculator-options')).to.equal(JSON.stringify({ - model: 'CameronModel', - riegelExponent: 1.3, - })); -}); - diff --git a/tests/unit/views/SplitCalculator.spec.js b/tests/unit/views/SplitCalculator.spec.js @@ -1,25 +1,33 @@ import { beforeEach, test, expect } from 'vitest'; import { shallowMount } from '@vue/test-utils'; import SplitCalculator from '@/views/SplitCalculator.vue'; +import { defaultTargetSets } from '@/core/targets'; +import { detectDefaultUnitSystem } from '@/core/units'; beforeEach(() => { localStorage.clear(); -}) - -test('should load selected target set from localStorage', async () => { - // Initialize localStorage - localStorage.setItem('running-tools.split-calculator-target-set', '"B"'); +}); +test('should initialize options to default values', async () => { // Initialize component const wrapper = shallowMount(SplitCalculator); - // Assert selection is loaded - expect(wrapper.findComponent({ name: 'target-set-selector' }).vm.selectedTargetSet).to.equal('B'); + // Assert options are initialized + expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.globalOptions + .defaultUnitSystem).to.equal(detectDefaultUnitSystem()); + expect(wrapper.findComponent({ name: 'split-output-table' }).vm.defaultUnitSystem) + .to.equal(detectDefaultUnitSystem()); + expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.options).to.deep.equal({ + selectedTargetSet: '_split_targets', + }); + expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.targetSets) + .to.deep.equal({ _split_targets: defaultTargetSets._split_targets }); + expect(wrapper.findComponent({ name: 'split-output-table' }).vm.modelValue) + .to.deep.equal(defaultTargetSets._split_targets.targets); }); -test('should load targets from localStorage and pass to splitOutputTable', async () => { - // Initialize localStorage - localStorage.setItem('running-tools.split-calculator-target-sets', JSON.stringify({ +test('should load options from localStorage', async () => { + const targetSets = { '_split_targets': { name: 'Split targets', targets: [ @@ -36,150 +44,175 @@ test('should load targets from localStorage and pass to splitOutputTable', async { type: 'distance', distanceValue: 3000, distanceUnit: 'meters', split: 200 }, ], }, - })); + }; - // Initialize component - const wrapper = shallowMount(SplitCalculator); - - // Assert default split targets are initially loaded - expect(wrapper.findComponent({ name: 'target-set-selector' }).vm.selectedTargetSet) - .to.equal('_split_targets'); - expect(wrapper.findComponent({ name: 'split-output-table' }).vm.modelValue).to.deep.equal([ - { type: 'distance', distanceValue: 1, distanceUnit: 'miles' }, - { type: 'distance', distanceValue: 2, distanceUnit: 'miles' }, - { type: 'distance', distanceValue: 5, distanceUnit: 'kilometers' }, - ]); - - // Select a new target set - await wrapper.findComponent({ name: 'target-set-selector' }).setValue('B', 'selectedTargetSet'); - - // Assert new target set is loaded - expect(wrapper.findComponent({ name: 'target-set-selector' }).vm.selectedTargetSet) - .to.equal('B'); - expect(wrapper.findComponent({ name: 'split-output-table' }).vm.modelValue).to.deep.equal([ - { type: 'distance', distanceValue: 1, distanceUnit: 'kilometers', split: 180 }, - { type: 'distance', distanceValue: 2, distanceUnit: 'kilometers', split: 190 }, - { type: 'distance', distanceValue: 3000, distanceUnit: 'meters', split: 200 }, - ]); -}); - -test('should correctly handle null target set', async () => { // Initialize localStorage - localStorage.setItem('running-tools.split-calculator-target-set', '"does_not_exist"'); + localStorage.setItem('running-tools.global-options', JSON.stringify({ + defaultUnitSystem: 'imperial', + racePredictionOptions: { + model: 'AverageModel', + riegelExponent: 1.06, + }, + })); + localStorage.setItem('running-tools.split-calculator-target-sets', JSON.stringify(targetSets)); + localStorage.setItem('running-tools.split-calculator-options', JSON.stringify({ + selectedTargetSet: 'B', + })); // Initialize component const wrapper = shallowMount(SplitCalculator); - // Assert selection is loaded - expect(wrapper.findComponent({ name: 'target-set-selector' }).vm.selectedTargetSet).to.equal('does_not_exist'); - - // Assert empty array passed to SplitOutputTable - expect(wrapper.findComponent({ name: 'split-output-table' }).vm.modelValue).to.deep.equal([]); - - // Switch to valid target set - await wrapper.findComponent({ name: 'target-set-selector' }) - .setValue('_split_targets', 'selectedTargetSet'); - - // Assert non-empty target set passed to SplitOutputTable - expect(wrapper.findComponent({ name: 'split-output-table' }).vm.modelValue).to.deep.equal([ - { type: 'distance', distanceValue: 1, distanceUnit: 'miles' }, - { type: 'distance', distanceValue: 2, distanceUnit: 'miles' }, - { type: 'distance', distanceValue: 5, distanceUnit: 'kilometers' }, - ]); + // Assert options are loaded + expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.globalOptions + .defaultUnitSystem).to.equal('imperial'); + expect(wrapper.findComponent({ name: 'split-output-table' }).vm.defaultUnitSystem) + .to.equal('imperial'); + expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.options).to.deep.equal({ + selectedTargetSet: 'B', + }); + expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.targetSets) + .to.deep.equal(targetSets); + expect(wrapper.findComponent({ name: 'split-output-table' }).vm.modelValue) + .to.deep.equal(targetSets.B.targets); }); -test('should update targets in localStorage when modified by splitOutputTable', async () => { - // Initialize localStorage - localStorage.setItem('running-tools.split-calculator-target-sets', JSON.stringify({ +test('should save options to localStorage when modified', async () => { + const targetSets1 = { '_split_targets': { name: 'Split targets', targets: [ - { type: 'distance', distanceValue: 1, distanceUnit: 'kilometers', split: 180 }, - { type: 'distance', distanceValue: 2, distanceUnit: 'kilometers', split: 180 }, - { type: 'distance', distanceValue: 3000, distanceUnit: 'meters', split: 180 }, + { type: 'distance', distanceValue: 1, distanceUnit: 'miles' }, + { type: 'distance', distanceValue: 2, distanceUnit: 'miles' }, + { type: 'distance', distanceValue: 5, distanceUnit: 'kilometers' }, ], }, - })); - - // Initialize component - const wrapper = shallowMount(SplitCalculator); - - // Update split times - await wrapper.findComponent({ name: 'split-output-table' }).setValue([ - { type: 'distance', distanceValue: 1, distanceUnit: 'kilometers', split: 180 }, - { type: 'distance', distanceValue: 2, distanceUnit: 'kilometers', split: 190 }, - { type: 'distance', distanceValue: 3000, distanceUnit: 'meters', split: 200 }, - ]); - - // Assert targets correctly saved in localStorage - expect(localStorage.getItem('running-tools.split-calculator-target-sets')).to.equal(JSON.stringify({ - '_split_targets': { - name: 'Split targets', + 'B': { + name: 'Split targets #2', targets: [ { type: 'distance', distanceValue: 1, distanceUnit: 'kilometers', split: 180 }, { type: 'distance', distanceValue: 2, distanceUnit: 'kilometers', split: 190 }, { type: 'distance', distanceValue: 3000, distanceUnit: 'meters', split: 200 }, ], }, - })); -}); + }; + const targetSets2 = { + '_split_targets': { + name: 'Split targets', + targets: [ + { type: 'distance', distanceValue: 1, distanceUnit: 'miles' }, + { type: 'distance', distanceValue: 2, distanceUnit: 'miles' }, + { type: 'distance', distanceValue: 5, distanceUnit: 'kilometers' }, + ], + }, + 'B': { + name: 'Split targets #2', + targets: [ + // split times modified: + { type: 'distance', distanceValue: 1, distanceUnit: 'kilometers', split: 185 }, + { type: 'distance', distanceValue: 2, distanceUnit: 'kilometers', split: 195 }, + { type: 'distance', distanceValue: 3000, distanceUnit: 'meters', split: 205 }, + ], + }, + }; -test('should save selected target set to localStorage when modified', async () => { // Initialize component const wrapper = shallowMount(SplitCalculator); - // Select a new target set - await wrapper.findComponent({ name: 'target-set-selector' }) - .setValue('_race_targets', 'selectedTargetSet'); - - // New selected target set should be saved to localStorage - expect(localStorage.getItem('running-tools.split-calculator-target-set')) - .to.equal('"_race_targets"'); -}); + // Set default units setting + await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({ + defaultUnitSystem: 'metric', + racePredictionOptions: { + model: 'AverageModel', + riegelExponent: 1.06, + }, + }, 'globalOptions'); -test('should load default units from localStorage and pass to splitOutputTable', async () => { - // Initialize localStorage - localStorage.setItem('running-tools.default-unit-system', '"metric"'); + // New default units should be saved to localStorage + expect(localStorage.getItem('running-tools.global-options')).to.equal(JSON.stringify({ + defaultUnitSystem: 'metric', + racePredictionOptions: { + model: 'AverageModel', + riegelExponent: 1.06, + }, + })); - // Initialize component - const wrapper = shallowMount(SplitCalculator); + // Update default units setting + await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({ + defaultUnitSystem: 'imperial', + racePredictionOptions: { + model: 'AverageModel', + riegelExponent: 1.06, + }, + }, 'globalOptions'); - // Assert default units setting is initialy loaded - expect(wrapper.find('select', { name: 'Default units' }).element.value).to.equal('metric'); + // New default units should be saved to localStorage + expect(localStorage.getItem('running-tools.global-options')).to.equal(JSON.stringify({ + defaultUnitSystem: 'imperial', + racePredictionOptions: { + model: 'AverageModel', + riegelExponent: 1.06, + }, + })); - // Assert prop is correct - expect(wrapper.findComponent({ name: 'split-output-table' }).vm.defaultUnitSystem) - .to.equal('metric'); + // Update target sets and selected target set via AdvancedOptionsInput + await wrapper.findComponent({ name: 'advanced-options-input' }).setValue(targetSets1, + 'targetSets'); + await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({ + selectedTargetSet: 'B', + }, 'options'); + + // Assert data saved to localStorage + expect(localStorage.getItem('running-tools.split-calculator-target-sets')) + .to.equal(JSON.stringify(targetSets1)); + expect(localStorage.getItem('running-tools.split-calculator-options')).to.equal(JSON.stringify({ + selectedTargetSet: 'B', + })); - // Change default units - await wrapper.find('select').setValue('imperial'); + // Update target sets via SplitOutputTable + await wrapper.findComponent({ name: 'split-output-table' }).setValue(targetSets2.B.targets); - // Assert prop is correct - expect(wrapper.findComponent({ name: 'split-output-table' }).vm.defaultUnitSystem) - .to.equal('imperial'); + // Assert data saved to localStorage + expect(localStorage.getItem('running-tools.split-calculator-target-sets')) + .to.equal(JSON.stringify(targetSets2)); + expect(localStorage.getItem('running-tools.split-calculator-options')).to.equal(JSON.stringify({ + selectedTargetSet: 'B', + })); }); -test('should save default units setting to localStorage when modified', async () => { +test('should correctly handle null target set', async () => { + // Initialize localStorage + localStorage.setItem('running-tools.split-calculator-options', JSON.stringify({ + selectedTargetSet: 'does_not_exist', + })); + // Initialize component const wrapper = shallowMount(SplitCalculator); - // Set default units setting - await wrapper.find('select').setValue('metric'); + // Assert selection is loaded + expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.options).to.deep.equal({ + selectedTargetSet: 'does_not_exist', + }); - // New default units should be saved to localStorage - expect(localStorage.getItem('running-tools.default-unit-system')).to.equal('"metric"'); + // Assert empty array passed to SplitOutputTable + expect(wrapper.findComponent({ name: 'split-output-table' }).vm.modelValue).to.deep.equal([]); - // Set default units setting - await wrapper.find('select').setValue('imperial'); + // Switch to valid target set + await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({ + selectedTargetSet: '_split_targets', + }, 'options'); - // New default units should be saved to localStorage - expect(localStorage.getItem('running-tools.default-unit-system')).to.equal('"imperial"'); + // Assert non-empty target set passed to SplitOutputTable + expect(wrapper.findComponent({ name: 'split-output-table' }).vm.modelValue).to.deep.equal([ + { type: 'distance', distanceValue: 1, distanceUnit: 'miles' }, + { type: 'distance', distanceValue: 2, distanceUnit: 'miles' }, + { type: 'distance', distanceValue: 5, distanceUnit: 'kilometers' }, + ]); }); -test('should correctly set targetSetSelector setType prop', async () => { +test('should correctly set AdvancedOptionsInput type prop', async () => { // Initialize component const wrapper = shallowMount(SplitCalculator); - // Assert setType prop is correctly set - expect(wrapper.findComponent({ name: 'target-set-selector' }).vm.setType).to.equal('split'); + // Assert type prop is correctly set + expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.type).to.equal('split'); }); diff --git a/tests/unit/views/WorkoutCalculator.spec.js b/tests/unit/views/WorkoutCalculator.spec.js @@ -1,148 +1,132 @@ import { beforeEach, test, expect } from 'vitest'; import { shallowMount } from '@vue/test-utils'; import WorkoutCalculator from '@/views/WorkoutCalculator.vue'; -import { defaultTargetSets } from '@/utils/targets'; +import { defaultTargetSets } from '@/core/targets'; +import { detectDefaultUnitSystem } from '@/core/units'; beforeEach(() => { localStorage.clear(); -}) +}); -test('should correctly predict workout splits', async () => { +test('should initialize options to default values', async () => { // Initialize component const wrapper = shallowMount(WorkoutCalculator); - // Enter input race data - await wrapper.findComponent({ name: 'pace-input' }).setValue({ - distanceValue: 5, - distanceUnit: 'kilometers', - time: 1200, + // Assert options are initialized + expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.globalOptions).to.deep.equal({ + defaultUnitSystem: detectDefaultUnitSystem(), + racePredictionOptions: { + model: 'AverageModel', + riegelExponent: 1.06, + }, }); - - // Calculate result - const calculateResult = wrapper.findComponent({ name: 'single-output-table' }).vm.calculateResult; - const result = calculateResult({ - splitValue: 1, splitUnit: 'kilometers', - type: 'distance', distanceValue: 10, distanceUnit: 'kilometers', + expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.options).to.deep.equal({ + customTargetNames: false, + input: { + distanceValue: 5, + distanceUnit: 'kilometers', + time: 1200, + }, + selectedTargetSet: '_workout_targets', }); - - // Assert result is correct - expect(result.key).to.equal('1 km @ 10 km'); - expect(result.value).to.equal('4:09.48'); - expect(result.result).to.equal('value'); - expect(result.sort).to.be.closeTo(249.48, 0.01); -}); - -test('should correctly handle null target set', async () => { - // Initialize component - const wrapper = shallowMount(WorkoutCalculator); - - // Switch to invalid target set - await wrapper.findComponent({ name: 'target-set-selector' }) - .setValue('does_not_exist', 'selectedTargetSet'); - - // Assert empty array passed to SingleOutputTable component - expect(wrapper.findComponent({ name: 'single-output-table' }).vm.targets).to.deep.equal([]); - - // Switch to valid target set - await wrapper.findComponent({ name: 'target-set-selector' }) - .setValue('_workout_targets', 'selectedTargetSet'); - - // Assert valid targets passed to SingleOutputTable component - const workoutTargets = defaultTargetSets._workout_targets.targets; + expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.targetSets) + .to.deep.equal({ _workout_targets: defaultTargetSets._workout_targets }); expect(wrapper.findComponent({ name: 'single-output-table' }).vm.targets) - .to.deep.equal(workoutTargets); + .to.deep.equal(defaultTargetSets._workout_targets.targets); }); -test('should correctly calculate results according to advanced model options', async () => { - // Initialize component - const wrapper = shallowMount(WorkoutCalculator); - - // Enter input race data - await wrapper.findComponent({ name: 'pace-input' }).setValue({ - distanceValue: 5, - distanceUnit: 'kilometers', - time: 1200, - }); - - // Update model and Riegel Exponent - await wrapper.findComponent({ name: 'RaceOptions' }).setValue({ - model: 'RiegelModel', - riegelExponent: 1.10, - }); - - // Calculate result - const calculateResult = wrapper.findComponent({ name: 'single-output-table' }).vm.calculateResult; - let result = calculateResult({ - splitValue: 1, splitUnit: 'kilometers', - type: 'distance', distanceValue: 10, distanceUnit: 'kilometers', - }); - - // Assert result is correct - expect(result.key).to.equal('1 km @ 10 km'); - expect(result.value).to.equal('4:17.23'); -}); +test('should load options from localStorage', async () => { + const targetSets = { + '_workout_targets': { + name: 'Workout targets #1', + targets: [ + { + splitValue: 400, splitUnit: 'meters', + type: 'distance', distanceValue: 1, distanceUnit: 'miles', + }, + { + splitValue: 800, splitUnit: 'meters', + type: 'distance', distanceValue: 5, distanceUnit: 'kilometers', + }, + { + splitValue: 1600, splitUnit: 'meters', + type: 'time', time: 3600, + }, + { + splitValue: 2, splitUnit: 'miles', + type: 'time', time: 7200, + }, + ], + }, + 'B': { + name: 'Workout targets #2', + targets: [ + { + distanceUnit: 'miles', distanceValue: 2, + splitUnit: 'meters', splitValue: 400, + type: 'distance', + }, + { + time: 6000, + splitUnit: 'kilometers', splitValue: 2, + type: 'time', + }, + { + distanceUnit: 'kilometers', distanceValue: 5, + splitUnit: 'miles', splitValue: 1, + type: 'distance' + }, + ], + }, + }; -test('should load input race from localStorage', async () => { // Initialize localStorage - localStorage.setItem('running-tools.workout-calculator-input', JSON.stringify({ - distanceValue: 1, - distanceUnit: 'miles', - time: 600, + localStorage.setItem('running-tools.global-options', JSON.stringify({ + defaultUnitSystem: 'imperial', + racePredictionOptions: { + model: 'PurdyPointsModel', + riegelExponent: 1.2, + }, + })); + localStorage.setItem('running-tools.workout-calculator-target-sets', JSON.stringify(targetSets)); + localStorage.setItem('running-tools.workout-calculator-options', JSON.stringify({ + customTargetNames: true, + input: { + distanceValue: 1, + distanceUnit: 'miles', + time: 600, + }, + selectedTargetSet: 'B', })); // Initialize component const wrapper = shallowMount(WorkoutCalculator); - // Assert data loaded - expect(wrapper.findComponent({ name: 'pace-input' }).vm.modelValue).to.deep.equal({ - distanceValue: 1, - distanceUnit: 'miles', - time: 600, + // Assert options are loaded + expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.globalOptions).to.deep.equal({ + defaultUnitSystem: 'imperial', + racePredictionOptions: { + model: 'PurdyPointsModel', + riegelExponent: 1.2, + }, }); -}); - -test('should save input race to localStorage', async () => { - // Initialize component - const wrapper = shallowMount(WorkoutCalculator); - - // Enter input race data - await wrapper.findComponent({ name: 'pace-input' }).setValue({ - distanceValue: 1, - distanceUnit: 'miles', - time: 600, + expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.options).to.deep.equal({ + customTargetNames: true, + input: { + distanceValue: 1, + distanceUnit: 'miles', + time: 600, + }, + selectedTargetSet: 'B', }); - - // Assert data saved to localStorage - expect(localStorage.getItem('running-tools.workout-calculator-input')).to.equal(JSON.stringify({ - distanceValue: 1, - distanceUnit: 'miles', - time: 600, - })); + expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.targetSets) + .to.deep.equal(targetSets); + expect(wrapper.findComponent({ name: 'single-output-table' }).vm.targets) + .to.deep.equal(targetSets.B.targets); }); -test('should load selected target set from localStorage', async () => { - // Initialize localStorage - const targetSet2 = { - name: 'Workout targets #2', - targets: [ - { - distanceUnit: 'miles', distanceValue: 2, - splitUnit: 'meters', splitValue: 400, - type: 'distance', - }, - { - time: 6000, - splitUnit: 'kilometers', splitValue: 2, - type: 'time', - }, - { - distanceUnit: 'kilometers', distanceValue: 5, - splitUnit: 'miles', splitValue: 1, - type: 'distance' - }, - ], - }; - localStorage.setItem('running-tools.workout-calculator-target-sets', JSON.stringify({ +test('should save options to localStorage when modified', async () => { + const targetSets = { '_workout_targets': { name: 'Workout targets #1', targets: [ @@ -164,75 +148,235 @@ test('should load selected target set from localStorage', async () => { }, ], }, - 'B': targetSet2, - })); - localStorage.setItem('running-tools.workout-calculator-target-set', '"B"'); + 'B': { + name: 'Workout targets #2', + targets: [ + { + distanceUnit: 'miles', distanceValue: 2, + splitUnit: 'meters', splitValue: 400, + type: 'distance', + }, + { + time: 6000, + splitUnit: 'kilometers', splitValue: 2, + type: 'time', + }, + { + distanceUnit: 'kilometers', distanceValue: 5, + splitUnit: 'miles', splitValue: 1, + type: 'distance' + }, + ], + }, + }; // Initialize component const wrapper = shallowMount(WorkoutCalculator); - // Assert selection is loaded - expect(wrapper.findComponent({ name: 'target-set-selector' }).vm.selectedTargetSet) - .to.equal('B'); - expect(wrapper.findComponent({ name: 'single-output-table' }).vm.targets) - .to.deep.equal(targetSet2.targets); + // Update options + await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({ + defaultUnitSystem: 'imperial', + racePredictionOptions: { + model: 'CameronModel', + riegelExponent: 1.3, + }, + }, 'globalOptions'); + + // Assert data saved to localStorage + expect(localStorage.getItem('running-tools.global-options')).to.equal(JSON.stringify({ + defaultUnitSystem: 'imperial', + racePredictionOptions: { + model: 'CameronModel', + riegelExponent: 1.3, + }, + })); + + // Update input race + await wrapper.findComponent({ name: 'pace-input' }).setValue({ + distanceValue: 1, + distanceUnit: 'miles', + time: 600, + }); + + // Assert data saved to localStorage + expect(localStorage.getItem('running-tools.workout-calculator-options')).to.equal(JSON.stringify({ + customTargetNames: false, + input: { + distanceValue: 1, + distanceUnit: 'miles', + time: 600, + }, + selectedTargetSet: '_workout_targets', + })); + + // Update target name customization + await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({ + customTargetNames: true, + input: { + distanceValue: 1, + distanceUnit: 'miles', + time: 600, + }, + selectedTargetSet: '_workout_targets', + }, 'options'); + + // Assert data saved to localStorage + expect(localStorage.getItem('running-tools.workout-calculator-options')).to.equal(JSON.stringify({ + customTargetNames: true, + input: { + distanceValue: 1, + distanceUnit: 'miles', + time: 600, + }, + selectedTargetSet: '_workout_targets', + })); + + // Update target sets and selected target set + await wrapper.findComponent({ name: 'advanced-options-input' }).setValue(targetSets, + 'targetSets'); + await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({ + customTargetNames: true, + input: { + distanceValue: 1, + distanceUnit: 'miles', + time: 600, + }, + selectedTargetSet: 'B', + }, 'options'); + + // Assert data saved to localStorage + expect(localStorage.getItem('running-tools.workout-calculator-target-sets')) + .to.equal(JSON.stringify(targetSets)); + expect(localStorage.getItem('running-tools.workout-calculator-options')).to.equal(JSON.stringify({ + customTargetNames: true, + input: { + distanceValue: 1, + distanceUnit: 'miles', + time: 600, + }, + selectedTargetSet: 'B', + })); }); -test('should save selected target set to localStorage when modified', async () => { +test('should correctly predict workout splits', async () => { // Initialize component const wrapper = shallowMount(WorkoutCalculator); - // Select a new target set - await wrapper.findComponent({ name: 'target-set-selector' }) - .setValue('B', 'selectedTargetSet'); + // Enter input race data + await wrapper.findComponent({ name: 'pace-input' }).setValue({ + distanceValue: 5, + distanceUnit: 'kilometers', + time: 1200, + }); - // New selected target set should be saved to localStorage - expect(localStorage.getItem('running-tools.workout-calculator-target-set')) - .to.equal('"B"'); + // Calculate result + const calculateResult = wrapper.findComponent({ name: 'single-output-table' }).vm.calculateResult; + const result = calculateResult({ + splitValue: 1, splitUnit: 'kilometers', + type: 'distance', distanceValue: 10, distanceUnit: 'kilometers', + }); + + // Assert result is correct + expect(result.key).to.equal('1 km @ 10 km'); + expect(result.value).to.equal('4:09.48'); + expect(result.result).to.equal('value'); + expect(result.sort).to.be.closeTo(249.48, 0.01); }); -test('should save default units setting to localStorage when modified', async () => { +test('should correctly handle null target set', async () => { // Initialize component const wrapper = shallowMount(WorkoutCalculator); - // Change default units - await wrapper.find('select[aria-label="Default units"]').setValue('metric'); - await wrapper.find('select[aria-label="Default units"]').setValue('imperial'); + // Switch to invalid target set + await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({ + customTargetNames: false, + input: { + distanceValue: 5, + distanceUnit: 'kilometers', + time: 1200, + }, + selectedTargetSet: 'does_not_exist', + }, 'options'); + + // Assert empty array passed to SingleOutputTable component + expect(wrapper.findComponent({ name: 'single-output-table' }).vm.targets).to.deep.equal([]); - // New default units should be saved to localStorage - expect(localStorage.getItem('running-tools.default-unit-system')).to.equal('"imperial"'); -}); + // Switch to valid target set + await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({ + customTargetNames: false, + input: { + distanceValue: 5, + distanceUnit: 'kilometers', + time: 1200, + }, + selectedTargetSet: '_workout_targets', + }, 'options'); -test('should load advanced model options from localStorage', async () => { - // Initialize localStorage - localStorage.setItem('running-tools.workout-calculator-options', JSON.stringify({ - model: 'PurdyPointsModel', - riegelExponent: 1.2, - })); + // Assert valid targets passed to SingleOutputTable component + const workoutTargets = defaultTargetSets._workout_targets.targets; + expect(wrapper.findComponent({ name: 'single-output-table' }).vm.targets) + .to.deep.equal(workoutTargets); +}); +test('should correctly calculate results according to options', async () => { // Initialize component const wrapper = shallowMount(WorkoutCalculator); - // Assert data loaded - expect(wrapper.findComponent({ name: 'RaceOptions' }).vm.modelValue).to.deep.equal({ - model: 'PurdyPointsModel', - riegelExponent: 1.2, + // Enter input race data + await wrapper.findComponent({ name: 'pace-input' }).setValue({ + distanceValue: 2, + distanceUnit: 'miles', + time: 630, + }); + + // Update model and Riegel exponent + await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({ + defaultUnitSystem: 'imperial', + racePredictionOptions: { + model: 'RiegelModel', + riegelExponent: 1.10, + }, + }, 'globalOptions'); + + // Calculate result + const calculateResult = wrapper.findComponent({ name: 'single-output-table' }).vm.calculateResult; + let result = calculateResult({ + customName: 'foo', + splitValue: 1, splitUnit: 'kilometers', + type: 'distance', distanceValue: 10, distanceUnit: 'kilometers', + }); + + // Assert result is correct + expect(result.key).to.equal('1 km @ 10 km'); + expect(result.value).to.equal('3:39.23'); + + // Update target name customization + await wrapper.findComponent({ name: 'advanced-options-input' }).setValue({ + customTargetNames: true, + input: { + distanceValue: 2, + distanceUnit: 'miles', + time: 630, + }, + selectedTargetSet: '_workout_targets', + }, 'options'); + + // Calculate result + result = calculateResult({ + customName: 'foo', + splitValue: 1, splitUnit: 'kilometers', + type: 'distance', distanceValue: 10, distanceUnit: 'kilometers', }); + + // Assert result is correct + expect(result.key).to.equal('foo'); + expect(result.value).to.equal('3:39.23'); }); -test('should save advanced model options to localStorage when modified', async () => { +test('should correctly set AdvancedOptionsInput type prop', async () => { // Initialize component const wrapper = shallowMount(WorkoutCalculator); - // Update advanced model options - await wrapper.findComponent({ name: 'RaceOptions' }).setValue({ - model: 'CameronModel', - riegelExponent: 1.30, - }); - - // Assert data saved to localStorage - expect(localStorage.getItem('running-tools.workout-calculator-options')).to.equal(JSON.stringify({ - model: 'CameronModel', - riegelExponent: 1.3, - })); + // Assert type prop is correctly set + expect(wrapper.findComponent({ name: 'advanced-options-input' }).vm.type).to.equal('workout'); }); diff --git a/tsconfig.app.json b/tsconfig.app.json @@ -0,0 +1,14 @@ +{ + "extends": "@vue/tsconfig/tsconfig.dom.json", + "include": ["vite-env.d.ts", "src/**/*"], + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "resolveJsonModule": true, + + "paths": { + "@/*": ["./src/*"], + "/*": ["./*"] + }, + "types": ["node", "vite/client"] + } +} diff --git a/tsconfig.json b/tsconfig.json @@ -0,0 +1,11 @@ +{ + "files": [], + "references": [ + { + "path": "./tsconfig.node.json" + }, + { + "path": "./tsconfig.app.json" + } + ] +} diff --git a/tsconfig.node.json b/tsconfig.node.json @@ -0,0 +1,12 @@ +{ + "extends": "@tsconfig/node22/tsconfig.json", + "files": [], + "compilerOptions": { + "noEmit": true, + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + + "module": "ESNext", + "moduleResolution": "Bundler", + "types": ["node"] + } +} diff --git a/vite-env.d.ts b/vite-env.d.ts @@ -0,0 +1 @@ +declare const __CHANGELOG_HTML__: string diff --git a/vite.config.js b/vite.config.js @@ -1,64 +0,0 @@ -import { fileURLToPath, URL } from 'node:url'; -import { resolve } from 'path'; -import { defineConfig } from 'vite'; -import { VitePWA } from 'vite-plugin-pwa'; -import vue from '@vitejs/plugin-vue'; - -// https://vitejs.dev/config/ -export default defineConfig({ - build: { - rollupOptions: { - input: { - index: resolve(__dirname, 'index.html'), - not_found: resolve(__dirname, '404.html'), - }, - }, - }, - plugins: [ - vue(), - VitePWA({ - injectRegister: 'inline', - manifest: { - name: 'Running Tools', - short_name: 'Running Tools', - description: 'A collection of tools for runners and their coaches that calculate splits, predict race times, convert units, and more', - theme_color: '#ff8000', - background_color: '#ff8000', - icons: [ - { - "src": "./img/icons/android-chrome-192x192.png", - "sizes": "192x192", - "type": "image/png", - }, - { - "src": "./img/icons/android-chrome-512x512.png", - "sizes": "512x512", - "type": "image/png", - }, - { - "src": "./img/icons/android-chrome-maskable-192x192.png", - "sizes": "192x192", - "type": "image/png", - "purpose": "maskable", - }, - { - "src": "./img/icons/android-chrome-maskable-512x512.png", - "sizes": "512x512", - "type": "image/png", - "purpose": "maskable", - }, - ], - }, - }), - ], - resolve: { - alias: { - '@': fileURLToPath(new URL('./src', import.meta.url)), - }, - }, - base: process.env.BASE_URL ? process.env.BASE_URL : '/', - test: { - environment: 'jsdom', - include: ['tests/unit/**/*.{test,spec}.?(c|m)[jt]s?(x)'], - }, -}); diff --git a/vite.config.ts b/vite.config.ts @@ -0,0 +1,80 @@ +import { fileURLToPath, URL } from 'node:url'; +import { resolve } from 'path'; +import { defineConfig } from 'vite'; +import { VitePWA } from 'vite-plugin-pwa'; +import fs from 'fs'; +import markdownit from 'markdown-it' +import vue from '@vitejs/plugin-vue'; + +import { description } from './package.json'; + +// Convert changelog from markdown to HTML +const changelog_md: string = fs.readFileSync('./CHANGELOG.md', 'utf-8'); +const changelog_html: string = markdownit({ + typographer: true, // needed to convert '--' to en-dash +}).render(changelog_md.slice(changelog_md.indexOf('\n') + 1)) // render without h1 on first line + .replace(/\n/g, ' '); // join lines together + +// https://vitejs.dev/config/ +export default defineConfig({ + build: { + rollupOptions: { + input: { + index: resolve(__dirname, 'index.html'), + not_found: resolve(__dirname, '404.html'), + }, + }, + }, + plugins: [ + vue(), + VitePWA({ + injectRegister: 'inline', + manifest: { + name: 'Running Tools', + short_name: 'Running Tools', + description: description, + theme_color: '#ff8000', + background_color: '#ff8000', + icons: [ + { + "src": "./img/icons/android-chrome-192x192.png", + "sizes": "192x192", + "type": "image/png", + }, + { + "src": "./img/icons/android-chrome-512x512.png", + "sizes": "512x512", + "type": "image/png", + }, + { + "src": "./img/icons/android-chrome-maskable-192x192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable", + }, + { + "src": "./img/icons/android-chrome-maskable-512x512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable", + }, + ], + }, + }), + ], + resolve: { + alias: { + '@': fileURLToPath(new URL('./src', import.meta.url)), + }, + }, + base: process.env.BASE_URL || '/', + define: { + 'import.meta.env.VITE_DESCRIPTION': `"${description}"`, + 'import.meta.env.VITE_DOMAIN': process.env.DOMAIN ? `"https://${process.env.DOMAIN}"` : '""', + '__CHANGELOG_HTML__': JSON.stringify(changelog_html), + }, + test: { + environment: 'jsdom', + include: ['tests/unit/**/*.{test,spec}.?(c|m)[jt]s?(x)'], + }, +});