From 977201ecae4f4611421f83115f7ac2e4537a2580 Mon Sep 17 00:00:00 2001 From: Maksym Sadovnychyy Date: Sun, 24 May 2026 19:07:13 +0200 Subject: [PATCH] (feature): packages updates and tests --- .vscode/settings.json | 4 + CHANGELOG.md | 23 +- README.md | 15 +- assets/badges/coverage-branches.svg | 21 + assets/badges/coverage-lines.svg | 21 + assets/badges/coverage-methods.svg | 21 + assets/docs/NPM_CONSUMPTION.md | 10 +- assets/docs/NPM_PUBLISH.md | 46 +- scripts/publish-npm.ps1 | 52 - src/coverage/coverage-summary.json | 61 + src/jest.config.cjs | 26 + src/package-lock.json | 4136 ++++++++++++++++- src/package.json | 15 +- src/packages/components/README.md | 66 + src/packages/components/package.json | 35 +- .../src/components/DataTable/DataTable.tsx | 4 +- .../components/DataTable/DataTableFilter.tsx | 2 +- .../components/DataTable/DataTableLabel.tsx | 2 +- .../components/Scopes/EntityScopesSummary.tsx | 2 +- .../components/src/components/Toast/index.tsx | 5 +- .../editors/FileUploadComponent.tsx | 4 +- .../editors/RemoteSelectBoxComponent.tsx | 6 +- .../components/editors/SelectBoxComponent.tsx | 2 +- src/packages/components/tsconfig.build.json | 6 + src/packages/contracts/README.md | 48 + src/packages/contracts/package.json | 15 +- src/packages/contracts/src/PagedRequest.ts | 5 +- .../contracts/src/PatchRequestModelBase.ts | 5 +- .../src/identity/login/LoginRequest.ts | 8 +- src/packages/contracts/src/schemas.test.ts | 73 + src/packages/contracts/tsconfig.build.json | 6 + src/packages/contracts/tsconfig.json | 3 +- src/packages/core/README.md | 60 + src/packages/core/package.json | 27 +- .../src/functions/acl/parseAclEntry.test.ts | 31 + .../dataTable/dataTableFilters.test.ts | 22 + .../dataTable/dataTablePaged.test.ts | 51 + .../src/functions/dataTable/dataTablePaged.ts | 2 +- .../functions/date/isValidDateString.test.ts | 14 + .../core/src/functions/deep/deepDelta.test.ts | 114 + .../core/src/functions/deep/deepDelta.ts | 2 +- .../core/src/functions/deep/deepEqual.test.ts | 39 + .../core/src/functions/enum/flags.test.ts | 32 + .../core/src/functions/guid/isGuid.test.ts | 15 + .../extractFilenameFromHeaders.test.ts | 31 + .../functions/zod/validateFormState.test.ts | 24 + src/packages/core/src/http/errorHandler.ts | 2 +- .../core/src/http/problemDetails.test.ts | 26 + src/packages/core/src/http/problemDetails.ts | 2 +- .../core/src/localStorage/identity.ts | 2 +- src/packages/core/src/types/index.ts | 2 +- src/packages/core/tsconfig.build.json | 6 + src/packages/core/tsconfig.json | 3 +- src/tsconfig.jest.json | 11 + .../Generate-CoverageBadges.ps1 | 259 +- .../scriptsettings.json | 45 +- .../CorePlugins/DockerPush.psm1 | 18 +- utils/Release-Package/CorePlugins/GitHub.psm1 | 45 +- .../Release-Package/CorePlugins/NpmBuild.psm1 | 3 +- .../CorePlugins/NpmPublish.psm1 | 3 +- .../CorePlugins/NpmReleaseVersion.psm1 | 7 +- utils/Release-Package/EngineSupport.psm1 | 8 +- utils/Release-Package/README.md | 8 +- utils/Release-Package/ReleaseContext.psm1 | 88 +- utils/Release-Package/scriptsettings.json | 9 +- .../Run-Tests/CorePlugins/CoverageBadges.psm1 | 177 + utils/Run-Tests/CorePlugins/DotNetTest.psm1 | 98 + utils/Run-Tests/CorePlugins/NpmJestTest.psm1 | 82 + utils/Run-Tests/CorePlugins/QualityGate.psm1 | 184 + utils/Run-Tests/CustomPlugins/.gitkeep | 0 utils/Run-Tests/EngineSupport.psm1 | 35 + utils/Run-Tests/PluginSupport.psm1 | 376 ++ utils/Run-Tests/README.md | 58 + utils/Run-Tests/Run-Tests.bat | 3 + utils/Run-Tests/Run-Tests.ps1 | 76 + utils/Run-Tests/scriptsettings.json | 63 + utils/TestRunner.psm1 | 108 +- utils/Update-RepoUtils/scriptsettings.json | 3 +- 78 files changed, 6483 insertions(+), 539 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 assets/badges/coverage-branches.svg create mode 100644 assets/badges/coverage-lines.svg create mode 100644 assets/badges/coverage-methods.svg delete mode 100644 scripts/publish-npm.ps1 create mode 100644 src/coverage/coverage-summary.json create mode 100644 src/jest.config.cjs create mode 100644 src/packages/components/README.md create mode 100644 src/packages/components/tsconfig.build.json create mode 100644 src/packages/contracts/README.md create mode 100644 src/packages/contracts/src/schemas.test.ts create mode 100644 src/packages/contracts/tsconfig.build.json create mode 100644 src/packages/core/README.md create mode 100644 src/packages/core/src/functions/acl/parseAclEntry.test.ts create mode 100644 src/packages/core/src/functions/dataTable/dataTableFilters.test.ts create mode 100644 src/packages/core/src/functions/dataTable/dataTablePaged.test.ts create mode 100644 src/packages/core/src/functions/date/isValidDateString.test.ts create mode 100644 src/packages/core/src/functions/deep/deepDelta.test.ts create mode 100644 src/packages/core/src/functions/deep/deepEqual.test.ts create mode 100644 src/packages/core/src/functions/enum/flags.test.ts create mode 100644 src/packages/core/src/functions/guid/isGuid.test.ts create mode 100644 src/packages/core/src/functions/headers/extractFilenameFromHeaders.test.ts create mode 100644 src/packages/core/src/functions/zod/validateFormState.test.ts create mode 100644 src/packages/core/src/http/problemDetails.test.ts create mode 100644 src/packages/core/tsconfig.build.json create mode 100644 src/tsconfig.jest.json create mode 100644 utils/Run-Tests/CorePlugins/CoverageBadges.psm1 create mode 100644 utils/Run-Tests/CorePlugins/DotNetTest.psm1 create mode 100644 utils/Run-Tests/CorePlugins/NpmJestTest.psm1 create mode 100644 utils/Run-Tests/CorePlugins/QualityGate.psm1 create mode 100644 utils/Run-Tests/CustomPlugins/.gitkeep create mode 100644 utils/Run-Tests/EngineSupport.psm1 create mode 100644 utils/Run-Tests/PluginSupport.psm1 create mode 100644 utils/Run-Tests/README.md create mode 100644 utils/Run-Tests/Run-Tests.bat create mode 100644 utils/Run-Tests/Run-Tests.ps1 create mode 100644 utils/Run-Tests/scriptsettings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..fb0ecaa --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,4 @@ +{ + "typescript.tsdk": "src/node_modules/typescript/lib", + "typescript.enablePromptUseWorkspaceTsdk": true +} diff --git a/CHANGELOG.md b/CHANGELOG.md index f8c9a6b..213be9f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,8 +4,27 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [Unreleased] +## [v0.2.0] - 2026-05-24 ### Added -- Initial `@maksit/webui-contracts`, `@maksit/webui-core`, and `@maksit/webui-components` packages extracted from Certs UI and Vault WebUI. +- Jest test suite (50 tests) covering `@maks-it.com/webui-core` utilities and `@maks-it.com/webui-contracts` schemas. +- Root `npm test` script and per-package build tsconfigs (`tsconfig.build.json`) for TypeScript 6 declaration emit. + +### Changed + +- Updated dependencies to current majors: TypeScript 6, Jest 30, Zod 4.4, lucide-react 1.x, axios 1.16, and React 19.2. +- Migrated Zod schemas to v4 APIs: `intersection()` replaces `.and()`, custom refinements use `'custom'` issue codes. +- `@maks-it.com/webui-components` Toast IDs now use `crypto.randomUUID()`; lodash imports use `lodash/debounce` subpaths. +- Peer dependency ranges: `zod` ^4.4, `axios` ^1.16, `lucide-react` ^1.0. + +### Removed + +- `uuid` runtime dependency from `@maks-it.com/webui-components`. + +## [v0.1.0] - 2026-05-24 + +### Added + +- Initial `@maks-it.com/webui-contracts`, `@maks-it.com/webui-core`, and `@maks-it.com/webui-components` packages extracted from Certs UI and Vault WebUI. +- npm publish under the `@maks-it.com` org scope on registry.npmjs.org. diff --git a/README.md b/README.md index 725307f..51af5dc 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,6 @@ -# maksit-webui +# MaksIT.WebUI + +![Line Coverage](/assets/badges/coverage-lines.svg) ![Branch Coverage](/assets/badges/coverage-branches.svg) ![Method Coverage](/assets/badges/coverage-methods.svg) Shared React UI library for **maksit-certs-ui** and **maksit-vault** WebUI apps. @@ -6,9 +8,9 @@ Shared React UI library for **maksit-certs-ui** and **maksit-vault** WebUI apps. | npm package | Description | |-------------|-------------| -| `@maksit/webui-contracts` | Shared TypeScript contracts (paging, gallery types, patch ops, scopes) | -| `@maksit/webui-core` | Utilities (`deepDelta`, enum helpers, ACL parsers) and `useFormState` | -| `@maksit/webui-components` | React components, layout, editors, DataTable, auth shell | +| `@maks-it.com/webui-contracts` | Shared TypeScript contracts (paging, gallery types, patch ops, scopes) | +| `@maks-it.com/webui-core` | Utilities (`deepDelta`, enum helpers, ACL parsers) and `useFormState` | +| `@maks-it.com/webui-components` | React components, layout, editors, DataTable, auth shell | Source lives under `src/` (npm workspaces). Release automation lives under `utils/` (from [maksit-repoutils](https://github.com/MAKS-IT-COM/maksit-repoutils)). @@ -18,8 +20,11 @@ Source lives under `src/` (npm workspaces). Release automation lives under `util cd src npm install npm run build +npm test ``` +Tests and coverage badges: **`utils/Run-Tests/Run-Tests.bat`** (plugin config in `utils/Run-Tests/scriptsettings.json`; uses `NpmJestTest`). + ## Release to npmjs 1. Set **`NPMJS_MAKS_IT`** to your npm automation token (same pattern as `NUGET_MAKS_IT` for NuGet). @@ -41,7 +46,7 @@ Refresh shared utils from repoutils: **`utils/Update-RepoUtils/Update-RepoUtils. ## Consume in product repos ```bash -npm install @maksit/webui-contracts @maksit/webui-core @maksit/webui-components +npm install @maks-it.com/webui-contracts @maks-it.com/webui-core @maks-it.com/webui-components ``` Wrap the app with `WebUiProvider` and pass axios/redux adapters — see [assets/docs/NPM_CONSUMPTION.md](assets/docs/NPM_CONSUMPTION.md). diff --git a/assets/badges/coverage-branches.svg b/assets/badges/coverage-branches.svg new file mode 100644 index 0000000..74be9ef --- /dev/null +++ b/assets/badges/coverage-branches.svg @@ -0,0 +1,21 @@ + + Branch Coverage: 48% + + + + + + + + + + + + + + + Branch Coverage + + 48% + + diff --git a/assets/badges/coverage-lines.svg b/assets/badges/coverage-lines.svg new file mode 100644 index 0000000..fc05df3 --- /dev/null +++ b/assets/badges/coverage-lines.svg @@ -0,0 +1,21 @@ + + Line Coverage: 42.7% + + + + + + + + + + + + + + + Line Coverage + + 42.7% + + diff --git a/assets/badges/coverage-methods.svg b/assets/badges/coverage-methods.svg new file mode 100644 index 0000000..e14bb74 --- /dev/null +++ b/assets/badges/coverage-methods.svg @@ -0,0 +1,21 @@ + + Method Coverage: 21.5% + + + + + + + + + + + + + + + Method Coverage + + 21.5% + + diff --git a/assets/docs/NPM_CONSUMPTION.md b/assets/docs/NPM_CONSUMPTION.md index dcbf010..0cf3e96 100644 --- a/assets/docs/NPM_CONSUMPTION.md +++ b/assets/docs/NPM_CONSUMPTION.md @@ -1,15 +1,15 @@ -# Consuming @maksit/webui-* in Certs UI / Vault +# Consuming @maks-it.com/webui-* in Certs UI / Vault Install: ```bash -npm install @maksit/webui-contracts @maksit/webui-core @maksit/webui-components +npm install @maks-it.com/webui-contracts @maks-it.com/webui-core @maks-it.com/webui-components ``` Wrap the app: ```tsx -import { WebUiProvider, Loader, Authorization } from '@maksit/webui-components' +import { WebUiProvider, Loader, Authorization } from '@maks-it.com/webui-components' /packages/core/src', '/packages/contracts/src'], + testMatch: ['**/*.test.ts'], + collectCoverageFrom: [ + 'packages/core/src/**/*.ts', + 'packages/contracts/src/**/*.ts', + '!**/*.test.ts', + ], + coverageDirectory: 'coverage', + coverageReporters: ['json-summary', 'text'], + moduleNameMapper: { + '^(\\.{1,2}/.*)\\.js$': '$1', + '^@maks-it.com/webui-contracts$': '/packages/contracts/src/index.ts', + }, + transform: { + '^.+\\.tsx?$': [ + 'ts-jest', + { + tsconfig: '/tsconfig.jest.json', + }, + ], + }, +} diff --git a/src/package-lock.json b/src/package-lock.json index bb16b00..6107c44 100644 --- a/src/package-lock.json +++ b/src/package-lock.json @@ -1,20 +1,467 @@ { "name": "maksit-webui", - "version": "0.1.0", + "version": "0.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "maksit-webui", - "version": "0.1.0", + "version": "0.2.0", "license": "MIT", "workspaces": [ "packages/*" ], + "devDependencies": { + "@types/jest": "^30.0.0", + "jest": "^30.4.2", + "ts-jest": "^29.4.11" + }, "engines": { "node": ">=20" } }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.3", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.3.tgz", + "integrity": "sha512-LIVqM46zQWZhj17qA8wb4nW/ixr2y1Nw+r1etiAWgRM6U1IqP+LNhL1yg440jYZR72jCWcWbLWzIosH+uP1fqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@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" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "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/helpers": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", + "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.3.tgz", + "integrity": "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.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==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.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==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.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==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz", + "integrity": "sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@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==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz", + "integrity": "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-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==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.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==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.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==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.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==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.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==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz", + "integrity": "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/runtime": { "version": "7.29.2", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", @@ -25,6 +472,72 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.27.7", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", @@ -467,6 +980,374 @@ "node": ">=18" } }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.6.tgz", + "integrity": "sha512-+Sg6GCR/wy1oSmQDFq4LQDAhm3ETKnorxN+y5nbLULOR3P0c14f2Wurzj3/xqPXtasLFfHd5iRFQ7AJt4KH2cw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.4.1.tgz", + "integrity": "sha512-v3bhyxUh9Hgmo5p6hAOXe14/R3ZxZDOsvHleh4B07z3m/x4/ngPUXEm9XwK4sF4u+f+P2ORb0Ge+MgpaqRMVDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.4.1", + "@types/node": "*", + "chalk": "^4.1.2", + "jest-message-util": "30.4.1", + "jest-util": "30.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/core": { + "version": "30.4.2", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-30.4.2.tgz", + "integrity": "sha512-TZJA6cPJUFxoWhxaLo8t0VX/MZX2wPWr0uIDvLSHIvN4gu9h02vSzqI2kBADG1ExqQlC+cY09xKMSreivvrChQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "30.4.1", + "@jest/pattern": "30.4.0", + "@jest/reporters": "30.4.1", + "@jest/test-result": "30.4.1", + "@jest/transform": "30.4.1", + "@jest/types": "30.4.1", + "@types/node": "*", + "ansi-escapes": "^4.3.2", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "exit-x": "^0.2.2", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.11", + "jest-changed-files": "30.4.1", + "jest-config": "30.4.2", + "jest-haste-map": "30.4.1", + "jest-message-util": "30.4.1", + "jest-regex-util": "30.4.0", + "jest-resolve": "30.4.1", + "jest-resolve-dependencies": "30.4.2", + "jest-runner": "30.4.2", + "jest-runtime": "30.4.2", + "jest-snapshot": "30.4.1", + "jest-util": "30.4.1", + "jest-validate": "30.4.1", + "jest-watcher": "30.4.1", + "pretty-format": "30.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/diff-sequences": { + "version": "30.4.0", + "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.4.0.tgz", + "integrity": "sha512-zOpzlfUs45l6u7jm39qr87JCHUDsaeCtvL+kQe/Vn9jSnRB4/5IPXISm0h9I1vZW/o00Kn4UTJ2MOlhnUGwv3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/environment": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.4.1.tgz", + "integrity": "sha512-AK9yNRqgKxiabqMoe4oW+3/TSSeV8vkdC7BGaxZdU0AFXfOpofTLqdru2GXKZghP3sdgwE9XXpnVwfZ8JnFV4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/fake-timers": "30.4.1", + "@jest/types": "30.4.1", + "@types/node": "*", + "jest-mock": "30.4.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/expect": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.4.1.tgz", + "integrity": "sha512-ginrj6TMgh2GshLUGCjO94Ptx9HhdZA/I6A9iUfyeLKFtdAjnKzHDgzgP9HYQgbxM1lbXScQ2eUBz2lGeVDPWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "30.4.1", + "jest-snapshot": "30.4.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.4.1.tgz", + "integrity": "sha512-ZBn5CglH8fBsQsvs4VWNzD4aWfUYks+IdOOQU3MEK71ol/BcVm+P+rtb1KpiFBpSWSCE27uOahyyf1vfqOVbcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/get-type": "30.1.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.4.1.tgz", + "integrity": "sha512-iW5umdmfPeWzehrVhugFQZqCchSCud5S1l2YT0O9ZhjRR0ExclANDZkiSBwzqtnlOn0J1JXvO+HZ6rkuyOVOgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.4.1", + "@sinonjs/fake-timers": "^15.4.0", + "@types/node": "*", + "jest-message-util": "30.4.1", + "jest-mock": "30.4.1", + "jest-util": "30.4.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/get-type": { + "version": "30.1.0", + "resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.1.0.tgz", + "integrity": "sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.4.1.tgz", + "integrity": "sha512-ZbuY4cmXC8DkxYjfvT2DbcHWL2T6vmsMhXCDcmTB2T0y0gaezBI77ufq5ZAIdcRkYZ7NEQEDg1xFeKbxUJ5v5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "30.4.1", + "@jest/expect": "30.4.1", + "@jest/types": "30.4.1", + "jest-mock": "30.4.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/pattern": { + "version": "30.4.0", + "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.4.0.tgz", + "integrity": "sha512-RAWn3+f9u8BsHijKJ71uHcFp6vmyEt6VvoWXkl6hKF3qVIuWNmudVjg12DlBPGup/frIl5UcUlH5HfEuvHpEXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-regex-util": "30.4.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.4.1.tgz", + "integrity": "sha512-/SnkPCzEQpUaBH81kjdEdDdo2WZl5hxw+BmLDGWjRkm8o7XlhjwsU36cqwe5PGBE5WYpBvDzRSdXx9rbGuJtNA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "30.4.1", + "@jest/test-result": "30.4.1", + "@jest/transform": "30.4.1", + "@jest/types": "30.4.1", + "@jridgewell/trace-mapping": "^0.3.25", + "@types/node": "*", + "chalk": "^4.1.2", + "collect-v8-coverage": "^1.0.2", + "exit-x": "^0.2.2", + "glob": "^10.5.0", + "graceful-fs": "^4.2.11", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^6.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^5.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "30.4.1", + "jest-util": "30.4.1", + "jest-worker": "30.4.1", + "slash": "^3.0.0", + "string-length": "^4.0.2", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/schemas": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.4.1.tgz", + "integrity": "sha512-i6b4qw5qnP8c5FEeBJg/uZQ4ddrkN6Ca8qISJh0pr7a5hfn3h3v5x60BEbOC7OYAGZNMs1LfFLwnW2CuK8F57Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/snapshot-utils": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.4.1.tgz", + "integrity": "sha512-ObY4ljvQ95mt6iwKtVLetR/4yXiAgl3H4nJxhztr0MTjrN97TwDYrnCp/kF60Ec9HdhkWTHSu+Hg05aXfngpOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.4.1", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "natural-compare": "^1.4.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-30.0.1.tgz", + "integrity": "sha512-MIRWMUUR3sdbP36oyNyhbThLHyJ2eEDClPCiHVbrYAe5g3CHRArIVpBw7cdSB5fr+ofSfIb2Tnsw8iEHL0PYQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.25", + "callsites": "^3.1.0", + "graceful-fs": "^4.2.11" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/test-result": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.4.1.tgz", + "integrity": "sha512-/ZG7pgEiOmmWkN9TplKbOu4id2N5lh7FHwRwlkgBVAzGdRH+OkkQ8wX/kIxg4zmd3ZQvAL1RwL2yWsvNYYECTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "30.4.1", + "@jest/types": "30.4.1", + "@types/istanbul-lib-coverage": "^2.0.6", + "collect-v8-coverage": "^1.0.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.4.1.tgz", + "integrity": "sha512-PeYE+4td5rKjoRPxztObrXU+H8hsjZfxKMXOcmrr34JerSyB/ROOxbbicz8B7A5j9R9VayDnVPvBmedqCsFCdw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "30.4.1", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/transform": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.4.1.tgz", + "integrity": "sha512-Wz0LyktlTvRefoymh+n64hQ84KNXsRGcwdoZ8CSa0Ea+fgYcHZlnk+hDP7v2MS7il2bQ5uTEIxf4/NNfhMN4KQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.27.4", + "@jest/types": "30.4.1", + "@jridgewell/trace-mapping": "^0.3.25", + "babel-plugin-istanbul": "^7.0.1", + "chalk": "^4.1.2", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.4.1", + "jest-regex-util": "30.4.0", + "jest-util": "30.4.1", + "pirates": "^4.0.7", + "slash": "^3.0.0", + "write-file-atomic": "^5.0.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/types": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.4.1.tgz", + "integrity": "sha512-f1x/vJXIfjOlEmejYpbkbgw1gOqpPECwMvMEtBqe47j7H2Hg8h8w3o3ikhSXq3MI15kg+oQ0exWO0uCtTNJLoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.4.0", + "@jest/schemas": "30.4.1", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", @@ -478,6 +1359,17 @@ "@jridgewell/trace-mapping": "^0.3.24" } }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, "node_modules/@jridgewell/resolve-uri": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", @@ -506,18 +1398,50 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@maksit/webui-components": { + "node_modules/@maks-it.com/webui-components": { "resolved": "packages/components", "link": true }, - "node_modules/@maksit/webui-contracts": { + "node_modules/@maks-it.com/webui-contracts": { "resolved": "packages/contracts", "link": true }, - "node_modules/@maksit/webui-core": { + "node_modules/@maks-it.com/webui-core": { "resolved": "packages/core", "link": true }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", + "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, + "node_modules/@pkgr/core": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz", + "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/pkgr" + } + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.60.4", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.4.tgz", @@ -868,6 +1792,33 @@ "win32" ] }, + "node_modules/@sinclair/typebox": { + "version": "0.34.49", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.49.tgz", + "integrity": "sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "15.4.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-15.4.0.tgz", + "integrity": "sha512-DsG+8/LscQIQg68J6Ef3dv10u6nVyetYn923s3/sus5eaGfTo1of5WMZSLf0UJc9KDuKPilPH0UDJCjvNbDNCA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.1" + } + }, "node_modules/@tanstack/react-table": { "version": "8.21.3", "resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.21.3.tgz", @@ -903,6 +1854,62 @@ "url": "https://github.com/sponsors/tannerlinsley" } }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", + "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -910,6 +1917,44 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jest": { + "version": "30.0.0", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-30.0.0.tgz", + "integrity": "sha512-XTYugzhuwqWjws0CVz8QpM36+T+Dz5mTEBKhNs/esGLnCIlGdRy+Dq78NRjd7ls7r8BC8ZRMOrKlkO1hU0JOwA==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "^30.0.0", + "pretty-format": "^30.0.0" + } + }, "node_modules/@types/lodash": { "version": "4.17.24", "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.24.tgz", @@ -917,6 +1962,16 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/node": { + "version": "25.9.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.1.tgz", + "integrity": "sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": ">=7.24.0 <7.24.7" + } + }, "node_modules/@types/prop-types": { "version": "15.7.15", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", @@ -956,6 +2011,350 @@ "@types/react": "*" } }, + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/yargs": { + "version": "17.0.35", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", + "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.1.tgz", + "integrity": "sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/@unrs/resolver-binding-android-arm-eabi": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.12.2.tgz", + "integrity": "sha512-g5T90pqg1bo/7mytQx6F4iBNC0Wsh9cu+z9veDbFjc7HjpesJFWD7QMS0NGStXM075+7dJPPVvBbpZlnrdpi/w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-android-arm64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.12.2.tgz", + "integrity": "sha512-YGCRZv/9GLhwmz6mYDeTsm/92BAyR28l6c2ReweVW5pWgfsitWLY8upvfRlGdoyD8HjeTHSYJWyZGD4KJA/nFQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-arm64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.12.2.tgz", + "integrity": "sha512-u9DiNT1auQMO20A9SyTuG3wUgQWB9Z7KjAg0uFuCDR1FsAY8A0CG2S6JpHS1xwm/w1G08bjXZDcyOCjv1WAm2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-x64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.12.2.tgz", + "integrity": "sha512-f7rPLi/T1HVKZu/u6t87lroib16n8vrSzcyxI7lg4BGO9UF26KhQL44sd9eOUgrTYhvRXtWOIZT5PejdPyJfUA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-freebsd-x64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.12.2.tgz", + "integrity": "sha512-BpcOjWCJub6nRZUS2zA20pmLvjtqAtGejETaIyRLiZiQf++cbrjltLA5NN/xaXfqeOBOSlMFbemIl5/S5tljmg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.12.2.tgz", + "integrity": "sha512-vZTDvdSISZjJx66OzJqtsOhzifbqRjbmI1Mnu49fQDwog5GtDI4QidRiEAYbZCRj9C8YZEW+3ZjqsyS9GR4k2A==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.12.2.tgz", + "integrity": "sha512-BiPI+IrIlwcW4nLLMM21+B1dFPzd55yAVgVGrdgDjNef+ch03GdxrcyaIz8X9SsQirh/kCQ7mviyWlMxdh2D7g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.12.2.tgz", + "integrity": "sha512-zJc0H99FEPoFfSrNpa91HYfxzfAJCr502oxNK1cfdC9hlaFI43RT+JFCann9JUgZmLzzntChHyn13Sgn9ljHNg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-musl": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.12.2.tgz", + "integrity": "sha512-KQ3Lki6l+Pz1k/eBipN41ES+YUK30beLGb9YqcB1O542cyLCNE6GaxrfcY3T6EezmGGk84wb5XyO9loTM9tkcA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-loong64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-loong64-gnu/-/resolver-binding-linux-loong64-gnu-1.12.2.tgz", + "integrity": "sha512-3SJGEh1DborhG6pyxvhPzCT4bbSIVihsvgJc13P1bHG7KLdNDaF9T3gsTwFc7Jw/5Y5/iWOjkEx7Zy0NvCGX3Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-loong64-musl": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-loong64-musl/-/resolver-binding-linux-loong64-musl-1.12.2.tgz", + "integrity": "sha512-jiuG/Obbel7uw1PwHNFfrkiKhLAF6mnyZ6aWlOAVN9WqKm8v0OFGnciJIHu8+CMvXLQ8AD51LPzAoUfT21D5Ew==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.12.2.tgz", + "integrity": "sha512-q7xRvVpmcfeL+LlZg8Pbbo6QaTZwDU5BaGZbwfhkEsXJn3Was8xYfE0RBH266xZt0rM6B7i8xAYIvjthuUIWHg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.12.2.tgz", + "integrity": "sha512-0CVdx6lcnT3Q9inOH8tsMIOJ6ImndllMjqJHg8RLVdB7Vq4SfkEXl9mCSsVNuNA4MCYycRicCUxPCabVHJRr6A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.12.2.tgz", + "integrity": "sha512-iOwlRo9vnp6R6ohHQS11n0NnfdXx/omhkocmIfaPRpQhKZ+3BDMkkdRVh53qjkFkpPddf+FETA28NwGN7l5l+w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.12.2.tgz", + "integrity": "sha512-HYJtLfXq94q8iZNFT1lknx258wlkkWhZeUXJRqzKBBUJ00CvZ+N33zgbCqimLjsyw5Va6uUxhVa12mI+kaveEw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.12.2.tgz", + "integrity": "sha512-mPsUhunKKDih5O96Y6enDQyHc1SqBPlY1E/SfMWDM3EdJ95Z9CArPeCVwCCqbP45ljvivdEk8Fxn+SIb1rDAJQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-musl": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.12.2.tgz", + "integrity": "sha512-azrt6+5ydLd8Vt210AAFis/lZevSfPw93EJRIJG+xPu4WCJ8K0kppCTpMyLPcKT7H15M4Jnt2tMp5bOvCkRC6A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-openharmony-arm64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-openharmony-arm64/-/resolver-binding-openharmony-arm64-1.12.2.tgz", + "integrity": "sha512-YZ9hP4O0X9PQb8eO980qmLNGH4zT3I9+SZTdt0Pr0YyuGQhYKoOZkV02VzrzyOZJ5xIJ3UFIenKkUkGg8GjgWQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@unrs/resolver-binding-wasm32-wasi": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.12.2.tgz", + "integrity": "sha512-tYFDIkMxSflfEc/h92ZWNsZlHSwgimbNHSO3PL2JWQHfCuC2q316jMyYU9TIWZsFK2bQwyK5VAdYgn8ygPj69A==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.10.0", + "@emnapi/runtime": "1.10.0", + "@napi-rs/wasm-runtime": "^1.1.4" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.12.2.tgz", + "integrity": "sha512-qzNyg3xL0VPQmCaUh+N5jSitce6k+uCBfMDesWRnlULOZaqUkaJ0ybdT+UqlAWJoQjuqfIU/0Ptx9bteN4D82g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.12.2.tgz", + "integrity": "sha512-WD9sY00OfpHVGfsnHZoA8jVT+esS/Bg8z8jzxp5BnDCjjwsuKsPQrzswwpFy4J1AUJbXPRfkpcX0mXrzeXW79g==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-x64-msvc": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.12.2.tgz", + "integrity": "sha512-nAB74NfSNKknqQ1RrYj6uz8FcXEomu/MATJZxh/x+BArzN2U3JbOYC0APYzUIGhVY3m5hRxA8VPNdPBoG8txlA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/acorn": { "version": "8.16.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", @@ -982,6 +2381,38 @@ "node": ">= 6.0.0" } }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/any-promise": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", @@ -989,6 +2420,43 @@ "dev": true, "license": "MIT" }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/anymatch/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -1009,6 +2477,206 @@ "proxy-from-env": "^2.1.0" } }, + "node_modules/babel-jest": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.4.1.tgz", + "integrity": "sha512-fATAbM8piYxkiXQp3RBXmZHxZVNJZAVXXfyeyCN2Tida3+qJ8ea9UxhiJ2y4fLO90ZImKt6k9FlcH2+rLkJGhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/transform": "30.4.1", + "@types/babel__core": "^7.20.5", + "babel-plugin-istanbul": "^7.0.1", + "babel-preset-jest": "30.4.0", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "slash": "^3.0.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.11.0 || ^8.0.0-0" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-7.0.1.tgz", + "integrity": "sha512-D8Z6Qm8jCvVXtIRkBnqNHX0zJ37rQcFJ9u8WOS6tkYOsRdHBzypCstaxWiu5ZIlqQtviRYbgnRLSoCEvjqcqbA==", + "dev": true, + "license": "BSD-3-Clause", + "workspaces": [ + "test/babel-8" + ], + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-instrument": "^6.0.2", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "30.4.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.4.0.tgz", + "integrity": "sha512-9EdtWM/sSfXLOGLwSn+GS6pIXyBnL07/8gyJlwFXjWy4DxMOyItqyUT29d4lQiS380EZwYlX7/At4PgBS+m2aA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/babel__core": "^7.20.5" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz", + "integrity": "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-import-attributes": "^7.24.7", + "@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" + }, + "peerDependencies": { + "@babel/core": "^7.0.0 || ^8.0.0-0" + } + }, + "node_modules/babel-preset-jest": { + "version": "30.4.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-30.4.0.tgz", + "integrity": "sha512-lBY4jxsNmCnSiu7kquw8ZC9F4+XLMOKypT3RnNHPvU2Kpd4W0xaPuLr5ZkRyOsvLYAY4yaW1ZwTW4xB7NIiZzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "babel-plugin-jest-hoist": "30.4.0", + "babel-preset-current-node-syntax": "^1.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.11.0 || ^8.0.0-beta.1" + } + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.32", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.32.tgz", + "integrity": "sha512-wbPvpyjJPC0zdfdKXxqEL3Ea+bOMD/87X4lftiJkkaBiuG6ALQy1SLmEd7BSmVCuwCQsBrCamgBoLyfFDD1EPg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/brace-expansion": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/browserslist": { + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "baseline-browser-mapping": "^2.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bs-logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", + "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-json-stable-stringify": "2.x" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, "node_modules/bundle-require": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/bundle-require/-/bundle-require-5.1.0.tgz", @@ -1049,6 +2717,74 @@ "node": ">= 0.4" } }, + "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/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001793", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001793.tgz", + "integrity": "sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/chokidar": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", @@ -1065,6 +2801,107 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/ci-info": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz", + "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cjs-module-lexer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.2.0.tgz", + "integrity": "sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==", + "dev": true, + "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": ">=8" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "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": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/clsx": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", @@ -1075,6 +2912,44 @@ "node": ">=6" } }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.3.tgz", + "integrity": "sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==", + "dev": true, + "license": "MIT" + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -1115,6 +2990,13 @@ "node": "^14.18.0 || >=16.10.0" } }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, "node_modules/cookie": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", @@ -1129,6 +3011,21 @@ "url": "https://opencollective.com/express" } }, + "node_modules/cross-spawn": { + "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", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/csstype": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", @@ -1164,6 +3061,31 @@ } } }, + "node_modules/dedent": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.2.tgz", + "integrity": "sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, + "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/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -1174,6 +3096,16 @@ "node": ">=0.4.0" } }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/dom-helpers": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", @@ -1200,6 +3132,36 @@ "node": ">= 0.4" } }, + "node_modules/electron-to-chromium": { + "version": "1.5.361", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.361.tgz", + "integrity": "sha512-Q6Hts7N9FnJc5LeGRINFvLhCI9xZmNtTDe5ZbcVezQz7cU4a8Aua3GH1b8J2XY8Al9PF+OCwYqhgsOOheMdvkA==", + "dev": true, + "license": "ISC" + }, + "node_modules/emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, "node_modules/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", @@ -1292,6 +3254,109 @@ "@esbuild/win32-x64": "0.27.7" } }, + "node_modules/escalade": { + "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" + } + }, + "node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/exit-x": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/exit-x/-/exit-x-0.2.2.tgz", + "integrity": "sha512-+I6B/IkJc1o/2tiURyz/ivu/O0nKNEArIUB5O7zBrlDVJr22SCLH3xTeEry428LvFhRzIA1g8izguxJ/gbNcVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expect": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/expect/-/expect-30.4.1.tgz", + "integrity": "sha512-PMARsyh/JtqC20HoGqlFcIlQAyqUtW4PlI1rup1uhYJtKuwAjbvWi3GQMAn+STdHum/dk8xrKfUM1+5SAwpolA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/expect-utils": "30.4.1", + "@jest/get-type": "30.1.0", + "jest-matcher-utils": "30.4.1", + "jest-message-util": "30.4.1", + "jest-mock": "30.4.1", + "jest-util": "30.4.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bser": "2.1.1" + } + }, "node_modules/fdir": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", @@ -1310,6 +3375,20 @@ } } }, + "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, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/fix-dts-default-cjs-exports": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/fix-dts-default-cjs-exports/-/fix-dts-default-cjs-exports-1.0.1.tgz", @@ -1385,6 +3464,26 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "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-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": "6.* || 8.* || >= 10.*" + } + }, "node_modules/get-intrinsic": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", @@ -1410,6 +3509,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/get-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", @@ -1424,6 +3533,37 @@ "node": ">= 0.4" } }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "13.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -1437,6 +3577,55 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/handlebars": { + "version": "4.7.9", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.9.tgz", + "integrity": "sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, + "node_modules/handlebars/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", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/has-symbols": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", @@ -1479,6 +3668,13 @@ "node": ">= 0.4" } }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, "node_modules/https-proxy-agent": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", @@ -1493,6 +3689,771 @@ "node": ">= 6" } }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/import-local": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "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, + "license": "MIT" + }, + "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-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "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" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz", + "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", + "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.23", + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest": { + "version": "30.4.2", + "resolved": "https://registry.npmjs.org/jest/-/jest-30.4.2.tgz", + "integrity": "sha512-Yi1jqNC/Oq0N4hBgNH/YvBpP1P57QqundgytzYqy3yqAa7NZPNjSoi4SGbRAXDMdBzNE6xBCi5U7RgfrvMEUVQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/core": "30.4.2", + "@jest/types": "30.4.1", + "import-local": "^3.2.0", + "jest-cli": "30.4.2" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-changed-files": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-30.4.1.tgz", + "integrity": "sha512-IuctmYrxi21iOSOaIXpJWalHyPAsVv0GeBHKDn8C1CA4W5htHn7INL+wdnL4Bo0+olEndvAFkmb++tIQJG+vvg==", + "dev": true, + "license": "MIT", + "dependencies": { + "execa": "^5.1.1", + "jest-util": "30.4.1", + "p-limit": "^3.1.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-circus": { + "version": "30.4.2", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.4.2.tgz", + "integrity": "sha512-rvHH7VlY6LgbJXJTQ87GW62g1FntOtbhh0zT+v04kC+pgL6aBKyYINXxWukCpj3dcIBMw5/XUbtDS9dU9JTXeQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "30.4.1", + "@jest/expect": "30.4.1", + "@jest/test-result": "30.4.1", + "@jest/types": "30.4.1", + "@types/node": "*", + "chalk": "^4.1.2", + "co": "^4.6.0", + "dedent": "^1.6.0", + "is-generator-fn": "^2.1.0", + "jest-each": "30.4.1", + "jest-matcher-utils": "30.4.1", + "jest-message-util": "30.4.1", + "jest-runtime": "30.4.2", + "jest-snapshot": "30.4.1", + "jest-util": "30.4.1", + "p-limit": "^3.1.0", + "pretty-format": "30.4.1", + "pure-rand": "^7.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.6" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-cli": { + "version": "30.4.2", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.4.2.tgz", + "integrity": "sha512-jfA2ocvVHMXS2QijrJ0d31ektP+d/W0T5RpcTX2Pq+3sVqHlsXVCM2+FmwpL+bdY8OfHpIg9xMxLF17Zg0U49Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/core": "30.4.2", + "@jest/test-result": "30.4.1", + "@jest/types": "30.4.1", + "chalk": "^4.1.2", + "exit-x": "^0.2.2", + "import-local": "^3.2.0", + "jest-config": "30.4.2", + "jest-util": "30.4.1", + "jest-validate": "30.4.1", + "yargs": "^17.7.2" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-config": { + "version": "30.4.2", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.4.2.tgz", + "integrity": "sha512-rNHAShJQqQwFNoL0hbf3BphSBOWnpOUAKvidLS/AjNVLPfoj5mSf4jQMfW3cYOs6hXeZC7nF7mDHaBnbxELOzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.27.4", + "@jest/get-type": "30.1.0", + "@jest/pattern": "30.4.0", + "@jest/test-sequencer": "30.4.1", + "@jest/types": "30.4.1", + "babel-jest": "30.4.1", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "deepmerge": "^4.3.1", + "glob": "^10.5.0", + "graceful-fs": "^4.2.11", + "jest-circus": "30.4.2", + "jest-docblock": "30.4.0", + "jest-environment-node": "30.4.1", + "jest-regex-util": "30.4.0", + "jest-resolve": "30.4.1", + "jest-runner": "30.4.2", + "jest-util": "30.4.1", + "jest-validate": "30.4.1", + "parse-json": "^5.2.0", + "pretty-format": "30.4.1", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "esbuild-register": ">=3.4.0", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "esbuild-register": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/jest-diff": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.4.1.tgz", + "integrity": "sha512-CRpFK0RtLriVDGcPPAnR6HMVI8bSR2jnUIgralhauzYQZIb4RH9AtEInTuQr65LmmGggGcRT6HIASxwqsVsmlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/diff-sequences": "30.4.0", + "@jest/get-type": "30.1.0", + "chalk": "^4.1.2", + "pretty-format": "30.4.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-docblock": { + "version": "30.4.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-30.4.0.tgz", + "integrity": "sha512-ZPMabUZCx5MpbZ2eBYSvZ0J8fvo3dR9oM+eeUpb3aKNQFuS2tu3Duw1TNlMoP8k3WQgKGJuhcMFvwcVuq6T7oA==", + "dev": true, + "license": "MIT", + "dependencies": { + "detect-newline": "^3.1.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-each": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-30.4.1.tgz", + "integrity": "sha512-/8MJbH6fuj48TstjrMf+u/pd06Qezz5xOXvZA6442heNOWr8bdeoGZX2d9fCn028CoMgYmroH9//zky5GfyYmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/get-type": "30.1.0", + "@jest/types": "30.4.1", + "chalk": "^4.1.2", + "jest-util": "30.4.1", + "pretty-format": "30.4.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-environment-node": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.4.1.tgz", + "integrity": "sha512-4FZYVOk85hz2AyT6BbarKy9u37g6DbrDyCdFhsnDdXqyrueYQvB+0zO4f/kqLCRD0BsPRXPMNJeQwihKZV8naw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "30.4.1", + "@jest/fake-timers": "30.4.1", + "@jest/types": "30.4.1", + "@types/node": "*", + "jest-mock": "30.4.1", + "jest-util": "30.4.1", + "jest-validate": "30.4.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-haste-map": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.4.1.tgz", + "integrity": "sha512-rFrcONd8jeFsyw+Z9CrScJgglRf2+NFmNam8dKu7n+SoHqNYT47mn0DdEcVUZJpvh7Iz6/si7f7yUH7GJHVgnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.4.1", + "@types/node": "*", + "anymatch": "^3.1.3", + "fb-watchman": "^2.0.2", + "graceful-fs": "^4.2.11", + "jest-regex-util": "30.4.0", + "jest-util": "30.4.1", + "jest-worker": "30.4.1", + "picomatch": "^4.0.3", + "walker": "^1.0.8" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.3" + } + }, + "node_modules/jest-leak-detector": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.4.1.tgz", + "integrity": "sha512-IpmyiioeHxiWDhesHnUFmOxcTzwCwKpgACgWajtAP+nYQXiY7DakTxB6Bx9JFiRMljr0AX1PvnQdaU1KFoz6NQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/get-type": "30.1.0", + "pretty-format": "30.4.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.4.1.tgz", + "integrity": "sha512-zvYfX5CaeEkFrrLS9suWe9rvJrm9J1Iv3ua8kIBv9GEPzcnsfBf0bob37la7s67fs0nlBC3EuvkOLnXQKxtx4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/get-type": "30.1.0", + "chalk": "^4.1.2", + "jest-diff": "30.4.1", + "pretty-format": "30.4.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-message-util": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.4.1.tgz", + "integrity": "sha512-kwCKIvq0MCW1HzLoGola9Te6JUdzgV0loyKJ3Qghrkz9i5/RRIHsL95BMQc2HBBhlBKC4j22K9p11TGHH8RBpQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@jest/types": "30.4.1", + "@types/stack-utils": "^2.0.3", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "jest-util": "30.4.1", + "picomatch": "^4.0.3", + "pretty-format": "30.4.1", + "slash": "^3.0.0", + "stack-utils": "^2.0.6" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-mock": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.4.1.tgz", + "integrity": "sha512-/i8SVb8/NSB7RfNi8gfqu8gxLV23KaL5EpAttyb9iz8qWRIqXRLflycz/32wXsYkOnaUlx8NAKnJYtpsmXUmfw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.4.1", + "@types/node": "*", + "jest-util": "30.4.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-regex-util": { + "version": "30.4.0", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.4.0.tgz", + "integrity": "sha512-mWlvLviKIgIQ8VCuM1xRdD0TWp3zlzionlmDBjuXVBs+VkmXq6FgW9T4Emr7oGz/Rk6feDCGyiugolcQEyp3mg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-30.4.1.tgz", + "integrity": "sha512-Zry8Yq/yJcNAZ7dJ5F2heic8AheXvbFZ7XI5V+h28nrYZ7Qoyy4dItq8OodjnYD270mvX+ZudmrNV9cysqhW5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.4.1", + "jest-pnp-resolver": "^1.2.3", + "jest-util": "30.4.1", + "jest-validate": "30.4.1", + "slash": "^3.0.0", + "unrs-resolver": "^1.7.11" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "30.4.2", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.4.2.tgz", + "integrity": "sha512-gDiVh1I+GxYzz9oXlyw+1wv6VOYX1WYxMOfjsA3iGKePV2oxmbHhwxfkALxNxYy1ciw6APWwkW2zZONwP97aEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-regex-util": "30.4.0", + "jest-snapshot": "30.4.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-runner": { + "version": "30.4.2", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.4.2.tgz", + "integrity": "sha512-2dw0PslVYXxffXGpLo+Ejad+KcI1Qkjn7f4X4619gf21oCUmL+SPfjqIa/losUem3yEOvfNZe/F1HWUcNpODcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "30.4.1", + "@jest/environment": "30.4.1", + "@jest/test-result": "30.4.1", + "@jest/transform": "30.4.1", + "@jest/types": "30.4.1", + "@types/node": "*", + "chalk": "^4.1.2", + "emittery": "^0.13.1", + "exit-x": "^0.2.2", + "graceful-fs": "^4.2.11", + "jest-docblock": "30.4.0", + "jest-environment-node": "30.4.1", + "jest-haste-map": "30.4.1", + "jest-leak-detector": "30.4.1", + "jest-message-util": "30.4.1", + "jest-resolve": "30.4.1", + "jest-runtime": "30.4.2", + "jest-util": "30.4.1", + "jest-watcher": "30.4.1", + "jest-worker": "30.4.1", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-runtime": { + "version": "30.4.2", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.4.2.tgz", + "integrity": "sha512-3/5e8iPz2k/VLqlr8DgTftYyLUv8Su3FkCAO2/Od81UsUTpSxOrS6O5x5KkoQwyUjmpYyDJKeyAvg2T2nvpNkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "30.4.1", + "@jest/fake-timers": "30.4.1", + "@jest/globals": "30.4.1", + "@jest/source-map": "30.0.1", + "@jest/test-result": "30.4.1", + "@jest/transform": "30.4.1", + "@jest/types": "30.4.1", + "@types/node": "*", + "chalk": "^4.1.2", + "cjs-module-lexer": "^2.1.0", + "collect-v8-coverage": "^1.0.2", + "glob": "^10.5.0", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.4.1", + "jest-message-util": "30.4.1", + "jest-mock": "30.4.1", + "jest-regex-util": "30.4.0", + "jest-resolve": "30.4.1", + "jest-snapshot": "30.4.1", + "jest-util": "30.4.1", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-snapshot": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.4.1.tgz", + "integrity": "sha512-tEOkkfOMppUyeiHwjZswOQ3lcnoTnws/q5FnGIaeIh/jmoU0ZlgMYRR8sTlTj+nNGCoJ0RDq6SfxGxCsyMTPmw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.27.4", + "@babel/generator": "^7.27.5", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/plugin-syntax-typescript": "^7.27.1", + "@babel/types": "^7.27.3", + "@jest/expect-utils": "30.4.1", + "@jest/get-type": "30.1.0", + "@jest/snapshot-utils": "30.4.1", + "@jest/transform": "30.4.1", + "@jest/types": "30.4.1", + "babel-preset-current-node-syntax": "^1.2.0", + "chalk": "^4.1.2", + "expect": "30.4.1", + "graceful-fs": "^4.2.11", + "jest-diff": "30.4.1", + "jest-matcher-utils": "30.4.1", + "jest-message-util": "30.4.1", + "jest-util": "30.4.1", + "pretty-format": "30.4.1", + "semver": "^7.7.2", + "synckit": "^0.11.8" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/semver": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz", + "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-util": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.4.1.tgz", + "integrity": "sha512-vjQb1sACEiv13DKJMDToJpzVW0joCsIQrmbg0fi7CyOOt+g9jTuQl2A216pWRBYhOVt53XbL/2LbMKg1BECWOw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.4.1", + "@types/node": "*", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "graceful-fs": "^4.2.11", + "picomatch": "^4.0.3" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-validate": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.4.1.tgz", + "integrity": "sha512-PDWi4SOwLnwqNDfHZjOcsEFyZ4fc/2W2gVL3DEoyqnB6jCQMLRtfBong8s6omIw3lI0HWOus12xfnFmQtjW3fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/get-type": "30.1.0", + "@jest/types": "30.4.1", + "camelcase": "^6.3.0", + "chalk": "^4.1.2", + "leven": "^3.1.0", + "pretty-format": "30.4.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-watcher": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.4.1.tgz", + "integrity": "sha512-/l9UonmvCwjHH7d2h3iAwIloLc1H0S8mJZ/LNK3i86hqwPAz8otUJjP9MfYtz9Tt77Su5FD2xGjZn8d31IZHlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "30.4.1", + "@jest/types": "30.4.1", + "@types/node": "*", + "ansi-escapes": "^4.3.2", + "chalk": "^4.1.2", + "emittery": "^0.13.1", + "jest-util": "30.4.1", + "string-length": "^4.0.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-worker": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.4.1.tgz", + "integrity": "sha512-SHynN/q/QD++iNyvMdy+WMmbCGk8jIsNcRxycXbWubSOhvo6T+j2afcfUSl+3hYsiBebOTo0cT7c2H7CXugu1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@ungap/structured-clone": "^1.3.0", + "jest-util": "30.4.1", + "merge-stream": "^2.0.0", + "supports-color": "^8.1.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, "node_modules/joycon": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", @@ -1510,6 +4471,63 @@ "dev": true, "license": "MIT" }, + "node_modules/js-yaml": { + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "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": ">=6" + } + }, + "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, + "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" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "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" + } + }, "node_modules/lilconfig": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", @@ -1540,12 +4558,32 @@ "node": "^12.20.0 || ^14.13.1 || >=16.0.0" } }, + "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, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/lodash": { "version": "4.18.1", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", "license": "MIT" }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "dev": true, + "license": "MIT" + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -1559,10 +4597,20 @@ "loose-envify": "cli.js" } }, + "node_modules/lru-cache": { + "version": "5.1.1", + "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" + } + }, "node_modules/lucide-react": { - "version": "0.576.0", - "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.576.0.tgz", - "integrity": "sha512-koNxU14BXrxUfZQ9cUaP0ES1uyPZKYDjk31FQZB6dQ/x+tXk979sVAn9ppZ/pVeJJyOxVM8j1E+8QEuSc02Vug==", + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.16.0.tgz", + "integrity": "sha512-dYwyPzb4MEKpGUmNYk3WKWPnMrHs3FKM+q94kAnJrcDIqqn1hq2xY8scaS2ovsOCM5D51ey2gaRG3PBb1vgoYQ==", "dev": true, "license": "ISC", "peerDependencies": { @@ -1579,6 +4627,52 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz", + "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true, + "license": "ISC" + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tmpl": "1.0.5" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -1589,6 +4683,13 @@ "node": ">= 0.4" } }, + "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, + "license": "MIT" + }, "node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", @@ -1612,6 +4713,52 @@ "node": ">= 0.6" } }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/mlly": { "version": "1.8.2", "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.2.tgz", @@ -1644,6 +4791,76 @@ "thenify-all": "^1.0.0" } }, + "node_modules/napi-postinstall": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz", + "integrity": "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==", + "dev": true, + "license": "MIT", + "bin": { + "napi-postinstall": "lib/cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/napi-postinstall" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.46", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.46.tgz", + "integrity": "sha512-GYVXHE2KnrzAfsAjl4uP++evGFCrAU1jta4ubEjIG7YWt/64Gqv66a30yKwWczVjA6j3bM4nBwH7Pk1JmDHaxQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -1654,6 +4871,143 @@ "node": ">=0.10.0" } }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-locate/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, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "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" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "11.5.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.0.tgz", + "integrity": "sha512-5YgH9UJd7wVb9hIouI2adWpgqrrICkt070Dnj8EUY1+B4B2P9eRLPAkAAo6NICA7CEhOIeBHl46u9zSNpNu7zA==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, "node_modules/pathe": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", @@ -1692,6 +5046,19 @@ "node": ">= 6" } }, + "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, + "license": "MIT", + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/pkg-types": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", @@ -1747,6 +5114,35 @@ } } }, + "node_modules/pretty-format": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.4.1.tgz", + "integrity": "sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "30.4.1", + "ansi-styles": "^5.2.0", + "react-is-18": "npm:react-is@^18.3.1", + "react-is-19": "npm:react-is@^19.2.5" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.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, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", @@ -1769,6 +5165,23 @@ "node": ">=10" } }, + "node_modules/pure-rand": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-7.0.1.tgz", + "integrity": "sha512-oTUZM/NAZS8p7ANR3SHh30kXB+zK2r2BPcEn/awJIbOvq82WoMN4p62AWWp3Hhw50G0xMsw1mhIBLqHw64EcNQ==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT" + }, "node_modules/react": { "version": "19.2.6", "resolved": "https://registry.npmjs.org/react/-/react-19.2.6.tgz", @@ -1801,6 +5214,22 @@ "dev": true, "license": "MIT" }, + "node_modules/react-is-18": { + "name": "react-is", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/react-is-19": { + "name": "react-is", + "version": "19.2.6", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.6.tgz", + "integrity": "sha512-XjBR15BhXuylgWGuslhDKqlSayuqvqBX91BP8pauG8kd1zY8kotkNWbXksTCNRarse4kuGbe2kIY05ARtwNIvw==", + "dev": true, + "license": "MIT" + }, "node_modules/react-lifecycles-compat": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", @@ -1881,6 +5310,29 @@ "url": "https://paulmillr.com/funding/" } }, + "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/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/resolve-from": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", @@ -1943,6 +5395,16 @@ "dev": true, "license": "MIT" }, + "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/set-cookie-parser": { "version": "2.7.2", "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", @@ -1950,6 +5412,46 @@ "dev": true, "license": "MIT" }, + "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" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/source-map": { "version": "0.7.6", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", @@ -1960,6 +5462,117 @@ "node": ">= 12" } }, + "node_modules/source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-support/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", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-length/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-length/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/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "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" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/sucrase": { "version": "3.35.1", "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", @@ -1983,6 +5596,50 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/synckit": { + "version": "0.11.12", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.12.tgz", + "integrity": "sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@pkgr/core": "^0.2.9" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/synckit" + } + }, + "node_modules/test-exclude": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-8.0.0.tgz", + "integrity": "sha512-ZOffsNrXYggvU1mDGHk54I96r26P8SyMjO5slMKSc7+IWmtB/MQKnEC2fP51imB3/pT6YK5cT5E8f+Dd9KdyOQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^13.0.6", + "minimatch": "^10.2.2" + }, + "engines": { + "node": "20 || >=22" + } + }, "node_modules/thenify": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", @@ -2030,6 +5687,13 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true, + "license": "BSD-3-Clause" + }, "node_modules/tree-kill": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", @@ -2047,6 +5711,93 @@ "dev": true, "license": "Apache-2.0" }, + "node_modules/ts-jest": { + "version": "29.4.11", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.11.tgz", + "integrity": "sha512-IrFl7l9AuB/qrNw5quqvAv/hmKMb8dhWOH4jQOGo0Oq8tCeo1O86/iTFG1FaRimgUkF13l4PcepO8ATFT6Ns4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "bs-logger": "^0.2.6", + "fast-json-stable-stringify": "^2.1.0", + "handlebars": "^4.7.9", + "json5": "^2.2.3", + "lodash.memoize": "^4.1.2", + "make-error": "^1.3.6", + "semver": "^7.8.0", + "type-fest": "^4.41.0", + "yargs-parser": "^21.1.1" + }, + "bin": { + "ts-jest": "cli.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "@babel/core": ">=7.0.0-beta.0 <8", + "@jest/transform": "^29.0.0 || ^30.0.0", + "@jest/types": "^29.0.0 || ^30.0.0", + "babel-jest": "^29.0.0 || ^30.0.0", + "jest": "^29.0.0 || ^30.0.0", + "jest-util": "^29.0.0 || ^30.0.0", + "typescript": ">=4.3 <7" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "@jest/transform": { + "optional": true + }, + "@jest/types": { + "optional": true + }, + "babel-jest": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jest-util": { + "optional": true + } + } + }, + "node_modules/ts-jest/node_modules/semver": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz", + "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ts-jest/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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", + "optional": true + }, "node_modules/tsup": { "version": "8.5.1", "resolved": "https://registry.npmjs.org/tsup/-/tsup-8.5.1.tgz", @@ -2100,12 +5851,36 @@ } } }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -2121,17 +5896,273 @@ "dev": true, "license": "MIT" }, - "node_modules/uuid": { - "version": "13.0.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-13.0.2.tgz", - "integrity": "sha512-vzi9uRZ926x4XV73S/4qQaTwPXM2JBj6/6lI/byHH1jOpCzb0zDbfytgA9LcN/hzb2l7WQSQnxITOVx5un/wGw==", + "node_modules/uglify-js": { + "version": "3.19.3", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", + "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", + "dev": true, + "license": "BSD-2-Clause", + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/undici-types": { + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz", + "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==", + "dev": true, + "license": "MIT" + }, + "node_modules/unrs-resolver": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.12.2.tgz", + "integrity": "sha512-dmlRxBJJayXjqTwC+JtF1HhJmgf3ftQ3YejFcZrf4+KKtJv0qDsK1pjqaaVjG7wJ5NJ6UVP1OqRMQ71Z4C3rxQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "napi-postinstall": "^0.3.4" + }, + "funding": { + "url": "https://opencollective.com/unrs-resolver" + }, + "optionalDependencies": { + "@unrs/resolver-binding-android-arm-eabi": "1.12.2", + "@unrs/resolver-binding-android-arm64": "1.12.2", + "@unrs/resolver-binding-darwin-arm64": "1.12.2", + "@unrs/resolver-binding-darwin-x64": "1.12.2", + "@unrs/resolver-binding-freebsd-x64": "1.12.2", + "@unrs/resolver-binding-linux-arm-gnueabihf": "1.12.2", + "@unrs/resolver-binding-linux-arm-musleabihf": "1.12.2", + "@unrs/resolver-binding-linux-arm64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-arm64-musl": "1.12.2", + "@unrs/resolver-binding-linux-loong64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-loong64-musl": "1.12.2", + "@unrs/resolver-binding-linux-ppc64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-riscv64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-riscv64-musl": "1.12.2", + "@unrs/resolver-binding-linux-s390x-gnu": "1.12.2", + "@unrs/resolver-binding-linux-x64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-x64-musl": "1.12.2", + "@unrs/resolver-binding-openharmony-arm64": "1.12.2", + "@unrs/resolver-binding-wasm32-wasi": "1.12.2", + "@unrs/resolver-binding-win32-arm64-msvc": "1.12.2", + "@unrs/resolver-binding-win32-ia32-msvc": "1.12.2", + "@unrs/resolver-binding-win32-x64-msvc": "1.12.2" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } ], "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, "bin": { - "uuid": "dist-node/bin/uuid" + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/v8-to-istanbul": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "makeerror": "1.0.12" + } + }, + "node_modules/which": { + "version": "2.0.2", + "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" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/write-file-atomic": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", + "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/write-file-atomic/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" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "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": "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": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/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/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/zod": { @@ -2145,71 +6176,70 @@ } }, "packages/components": { - "name": "@maksit/webui-components", - "version": "0.1.0", + "name": "@maks-it.com/webui-components", + "version": "0.2.0", "dependencies": { - "@maksit/webui-contracts": "^0.1.0", - "@maksit/webui-core": "^0.1.0", - "date-fns": "^4.1.0", - "lodash": "^4.17.23", - "uuid": "^13.0.0" + "@maks-it.com/webui-contracts": "^0.2.0", + "@maks-it.com/webui-core": "^0.2.0", + "date-fns": "^4.3.0", + "lodash": "^4.18.1" }, "devDependencies": { "@tanstack/react-table": "^8.21.3", "@types/lodash": "^4.17.24", - "@types/react": "^19.2.14", + "@types/react": "^19.2.15", "@types/react-dom": "^19.2.3", "@types/react-virtualized": "^9.22.3", - "lucide-react": "^0.576.0", - "react": "^19.2.4", - "react-dom": "^19.2.4", - "react-router-dom": "^7.13.1", + "lucide-react": "^1.16.0", + "react": "^19.2.6", + "react-dom": "^19.2.6", + "react-router-dom": "^7.15.1", "react-virtualized": "^9.22.6", - "tsup": "^8.5.0", - "typescript": "^5.9.3", - "zod": "^4.3.6" + "tsup": "^8.5.1", + "typescript": "^6.0.3", + "zod": "^4.4.3" }, "peerDependencies": { "@tanstack/react-table": "^8.0.0", - "lucide-react": "^0.500.0", + "lucide-react": "^1.0.0", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0", "react-router-dom": "^7.0.0", "react-virtualized": "^9.22.0", - "zod": "^4.0.0" + "zod": "^4.4.0" } }, "packages/contracts": { - "name": "@maksit/webui-contracts", - "version": "0.1.0", + "name": "@maks-it.com/webui-contracts", + "version": "0.2.0", "devDependencies": { - "tsup": "^8.5.0", - "typescript": "^5.9.3", - "zod": "^4.3.6" + "tsup": "^8.5.1", + "typescript": "^6.0.3", + "zod": "^4.4.3" }, "peerDependencies": { - "zod": "^4.0.0" + "zod": "^4.4.0" } }, "packages/core": { - "name": "@maksit/webui-core", - "version": "0.1.0", + "name": "@maks-it.com/webui-core", + "version": "0.2.0", "dependencies": { - "@maksit/webui-contracts": "^0.1.0", - "date-fns": "^4.1.0" + "@maks-it.com/webui-contracts": "^0.2.0", + "date-fns": "^4.3.0" }, "devDependencies": { - "@types/react": "^19.2.14", - "axios": "^1.13.2", - "react": "^19.2.4", - "tsup": "^8.5.0", - "typescript": "^5.9.3", - "zod": "^4.3.6" + "@types/react": "^19.2.15", + "axios": "^1.16.1", + "react": "^19.2.6", + "tsup": "^8.5.1", + "typescript": "^6.0.3", + "zod": "^4.4.3" }, "peerDependencies": { - "axios": "^1.7.0", + "axios": "^1.16.0", "react": "^18.0.0 || ^19.0.0", - "zod": "^4.0.0" + "zod": "^4.4.0" } } } diff --git a/src/package.json b/src/package.json index 28dd05b..de14fce 100644 --- a/src/package.json +++ b/src/package.json @@ -1,7 +1,7 @@ { "name": "maksit-webui", "private": true, - "version": "0.1.0", + "version": "0.2.0", "description": "Shared React UI library for MaksIT Certs UI and Vault WebUI", "workspaces": [ "packages/*" @@ -10,13 +10,22 @@ "node": ">=20" }, "overrides": { - "zod": "^4.3.6" + "zod": "^4.4.3", + "glob": "^13.0.6", + "test-exclude": "^8.0.0" }, "scripts": { - "build": "npm run build -w @maksit/webui-contracts && npm run build -w @maksit/webui-core && npm run build -w @maksit/webui-components", + "build": "npm run build -w @maks-it.com/webui-contracts && npm run build -w @maks-it.com/webui-core && npm run build -w @maks-it.com/webui-components", + "test": "jest --config jest.config.cjs", + "test:coverage": "jest --config jest.config.cjs --coverage", "typecheck": "npm run typecheck --workspaces --if-present", "clean": "npm run clean --workspaces --if-present" }, + "devDependencies": { + "@types/jest": "^30.0.0", + "jest": "^30.4.2", + "ts-jest": "^29.4.11" + }, "author": "MaksIT", "license": "MIT" } diff --git a/src/packages/components/README.md b/src/packages/components/README.md new file mode 100644 index 0000000..d052635 --- /dev/null +++ b/src/packages/components/README.md @@ -0,0 +1,66 @@ +# @maks-it.com/webui-components + +Shared React UI components for MaksIT WebUI apps: forms, DataTable, layout, editors, and list views. + +Depends on `@maks-it.com/webui-core` and `@maks-it.com/webui-contracts`. Peer dependencies must be installed in the host app. + +## Install + +```bash +npm install @maks-it.com/webui-components @maks-it.com/webui-core @maks-it.com/webui-contracts +npm install react react-dom react-router-dom lucide-react @tanstack/react-table react-virtualized zod +``` + +## Components + +| Export | Purpose | +|--------|---------| +| `DataTable`, `DataTableFilter`, `DataTableLabel` | Virtualized server-paged tables | +| `RemoteSelectBoxComponent` | Async search select (pass `searchRoute` API path) | +| `SecretComponent` | Secret field with optional generate action | +| `FormContainer`, `FormHeader`, `FormContent`, `FormFooter` | Form layout shell | +| `Layout` | App chrome / navigation wrapper | +| `Offcanvas` | Slide-over panel | +| `LazyLoadTable` | Incrementally loaded table | +| `VaultStyleDataTable`, `VaultStyleListSection` | Vault-style list layouts | +| `EntityScopesSummary` | Entity scope permissions summary | +| `Toast`, `addToast` | Toast notifications | +| `FieldContainer` | Label + validation wrapper for fields | + +## Example — DataTable + +```tsx +import { DataTable, createColumns } from '@maks-it.com/webui-components' + +const columns = createColumns([ + { key: 'name', header: 'Name' }, + { key: 'createdAt', header: 'Created', label: 'date' }, +]) + + +``` + +## Example — remote select + +```tsx +import { RemoteSelectBoxComponent } from '@maks-it.com/webui-components' + + +``` + +## Repository + +[github.com/MAKS-IT-COM/maksit-webui](https://github.com/MAKS-IT-COM/maksit-webui) — `src/packages/components` + +## License + +MIT diff --git a/src/packages/components/package.json b/src/packages/components/package.json index 0aeaab8..ebbfc6b 100644 --- a/src/packages/components/package.json +++ b/src/packages/components/package.json @@ -1,6 +1,6 @@ { - "name": "@maksit/webui-components", - "version": "0.1.0", + "name": "@maks-it.com/webui-components", + "version": "0.2.0", "description": "Shared React components for MaksIT WebUI apps", "type": "module", "main": "./dist/index.cjs", @@ -18,7 +18,7 @@ "README.md" ], "scripts": { - "build": "tsup src/index.ts --format esm,cjs --dts --clean --external react --external react-dom --external react-router-dom --external lucide-react --external @tanstack/react-table --external react-virtualized", + "build": "tsup src/index.ts --format esm,cjs --dts --clean --tsconfig tsconfig.build.json --external react --external react-dom --external react-router-dom --external lucide-react --external @tanstack/react-table --external react-virtualized", "typecheck": "tsc -p tsconfig.json --noEmit", "clean": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\"", "prepublishOnly": "npm run build" @@ -33,34 +33,33 @@ "directory": "src/packages/components" }, "dependencies": { - "@maksit/webui-contracts": "^0.1.0", - "@maksit/webui-core": "^0.1.0", - "date-fns": "^4.1.0", - "lodash": "^4.17.23", - "uuid": "^13.0.0" + "@maks-it.com/webui-contracts": "^0.2.0", + "@maks-it.com/webui-core": "^0.2.0", + "date-fns": "^4.3.0", + "lodash": "^4.18.1" }, "peerDependencies": { "@tanstack/react-table": "^8.0.0", - "lucide-react": "^0.500.0", + "lucide-react": "^1.0.0", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0", "react-router-dom": "^7.0.0", "react-virtualized": "^9.22.0", - "zod": "^4.0.0" + "zod": "^4.4.0" }, "devDependencies": { "@tanstack/react-table": "^8.21.3", "@types/lodash": "^4.17.24", - "@types/react": "^19.2.14", + "@types/react": "^19.2.15", "@types/react-dom": "^19.2.3", "@types/react-virtualized": "^9.22.3", - "lucide-react": "^0.576.0", - "react": "^19.2.4", - "react-dom": "^19.2.4", - "react-router-dom": "^7.13.1", + "lucide-react": "^1.16.0", + "react": "^19.2.6", + "react-dom": "^19.2.6", + "react-router-dom": "^7.15.1", "react-virtualized": "^9.22.6", - "tsup": "^8.5.0", - "typescript": "^5.9.3", - "zod": "^4.3.6" + "tsup": "^8.5.1", + "typescript": "^6.0.3", + "zod": "^4.4.3" } } diff --git a/src/packages/components/src/components/DataTable/DataTable.tsx b/src/packages/components/src/components/DataTable/DataTable.tsx index f6a5639..ef07043 100644 --- a/src/packages/components/src/components/DataTable/DataTable.tsx +++ b/src/packages/components/src/components/DataTable/DataTable.tsx @@ -1,9 +1,9 @@ import React, { useState, useMemo, useRef, useEffect } from 'react' import { AutoSizer, MultiGrid, GridCellProps } from 'react-virtualized' -import { mapPagedToDataTable, type DataTablePageView, type PagedResponse } from '@maksit/webui-core' +import { mapPagedToDataTable, type DataTablePageView, type PagedResponse } from '@maks-it.com/webui-core' import { Plus, Trash2, Edit } from 'lucide-react' -import { debounce } from 'lodash' +import debounce from 'lodash/debounce' interface FilterProps { diff --git a/src/packages/components/src/components/DataTable/DataTableFilter.tsx b/src/packages/components/src/components/DataTable/DataTableFilter.tsx index a0d1198..33e5f58 100644 --- a/src/packages/components/src/components/DataTable/DataTableFilter.tsx +++ b/src/packages/components/src/components/DataTable/DataTableFilter.tsx @@ -1,5 +1,5 @@ import { useMemo, useState } from 'react' -import { debounce } from 'lodash' +import debounce from 'lodash/debounce' interface FilterPropsBase { filterId?: string diff --git a/src/packages/components/src/components/DataTable/DataTableLabel.tsx b/src/packages/components/src/components/DataTable/DataTableLabel.tsx index d53c41c..c85687f 100644 --- a/src/packages/components/src/components/DataTable/DataTableLabel.tsx +++ b/src/packages/components/src/components/DataTable/DataTableLabel.tsx @@ -1,5 +1,5 @@ import { useEffect, useMemo, useState } from 'react' -import { formatISODateString } from '@maksit/webui-core' +import { formatISODateString } from '@maks-it.com/webui-core' interface NormalLabelProps { type: 'normal' diff --git a/src/packages/components/src/components/Scopes/EntityScopesSummary.tsx b/src/packages/components/src/components/Scopes/EntityScopesSummary.tsx index 44ec519..34abae1 100644 --- a/src/packages/components/src/components/Scopes/EntityScopesSummary.tsx +++ b/src/packages/components/src/components/Scopes/EntityScopesSummary.tsx @@ -1,4 +1,4 @@ -import type { SearchEntityScopeEntry } from '@maksit/webui-contracts' +import type { SearchEntityScopeEntry } from '@maks-it.com/webui-contracts' export interface EntityScopesSummaryProps { entries: SearchEntityScopeEntry[] diff --git a/src/packages/components/src/components/Toast/index.tsx b/src/packages/components/src/components/Toast/index.tsx index 74260f3..7934fb1 100644 --- a/src/packages/components/src/components/Toast/index.tsx +++ b/src/packages/components/src/components/Toast/index.tsx @@ -1,5 +1,4 @@ import { useState, useEffect, FC } from 'react' -import { v4 as uuidv4 } from 'uuid' // Define types for a toast interface Toast { @@ -9,6 +8,8 @@ interface Toast { duration?: number; } +const createToastId = (): string => crypto.randomUUID() + const Toast: FC = () => { const [toasts, setToasts] = useState([]) @@ -17,7 +18,7 @@ const Toast: FC = () => { const { message, type, duration } = event.detail // Add the new toast, avoiding duplicates with same message & type - const id = uuidv4() + const id = createToastId() setToasts(prev => { const hasDuplicate = prev.some(t => t.message === message && t.type === type) if (hasDuplicate) return prev diff --git a/src/packages/components/src/components/editors/FileUploadComponent.tsx b/src/packages/components/src/components/editors/FileUploadComponent.tsx index 1ee1cb3..ebe0f8d 100644 --- a/src/packages/components/src/components/editors/FileUploadComponent.tsx +++ b/src/packages/components/src/components/editors/FileUploadComponent.tsx @@ -1,6 +1,6 @@ import React, { useRef, useState } from 'react' import { ButtonComponent } from './ButtonComponent' -import { TrashIcon } from 'lucide-react' +import { Trash2 } from 'lucide-react' interface FileUploadComponentProps { label?: string @@ -163,7 +163,7 @@ const FileUploadComponent: React.FC = ({ disabled={disabled || displayFiles.length === 0} colspan={1} > - + {/* Select files button */} diff --git a/src/packages/components/src/components/editors/RemoteSelectBoxComponent.tsx b/src/packages/components/src/components/editors/RemoteSelectBoxComponent.tsx index 9f37bb4..0be9346 100644 --- a/src/packages/components/src/components/editors/RemoteSelectBoxComponent.tsx +++ b/src/packages/components/src/components/editors/RemoteSelectBoxComponent.tsx @@ -1,7 +1,7 @@ import { useState, useCallback, ChangeEvent, useEffect, useRef } from 'react' -import type { PagedRequest } from '@maksit/webui-contracts' -import type { SearchResponseBase } from '@maksit/webui-contracts' -import { deepEqual } from '@maksit/webui-core' +import type { PagedRequest } from '@maks-it.com/webui-contracts' +import type { SearchResponseBase } from '@maks-it.com/webui-contracts' +import { deepEqual } from '@maks-it.com/webui-core' import { SelectBoxComponent } from './SelectBoxComponent' export type RemoteSelectSearchDataSource = ( diff --git a/src/packages/components/src/components/editors/SelectBoxComponent.tsx b/src/packages/components/src/components/editors/SelectBoxComponent.tsx index 73c1064..8a3b65a 100644 --- a/src/packages/components/src/components/editors/SelectBoxComponent.tsx +++ b/src/packages/components/src/components/editors/SelectBoxComponent.tsx @@ -1,4 +1,4 @@ -import { debounce } from 'lodash' +import debounce from 'lodash/debounce' import { CircleX } from 'lucide-react' import { ChangeEvent, FC, useCallback, useEffect, useMemo, useRef, useState } from 'react' import { FieldContainer } from './FieldContainer' diff --git a/src/packages/components/tsconfig.build.json b/src/packages/components/tsconfig.build.json new file mode 100644 index 0000000..35bcdb2 --- /dev/null +++ b/src/packages/components/tsconfig.build.json @@ -0,0 +1,6 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "ignoreDeprecations": "6.0" + } +} diff --git a/src/packages/contracts/README.md b/src/packages/contracts/README.md new file mode 100644 index 0000000..3247510 --- /dev/null +++ b/src/packages/contracts/README.md @@ -0,0 +1,48 @@ +# @maks-it.com/webui-contracts + +Shared TypeScript contracts and Zod schemas for MaksIT WebUI apps (Certs UI, Vault WebUI, and related products). + +## Install + +```bash +npm install @maks-it.com/webui-contracts zod +``` + +`zod` is a peer dependency (schemas use Zod v4). + +## Contents + +| Area | Exports | +|------|---------| +| Paging | `PagedRequest`, `PagedRequestSchema`, `PagedResponse` | +| PATCH | `PatchOperation`, `PatchRequestModelBase`, `PatchRequestModelBaseSchema` | +| API errors | `ProblemDetails` | +| Search | `SearchResponseBase`, `SearchEntityScopeEntry` | +| Identity | `LoginRequest` / `LoginRequestSchema`, `LoginResponse`, `RefreshTokenRequest`, `LogoutRequest`, `Claims` | +| Misc | `TrngResponse`, `RequestModelBase`, `ResponseModelBase` | + +## Example + +```ts +import { + LoginRequestSchema, + type PagedRequest, + PatchOperation, +} from '@maks-it.com/webui-contracts' + +const login = LoginRequestSchema.parse({ username: 'admin', password: '***' }) + +const page: PagedRequest = { + pageNumber: 1, + pageSize: 25, + filters: 'Name.Contains("cert")', +} +``` + +## Repository + +[github.com/MAKS-IT-COM/maksit-webui](https://github.com/MAKS-IT-COM/maksit-webui) — `src/packages/contracts` + +## License + +MIT diff --git a/src/packages/contracts/package.json b/src/packages/contracts/package.json index 5bd15d0..4c7c712 100644 --- a/src/packages/contracts/package.json +++ b/src/packages/contracts/package.json @@ -1,6 +1,6 @@ { - "name": "@maksit/webui-contracts", - "version": "0.1.0", + "name": "@maks-it.com/webui-contracts", + "version": "0.2.0", "description": "Shared TypeScript contracts for MaksIT WebUI apps", "type": "module", "main": "./dist/index.cjs", @@ -18,7 +18,8 @@ "README.md" ], "scripts": { - "build": "tsup src/index.ts --format esm,cjs --dts --clean", + "build": "tsup src/index.ts --format esm,cjs --dts --clean --tsconfig tsconfig.build.json", + "test": "jest --config ../../jest.config.cjs --testPathPatterns packages/contracts", "typecheck": "tsc -p tsconfig.json --noEmit", "clean": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\"", "prepublishOnly": "npm run build" @@ -33,11 +34,11 @@ "directory": "src/packages/contracts" }, "peerDependencies": { - "zod": "^4.0.0" + "zod": "^4.4.0" }, "devDependencies": { - "tsup": "^8.5.0", - "typescript": "^5.9.3", - "zod": "^4.3.6" + "tsup": "^8.5.1", + "typescript": "^6.0.3", + "zod": "^4.4.3" } } diff --git a/src/packages/contracts/src/PagedRequest.ts b/src/packages/contracts/src/PagedRequest.ts index 450b04b..62f4a5d 100644 --- a/src/packages/contracts/src/PagedRequest.ts +++ b/src/packages/contracts/src/PagedRequest.ts @@ -1,4 +1,4 @@ -import { boolean, number, object, record, string, type ZodType } from 'zod' +import { boolean, intersection, number, object, record, string, type ZodType } from 'zod' import type { RequestModelBase } from './RequestModelBase' import { RequestModelBaseSchema } from './RequestModelBase' @@ -11,7 +11,8 @@ export interface PagedRequest extends RequestModelBase { isAscending?: boolean } -export const PagedRequestSchema: ZodType = RequestModelBaseSchema.and( +export const PagedRequestSchema: ZodType = intersection( + RequestModelBaseSchema, object({ pageSize: number().optional(), pageNumber: number().optional(), diff --git a/src/packages/contracts/src/PatchRequestModelBase.ts b/src/packages/contracts/src/PatchRequestModelBase.ts index f88edee..0ebf0f5 100644 --- a/src/packages/contracts/src/PatchRequestModelBase.ts +++ b/src/packages/contracts/src/PatchRequestModelBase.ts @@ -1,4 +1,4 @@ -import z, { object, record, string, type ZodType } from 'zod' +import z, { intersection, object, record, string, type ZodType } from 'zod' import { PatchOperation } from './PatchOperation' import { RequestModelBase, RequestModelBaseSchema } from './RequestModelBase' @@ -8,7 +8,8 @@ export interface PatchRequestModelBase extends RequestModelBase { [key: string]: unknown } -export const PatchRequestModelBaseSchema: ZodType = RequestModelBaseSchema.and( +export const PatchRequestModelBaseSchema: ZodType = intersection( + RequestModelBaseSchema, object({ operations: record(string(), z.enum(PatchOperation)).optional(), }) diff --git a/src/packages/contracts/src/identity/login/LoginRequest.ts b/src/packages/contracts/src/identity/login/LoginRequest.ts index 6bdfaaf..5f3a467 100644 --- a/src/packages/contracts/src/identity/login/LoginRequest.ts +++ b/src/packages/contracts/src/identity/login/LoginRequest.ts @@ -1,4 +1,4 @@ -import { object, RefinementCtx, string, ZodIssueCode, type ZodType } from 'zod' +import { object, RefinementCtx, string, type ZodType } from 'zod' export interface LoginRequest { username: string @@ -10,7 +10,7 @@ export interface LoginRequest { const loginRequestSchemaRefine = (data: LoginRequest, ctx: RefinementCtx) => { if (data.username === '') { ctx.addIssue({ - code: ZodIssueCode.custom, + code: 'custom', message: 'Username cannot be empty', path: ['username'], }) @@ -18,7 +18,7 @@ const loginRequestSchemaRefine = (data: LoginRequest, ctx: RefinementCtx) => { if (data.password === '') { ctx.addIssue({ - code: ZodIssueCode.custom, + code: 'custom', message: 'Password cannot be empty', path: ['password'], }) @@ -26,7 +26,7 @@ const loginRequestSchemaRefine = (data: LoginRequest, ctx: RefinementCtx) => { if (data.twoFactorCode && data.twoFactorRecoveryCode) { ctx.addIssue({ - code: ZodIssueCode.custom, + code: 'custom', message: 'Cannot have both twoFactorCode and twoFactorRecoveryCode', path: ['twoFactorCode', 'twoFactorRecoveryCode'], }) diff --git a/src/packages/contracts/src/schemas.test.ts b/src/packages/contracts/src/schemas.test.ts new file mode 100644 index 0000000..2d87b77 --- /dev/null +++ b/src/packages/contracts/src/schemas.test.ts @@ -0,0 +1,73 @@ +import { PatchOperation } from './PatchOperation' +import { LoginRequestSchema } from './identity/login/LoginRequest' +import { PagedRequestSchema } from './PagedRequest' +import { PatchRequestModelBaseSchema } from './PatchRequestModelBase' +import { RefreshTokenRequestSchema } from './identity/login/RefreshTokenRequest' + +describe('LoginRequestSchema', () => { + it('accepts valid credentials', () => { + const result = LoginRequestSchema.safeParse({ + username: 'alice', + password: 'secret', + }) + + expect(result.success).toBe(true) + }) + + it('rejects empty username and password', () => { + const result = LoginRequestSchema.safeParse({ + username: '', + password: '', + }) + + expect(result.success).toBe(false) + if (!result.success) { + expect(result.error.issues.map((issue) => issue.message)).toEqual( + expect.arrayContaining(['Username cannot be empty', 'Password cannot be empty']) + ) + } + }) + + it('rejects both two-factor fields together', () => { + const result = LoginRequestSchema.safeParse({ + username: 'alice', + password: 'secret', + twoFactorCode: '123456', + twoFactorRecoveryCode: 'recovery', + }) + + expect(result.success).toBe(false) + }) +}) + +describe('PagedRequestSchema', () => { + it('accepts optional paging fields', () => { + const result = PagedRequestSchema.safeParse({ + pageNumber: 2, + pageSize: 25, + sortBy: 'name', + isAscending: true, + }) + + expect(result.success).toBe(true) + }) +}) + +describe('PatchRequestModelBaseSchema', () => { + it('accepts patch operations keyed by field name', () => { + const result = PatchRequestModelBaseSchema.safeParse({ + operations: { + name: PatchOperation.SetField, + }, + }) + + expect(result.success).toBe(true) + }) +}) + +describe('RefreshTokenRequestSchema', () => { + it('requires a non-empty refresh token', () => { + expect(RefreshTokenRequestSchema.safeParse({ refreshToken: 'token' }).success).toBe(true) + expect(RefreshTokenRequestSchema.safeParse({ refreshToken: '' }).success).toBe(false) + }) +}) diff --git a/src/packages/contracts/tsconfig.build.json b/src/packages/contracts/tsconfig.build.json new file mode 100644 index 0000000..35bcdb2 --- /dev/null +++ b/src/packages/contracts/tsconfig.build.json @@ -0,0 +1,6 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "ignoreDeprecations": "6.0" + } +} diff --git a/src/packages/contracts/tsconfig.json b/src/packages/contracts/tsconfig.json index 5285d28..90d6d34 100644 --- a/src/packages/contracts/tsconfig.json +++ b/src/packages/contracts/tsconfig.json @@ -4,5 +4,6 @@ "rootDir": "src", "outDir": "dist" }, - "include": ["src"] + "include": ["src"], + "exclude": ["src/**/*.test.ts"] } diff --git a/src/packages/core/README.md b/src/packages/core/README.md new file mode 100644 index 0000000..fb83459 --- /dev/null +++ b/src/packages/core/README.md @@ -0,0 +1,60 @@ +# @maks-it.com/webui-core + +Shared utilities, HTTP helpers, and React hooks for MaksIT WebUI apps. + +Depends on `@maks-it.com/webui-contracts`. Install peer dependencies in the host app: `react`, `axios`, `zod`. + +## Install + +```bash +npm install @maks-it.com/webui-core @maks-it.com/webui-contracts axios react zod +``` + +## Highlights + +| Module | Exports | +|--------|---------| +| Deep diff | `deepDelta`, `deltaHasOperations`, collection policies | +| Deep utils | `deepCopy`, `deepEqual`, `deepMerge`, `deepPatternMatch` | +| Forms | `useFormState`, `validateFormState`, `applyFormFieldChange`, `createFormFieldUpdater` | +| DataTable | `mapPagedToDataTable`, `extractPropFilter`, `DataTablePageView` | +| ACL | `parseAclEntry`, `parseAclEntries` | +| HTTP | `createWebUiHttpClient`, auth interceptors, Problem Details helpers | +| Enum / flags | `enumToArr`, `flagsToString`, `hasFlag`, `toggleFlag` | +| Identity storage | `readIdentity`, `writeIdentity`, `removeIdentity` | + +## Example — form state + +```tsx +import { z } from 'zod' +import { useFormState } from '@maks-it.com/webui-core' + +const schema = z.object({ name: z.string().min(1) }) + +function MyForm() { + const { formState, errors, formIsValid, handleInputChange } = useFormState({ + initialState: { name: '' }, + validationSchema: schema, + }) + // ... +} +``` + +## Example — PATCH delta + +```ts +import { deepDelta, deltaHasOperations } from '@maks-it.com/webui-core' + +const delta = deepDelta(formState, backupState, { arrays: { items: { identityKey: 'id' } } }) +if (deltaHasOperations(delta)) { + await api.patch('/resource', delta) +} +``` + +## Repository + +[github.com/MAKS-IT-COM/maksit-webui](https://github.com/MAKS-IT-COM/maksit-webui) — `src/packages/core` + +## License + +MIT diff --git a/src/packages/core/package.json b/src/packages/core/package.json index 42b20f4..447370e 100644 --- a/src/packages/core/package.json +++ b/src/packages/core/package.json @@ -1,6 +1,6 @@ { - "name": "@maksit/webui-core", - "version": "0.1.0", + "name": "@maks-it.com/webui-core", + "version": "0.2.0", "description": "Shared utilities and hooks for MaksIT WebUI apps", "type": "module", "main": "./dist/index.cjs", @@ -18,7 +18,8 @@ "README.md" ], "scripts": { - "build": "tsup src/index.ts --format esm,cjs --dts --clean", + "build": "tsup src/index.ts --format esm,cjs --dts --clean --tsconfig tsconfig.build.json", + "test": "jest --config ../../jest.config.cjs", "typecheck": "tsc -p tsconfig.json --noEmit", "clean": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\"", "prepublishOnly": "npm run build" @@ -33,20 +34,20 @@ "directory": "src/packages/core" }, "dependencies": { - "@maksit/webui-contracts": "^0.1.0", - "date-fns": "^4.1.0" + "@maks-it.com/webui-contracts": "^0.2.0", + "date-fns": "^4.3.0" }, "peerDependencies": { - "axios": "^1.7.0", + "axios": "^1.16.0", "react": "^18.0.0 || ^19.0.0", - "zod": "^4.0.0" + "zod": "^4.4.0" }, "devDependencies": { - "@types/react": "^19.2.14", - "axios": "^1.13.2", - "react": "^19.2.4", - "tsup": "^8.5.0", - "typescript": "^5.9.3", - "zod": "^4.3.6" + "@types/react": "^19.2.15", + "axios": "^1.16.1", + "react": "^19.2.6", + "tsup": "^8.5.1", + "typescript": "^6.0.3", + "zod": "^4.4.3" } } diff --git a/src/packages/core/src/functions/acl/parseAclEntry.test.ts b/src/packages/core/src/functions/acl/parseAclEntry.test.ts new file mode 100644 index 0000000..475e4a8 --- /dev/null +++ b/src/packages/core/src/functions/acl/parseAclEntry.test.ts @@ -0,0 +1,31 @@ +import { ScopePermission } from '../../types/ScopePermissions' +import { parseAclEntry, parseAclEntries } from './parseAclEntry' + +const entityTypeMap = { O: 1, V: 2 } as const + +describe('parseAclEntry', () => { + it('parses a valid ACL entry', () => { + expect(parseAclEntry('O:entity-123:3', entityTypeMap)).toEqual({ + entityType: 1, + entityId: 'entity-123', + scope: ScopePermission.Read | ScopePermission.Write, + }) + }) + + it('returns null for malformed entries', () => { + expect(parseAclEntry('invalid', entityTypeMap)).toBeNull() + expect(parseAclEntry('X:entity:1', entityTypeMap)).toBeNull() + expect(parseAclEntry(null as unknown as string, entityTypeMap)).toBeNull() + }) +}) + +describe('parseAclEntries', () => { + it('keeps only valid entries in order', () => { + expect( + parseAclEntries(['O:a:1', 'bad', 'V:b:2'], entityTypeMap) + ).toEqual([ + { entityType: 1, entityId: 'a', scope: ScopePermission.Read }, + { entityType: 2, entityId: 'b', scope: ScopePermission.Write }, + ]) + }) +}) diff --git a/src/packages/core/src/functions/dataTable/dataTableFilters.test.ts b/src/packages/core/src/functions/dataTable/dataTableFilters.test.ts new file mode 100644 index 0000000..ec03d09 --- /dev/null +++ b/src/packages/core/src/functions/dataTable/dataTableFilters.test.ts @@ -0,0 +1,22 @@ +import { extractPropFilter } from './dataTableFilters' + +describe('extractPropFilter', () => { + it('extracts Contains filter values', () => { + expect(extractPropFilter('CommonName.Contains("example")', 'CommonName')).toBe('example') + }) + + it('extracts StartsWith and EndsWith filter values', () => { + expect(extractPropFilter('Host.StartsWith("api")', 'Host')).toBe('api') + expect(extractPropFilter('Host.EndsWith(".com")', 'Host')).toBe('.com') + }) + + it('is case-insensitive for property and operator names', () => { + expect(extractPropFilter('commonname.contains("test")', 'CommonName')).toBe('test') + }) + + it('returns undefined for empty or non-matching filters', () => { + expect(extractPropFilter(undefined, 'CommonName')).toBeUndefined() + expect(extractPropFilter(' ', 'CommonName')).toBeUndefined() + expect(extractPropFilter('OtherField.Contains("x")', 'CommonName')).toBeUndefined() + }) +}) diff --git a/src/packages/core/src/functions/dataTable/dataTablePaged.test.ts b/src/packages/core/src/functions/dataTable/dataTablePaged.test.ts new file mode 100644 index 0000000..22773a8 --- /dev/null +++ b/src/packages/core/src/functions/dataTable/dataTablePaged.test.ts @@ -0,0 +1,51 @@ +import type { PagedResponse } from '@maks-it.com/webui-contracts' +import { mapPagedToDataTable } from './dataTablePaged' + +describe('mapPagedToDataTable', () => { + it('returns an empty page for missing responses', () => { + expect(mapPagedToDataTable(undefined)).toEqual({ + items: [], + pageNumber: 1, + pageSize: 0, + totalCount: 0, + totalPages: 1, + hasPreviousPage: false, + hasNextPage: false, + }) + }) + + it('maps paged response fields with defaults', () => { + expect( + mapPagedToDataTable({ + items: [{ id: '1' }], + pageNumber: 2, + pageSize: 25, + totalCount: 100, + totalPages: 4, + hasPreviousPage: true, + hasNextPage: true, + }) + ).toEqual({ + items: [{ id: '1' }], + pageNumber: 2, + pageSize: 25, + totalCount: 100, + totalPages: 4, + hasPreviousPage: true, + hasNextPage: true, + }) + }) + + it('fills in defaults for partial responses', () => { + const partial = { items: [] } as PagedResponse + expect(mapPagedToDataTable(partial)).toEqual({ + items: [], + pageNumber: 1, + pageSize: 0, + totalCount: 0, + totalPages: 1, + hasPreviousPage: false, + hasNextPage: false, + }) + }) +}) diff --git a/src/packages/core/src/functions/dataTable/dataTablePaged.ts b/src/packages/core/src/functions/dataTable/dataTablePaged.ts index 18b1139..11282f1 100644 --- a/src/packages/core/src/functions/dataTable/dataTablePaged.ts +++ b/src/packages/core/src/functions/dataTable/dataTablePaged.ts @@ -1,4 +1,4 @@ -import type { PagedResponse } from '@maksit/webui-contracts' +import type { PagedResponse } from '@maks-it.com/webui-contracts' /** * Virtualized DataTable view model used by client paging and search helpers. diff --git a/src/packages/core/src/functions/date/isValidDateString.test.ts b/src/packages/core/src/functions/date/isValidDateString.test.ts new file mode 100644 index 0000000..adc88e9 --- /dev/null +++ b/src/packages/core/src/functions/date/isValidDateString.test.ts @@ -0,0 +1,14 @@ +import { isValidISODateString } from './isValidDateString' + +describe('isValidISODateString', () => { + it('accepts valid ISO date strings', () => { + expect(isValidISODateString('2024-01-15')).toBe(true) + expect(isValidISODateString('2024-01-15T10:30:00Z')).toBe(true) + }) + + it('rejects empty or invalid strings', () => { + expect(isValidISODateString('')).toBe(false) + expect(isValidISODateString('not-a-date')).toBe(false) + expect(isValidISODateString('2024-13-40')).toBe(false) + }) +}) diff --git a/src/packages/core/src/functions/deep/deepDelta.test.ts b/src/packages/core/src/functions/deep/deepDelta.test.ts new file mode 100644 index 0000000..3526e7c --- /dev/null +++ b/src/packages/core/src/functions/deep/deepDelta.test.ts @@ -0,0 +1,114 @@ +import { COLLECTION_ITEM_OPERATION, PatchOperation } from '@maks-it.com/webui-contracts' +import { deepDelta, deltaHasOperations } from './deepDelta' + +describe('deepDelta', () => { + it('detects primitive field changes', () => { + const backup = { name: 'old', count: 1 } + const form = { name: 'new', count: 1 } + + const delta = deepDelta(form, backup) + + expect(delta.name).toBe('new') + expect(delta.operations?.name).toBe(PatchOperation.SetField) + expect(delta.count).toBeUndefined() + }) + + it('marks nullish values as RemoveField', () => { + const backup = { name: 'value', optional: 'present' } + const form = { name: 'value', optional: null } + + const delta = deepDelta(form, backup) + + expect(delta.optional).toBeUndefined() + expect(delta.operations?.optional).toBe(PatchOperation.RemoveField) + }) + + it('replaces primitive arrays when values differ', () => { + const backup = { tags: ['a', 'b'] } + const form = { tags: ['a', 'b', 'c'] } + + const delta = deepDelta(form, backup) + + expect(delta.tags).toEqual(['a', 'b', 'c']) + expect(delta.operations?.tags).toBe(PatchOperation.SetField) + }) + + it('skips unchanged primitive arrays', () => { + const backup = { tags: ['a', 'b'] } + const form = { tags: ['b', 'a'] } + + const delta = deepDelta({ tags: ['a', 'b'] }, { tags: ['a', 'b'] }) + + expect(delta.tags).toBeUndefined() + expect(delta.operations?.tags).toBeUndefined() + }) + + it('diffs identifiable object arrays by id', () => { + const backup = { + items: [{ id: '1', name: 'first' }, { id: '2', name: 'second' }], + } + const form = { + items: [{ id: '1', name: 'updated' }, { id: '3', name: 'new' }], + } + + const delta = deepDelta(form, backup) + + expect(delta.items).toEqual( + expect.arrayContaining([ + expect.objectContaining({ id: '1', name: 'updated' }), + expect.objectContaining({ + id: '3', + name: 'new', + operations: { [COLLECTION_ITEM_OPERATION]: PatchOperation.AddToCollection }, + }), + expect.objectContaining({ + id: '2', + operations: { [COLLECTION_ITEM_OPERATION]: PatchOperation.RemoveFromCollection }, + }), + ]) + ) + }) + + it('uses identityKey when items have no id', () => { + const backup = { hostnames: [{ hostname: 'a.example.com', enabled: true }] } + const form = { hostnames: [{ hostname: 'a.example.com', enabled: false }] } + + const delta = deepDelta(form, backup, { + arrays: { hostnames: { identityKey: 'hostname', idFieldKey: 'hostname' } }, + }) + + expect(delta.hostnames).toEqual([ + expect.objectContaining({ + hostname: 'a.example.com', + enabled: false, + operations: expect.objectContaining({ enabled: PatchOperation.SetField }), + }), + ]) + }) +}) + +describe('deltaHasOperations', () => { + it('returns false for empty delta', () => { + expect(deltaHasOperations({})).toBe(false) + }) + + it('returns true when top-level operations exist', () => { + expect(deltaHasOperations({ operations: { name: PatchOperation.SetField } })).toBe(true) + }) + + it('returns true for nested object operations', () => { + expect( + deltaHasOperations({ + nested: { operations: { field: PatchOperation.SetField } }, + }) + ).toBe(true) + }) + + it('returns true for array item operations', () => { + expect( + deltaHasOperations({ + items: [{ operations: { [COLLECTION_ITEM_OPERATION]: PatchOperation.AddToCollection } }], + }) + ).toBe(true) + }) +}) diff --git a/src/packages/core/src/functions/deep/deepDelta.ts b/src/packages/core/src/functions/deep/deepDelta.ts index 54d9e71..1cd2c30 100644 --- a/src/packages/core/src/functions/deep/deepDelta.ts +++ b/src/packages/core/src/functions/deep/deepDelta.ts @@ -1,4 +1,4 @@ -import { COLLECTION_ITEM_OPERATION, PatchOperation } from '@maksit/webui-contracts' +import { COLLECTION_ITEM_OPERATION, PatchOperation } from '@maks-it.com/webui-contracts' import { deepCopy } from './deepCopy' import { deepEqual } from './deepEqual' diff --git a/src/packages/core/src/functions/deep/deepEqual.test.ts b/src/packages/core/src/functions/deep/deepEqual.test.ts new file mode 100644 index 0000000..b5f2845 --- /dev/null +++ b/src/packages/core/src/functions/deep/deepEqual.test.ts @@ -0,0 +1,39 @@ +import { deepEqual, deepEqualArrays } from './deepEqual' + +describe('deepEqual', () => { + it('returns true for identical primitives', () => { + expect(deepEqual(1, 1)).toBe(true) + expect(deepEqual('a', 'a')).toBe(true) + expect(deepEqual(null, null)).toBe(true) + }) + + it('returns false for different primitives', () => { + expect(deepEqual(1, 2)).toBe(false) + expect(deepEqual('a', 'b')).toBe(false) + expect(deepEqual(null, undefined)).toBe(false) + }) + + it('compares nested objects by value', () => { + expect(deepEqual({ a: 1, b: { c: 2 } }, { a: 1, b: { c: 2 } })).toBe(true) + expect(deepEqual({ a: 1 }, { a: 2 })).toBe(false) + expect(deepEqual({ a: 1 }, { a: 1, b: 2 })).toBe(false) + }) + + it('compares arrays regardless of element order', () => { + expect(deepEqual([1, 2, 3], [3, 2, 1])).toBe(true) + expect(deepEqual([{ id: 1 }, { id: 2 }], [{ id: 2 }, { id: 1 }])).toBe(true) + expect(deepEqual([1, 2], [1, 2, 3])).toBe(false) + }) +}) + +describe('deepEqualArrays', () => { + it('returns true for empty arrays', () => { + expect(deepEqualArrays([], [])).toBe(true) + }) + + it('matches multiset equality', () => { + expect(deepEqualArrays(['a', 'b'], ['b', 'a'])).toBe(true) + expect(deepEqualArrays([1, 1, 2], [2, 1, 1])).toBe(true) + expect(deepEqualArrays([1, 2], [1, 3])).toBe(false) + }) +}) diff --git a/src/packages/core/src/functions/enum/flags.test.ts b/src/packages/core/src/functions/enum/flags.test.ts new file mode 100644 index 0000000..d64f147 --- /dev/null +++ b/src/packages/core/src/functions/enum/flags.test.ts @@ -0,0 +1,32 @@ +import { hasAnyFlag } from './hasAnyFlag' +import { hasFlag } from './hasFlag' +import { toggleFlag } from './toggleFlag' + +describe('hasFlag', () => { + it('returns true when all flag bits are set', () => { + expect(hasFlag(0b101, 0b001)).toBe(true) + expect(hasFlag(0b111, 0b101)).toBe(true) + }) + + it('returns false when any flag bit is missing', () => { + expect(hasFlag(0b100, 0b101)).toBe(false) + expect(hasFlag(0, 0b001)).toBe(false) + }) +}) + +describe('hasAnyFlag', () => { + it('returns true when any overlapping bit is set', () => { + expect(hasAnyFlag(0b100, 0b101)).toBe(true) + expect(hasAnyFlag(0b010, 0b001)).toBe(false) + }) +}) + +describe('toggleFlag', () => { + it('adds the flag when not fully set', () => { + expect(toggleFlag(0b100, 0b001)).toBe(0b101) + }) + + it('removes the flag when fully set', () => { + expect(toggleFlag(0b101, 0b001)).toBe(0b100) + }) +}) diff --git a/src/packages/core/src/functions/guid/isGuid.test.ts b/src/packages/core/src/functions/guid/isGuid.test.ts new file mode 100644 index 0000000..812464c --- /dev/null +++ b/src/packages/core/src/functions/guid/isGuid.test.ts @@ -0,0 +1,15 @@ +import { isGuid } from './isGuid' + +describe('isGuid', () => { + it('accepts valid GUIDs', () => { + expect(isGuid('550e8400-e29b-41d4-a716-446655440000')).toBe(true) + expect(isGuid('550E8400-E29B-41D4-A716-446655440000')).toBe(true) + }) + + it('rejects invalid GUIDs', () => { + expect(isGuid('')).toBe(false) + expect(isGuid('not-a-guid')).toBe(false) + expect(isGuid('550e8400-e29b-41d4-a716')).toBe(false) + expect(isGuid('550e8400e29b41d4a716446655440000')).toBe(false) + }) +}) diff --git a/src/packages/core/src/functions/headers/extractFilenameFromHeaders.test.ts b/src/packages/core/src/functions/headers/extractFilenameFromHeaders.test.ts new file mode 100644 index 0000000..3b2def5 --- /dev/null +++ b/src/packages/core/src/functions/headers/extractFilenameFromHeaders.test.ts @@ -0,0 +1,31 @@ +import { extractFilenameFromHeaders } from './extractFilenameFromHeaders' + +describe('extractFilenameFromHeaders', () => { + it('returns fallback when header is missing', () => { + expect(extractFilenameFromHeaders({}, 'default.bin')).toBe('default.bin') + }) + + it('parses RFC 5987 encoded filenames', () => { + expect( + extractFilenameFromHeaders({ + 'content-disposition': "attachment; filename*=UTF-8''report%20file.pdf", + }) + ).toBe('report file.pdf') + }) + + it('parses quoted filenames', () => { + expect( + extractFilenameFromHeaders({ + 'content-disposition': 'attachment; filename="archive.zip"', + }) + ).toBe('archive.zip') + }) + + it('parses plain filenames', () => { + expect( + extractFilenameFromHeaders({ + 'content-disposition': 'attachment; filename=download.bin', + }) + ).toBe('download.bin') + }) +}) diff --git a/src/packages/core/src/functions/zod/validateFormState.test.ts b/src/packages/core/src/functions/zod/validateFormState.test.ts new file mode 100644 index 0000000..2eb8e71 --- /dev/null +++ b/src/packages/core/src/functions/zod/validateFormState.test.ts @@ -0,0 +1,24 @@ +import { z } from 'zod' +import { validateFormState } from './validateFormState' + +describe('validateFormState', () => { + const schema = z.object({ + name: z.string().min(1, 'Name is required'), + age: z.number().min(0), + }) + + it('returns valid result for passing form state', () => { + const result = validateFormState({ name: 'Alice', age: 30 }, schema) + + expect(result.formIsValid).toBe(true) + expect(result.errors).toEqual({ name: '', age: '' }) + }) + + it('returns field errors for invalid form state', () => { + const result = validateFormState({ name: '', age: -1 }, schema) + + expect(result.formIsValid).toBe(false) + expect(result.errors.name).toBe('Name is required') + expect(result.errors.age).toBeTruthy() + }) +}) diff --git a/src/packages/core/src/http/errorHandler.ts b/src/packages/core/src/http/errorHandler.ts index 4489750..1cb3535 100644 --- a/src/packages/core/src/http/errorHandler.ts +++ b/src/packages/core/src/http/errorHandler.ts @@ -1,5 +1,5 @@ import type { AxiosError } from 'axios' -import type { ProblemDetails } from '@maksit/webui-contracts' +import type { ProblemDetails } from '@maks-it.com/webui-contracts' import { formatProblemDetailsMessage } from './problemDetails' /** Shows toast(s) for problem+json and 401 responses. */ diff --git a/src/packages/core/src/http/problemDetails.test.ts b/src/packages/core/src/http/problemDetails.test.ts new file mode 100644 index 0000000..e97ccbb --- /dev/null +++ b/src/packages/core/src/http/problemDetails.test.ts @@ -0,0 +1,26 @@ +import { formatProblemDetailsMessage } from './problemDetails' + +describe('formatProblemDetailsMessage', () => { + it('combines detail and field errors', () => { + const message = formatProblemDetailsMessage({ + title: 'Validation failed', + detail: 'One or more fields are invalid.', + errors: { + name: ['Name is required'], + email: ['Invalid email', 'Email is too long'], + }, + }) + + expect(message).toBe( + 'One or more fields are invalid. name: Name is required; email: Invalid email; email: Email is too long' + ) + }) + + it('falls back to title when detail and errors are empty', () => { + expect(formatProblemDetailsMessage({ title: 'Bad Request' })).toBe('Bad Request') + }) + + it('uses a generic message when nothing else is available', () => { + expect(formatProblemDetailsMessage({})).toBe('Request failed') + }) +}) diff --git a/src/packages/core/src/http/problemDetails.ts b/src/packages/core/src/http/problemDetails.ts index 19f8ae6..64ca814 100644 --- a/src/packages/core/src/http/problemDetails.ts +++ b/src/packages/core/src/http/problemDetails.ts @@ -1,4 +1,4 @@ -import type { ProblemDetails } from '@maksit/webui-contracts' +import type { ProblemDetails } from '@maks-it.com/webui-contracts' /** Builds a user-facing message from RFC 7807 problem details. */ export function formatProblemDetailsMessage(problem: ProblemDetails): string { diff --git a/src/packages/core/src/localStorage/identity.ts b/src/packages/core/src/localStorage/identity.ts index d7684cb..23dd882 100644 --- a/src/packages/core/src/localStorage/identity.ts +++ b/src/packages/core/src/localStorage/identity.ts @@ -1,4 +1,4 @@ -import type { LoginResponse } from '@maksit/webui-contracts' +import type { LoginResponse } from '@maks-it.com/webui-contracts' const readIdentity = () => { const json = localStorage.getItem('identity') diff --git a/src/packages/core/src/types/index.ts b/src/packages/core/src/types/index.ts index 81612f3..c298191 100644 --- a/src/packages/core/src/types/index.ts +++ b/src/packages/core/src/types/index.ts @@ -3,5 +3,5 @@ export type { FormValidationResult, FormValidationSchema, } from './FormValidationSchema' -export type { PagedResponse } from '@maksit/webui-contracts' +export type { PagedResponse } from '@maks-it.com/webui-contracts' export { ScopePermission } from './ScopePermissions' diff --git a/src/packages/core/tsconfig.build.json b/src/packages/core/tsconfig.build.json new file mode 100644 index 0000000..35bcdb2 --- /dev/null +++ b/src/packages/core/tsconfig.build.json @@ -0,0 +1,6 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "ignoreDeprecations": "6.0" + } +} diff --git a/src/packages/core/tsconfig.json b/src/packages/core/tsconfig.json index 5285d28..90d6d34 100644 --- a/src/packages/core/tsconfig.json +++ b/src/packages/core/tsconfig.json @@ -4,5 +4,6 @@ "rootDir": "src", "outDir": "dist" }, - "include": ["src"] + "include": ["src"], + "exclude": ["src/**/*.test.ts"] } diff --git a/src/tsconfig.jest.json b/src/tsconfig.jest.json new file mode 100644 index 0000000..64e8225 --- /dev/null +++ b/src/tsconfig.jest.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.base.json", + "compilerOptions": { + "noEmit": true, + "types": ["jest", "node"] + }, + "include": [ + "packages/core/src/**/*.ts", + "packages/contracts/src/**/*.ts" + ] +} diff --git a/utils/Generate-CoverageBadges/Generate-CoverageBadges.ps1 b/utils/Generate-CoverageBadges/Generate-CoverageBadges.ps1 index b862100..dff68c1 100644 --- a/utils/Generate-CoverageBadges/Generate-CoverageBadges.ps1 +++ b/utils/Generate-CoverageBadges/Generate-CoverageBadges.ps1 @@ -3,263 +3,20 @@ <# .SYNOPSIS - Generates SVG coverage badges for README. + Legacy entry point — forwards to the Run-Tests plugin engine. .DESCRIPTION - This script runs unit tests via TestRunner.psm1, then generates shields.io-style - SVG badges for line, branch, and method coverage. - - Configuration is stored in scriptsettings.json: - - openReport : Generate and open full HTML report (true/false) - - paths.testProjects : Array of relative paths to test projects (preferred) - - paths.testProject : Single test project path (legacy; use testProjects) - - paths.badgesDir : Relative path to badges output directory - - badges : Array of badges to generate (name, label, metric) - - colorThresholds : Coverage percentages for badge colors - - Badge colors based on coverage: - - brightgreen (>=80%), green (>=60%), yellowgreen (>=40%) - - yellow (>=20%), orange (>=10%), red (<10%) - If openReport is true, ReportGenerator is required: - dotnet tool install -g dotnet-reportgenerator-globaltool - -.EXAMPLE - pwsh -File .\Generate-CoverageBadges.ps1 - Runs tests and generates coverage badges (and optionally HTML report if configured). - -.OUTPUTS - SVG badge files in the configured badges directory. - -.NOTES - Author: MaksIT - Requires: .NET SDK, Coverlet (included in test project) + Generate-CoverageBadges.ps1 is kept for backward compatibility. + Configure plugins in src/Run-Tests/scriptsettings.json. #> $ErrorActionPreference = "Stop" -# Get the directory of the current script (for loading settings and relative paths) -$ScriptDir = $PSScriptRoot -$UtilsDir = Split-Path $ScriptDir -Parent - -#region Import Modules - -# Import TestRunner module (executes tests and collects coverage metrics) -$testRunnerModulePath = Join-Path $UtilsDir "TestRunner.psm1" -if (-not (Test-Path $testRunnerModulePath)) { - Write-Error "TestRunner module not found at: $testRunnerModulePath" - exit 1 -} -Import-Module $testRunnerModulePath -Force - -# Import shared ScriptConfig module (settings + command validation helpers) -$scriptConfigModulePath = Join-Path $UtilsDir "ScriptConfig.psm1" -if (-not (Test-Path $scriptConfigModulePath)) { - Write-Error "ScriptConfig module not found at: $scriptConfigModulePath" - exit 1 -} -Import-Module $scriptConfigModulePath -Force - -# Import shared Logging module (timestamped/aligned output) -$loggingModulePath = Join-Path $UtilsDir "Logging.psm1" -if (-not (Test-Path $loggingModulePath)) { - Write-Error "Logging module not found at: $loggingModulePath" - exit 1 -} -Import-Module $loggingModulePath -Force - -#endregion - -#region Load Settings - -$Settings = Get-ScriptSettings -ScriptDir $ScriptDir - -$thresholds = $Settings.colorThresholds - -#endregion - -#region Configuration - -# Runtime options from settings -$OpenReport = if ($null -ne $Settings.openReport) { [bool]$Settings.openReport } else { $false } - -# Resolve configured paths to absolute paths (one or more test projects) -$testProjectPaths = [System.Collections.Generic.List[string]]::new() -$pathsNode = $Settings.paths -if ($pathsNode.PSObject.Properties.Name -contains 'testProjects' -and $pathsNode.testProjects) { - foreach ($rel in @($pathsNode.testProjects)) { - if ([string]::IsNullOrWhiteSpace([string]$rel)) { continue } - $testProjectPaths.Add([System.IO.Path]::GetFullPath((Join-Path $ScriptDir $rel.Trim()))) - } -} -if ($testProjectPaths.Count -eq 0 -and $pathsNode.testProject) { - $testProjectPaths.Add([System.IO.Path]::GetFullPath((Join-Path $ScriptDir $pathsNode.testProject))) -} -if ($testProjectPaths.Count -eq 0) { - Write-Error "Configure paths.testProjects (array of relative paths) or paths.testProject (single path) in scriptsettings.json." +$runTestsScript = Join-Path (Split-Path $PSScriptRoot -Parent) "Run-Tests\Run-Tests.ps1" +if (-not (Test-Path $runTestsScript -PathType Leaf)) { + Write-Error "Run-Tests engine not found at: $runTestsScript" exit 1 } -$BadgesDir = [System.IO.Path]::GetFullPath((Join-Path $ScriptDir $Settings.paths.badgesDir)) - -# Ensure badges directory exists -if (-not (Test-Path $BadgesDir)) { - New-Item -ItemType Directory -Path $BadgesDir | Out-Null -} - -#endregion - -#region Helpers - -# Maps a coverage percentage to a shields.io color using configured thresholds. -function Get-BadgeColor { - param([double]$percentage) - - if ($percentage -ge $thresholds.brightgreen) { return "brightgreen" } - if ($percentage -ge $thresholds.green) { return "green" } - if ($percentage -ge $thresholds.yellowgreen) { return "yellowgreen" } - if ($percentage -ge $thresholds.yellow) { return "yellow" } - if ($percentage -ge $thresholds.orange) { return "orange" } - return "red" -} - -# Builds a shields.io-like SVG badge string for one metric. -function New-Badge { - param( - [string]$label, - [string]$value, - [string]$color - ) - - # Calculate widths (approximate character width of 6.5px for the font) - $labelWidth = [math]::Max(($label.Length * 6.5) + 10, 50) - $valueWidth = [math]::Max(($value.Length * 6.5) + 10, 40) - $totalWidth = $labelWidth + $valueWidth - $labelX = $labelWidth / 2 - $valueX = $labelWidth + ($valueWidth / 2) - - $colorMap = @{ - "brightgreen" = "#4c1" - "green" = "#97ca00" - "yellowgreen" = "#a4a61d" - "yellow" = "#dfb317" - "orange" = "#fe7d37" - "red" = "#e05d44" - } - $hexColor = $colorMap[$color] - if (-not $hexColor) { $hexColor = "#9f9f9f" } - - return @" - - $label`: $value - - - - - - - - - - - - - - - $label - - $value - - -"@ -} - -#endregion - -#region Main - -#region Test And Coverage - -$invokeCoverageParams = @{ - TestProjectPath = @($testProjectPaths) - KeepResults = $OpenReport -} -# Keep single-project results next to that project; for several projects use a shared folder under this script. -if ($testProjectPaths.Count -gt 1) { - $invokeCoverageParams.ResultsDirectory = Join-Path $ScriptDir "TestResults" -} -$coverage = Invoke-TestsWithCoverage @invokeCoverageParams -if (-not $coverage.Success) { - Write-Error "Tests failed: $($coverage.Error)" - exit 1 -} - -Write-Log -Level "OK" -Message "Tests passed!" - -$metrics = @{ - "line" = $coverage.LineRate - "branch" = $coverage.BranchRate - "method" = $coverage.MethodRate -} - -#endregion - -#region Generate Badges - -Write-LogStep -Message "Generating coverage badges..." - -foreach ($badge in $Settings.badges) { - $metricValue = $metrics[$badge.metric] - $color = Get-BadgeColor $metricValue - $svg = New-Badge -label $badge.label -value "$metricValue%" -color $color - $path = Join-Path $BadgesDir $badge.name - $svg | Out-File -FilePath $path -Encoding utf8NoBOM - Write-Log -Level "OK" -Message "$($badge.name): $($badge.label) = $metricValue%" -} - -#endregion - -#region Summary - -Write-Log -Level "INFO" -Message "Coverage Summary:" -Write-Log -Level "INFO" -Message "Line Coverage: $($coverage.LineRate)%" -Write-Log -Level "INFO" -Message "Branch Coverage: $($coverage.BranchRate)%" -Write-Log -Level "INFO" -Message "Method Coverage: $($coverage.MethodRate)% ($($coverage.CoveredMethods) of $($coverage.TotalMethods) methods)" -Write-Log -Level "OK" -Message "Badges generated in: $BadgesDir" -Write-Log -Level "STEP" -Message "Commit the badges/ folder to update README." - -#endregion - -#region Optional Html Report - -if ($OpenReport -and $coverage.CoverageFile) { - Write-LogStep -Message "Generating HTML report..." - Assert-Command reportgenerator - - # Cobertura file(s): single path, or semicolon-separated list from merged runs. - $firstCobertura = if ($coverage.CoverageFiles -and $coverage.CoverageFiles.Count -gt 0) { - $coverage.CoverageFiles[0] - } - else { - ($coverage.CoverageFile -split ';')[0].Trim() - } - $ResultsDir = Split-Path (Split-Path $firstCobertura -Parent) -Parent - $ReportDir = Join-Path $ResultsDir "report" - - $reportGenArgs = @( - "-reports:$($coverage.CoverageFile)" - "-targetdir:$ReportDir" - "-reporttypes:Html" - ) - & reportgenerator @reportGenArgs - - $IndexFile = Join-Path $ReportDir "index.html" - if (Test-Path $IndexFile) { - Start-Process $IndexFile - } - - Write-Log -Level "INFO" -Message "TestResults kept for HTML report viewing." -} - -#endregion - -#endregion +& pwsh -NoProfile -ExecutionPolicy Bypass -File $runTestsScript +exit $LASTEXITCODE diff --git a/utils/Generate-CoverageBadges/scriptsettings.json b/utils/Generate-CoverageBadges/scriptsettings.json index c48c539..a3169f5 100644 --- a/utils/Generate-CoverageBadges/scriptsettings.json +++ b/utils/Generate-CoverageBadges/scriptsettings.json @@ -1,47 +1,6 @@ { "$schema": "https://json-schema.org/draft-07/schema", "title": "Generate Coverage Badges Script Settings", - "description": "Configuration for Generate-CoverageBadges.ps1 script", - "openReport": false, - "paths": { - "testProjects": [ - "..\\..\\src\\MaksIT.Core.Tests" - ], - "badgesDir": "..\\..\\assets\\badges" - }, - "badges": [ - { - "name": "coverage-lines.svg", - "label": "Line Coverage", - "metric": "line" - }, - { - "name": "coverage-branches.svg", - "label": "Branch Coverage", - "metric": "branch" - }, - { - "name": "coverage-methods.svg", - "label": "Method Coverage", - "metric": "method" - } - ], - "colorThresholds": { - "brightgreen": 80, - "green": 60, - "yellowgreen": 40, - "yellow": 20, - "orange": 10, - "red": 0 - }, - "_comments": { - "openReport": "If true, generate and open full HTML coverage report (requires reportgenerator tool).", - "paths": { - "testProjects": "Array of relative paths (from this folder) to test project directories or .csproj files. All are run; badge metrics aggregate Cobertura line/branch/method stats.", - "testProject": "Optional legacy single path if testProjects is omitted.", - "badgesDir": "Relative path where SVG coverage badges are written." - }, - "badges": "List of output badges. Each entry maps a metric key (line|branch|method) to filename and label.", - "colorThresholds": "Coverage percentage thresholds used to pick badge colors." - } + "description": "Legacy settings file. Use utils/Run-Tests/scriptsettings.json instead.", + "_forwardTo": "..\\Run-Tests\\scriptsettings.json" } diff --git a/utils/Release-Package/CorePlugins/DockerPush.psm1 b/utils/Release-Package/CorePlugins/DockerPush.psm1 index e4371c2..ca7f44a 100644 --- a/utils/Release-Package/CorePlugins/DockerPush.psm1 +++ b/utils/Release-Package/CorePlugins/DockerPush.psm1 @@ -149,13 +149,21 @@ function Invoke-Plugin { throw "Each images[] entry must define 'service' and 'dockerfile'." } + $service = [string]$img.service $dockerfileRel = [string]$img.dockerfile - $dockerfilePath = [System.IO.Path]::GetFullPath((Join-Path $contextPath $dockerfileRel)) + + $imgContextPath = $contextPath + if ($img.PSObject.Properties.Name -contains 'contextPath' -and -not [string]::IsNullOrWhiteSpace([string]$img.contextPath)) { + $imgContextPath = [System.IO.Path]::GetFullPath((Join-Path $scriptDir ([string]$img.contextPath))) + if (-not (Test-Path $imgContextPath -PathType Container)) { + throw "Docker context directory not found for image '$service': $imgContextPath" + } + } + + $dockerfilePath = [System.IO.Path]::GetFullPath((Join-Path $imgContextPath $dockerfileRel)) if (-not (Test-Path $dockerfilePath -PathType Leaf)) { throw "Dockerfile not found: $dockerfilePath" } - - $service = [string]$img.service $baseName = "$registryUrl/$($pluginSettings.projectName)/$service" $versionEnvFiles = @() @@ -165,7 +173,7 @@ function Invoke-Plugin { continue } - $envFilePath = [System.IO.Path]::GetFullPath((Join-Path $contextPath ([string]$relativeEnvFile))) + $envFilePath = [System.IO.Path]::GetFullPath((Join-Path $imgContextPath ([string]$relativeEnvFile))) if (-not (Test-Path -LiteralPath $envFilePath -PathType Leaf)) { throw "Configured versionEnvFiles entry not found: $envFilePath" } @@ -187,7 +195,7 @@ function Invoke-Plugin { $primaryRef = "${baseName}:$($imageTags[0])" Write-Log -Level "STEP" -Message "Building $primaryRef ..." - docker build -t $primaryRef -f $dockerfilePath $contextPath + docker build -t $primaryRef -f $dockerfilePath $imgContextPath if ($LASTEXITCODE -ne 0) { throw "Docker build failed for $primaryRef" } diff --git a/utils/Release-Package/CorePlugins/GitHub.psm1 b/utils/Release-Package/CorePlugins/GitHub.psm1 index ddbc7a6..ef0f227 100644 --- a/utils/Release-Package/CorePlugins/GitHub.psm1 +++ b/utils/Release-Package/CorePlugins/GitHub.psm1 @@ -46,6 +46,32 @@ function Get-GitHubRepositoryInternal { throw "Could not parse GitHub repo from source: $repoSource. Configure plugins[].repository with 'owner/repo' or a GitHub URL." } +function Get-ChangelogVersionHeaderPatternInternal { + # Keep a Changelog: ## [1.0.0] - 2026-05-24, bare ## 1.0.0 - 2026-05-24, or legacy ## v1.0.0 + return '(?m)^##\s+(?:\[(\d+\.\d+\.\d+)\]|v(\d+\.\d+\.\d+)|(\d+\.\d+\.\d+)(?:\s*-\s*\d{4}-\d{2}-\d{2})?\s*$)' +} + +function Get-LatestChangelogVersionInternal { + param( + [Parameter(Mandatory = $true)] + [string]$ReleaseNotesContent + ) + + $match = [regex]::Match($ReleaseNotesContent, (Get-ChangelogVersionHeaderPatternInternal)) + if (-not $match.Success) { + return $null + } + + foreach ($groupIndex in 1..3) { + $version = $match.Groups[$groupIndex].Value + if (-not [string]::IsNullOrEmpty($version)) { + return $version + } + } + + return $null +} + function Get-ReleaseNotesInternal { param( [Parameter(Mandatory = $true)] @@ -61,11 +87,11 @@ function Get-ReleaseNotesInternal { } $releaseNotesContent = Get-Content $ReleaseNotesFile -Raw - if ($releaseNotesContent -notmatch '##\s+v(\d+\.\d+\.\d+)') { + $releaseNotesVersion = Get-LatestChangelogVersionInternal -ReleaseNotesContent $releaseNotesContent + if ([string]::IsNullOrWhiteSpace($releaseNotesVersion)) { throw "No version entry found in the configured release notes source." } - $releaseNotesVersion = $Matches[1] if ($releaseNotesVersion -ne $Version) { throw "Project version ($Version) does not match the latest release notes version ($releaseNotesVersion)." } @@ -73,7 +99,9 @@ function Get-ReleaseNotesInternal { Write-Log -Level "OK" -Message " Release notes version matches: v$releaseNotesVersion" Write-Log -Level "STEP" -Message "Extracting release notes..." - $pattern = "(?ms)^##\s+v$([regex]::Escape($Version))\b.*?(?=^##\s+v\d+\.\d+\.\d+|\Z)" + $escapedVersion = [regex]::Escape($Version) + $nextHeaderPattern = '(?m)^##\s+(?:\[\d+\.\d+\.\d+\]|v\d+\.\d+\.\d+|\d+\.\d+\.\d+(?:\s*-\s*\d{4}-\d{2}-\d{2})?\s*$)' + $pattern = "(?ms)^##\s+(?:\[$escapedVersion\]|v$escapedVersion|$escapedVersion(?:\s*-\s*\d{4}-\d{2}-\d{2})?).*?(?=$nextHeaderPattern|\Z)" $match = [regex]::Match($releaseNotesContent, $pattern) if (-not $match.Success) { @@ -133,8 +161,17 @@ function Invoke-Plugin { } } + $requireReleaseAssets = $true + if ($null -ne $pluginSettings.requireReleaseAssets) { + $requireReleaseAssets = [bool]$pluginSettings.requireReleaseAssets + } + + if ($releaseAssetPaths.Count -eq 0 -and $requireReleaseAssets) { + throw "GitHub release requires at least one prepared release asset (set requireReleaseAssets: false for notes-only npm releases)." + } + if ($releaseAssetPaths.Count -eq 0) { - throw "GitHub release requires at least one prepared release asset." + Write-Log -Level "INFO" -Message " Notes-only GitHub release (requireReleaseAssets: false)." } $repo = Get-GitHubRepositoryInternal -ConfiguredRepository $configuredRepository diff --git a/utils/Release-Package/CorePlugins/NpmBuild.psm1 b/utils/Release-Package/CorePlugins/NpmBuild.psm1 index 5307d8f..aeec2d0 100644 --- a/utils/Release-Package/CorePlugins/NpmBuild.psm1 +++ b/utils/Release-Package/CorePlugins/NpmBuild.psm1 @@ -35,7 +35,8 @@ function Invoke-Plugin { $workspaceRoot = $null if ($pluginSettings.workspaceRoot) { - $workspaceRoot = (Resolve-RelativePaths -Value $pluginSettings.workspaceRoot -BasePath $shared.scriptDir)[0] + $workspaceRoots = @(Resolve-RelativePaths -Value $pluginSettings.workspaceRoot -BasePath $shared.scriptDir) + $workspaceRoot = $workspaceRoots[0] } elseif ($shared.PSObject.Properties['npmWorkspaceRoot'] -and -not [string]::IsNullOrWhiteSpace([string]$shared.npmWorkspaceRoot)) { $workspaceRoot = [string]$shared.npmWorkspaceRoot diff --git a/utils/Release-Package/CorePlugins/NpmPublish.psm1 b/utils/Release-Package/CorePlugins/NpmPublish.psm1 index 7980830..1e996b4 100644 --- a/utils/Release-Package/CorePlugins/NpmPublish.psm1 +++ b/utils/Release-Package/CorePlugins/NpmPublish.psm1 @@ -45,7 +45,8 @@ function Invoke-Plugin { $workspaceRoot = $null if ($pluginSettings.workspaceRoot) { - $workspaceRoot = (Resolve-RelativePaths -Value $pluginSettings.workspaceRoot -BasePath $shared.scriptDir)[0] + $workspaceRoots = @(Resolve-RelativePaths -Value $pluginSettings.workspaceRoot -BasePath $shared.scriptDir) + $workspaceRoot = $workspaceRoots[0] } elseif ($shared.PSObject.Properties['npmWorkspaceRoot'] -and -not [string]::IsNullOrWhiteSpace([string]$shared.npmWorkspaceRoot)) { $workspaceRoot = [string]$shared.npmWorkspaceRoot diff --git a/utils/Release-Package/CorePlugins/NpmReleaseVersion.psm1 b/utils/Release-Package/CorePlugins/NpmReleaseVersion.psm1 index 00808e5..2045209 100644 --- a/utils/Release-Package/CorePlugins/NpmReleaseVersion.psm1 +++ b/utils/Release-Package/CorePlugins/NpmReleaseVersion.psm1 @@ -68,12 +68,11 @@ function Invoke-Plugin { $pluginSettings = $Settings $shared = $Settings.context - $packageJsonPath = if ($pluginSettings.packageJsonPath) { - (Resolve-RelativePaths -Value $pluginSettings.packageJsonPath -BasePath $shared.scriptDir)[0] - } - else { + $packageJsonPaths = @(Resolve-RelativePaths -Value $pluginSettings.packageJsonPath -BasePath $shared.scriptDir) + if ($packageJsonPaths.Count -eq 0) { throw "NpmReleaseVersion plugin requires 'packageJsonPath' in scriptsettings.json." } + $packageJsonPath = $packageJsonPaths[0] $version = Get-PackageJsonVersionInternal -PackageJsonPath $packageJsonPath $syncWorkspaceVersions = $false diff --git a/utils/Release-Package/EngineSupport.psm1 b/utils/Release-Package/EngineSupport.psm1 index 230a615..048192a 100644 --- a/utils/Release-Package/EngineSupport.psm1 +++ b/utils/Release-Package/EngineSupport.psm1 @@ -22,7 +22,7 @@ if (-not (Get-Command Get-PluginStageLabel -ErrorAction SilentlyContinue) -or -n } } -if (-not (Get-Command Resolve-DotNetReleaseVersion -ErrorAction SilentlyContinue)) { +if (-not (Get-Command Resolve-ReleaseVersion -ErrorAction SilentlyContinue)) { $releaseContextModulePath = Join-Path $PSScriptRoot "ReleaseContext.psm1" if (Test-Path $releaseContextModulePath -PathType Leaf) { Import-Module $releaseContextModulePath -Force @@ -79,7 +79,9 @@ function New-EngineContext { [psobject]$Settings ) - $version = (Resolve-DotNetReleaseVersion -Plugins $Plugins -ScriptDir $ScriptDir).version + $resolvedVersion = Resolve-ReleaseVersion -Plugins $Plugins -ScriptDir $ScriptDir + $version = $resolvedVersion.version + $versionSource = $resolvedVersion.source $artifactsDirectory = [System.IO.Path]::GetFullPath((Join-Path $ScriptDir '..\\..\\release')) $currentBranch = Get-CurrentBranch @@ -113,7 +115,7 @@ function New-EngineContext { Assert-WorkingTreeClean $tag = "v$version" - Write-Log -Level "INFO" -Message " Release tag default from DotNetReleaseVersion: $tag (ReleasePublishGuard may replace from git when publish is allowed)." + Write-Log -Level "INFO" -Message " Release tag default from ${versionSource}: $tag (ReleasePublishGuard may replace from git when publish is allowed)." return [pscustomobject]@{ scriptDir = $ScriptDir diff --git a/utils/Release-Package/README.md b/utils/Release-Package/README.md index 947a8b6..c684d48 100644 --- a/utils/Release-Package/README.md +++ b/utils/Release-Package/README.md @@ -10,14 +10,14 @@ Canonical source: this folder in **maksit-repoutils**. Product repositories refr |------|------| | `Release-Package.ps1` | Loads settings, builds `New-EngineContext`, runs plugins in order. | | `PluginSupport.psm1` | Plugin discovery, `Invoke-ConfiguredPlugin`; publish plugins honor `skipPublishPlugins` from `ReleasePublishGuard` (no per-plugin `branches` for GitHub/NuGet/Docker/Helm). | -| `ReleaseContext.psm1` | Resolves semver via `Resolve-DotNetReleaseVersion` from the `DotNetReleaseVersion` plugin `projectFiles` (first `.csproj` ``). | -| `EngineSupport.psm1` | Warn-only dirty-tree preflight; default `context.tag` = `v{version}` from DotNetReleaseVersion; `Initialize-ReleaseStageContext` sets `releaseDir` only. | +| `ReleaseContext.psm1` | Resolves semver via `Resolve-ReleaseVersion` from `DotNetReleaseVersion.projectFiles` (first `.csproj` ``) or `NpmReleaseVersion.packageJsonPath`. | +| `EngineSupport.psm1` | Warn-only dirty-tree preflight; default `context.tag` = `v{version}` from the configured version plugin; `Initialize-ReleaseStageContext` sets `releaseDir` only. | ## Plugins `CorePlugins/` — e.g. `DotNetReleaseVersion`, `NpmReleaseVersion`, `NpmBuild`, `NpmPublish`, `DockerPush`, `HelmPush`, `ReleasePublishGuard`. Optional `CustomPlugins/`. -`DotNetPack` and `QualityGate` (when used) can declare their own `projectFiles`; semver still comes only from `DotNetReleaseVersion.projectFiles`. +`DotNetPack` and `QualityGate` (when used) can declare their own `projectFiles`; semver still comes from the configured version plugin (`DotNetReleaseVersion` or `NpmReleaseVersion`). ## `ReleasePublishGuard` @@ -38,7 +38,7 @@ For TypeScript monorepos published to npmjs: ## Helm charts in git -Commit `Chart.yaml` with placeholder `version` and `appVersion` (for example `0.0.0`) so `helm lint` stays valid. `HelmPush` temporarily replaces both with the **bare** release semver from `context.version` (`DotNetReleaseVersion`, e.g. `3.3.4` without a `v` prefix) before packaging and OCI push; if `version` were missing, it would fall back to stripping `v`/`V` from `context.tag`. Then it restores `Chart.yaml`. `DockerPush` tags images with the **bare** semver from `context.version` (e.g. `3.3.4`), also pushes `vX.Y.Z` and `shared.tag` when they differ, and optional `latest` — not from `Chart.yaml`; optionally use per-image `versionEnvFiles` to temporarily set `VITE_APP_VERSION={shared.version}` in frontend `.env` files during docker build, then restore originals. +Commit `Chart.yaml` with placeholder `version` and `appVersion` (for example `0.0.0`) so `helm lint` stays valid. `HelmPush` temporarily replaces both with the **bare** release semver from `context.version` (`DotNetReleaseVersion`, e.g. `3.3.4` without a `v` prefix) before packaging and OCI push; if `version` were missing, it would fall back to stripping `v`/`V` from `context.tag`. Then it restores `Chart.yaml`. `DockerPush` tags images with the **bare** semver from `context.version` (e.g. `3.3.4`), also pushes `vX.Y.Z` and `shared.tag` when they differ, and optional `latest` — not from `Chart.yaml`; optionally use per-image `versionEnvFiles` to temporarily set `VITE_APP_VERSION={shared.version}` in frontend `.env` files during docker build, then restore originals. Each image may override the plugin `contextPath` with its own `contextPath` (paths relative to Release-Package); `dockerfile` and `versionEnvFiles` resolve against that per-image context. Sample chart: repository `charts/my-service/` (matches the sample `chartPath` in `scriptsettings.json`). Product repos often use `src/helm/` instead. diff --git a/utils/Release-Package/ReleaseContext.psm1 b/utils/Release-Package/ReleaseContext.psm1 index f6f7a33..c92fb06 100644 --- a/utils/Release-Package/ReleaseContext.psm1 +++ b/utils/Release-Package/ReleaseContext.psm1 @@ -6,9 +6,9 @@ Helpers to resolve release semver from plugin configuration. .DESCRIPTION - Used by New-EngineContext and the DotNetReleaseVersion plugin: - - Source: DotNetReleaseVersion plugin -> projectFiles - - Version from first path in projectFiles (SDK-style .csproj ) + Used by New-EngineContext and version plugins: + - DotNetReleaseVersion plugin -> projectFiles (.csproj ) + - NpmReleaseVersion plugin -> packageJsonPath (package.json version) #> if (-not (Get-Command Write-Log -ErrorAction SilentlyContinue)) { @@ -136,10 +136,90 @@ function Resolve-DotNetReleaseVersion { return [pscustomobject]@{ version = $version + source = 'DotNetReleaseVersion' } } -Export-ModuleMember -Function Get-CsprojPropertyValue, Get-CsprojVersions, Resolve-RelativePaths, Resolve-DotNetReleaseVersion +function Resolve-NpmReleaseVersion { + param( + [Parameter(Mandatory = $true)] + [object[]]$Plugins, + + [Parameter(Mandatory = $true)] + [string]$ScriptDir + ) + + $releaseVersionPlugin = @($Plugins | Where-Object { $_.name -eq 'NpmReleaseVersion' } | Select-Object -First 1) + if ($releaseVersionPlugin.Count -eq 0 -or $null -eq $releaseVersionPlugin[0]) { + Write-Error "Configure an NpmReleaseVersion plugin in scriptsettings.json with packageJsonPath." + exit 1 + } + + $releaseVersionSettings = $releaseVersionPlugin[0] + $packageJsonPaths = @(Resolve-RelativePaths -Value $releaseVersionSettings.packageJsonPath -BasePath $ScriptDir) + + if ($packageJsonPaths.Count -eq 0) { + Write-Error "Configure release version via NpmReleaseVersion.packageJsonPath." + exit 1 + } + + $packageJsonPath = $packageJsonPaths[0] + if (-not (Test-Path $packageJsonPath -PathType Leaf)) { + Write-Error "NpmReleaseVersion: package.json not found at: $packageJsonPath" + exit 1 + } + + Write-Log -Level "INFO" -Message "Reading version from npm package.json (packageJsonPath)..." + $json = Get-Content -Path $packageJsonPath -Raw -Encoding UTF8 | ConvertFrom-Json + $version = [string]$json.version + if ([string]::IsNullOrWhiteSpace($version)) { + Write-Error "NpmReleaseVersion: 'version' is missing in '$packageJsonPath'." + exit 1 + } + + if ($version -notmatch '^\d+\.\d+\.\d+') { + Write-Error "NpmReleaseVersion: version '$version' in '$packageJsonPath' is not a valid semver." + exit 1 + } + + Write-Log -Level "OK" -Message " $([System.IO.Path]::GetFileName($packageJsonPath)): $version" + + return [pscustomobject]@{ + version = $version + source = 'NpmReleaseVersion' + } +} + +function Resolve-ReleaseVersion { + param( + [Parameter(Mandatory = $true)] + [object[]]$Plugins, + + [Parameter(Mandatory = $true)] + [string]$ScriptDir + ) + + $dotnetPlugin = @($Plugins | Where-Object { $_.name -eq 'DotNetReleaseVersion' -and $_.enabled -ne $false }) + $npmPlugin = @($Plugins | Where-Object { $_.name -eq 'NpmReleaseVersion' -and $_.enabled -ne $false }) + + if ($dotnetPlugin.Count -gt 0 -and $npmPlugin.Count -gt 0) { + Write-Error "Configure only one release version plugin: DotNetReleaseVersion or NpmReleaseVersion, not both." + exit 1 + } + + if ($dotnetPlugin.Count -gt 0) { + return Resolve-DotNetReleaseVersion -Plugins $Plugins -ScriptDir $ScriptDir + } + + if ($npmPlugin.Count -gt 0) { + return Resolve-NpmReleaseVersion -Plugins $Plugins -ScriptDir $ScriptDir + } + + Write-Error "Configure a DotNetReleaseVersion plugin (projectFiles) or NpmReleaseVersion plugin (packageJsonPath) in scriptsettings.json." + exit 1 +} + +Export-ModuleMember -Function Get-CsprojPropertyValue, Get-CsprojVersions, Resolve-RelativePaths, Resolve-DotNetReleaseVersion, Resolve-NpmReleaseVersion, Resolve-ReleaseVersion diff --git a/utils/Release-Package/scriptsettings.json b/utils/Release-Package/scriptsettings.json index 6114251..51c26a7 100644 --- a/utils/Release-Package/scriptsettings.json +++ b/utils/Release-Package/scriptsettings.json @@ -39,7 +39,8 @@ "githubToken": "GITHUB_MAKS_IT_COM", "repository": "https://github.com/MAKS-IT-COM/maksit-webui", "releaseNotesFile": "..\\..\\CHANGELOG.md", - "releaseTitlePattern": "Release {version}" + "releaseTitlePattern": "Release {version}", + "requireReleaseAssets": false }, { "name": "NpmPublish", @@ -50,9 +51,9 @@ "access": "public", "workspaceRoot": "..\\..\\src", "publishOrder": [ - "@maksit/webui-contracts", - "@maksit/webui-core", - "@maksit/webui-components" + "@maks-it.com/webui-contracts", + "@maks-it.com/webui-core", + "@maks-it.com/webui-components" ] } ], diff --git a/utils/Run-Tests/CorePlugins/CoverageBadges.psm1 b/utils/Run-Tests/CorePlugins/CoverageBadges.psm1 new file mode 100644 index 0000000..398c295 --- /dev/null +++ b/utils/Run-Tests/CorePlugins/CoverageBadges.psm1 @@ -0,0 +1,177 @@ +#requires -Version 7.0 +#requires -PSEdition Core + +<# +.SYNOPSIS + Coverage badge plugin for the Run-Tests engine. + +.DESCRIPTION + Reads line/branch/method coverage from shared engine context and writes SVG badges. +#> + +if (-not (Get-Command Import-PluginDependency -ErrorAction SilentlyContinue)) { + $pluginSupportModulePath = Join-Path (Split-Path $PSScriptRoot -Parent) "PluginSupport.psm1" + if (Test-Path $pluginSupportModulePath -PathType Leaf) { + Import-Module $pluginSupportModulePath -Force -Global -ErrorAction Stop + } +} + +function Get-BadgeColorInternal { + param( + [double]$percentage, + [psobject]$thresholds + ) + + if ($percentage -ge $thresholds.brightgreen) { return 'brightgreen' } + if ($percentage -ge $thresholds.green) { return 'green' } + if ($percentage -ge $thresholds.yellowgreen) { return 'yellowgreen' } + if ($percentage -ge $thresholds.yellow) { return 'yellow' } + if ($percentage -ge $thresholds.orange) { return 'orange' } + return 'red' +} + +function New-BadgeSvgInternal { + param( + [string]$label, + [string]$value, + [string]$color + ) + + $labelWidth = [math]::Max(($label.Length * 6.5) + 10, 50) + $valueWidth = [math]::Max(($value.Length * 6.5) + 10, 40) + $totalWidth = $labelWidth + $valueWidth + $labelX = $labelWidth / 2 + $valueX = $labelWidth + ($valueWidth / 2) + + $colorMap = @{ + brightgreen = '#4c1' + green = '#97ca00' + yellowgreen = '#a4a61d' + yellow = '#dfb317' + orange = '#fe7d37' + red = '#e05d44' + } + $hexColor = $colorMap[$color] + if (-not $hexColor) { $hexColor = '#9f9f9f' } + + return @" + + $label`: $value + + + + + + + + + + + + + + + $label + + $value + + +"@ +} + +function Get-CoverageMetricsFromSharedContext { + param( + [Parameter(Mandatory = $true)] + $Shared + ) + + $line = $null + $branch = $null + $method = $null + + if ($Shared.PSObject.Properties.Name -contains 'coverageLineRate') { + $line = [double]$Shared.coverageLineRate + } + if ($Shared.PSObject.Properties.Name -contains 'coverageBranchRate') { + $branch = [double]$Shared.coverageBranchRate + } + if ($Shared.PSObject.Properties.Name -contains 'coverageMethodRate') { + $method = [double]$Shared.coverageMethodRate + } + + if ($null -eq $line -and $Shared.PSObject.Properties.Name -contains 'testResult' -and $null -ne $Shared.testResult) { + $line = [double]$Shared.testResult.LineRate + $branch = [double]$Shared.testResult.BranchRate + $method = [double]$Shared.testResult.MethodRate + } + + if ($null -eq $line) { + throw 'CoverageBadges requires coverage metrics on shared context. Run NpmJestTest or DotNetTest first.' + } + + return @{ + line = $line + branch = $branch + method = $method + } +} + +function Invoke-Plugin { + param( + [Parameter(Mandatory = $true)] + $Settings + ) + + Import-PluginDependency -ModuleName "Logging" -RequiredCommand "Write-Log" + Import-PluginDependency -ModuleName "ReleaseContext" -RequiredCommand "Resolve-RelativePaths" + + $pluginSettings = $Settings + $sharedSettings = $Settings.context + $scriptDir = $sharedSettings.scriptDir + $metrics = Get-CoverageMetricsFromSharedContext -Shared $sharedSettings + + $badgesDir = $sharedSettings.badgesDir + if ($pluginSettings.badgesDir) { + $badgesDirs = @(Resolve-RelativePaths -Value $pluginSettings.badgesDir -BasePath $scriptDir) + $badgesDir = $badgesDirs[0] + } + if ([string]::IsNullOrWhiteSpace([string]$badgesDir)) { + throw "CoverageBadges requires badgesDir in plugin settings or paths.badgesDir in scriptsettings.json." + } + + if (-not (Test-Path $badgesDir)) { + New-Item -ItemType Directory -Path $badgesDir | Out-Null + } + + $thresholds = $pluginSettings.colorThresholds + if ($null -eq $thresholds) { + $thresholds = [pscustomobject]@{ + brightgreen = 80 + green = 60 + yellowgreen = 40 + yellow = 20 + orange = 10 + red = 0 + } + } + + Write-Log -Level "STEP" -Message "Generating coverage badges..." + + foreach ($badge in @($pluginSettings.badges)) { + $metricValue = $metrics[[string]$badge.metric] + if ($null -eq $metricValue) { + throw "Unknown or missing coverage metric '$($badge.metric)' for badge '$($badge.name)'." + } + + $color = Get-BadgeColorInternal -percentage $metricValue -thresholds $thresholds + $svg = New-BadgeSvgInternal -label $badge.label -value "$metricValue%" -color $color + $path = Join-Path $badgesDir $badge.name + $svg | Out-File -FilePath $path -Encoding utf8NoBOM + Write-Log -Level "OK" -Message "$($badge.name): $($badge.label) = $metricValue%" + } + + Write-Log -Level "OK" -Message "Badges generated in: $badgesDir" + Write-Log -Level "STEP" -Message "Commit the badges folder to update README." +} + +Export-ModuleMember -Function Invoke-Plugin diff --git a/utils/Run-Tests/CorePlugins/DotNetTest.psm1 b/utils/Run-Tests/CorePlugins/DotNetTest.psm1 new file mode 100644 index 0000000..dba703e --- /dev/null +++ b/utils/Run-Tests/CorePlugins/DotNetTest.psm1 @@ -0,0 +1,98 @@ +#requires -Version 7.0 +#requires -PSEdition Core + +<# +.SYNOPSIS + .NET test plugin for executing automated tests. + +.DESCRIPTION + Resolves one or more .NET test projects (`project` or `projects`), runs tests once + via TestRunner, then publishes metrics on the shared engine context for any later + plugin: `qualityLineCoverage`, `testResult`, `coverageLineRate` / `coverageBranchRate` / `coverageMethodRate`, + method counts, `testResultsDirectory`, `coverageCoberturaPaths`. Quality gates read + those keys generically (not tied to this plugin by name). Cobertura files are removed + after parsing unless TestRunner gains KeepResults. +#> + +if (-not (Get-Command Import-PluginDependency -ErrorAction SilentlyContinue)) { + $pluginSupportModulePath = Join-Path (Split-Path $PSScriptRoot -Parent) "PluginSupport.psm1" + if (Test-Path $pluginSupportModulePath -PathType Leaf) { + # Same fallback pattern as the other plugins: use the existing shared module if it is already loaded. + Import-Module $pluginSupportModulePath -Force -Global -ErrorAction Stop + } +} + +function Invoke-Plugin { + param( + [Parameter(Mandatory = $true)] + $Settings + ) + + Import-PluginDependency -ModuleName "Logging" -RequiredCommand "Write-Log" + Import-PluginDependency -ModuleName "TestRunner" -RequiredCommand "Invoke-TestsWithCoverage" + + $pluginSettings = $Settings + $sharedSettings = $Settings.context + $testResultsDirSetting = $pluginSettings.resultsDir + $scriptDir = $sharedSettings.scriptDir + + $testProjectPaths = [System.Collections.Generic.List[string]]::new() + if ($pluginSettings.PSObject.Properties.Name -contains 'projects' -and $pluginSettings.projects) { + foreach ($rel in @($pluginSettings.projects)) { + if ([string]::IsNullOrWhiteSpace([string]$rel)) { continue } + $testProjectPaths.Add([System.IO.Path]::GetFullPath((Join-Path $scriptDir $rel.Trim()))) + } + } + if ($testProjectPaths.Count -eq 0 -and $pluginSettings.project) { + $testProjectPaths.Add([System.IO.Path]::GetFullPath((Join-Path $scriptDir $pluginSettings.project))) + } + if ($testProjectPaths.Count -eq 0) { + throw "DotNetTest plugin requires 'project' or 'projects' in scriptsettings.json." + } + + $testResultsDir = $null + if (-not [string]::IsNullOrWhiteSpace($testResultsDirSetting)) { + $testResultsDir = [System.IO.Path]::GetFullPath((Join-Path $scriptDir $testResultsDirSetting)) + } + elseif ($testProjectPaths.Count -gt 1) { + $testResultsDir = [System.IO.Path]::GetFullPath((Join-Path $scriptDir "TestResults")) + } + + Write-Log -Level "STEP" -Message "Running tests..." + + # Build a splatted hashtable so optional arguments can be added without duplicating the call site. + $invokeTestParams = @{ + TestProjectPath = @($testProjectPaths) + Silent = $true + } + if ($testResultsDir) { + $invokeTestParams.ResultsDirectory = $testResultsDir + } + + $testResult = Invoke-TestsWithCoverage @invokeTestParams + + if (-not $testResult.Success) { + throw "Tests failed. $($testResult.Error)" + } + + $sharedSettings | Add-Member -NotePropertyName testResult -NotePropertyValue $testResult -Force + $sharedSettings | Add-Member -NotePropertyName qualityLineCoverage -NotePropertyValue $testResult.LineRate -Force + $sharedSettings | Add-Member -NotePropertyName coverageLineRate -NotePropertyValue $testResult.LineRate -Force + $sharedSettings | Add-Member -NotePropertyName coverageBranchRate -NotePropertyValue $testResult.BranchRate -Force + $sharedSettings | Add-Member -NotePropertyName coverageMethodRate -NotePropertyValue $testResult.MethodRate -Force + $sharedSettings | Add-Member -NotePropertyName coverageTotalMethods -NotePropertyValue $testResult.TotalMethods -Force + $sharedSettings | Add-Member -NotePropertyName coverageCoveredMethods -NotePropertyValue $testResult.CoveredMethods -Force + if (($testResult.PSObject.Properties.Name -contains 'ResultsDirectory') -and $testResult.ResultsDirectory) { + $sharedSettings | Add-Member -NotePropertyName testResultsDirectory -NotePropertyValue $testResult.ResultsDirectory -Force + } + if ($testResult.CoverageFiles) { + $sharedSettings | Add-Member -NotePropertyName coverageCoberturaPaths -NotePropertyValue @($testResult.CoverageFiles) -Force + } + + Write-Log -Level "OK" -Message " All tests passed!" + Write-Log -Level "INFO" -Message " Line Coverage: $($testResult.LineRate)%" + Write-Log -Level "INFO" -Message " Branch Coverage: $($testResult.BranchRate)%" + Write-Log -Level "INFO" -Message " Method Coverage: $($testResult.MethodRate)%" +} + +Export-ModuleMember -Function Invoke-Plugin diff --git a/utils/Run-Tests/CorePlugins/NpmJestTest.psm1 b/utils/Run-Tests/CorePlugins/NpmJestTest.psm1 new file mode 100644 index 0000000..3566e56 --- /dev/null +++ b/utils/Run-Tests/CorePlugins/NpmJestTest.psm1 @@ -0,0 +1,82 @@ +#requires -Version 7.0 +#requires -PSEdition Core + +<# +.SYNOPSIS + npm/Jest test plugin for the Run-Tests engine. + +.DESCRIPTION + Runs Jest with coverage via TestRunner.Invoke-NpmJestTestsWithCoverage and publishes + normalized metrics on the shared engine context for downstream plugins. +#> + +if (-not (Get-Command Import-PluginDependency -ErrorAction SilentlyContinue)) { + $pluginSupportModulePath = Join-Path (Split-Path $PSScriptRoot -Parent) "PluginSupport.psm1" + if (Test-Path $pluginSupportModulePath -PathType Leaf) { + Import-Module $pluginSupportModulePath -Force -Global -ErrorAction Stop + } +} + +function Invoke-Plugin { + param( + [Parameter(Mandatory = $true)] + $Settings + ) + + Import-PluginDependency -ModuleName "Logging" -RequiredCommand "Write-Log" + Import-PluginDependency -ModuleName "TestRunner" -RequiredCommand "Invoke-NpmJestTestsWithCoverage" + Import-PluginDependency -ModuleName "ScriptConfig" -RequiredCommand "Assert-Command" + Import-PluginDependency -ModuleName "ReleaseContext" -RequiredCommand "Resolve-RelativePaths" + + $pluginSettings = $Settings + $sharedSettings = $Settings.context + $scriptDir = $sharedSettings.scriptDir + + Assert-Command npm + + if (-not $pluginSettings.workspaceRoot) { + throw "NpmJestTest plugin requires 'workspaceRoot' in scriptsettings.json." + } + + $workspaceRoots = @(Resolve-RelativePaths -Value $pluginSettings.workspaceRoot -BasePath $scriptDir) + $workspaceRoot = $workspaceRoots[0] + + $testScript = 'test' + if (-not [string]::IsNullOrWhiteSpace([string]$pluginSettings.testScript)) { + $testScript = [string]$pluginSettings.testScript + } + + $coverageDirectory = 'coverage' + if (-not [string]::IsNullOrWhiteSpace([string]$pluginSettings.coverageDirectory)) { + $coverageDirectory = [string]$pluginSettings.coverageDirectory + } + + $testResult = Invoke-NpmJestTestsWithCoverage -WorkspaceRoot $workspaceRoot -TestScript $testScript -CoverageDirectory $coverageDirectory + + if (-not $testResult.Success) { + throw "Tests failed. $($testResult.Error)" + } + + $sharedSettings | Add-Member -NotePropertyName npmWorkspaceRoot -NotePropertyValue $workspaceRoot -Force + $sharedSettings | Add-Member -NotePropertyName testResult -NotePropertyValue $testResult -Force + $sharedSettings | Add-Member -NotePropertyName qualityLineCoverage -NotePropertyValue $testResult.LineRate -Force + $sharedSettings | Add-Member -NotePropertyName coverageLineRate -NotePropertyValue $testResult.LineRate -Force + $sharedSettings | Add-Member -NotePropertyName coverageBranchRate -NotePropertyValue $testResult.BranchRate -Force + $sharedSettings | Add-Member -NotePropertyName coverageMethodRate -NotePropertyValue $testResult.MethodRate -Force + $sharedSettings | Add-Member -NotePropertyName coverageTotalMethods -NotePropertyValue $testResult.TotalMethods -Force + $sharedSettings | Add-Member -NotePropertyName coverageCoveredMethods -NotePropertyValue $testResult.CoveredMethods -Force + + if (($testResult.PSObject.Properties.Name -contains 'ResultsDirectory') -and $testResult.ResultsDirectory) { + $sharedSettings | Add-Member -NotePropertyName testResultsDirectory -NotePropertyValue $testResult.ResultsDirectory -Force + } + if (($testResult.PSObject.Properties.Name -contains 'CoverageSummaryFile') -and $testResult.CoverageSummaryFile) { + $sharedSettings | Add-Member -NotePropertyName coverageSummaryFile -NotePropertyValue $testResult.CoverageSummaryFile -Force + } + + Write-Log -Level "OK" -Message " All tests passed!" + Write-Log -Level "INFO" -Message " Line Coverage: $($testResult.LineRate)%" + Write-Log -Level "INFO" -Message " Branch Coverage: $($testResult.BranchRate)%" + Write-Log -Level "INFO" -Message " Method Coverage: $($testResult.MethodRate)%" +} + +Export-ModuleMember -Function Invoke-Plugin diff --git a/utils/Run-Tests/CorePlugins/QualityGate.psm1 b/utils/Run-Tests/CorePlugins/QualityGate.psm1 new file mode 100644 index 0000000..830c08b --- /dev/null +++ b/utils/Run-Tests/CorePlugins/QualityGate.psm1 @@ -0,0 +1,184 @@ +#requires -Version 7.0 +#requires -PSEdition Core + +<# +.SYNOPSIS + Quality gate plugin (coverage threshold + optional .NET vulnerability scan). + +.DESCRIPTION + Does not run tests or collect coverage. It reads whatever prior plugins left on the + shared engine context (same object passed to every plugin as .context). + + Line coverage for threshold checks is resolved in order (first present wins): + - qualityLineCoverage (generic; any plugin may set this) + - coverageLineRate (conventional flat metric) + - testResult.LineRate (object from a test plugin; property name is conventional) + + Configure coverageThreshold > 0 to require one of those inputs. With coverageThreshold 0 + and scanVulnerabilities false, the plugin is a no-op. + + When scanVulnerabilities is true, runs dotnet list package --vulnerable on projectFiles. + + Use stageLabel "qualityGate" in scriptsettings.json; plugin module: CorePlugins/QualityGate.psm1 (`"name": "QualityGate"`). +#> + +if (-not (Get-Command Import-PluginDependency -ErrorAction SilentlyContinue)) { + $pluginSupportModulePath = Join-Path (Split-Path $PSScriptRoot -Parent) "PluginSupport.psm1" + if (Test-Path $pluginSupportModulePath -PathType Leaf) { + Import-Module $pluginSupportModulePath -Force -Global -ErrorAction Stop + } +} + +function Test-VulnerablePackagesInternal { + param( + [Parameter(Mandatory = $true)] + [string[]]$ProjectFiles + ) + + $findings = @() + + foreach ($projectPath in $ProjectFiles) { + Write-Log -Level "STEP" -Message "Checking vulnerable packages: $([System.IO.Path]::GetFileName($projectPath))" + + $output = & dotnet list $projectPath package --vulnerable --include-transitive 2>&1 + if ($LASTEXITCODE -ne 0) { + throw "dotnet list package --vulnerable failed for $projectPath." + } + + $outputText = ($output | Out-String) + if ($outputText -match "(?im)\bhas the following vulnerable packages\b" -or $outputText -match "(?im)^\s*>\s+[A-Za-z0-9_.-]+\s") { + $findings += [pscustomobject]@{ + Project = $projectPath + Output = $outputText.Trim() + } + } + } + + return $findings +} + +function Get-LineCoveragePercentFromSharedContext { + param( + [Parameter(Mandatory = $true)] + $Shared + ) + + foreach ($prop in @('qualityLineCoverage', 'coverageLineRate')) { + if ($Shared.PSObject.Properties.Name -contains $prop) { + $raw = $Shared.$prop + if ($null -eq $raw) { continue } + $asString = [string]$raw + if ([string]::IsNullOrWhiteSpace($asString)) { continue } + return [double]$asString + } + } + + if ($Shared.PSObject.Properties.Name -contains 'testResult' -and $null -ne $Shared.testResult) { + $tr = $Shared.testResult + if ($tr.PSObject.Properties.Name -contains 'LineRate') { + return [double]$tr.LineRate + } + } + + return $null +} + +function Invoke-Plugin { + param( + [Parameter(Mandatory = $true)] + $Settings + ) + + Import-PluginDependency -ModuleName "Logging" -RequiredCommand "Write-Log" + Import-PluginDependency -ModuleName "ScriptConfig" -RequiredCommand "Assert-Command" + Import-PluginDependency -ModuleName "ReleaseContext" -RequiredCommand "Resolve-RelativePaths" + + $pluginSettings = $Settings + $sharedSettings = $Settings.context + $scriptDir = $sharedSettings.scriptDir + $coverageThresholdSetting = $pluginSettings.coverageThreshold + $failOnVulnerabilitiesSetting = $pluginSettings.failOnVulnerabilities + $scanVulnerabilities = $true + if ($null -ne $pluginSettings.scanVulnerabilities) { + $scanVulnerabilities = [bool]$pluginSettings.scanVulnerabilities + } + + if ($pluginSettings.PSObject.Properties['projectFiles'] -and $null -ne $pluginSettings.projectFiles) { + $projectFiles = @(Resolve-RelativePaths -Value $pluginSettings.projectFiles -BasePath $scriptDir) + } + elseif ($sharedSettings.PSObject.Properties['projectFiles'] -and $null -ne $sharedSettings.projectFiles) { + $projectFiles = @($sharedSettings.projectFiles) + } + else { + $projectFiles = @() + } + + $coverageThreshold = 0 + if ($null -ne $coverageThresholdSetting) { + $coverageThreshold = [double]$coverageThresholdSetting + } + + $needCoverageCheck = $coverageThreshold -gt 0 + if (-not $needCoverageCheck -and -not $scanVulnerabilities) { + Write-Log -Level "INFO" -Message " Quality gate: no checks enabled (coverageThreshold 0, scanVulnerabilities false)." + return + } + + $lineRate = $null + if ($needCoverageCheck) { + $lineRate = Get-LineCoveragePercentFromSharedContext -Shared $sharedSettings + if ($null -eq $lineRate) { + throw "coverageThreshold is $coverageThreshold but shared context has no line coverage. Set one of: qualityLineCoverage, coverageLineRate, or testResult.LineRate (from an earlier plugin)." + } + + Write-Log -Level "STEP" -Message "Checking line coverage threshold against shared context..." + if ($lineRate -lt $coverageThreshold) { + throw "Line coverage $lineRate% is below the configured threshold of $coverageThreshold%." + } + + Write-Log -Level "OK" -Message " Coverage threshold met: $lineRate% >= $coverageThreshold%" + } + else { + Write-Log -Level "INFO" -Message " Coverage threshold check not required (coverageThreshold is 0)." + } + + if (-not $scanVulnerabilities) { + Write-Log -Level "INFO" -Message " Vulnerability scan skipped (scanVulnerabilities is false)." + return + } + + Assert-Command dotnet + + $failOnVulnerabilities = $true + if ($null -ne $failOnVulnerabilitiesSetting) { + $failOnVulnerabilities = [bool]$failOnVulnerabilitiesSetting + } + + if ($projectFiles.Count -eq 0) { + throw "QualityGate requires projectFiles when scanVulnerabilities is true." + } + + $vulnerabilities = Test-VulnerablePackagesInternal -ProjectFiles $projectFiles + + if ($vulnerabilities.Count -eq 0) { + Write-Log -Level "OK" -Message " No vulnerable packages detected." + return + } + + foreach ($finding in $vulnerabilities) { + Write-Log -Level "WARN" -Message " Vulnerable packages detected in $([System.IO.Path]::GetFileName($finding.Project))" + $finding.Output -split "`r?`n" | ForEach-Object { + if (-not [string]::IsNullOrWhiteSpace($_)) { + Write-Log -Level "WARN" -Message " $_" + } + } + } + + if ($failOnVulnerabilities) { + throw "Vulnerable packages were detected and failOnVulnerabilities is enabled." + } + + Write-Log -Level "WARN" -Message "Vulnerable packages detected, but failOnVulnerabilities is disabled." +} + +Export-ModuleMember -Function Invoke-Plugin diff --git a/utils/Run-Tests/CustomPlugins/.gitkeep b/utils/Run-Tests/CustomPlugins/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/utils/Run-Tests/EngineSupport.psm1 b/utils/Run-Tests/EngineSupport.psm1 new file mode 100644 index 0000000..7233e17 --- /dev/null +++ b/utils/Run-Tests/EngineSupport.psm1 @@ -0,0 +1,35 @@ +#requires -Version 7.0 +#requires -PSEdition Core + +if (-not (Get-Command Write-Log -ErrorAction SilentlyContinue)) { + $loggingModulePath = Join-Path (Split-Path $PSScriptRoot -Parent) "Logging.psm1" + if (Test-Path $loggingModulePath -PathType Leaf) { + Import-Module $loggingModulePath -Force + } +} + +function New-EngineContext { + param( + [Parameter(Mandatory = $true)] + [string]$ScriptDir, + + [Parameter(Mandatory = $true)] + [string]$UtilsDir, + + [Parameter(Mandatory = $false)] + [psobject]$Settings + ) + + $badgesDir = $null + if ($Settings -and $Settings.PSObject.Properties['paths'] -and $Settings.paths.badgesDir) { + $badgesDir = [System.IO.Path]::GetFullPath((Join-Path $ScriptDir ([string]$Settings.paths.badgesDir))) + } + + return [pscustomobject]@{ + scriptDir = $ScriptDir + utilsDir = $UtilsDir + badgesDir = $badgesDir + } +} + +Export-ModuleMember -Function New-EngineContext diff --git a/utils/Run-Tests/PluginSupport.psm1 b/utils/Run-Tests/PluginSupport.psm1 new file mode 100644 index 0000000..e2bb0c3 --- /dev/null +++ b/utils/Run-Tests/PluginSupport.psm1 @@ -0,0 +1,376 @@ +#requires -Version 7.0 +#requires -PSEdition Core + +if (-not (Get-Command Write-Log -ErrorAction SilentlyContinue)) { + $loggingModulePath = Join-Path (Split-Path $PSScriptRoot -Parent) "Logging.psm1" + if (Test-Path $loggingModulePath -PathType Leaf) { + Import-Module $loggingModulePath -Force + } +} + +function Import-PluginDependency { + param( + [Parameter(Mandatory = $true)] + [string]$ModuleName, + + [Parameter(Mandatory = $true)] + [string]$RequiredCommand + ) + + if (Get-Command $RequiredCommand -ErrorAction SilentlyContinue) { + return + } + + $moduleRoot = Split-Path $PSScriptRoot -Parent + $modulePath = Join-Path $moduleRoot "$ModuleName.psm1" + if (Test-Path $modulePath -PathType Leaf) { + # Import into the global session so the calling plugin can see the exported commands. + # Importing only into this module's scope would make the dependency invisible to the plugin. + Import-Module $modulePath -Force -Global -ErrorAction Stop + } + + if (-not (Get-Command $RequiredCommand -ErrorAction SilentlyContinue)) { + throw "Required command '$RequiredCommand' is still unavailable after importing module '$ModuleName'." + } +} + +function Get-ConfiguredPlugins { + param( + [Parameter(Mandatory = $true)] + [psobject]$Settings + ) + + if (-not $Settings.PSObject.Properties['plugins'] -or $null -eq $Settings.plugins) { + return @() + } + + # JSON can deserialize a single plugin as one object or multiple plugins as an array. + # Always return an array so the engine can loop without special-case logic. + if ($Settings.plugins -is [System.Collections.IEnumerable] -and -not ($Settings.plugins -is [string])) { + return @($Settings.plugins) + } + + return @($Settings.plugins) +} + +function Get-PluginStageLabel { + param( + [Parameter(Mandatory = $true)] + $Plugin + ) + + if (-not $Plugin.PSObject.Properties['stageLabel'] -or [string]::IsNullOrWhiteSpace([string]$Plugin.stageLabel)) { + return 'release' + } + + return [string]$Plugin.stageLabel +} + +function Get-PluginBranches { + param( + [Parameter(Mandatory = $true)] + $Plugin + ) + + if (-not $Plugin.PSObject.Properties['branches'] -or $null -eq $Plugin.branches) { + return @() + } + + # Strings are also IEnumerable in PowerShell, so exclude them or we would split into characters. + if ($Plugin.branches -is [System.Collections.IEnumerable] -and -not ($Plugin.branches -is [string])) { + return @($Plugin.branches | Where-Object { -not [string]::IsNullOrWhiteSpace($_) }) + } + + if ([string]::IsNullOrWhiteSpace([string]$Plugin.branches)) { + return @() + } + + return @([string]$Plugin.branches) +} + +function Test-PluginAllowedOnBranch { + param( + [Parameter(Mandatory = $true)] + $Plugin, + + [Parameter(Mandatory = $true)] + [string]$CurrentBranch + ) + + $allowedBranches = Get-PluginBranches -Plugin $Plugin + if ($allowedBranches.Count -eq 0) { + return $true + } + + if ($allowedBranches -contains '*') { + return $true + } + + return $allowedBranches -contains $CurrentBranch +} + +function Test-IsPublishPlugin { + param( + [Parameter(Mandatory = $true)] + $Plugin + ) + + if ($null -eq $Plugin -or [string]::IsNullOrWhiteSpace([string]$Plugin.name)) { + return $false + } + + return @('GitHub', 'DotNetNuGet', 'DockerPush', 'HelmPush', 'NpmPublish') -contains ([string]$Plugin.name) +} + +function Get-PluginSettingValue { + param( + [Parameter(Mandatory = $true)] + [object[]]$Plugins, + + [Parameter(Mandatory = $true)] + [string]$PropertyName + ) + + foreach ($plugin in $Plugins) { + if ($null -eq $plugin -or [string]::IsNullOrWhiteSpace($plugin.name)) { + continue + } + + if (-not $plugin.PSObject.Properties[$PropertyName]) { + continue + } + + $value = $plugin.$PropertyName + if ($null -eq $value) { + continue + } + + if ($value -is [string] -and [string]::IsNullOrWhiteSpace($value)) { + continue + } + + return $value + } + + return $null +} + +function Get-PluginPathListSetting { + param( + [Parameter(Mandatory = $true)] + [object[]]$Plugins, + + [Parameter(Mandatory = $true)] + [string]$PropertyName, + + [Parameter(Mandatory = $true)] + [string]$BasePath + ) + + $rawPaths = @() + $value = Get-PluginSettingValue -Plugins $Plugins -PropertyName $PropertyName + + if ($null -eq $value) { + return @() + } + + # Same rule as above: treat a string as one path, not a char-by-char sequence. + if ($value -is [System.Collections.IEnumerable] -and -not ($value -is [string])) { + $rawPaths += $value + } + else { + $rawPaths += $value + } + + $resolvedPaths = @() + foreach ($path in $rawPaths) { + if ([string]::IsNullOrWhiteSpace([string]$path)) { + continue + } + + $resolvedPaths += [System.IO.Path]::GetFullPath((Join-Path $BasePath ([string]$path))) + } + + # Wrap again to stop PowerShell from unrolling a single-item array into a bare string. + return @($resolvedPaths) +} + +function Get-PluginPathSetting { + param( + [Parameter(Mandatory = $true)] + [object[]]$Plugins, + + [Parameter(Mandatory = $true)] + [string]$PropertyName, + + [Parameter(Mandatory = $true)] + [string]$BasePath + ) + + $value = Get-PluginSettingValue -Plugins $Plugins -PropertyName $PropertyName + if ($null -eq $value -or [string]::IsNullOrWhiteSpace([string]$value)) { + return $null + } + + return [System.IO.Path]::GetFullPath((Join-Path $BasePath ([string]$value))) +} + +function Get-ArchiveNamePattern { + param( + [Parameter(Mandatory = $true)] + [object[]]$Plugins, + + [Parameter(Mandatory = $true)] + [string]$CurrentBranch + ) + + foreach ($plugin in $Plugins) { + if ($null -eq $plugin -or [string]::IsNullOrWhiteSpace($plugin.name)) { + continue + } + + if (-not $plugin.enabled) { + continue + } + + if (-not (Test-PluginAllowedOnBranch -Plugin $plugin -CurrentBranch $CurrentBranch)) { + continue + } + + if ($plugin.PSObject.Properties['zipNamePattern'] -and -not [string]::IsNullOrWhiteSpace([string]$plugin.zipNamePattern)) { + return [string]$plugin.zipNamePattern + } + } + + return "release-{version}.zip" +} + +function Resolve-PluginModulePath { + param( + [Parameter(Mandatory = $true)] + $Plugin, + + [Parameter(Mandatory = $true)] + [string]$PluginsDirectory + ) + + $pluginFileName = "{0}.psm1" -f $Plugin.name + $candidatePaths = @( + (Join-Path $PluginsDirectory $pluginFileName), + (Join-Path (Join-Path (Split-Path $PluginsDirectory -Parent) "CustomPlugins") $pluginFileName) + ) + + foreach ($candidatePath in $candidatePaths) { + if (Test-Path $candidatePath -PathType Leaf) { + return $candidatePath + } + } + + return $candidatePaths[0] +} + +function Test-PluginRunnable { + param( + [Parameter(Mandatory = $true)] + $Plugin, + + [Parameter(Mandatory = $true)] + [psobject]$SharedSettings, + + [Parameter(Mandatory = $true)] + [string]$PluginsDirectory, + + [Parameter(Mandatory = $false)] + [bool]$WriteLogs = $true + ) + + if ($null -eq $Plugin -or [string]::IsNullOrWhiteSpace($Plugin.name)) { + if ($WriteLogs) { + Write-Log -Level "WARN" -Message "Skipping plugin entry with no name." + } + return $false + } + + if (-not $Plugin.enabled) { + if ($WriteLogs) { + Write-Log -Level "WARN" -Message "Skipping plugin '$($Plugin.name)' (disabled)." + } + return $false + } + + $pluginModulePath = Resolve-PluginModulePath -Plugin $Plugin -PluginsDirectory $PluginsDirectory + if (-not (Test-Path $pluginModulePath -PathType Leaf)) { + if ($WriteLogs) { + Write-Log -Level "ERROR" -Message "Plugin module not found: $pluginModulePath" + } + return $false + } + + return $true +} + +function New-PluginInvocationSettings { + param( + [Parameter(Mandatory = $true)] + $Plugin, + + [Parameter(Mandatory = $true)] + [psobject]$SharedSettings + ) + + $properties = @{} + foreach ($property in $Plugin.PSObject.Properties) { + $properties[$property.Name] = $property.Value + } + + # Plugins receive their own config plus shared runtime context. + $properties['context'] = $SharedSettings + return [pscustomobject]$properties +} + +function Invoke-ConfiguredPlugin { + param( + [Parameter(Mandatory = $true)] + $Plugin, + + [Parameter(Mandatory = $true)] + [psobject]$SharedSettings, + + [Parameter(Mandatory = $true)] + [string]$PluginsDirectory, + + [Parameter(Mandatory = $false)] + [bool]$ContinueOnError = $true + ) + + if (-not (Test-PluginRunnable -Plugin $Plugin -SharedSettings $SharedSettings -PluginsDirectory $PluginsDirectory -WriteLogs:$true)) { + return + } + + if ((Test-IsPublishPlugin -Plugin $Plugin) -and ($SharedSettings.PSObject.Properties.Name -contains 'skipPublishPlugins') -and $SharedSettings.skipPublishPlugins) { + Write-Log -Level "INFO" -Message "Skipping plugin '$($Plugin.name)' (ReleasePublishGuard suppressed publish)." + return + } + + $pluginModulePath = Resolve-PluginModulePath -Plugin $Plugin -PluginsDirectory $PluginsDirectory + Write-Log -Level "STEP" -Message "Running plugin '$($Plugin.name)'..." + + try { + $moduleInfo = Import-Module $pluginModulePath -Force -PassThru -ErrorAction Stop + # Resolve Invoke-Plugin from the imported module explicitly so we call the plugin we just loaded, + # not some command with the same name from another module already in session. + $invokeCommand = Get-Command -Name "Invoke-Plugin" -Module $moduleInfo.Name -ErrorAction Stop + $pluginSettings = New-PluginInvocationSettings -Plugin $Plugin -SharedSettings $SharedSettings + + & $invokeCommand -Settings $pluginSettings + Write-Log -Level "OK" -Message " Plugin '$($Plugin.name)' completed." + } + catch { + Write-Log -Level "ERROR" -Message " Plugin '$($Plugin.name)' failed: $($_.Exception.Message)" + if (-not $ContinueOnError) { + exit 1 + } + } +} + +Export-ModuleMember -Function Import-PluginDependency, Get-ConfiguredPlugins, Get-PluginStageLabel, Get-PluginBranches, Test-IsPublishPlugin, Get-PluginSettingValue, Get-PluginPathListSetting, Get-PluginPathSetting, Get-ArchiveNamePattern, Resolve-PluginModulePath, Test-PluginRunnable, New-PluginInvocationSettings, Invoke-ConfiguredPlugin diff --git a/utils/Run-Tests/README.md b/utils/Run-Tests/README.md new file mode 100644 index 0000000..db4c2a6 --- /dev/null +++ b/utils/Run-Tests/README.md @@ -0,0 +1,58 @@ +# Run Tests + +Plugin-driven test engine (same pattern as `src/Release-Package`). + +## Run + +```powershell +pwsh -File .\src\Run-Tests\Run-Tests.ps1 +``` + +Or: + +```bat +src\Run-Tests\Run-Tests.bat +``` + +## Core plugins + +| Plugin | Role | +|--------|------| +| `DotNetTest` | `dotnet test` + Coverlet Cobertura (`.NET` repos) | +| `NpmJestTest` | `npm test -- --coverage` + Jest `coverage-summary.json` | +| `QualityGate` | Optional line-coverage threshold from shared context | +| `CoverageBadges` | SVG badges for README (`assets/badges/`) | + +Configure plugin order and settings in `scriptsettings.json`. + +## Shared context + +Test plugins publish metrics for downstream plugins: + +- `qualityLineCoverage`, `coverageLineRate`, `coverageBranchRate`, `coverageMethodRate` +- `testResult` (full result object from `TestRunner`) + +`QualityGate` and `CoverageBadges` read these keys; they do not re-run tests. + +## npm/Jest example + +Replace `DotNetTest` with: + +```json +{ + "name": "NpmJestTest", + "stageLabel": "test", + "enabled": true, + "workspaceRoot": "..\\..\\src", + "testScript": "test", + "coverageDirectory": "coverage" +} +``` + +## Legacy entry point + +`src/Generate-CoverageBadges/Generate-CoverageBadges.ps1` forwards to this engine. + +## Custom plugins + +Add `CustomPlugins/YourPlugin.psm1` with `Invoke-Plugin`, then register it in `scriptsettings.json`. diff --git a/utils/Run-Tests/Run-Tests.bat b/utils/Run-Tests/Run-Tests.bat new file mode 100644 index 0000000..345b419 --- /dev/null +++ b/utils/Run-Tests/Run-Tests.bat @@ -0,0 +1,3 @@ +@echo off +pwsh -NoProfile -ExecutionPolicy Bypass -File "%~dp0Run-Tests.ps1" +pause diff --git a/utils/Run-Tests/Run-Tests.ps1 b/utils/Run-Tests/Run-Tests.ps1 new file mode 100644 index 0000000..79613b5 --- /dev/null +++ b/utils/Run-Tests/Run-Tests.ps1 @@ -0,0 +1,76 @@ +#requires -Version 7.0 +#requires -PSEdition Core + +<# +.SYNOPSIS + Plugin-driven test and coverage engine. + +.DESCRIPTION + Loads scriptsettings.json, builds shared execution context, and runs configured + plugins in order. Each plugin implements Invoke-Plugin and receives its own + settings plus shared context on Settings.context. + +.USAGE + pwsh -File .\Run-Tests.ps1 +#> + +$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path +$utilsDir = Split-Path $scriptDir -Parent + +$scriptConfigModulePath = Join-Path $utilsDir "ScriptConfig.psm1" +if (-not (Test-Path $scriptConfigModulePath)) { + Write-Error "ScriptConfig module not found at: $scriptConfigModulePath" + exit 1 +} +Import-Module $scriptConfigModulePath -Force + +$loggingModulePath = Join-Path $utilsDir "Logging.psm1" +if (-not (Test-Path $loggingModulePath)) { + Write-Error "Logging module not found at: $loggingModulePath" + exit 1 +} +Import-Module $loggingModulePath -Force + +$pluginSupportModulePath = Join-Path $scriptDir "PluginSupport.psm1" +if (-not (Test-Path $pluginSupportModulePath)) { + Write-Error "PluginSupport module not found at: $pluginSupportModulePath" + exit 1 +} +Import-Module $pluginSupportModulePath -Force + +$engineSupportModulePath = Join-Path $scriptDir "EngineSupport.psm1" +if (-not (Test-Path $engineSupportModulePath)) { + Write-Error "EngineSupport module not found at: $engineSupportModulePath" + exit 1 +} +Import-Module $engineSupportModulePath -Force + +$releaseContextModulePath = Join-Path $utilsDir "Release-Package\ReleaseContext.psm1" +if (-not (Test-Path $releaseContextModulePath)) { + Write-Error "ReleaseContext module not found at: $releaseContextModulePath" + exit 1 +} +Import-Module $releaseContextModulePath -Force + +$settings = Get-ScriptSettings -ScriptDir $scriptDir +$configuredPlugins = Get-ConfiguredPlugins -Settings $settings +$pluginsDir = Join-Path $scriptDir "CorePlugins" + +Write-Log -Level "STEP" -Message "==================================================" +Write-Log -Level "STEP" -Message "TEST ENGINE" +Write-Log -Level "STEP" -Message "==================================================" + +$engineContext = New-EngineContext -ScriptDir $scriptDir -UtilsDir $utilsDir -Settings $settings + +if ($configuredPlugins.Count -eq 0) { + Write-Log -Level "WARN" -Message "No plugins configured in scriptsettings.json." + exit 0 +} + +foreach ($plugin in $configuredPlugins) { + Invoke-ConfiguredPlugin -Plugin $plugin -SharedSettings $engineContext -PluginsDirectory $pluginsDir -ContinueOnError:$false +} + +Write-Log -Level "OK" -Message "==================================================" +Write-Log -Level "OK" -Message "TEST RUN COMPLETE" +Write-Log -Level "OK" -Message "==================================================" diff --git a/utils/Run-Tests/scriptsettings.json b/utils/Run-Tests/scriptsettings.json new file mode 100644 index 0000000..34158dc --- /dev/null +++ b/utils/Run-Tests/scriptsettings.json @@ -0,0 +1,63 @@ +{ + "$schema": "https://json-schema.org/draft-07/schema", + "title": "Run Tests Script Settings", + "description": "maksit-webui: plugin-driven Jest tests and coverage badges.", + "paths": { + "badgesDir": "..\\..\\assets\\badges" + }, + "plugins": [ + { + "name": "NpmJestTest", + "stageLabel": "test", + "enabled": true, + "workspaceRoot": "..\\..\\src", + "testScript": "test", + "coverageDirectory": "coverage" + }, + { + "name": "QualityGate", + "stageLabel": "qualityGate", + "enabled": true, + "coverageThreshold": 0, + "scanVulnerabilities": false + }, + { + "name": "CoverageBadges", + "stageLabel": "report", + "enabled": true, + "badgesDir": "..\\..\\assets\\badges", + "badges": [ + { + "name": "coverage-lines.svg", + "label": "Line Coverage", + "metric": "line" + }, + { + "name": "coverage-branches.svg", + "label": "Branch Coverage", + "metric": "branch" + }, + { + "name": "coverage-methods.svg", + "label": "Method Coverage", + "metric": "method" + } + ], + "colorThresholds": { + "brightgreen": 80, + "green": 60, + "yellowgreen": 40, + "yellow": 20, + "orange": 10, + "red": 0 + } + } + ], + "_comments": { + "plugins": { + "NpmJestTest": "Runs npm test with Jest coverage in workspaceRoot. Publishes coverage metrics on shared context.", + "QualityGate": "Reads shared context metrics; set coverageThreshold > 0 to enforce minimum line coverage.", + "CoverageBadges": "Writes SVG badges from shared context metrics into badgesDir." + } + } +} diff --git a/utils/TestRunner.psm1 b/utils/TestRunner.psm1 index 6a8616b..395acf4 100644 --- a/utils/TestRunner.psm1 +++ b/utils/TestRunner.psm1 @@ -283,4 +283,110 @@ function Invoke-TestsWithCoverage { } } -Export-ModuleMember -Function Invoke-TestsWithCoverage +function Invoke-NpmJestTestsWithCoverage { + <# + .SYNOPSIS + Runs npm/Jest tests with coverage and returns normalized metrics. + + .PARAMETER WorkspaceRoot + npm workspace root (folder containing package.json and jest.config). + + .PARAMETER TestScript + npm script name to run (default: test). Coverage flags are appended via `--`. + + .PARAMETER CoverageDirectory + Relative path under WorkspaceRoot where Jest writes coverage output. + + .PARAMETER Silent + Suppress console output from npm. + + .OUTPUTS + Same metric shape as Invoke-TestsWithCoverage, plus CoverageSummaryFile when available. + #> + param( + [Parameter(Mandatory = $true)] + [string]$WorkspaceRoot, + + [string]$TestScript = 'test', + + [string]$CoverageDirectory = 'coverage', + + [switch]$Silent + ) + + $ErrorActionPreference = 'Stop' + $workspaceFull = [System.IO.Path]::GetFullPath($WorkspaceRoot) + if (-not (Test-Path (Join-Path $workspaceFull 'package.json') -PathType Leaf)) { + return [PSCustomObject]@{ + Success = $false + Error = "package.json not found in workspace root: $workspaceFull" + } + } + + if (-not $Silent) { + Write-TestRunnerLogInternal -Level 'STEP' -Message 'Running npm/Jest tests with coverage...' + Write-TestRunnerLogInternal -Level 'INFO' -Message "Workspace: $workspaceFull" + } + + Push-Location $workspaceFull + try { + $npmArgs = @('run', $TestScript, '--', '--coverage', '--coverageReporters=json-summary', '--coverageReporters=text') + if ($Silent) { + $null = & npm @npmArgs 2>&1 + } + else { + & npm @npmArgs + } + + if ($LASTEXITCODE -ne 0) { + return [PSCustomObject]@{ + Success = $false + Error = "npm run $TestScript failed with exit code $LASTEXITCODE" + } + } + } + finally { + Pop-Location + } + + $summaryPath = Join-Path $workspaceFull (Join-Path $CoverageDirectory 'coverage-summary.json') + if (-not (Test-Path $summaryPath -PathType Leaf)) { + return [PSCustomObject]@{ + Success = $false + Error = "Jest coverage summary not found at: $summaryPath" + } + } + + $summaryJson = Get-Content -LiteralPath $summaryPath -Raw -Encoding UTF8 | ConvertFrom-Json + $total = $summaryJson.total + if ($null -eq $total) { + return [PSCustomObject]@{ + Success = $false + Error = "Jest coverage summary is missing 'total' metrics in: $summaryPath" + } + } + + $lineRate = [math]::Round([double]$total.lines.pct, 1) + $branchRate = [math]::Round([double]$total.branches.pct, 1) + $methodRate = [math]::Round([double]$total.functions.pct, 1) + $totalMethods = [int]$total.functions.total + $coveredMethods = [int]$total.functions.covered + $resultsDirectory = [System.IO.Path]::GetFullPath((Join-Path $workspaceFull $CoverageDirectory)) + + if (-not $Silent) { + Write-TestRunnerLogInternal -Level 'OK' -Message "Coverage summary: $summaryPath" + } + + return [PSCustomObject]@{ + Success = $true + LineRate = $lineRate + BranchRate = $branchRate + MethodRate = $methodRate + TotalMethods = $totalMethods + CoveredMethods = $coveredMethods + CoverageSummaryFile = $summaryPath + ResultsDirectory = $resultsDirectory + } +} + +Export-ModuleMember -Function Invoke-TestsWithCoverage, Invoke-NpmJestTestsWithCoverage diff --git a/utils/Update-RepoUtils/scriptsettings.json b/utils/Update-RepoUtils/scriptsettings.json index 568a597..ed0142c 100644 --- a/utils/Update-RepoUtils/scriptsettings.json +++ b/utils/Update-RepoUtils/scriptsettings.json @@ -9,7 +9,8 @@ "preserveFileName": "scriptsettings.json", "cloneDepth": 1, "skippedRelativeDirectories": [ - "Release-Package/CustomPlugins" + "Release-Package/CustomPlugins", + "Run-Tests/CustomPlugins" ] } }