diff --git a/.github/actions/build-docs/action.yml b/.github/actions/build-docs/action.yml
index 6e217a04..f33f0ecf 100644
--- a/.github/actions/build-docs/action.yml
+++ b/.github/actions/build-docs/action.yml
@@ -11,7 +11,7 @@ inputs:
pnpm_version:
description: "pnpm version to install"
required: false
- default: "10.33.0"
+ default: "11.15.1"
pnpm_filters:
description: "Additional pnpm workspace filters passed to pnpm install"
required: false
diff --git a/.github/actions/node-install/action.yaml b/.github/actions/node-install/action.yaml
new file mode 100644
index 00000000..65809ffc
--- /dev/null
+++ b/.github/actions/node-install/action.yaml
@@ -0,0 +1,27 @@
+name: 'Node install and setup'
+description: 'Setup node with pnpm and authenticate github package repository'
+
+inputs:
+ node-version:
+ description: 'Node.js version'
+ required: true
+ pnpm-version:
+ description: 'pnpm version'
+ required: true
+ GITHUB_TOKEN:
+ description: "Token for access to github package registry"
+ required: true
+
+runs:
+ using: 'composite'
+ steps:
+ - name: 'Setup pnpm'
+ uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
+ with:
+ version: '${{ inputs.pnpm-version }}'
+
+ - name: 'Use Node.js'
+ uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
+ with:
+ node-version: '${{ inputs.node-version }}'
+ cache: 'pnpm'
diff --git a/.github/workflows/release_created.yaml b/.github/workflows/release_created.yaml
index aefb9916..70e04934 100644
--- a/.github/workflows/release_created.yaml
+++ b/.github/workflows/release_created.yaml
@@ -1,6 +1,16 @@
name: Github Release Created
on:
+ workflow_call:
+ inputs:
+ nodejs_version:
+ description: "Node.js version, set by the CI/CD pipeline workflow"
+ required: true
+ type: string
+ pnpm_version:
+ description: "pnpm version, set by the CI/CD pipeline workflow"
+ required: true
+ type: string
release:
types: ["published"] # Inherits all input defaults
@@ -9,6 +19,23 @@ concurrency:
cancel-in-progress: false
jobs:
+ metadata:
+ name: "Set CI/CD metadata"
+ runs-on: ubuntu-latest
+ timeout-minutes: 1
+ permissions:
+ contents: read
+ outputs:
+ nodejs_version: ${{ steps.variables.outputs.nodejs_version }}
+ pnpm_version: ${{ steps.variables.outputs.pnpm_version }}
+ steps:
+ - name: "Checkout code"
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ - name: "Set CI/CD variables"
+ id: variables
+ run: |
+ echo "nodejs_version=$(grep "^nodejs\s" .tool-versions | cut -f2 -d' ')" >> $GITHUB_OUTPUT
+ echo "pnpm_version=$(grep "^pnpm\s" .tool-versions | cut -f2 -d' ')" >> $GITHUB_OUTPUT
deploy-main:
name: Package and Publish Terraform Modules to GitHub Releases assets
runs-on: ubuntu-latest
@@ -20,6 +47,14 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ - name: Node install and setup
+ uses: ./.github/actions/node-install
+ with:
+ node-version: ${{ inputs.nodejs_version }}
+ pnpm-version: ${{ inputs.pnpm_version }}
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ - name: "Install dependencies"
+ run: pnpm install --frozen-lockfile
- name: "Package and Publish Terraform modules"
run: |
ARTIFACTS_DIR="$PWD/../../artifacts"
@@ -27,6 +62,10 @@ jobs:
cd infrastructure/terraform/modules
for module in */; do
module_name=${module%/}
+ if [ -f "$module_name/pre.sh" ]; then
+ echo "Running pre.sh for $module_name..."
+ (cd "$module_name" && bash pre.sh)
+ fi
echo "Zipping contents of $module_name..."
(cd "$module_name" && zip -r "$ARTIFACTS_DIR/terraform-${module_name}.zip" .)
echo "Publishing $module_name module..."
diff --git a/.github/workflows/stage-2-test.yaml b/.github/workflows/stage-2-test.yaml
index e4176dab..eff5c21b 100644
--- a/.github/workflows/stage-2-test.yaml
+++ b/.github/workflows/stage-2-test.yaml
@@ -40,43 +40,89 @@ jobs:
test-unit:
name: "Unit tests"
runs-on: ubuntu-latest
- timeout-minutes: 5
+ timeout-minutes: 15
+ permissions:
+ contents: read
+ packages: read
steps:
- name: "Checkout code"
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
+ - name: Node install and setup
+ uses: ./.github/actions/node-install
+ with:
+ node-version: ${{ inputs.nodejs_version }}
+ pnpm-version: ${{ inputs.pnpm_version }}
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ - name: "Install dependencies"
+ run: pnpm install --frozen-lockfile
- name: "Run unit test suite"
run: |
make test-unit
- name: "Save the result of fast test suite"
- run: |
- echo "Nothing to save"
+ uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
+ with:
+ name: unit-tests
+ path: |
+ src/**/.reports/unit/test-report.html
+ utils/**/.reports/unit/test-report.html
+ lambdas/**/.reports/unit/test-report.html
+ tests/**/.reports/unit/test-report.html
+ !**/node_modules/**
+ include-hidden-files: true
+ if: always()
+ - name: "Save the result of code coverage"
+ uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
+ with:
+ name: code-coverage-report
+ path: ".reports/lcov.info"
+ - name: "Save Python coverage reports"
+ uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
+ with:
+ name: python-coverage-reports
+ path: |
+ src/**/coverage.xml
+ utils/**/coverage.xml
+ lambdas/**/coverage.xml
test-lint:
name: "Linting"
runs-on: ubuntu-latest
- timeout-minutes: 5
+ timeout-minutes: 8
+ permissions:
+ contents: read
+ packages: read
steps:
- name: "Checkout code"
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
+ - name: Node install and setup
+ uses: ./.github/actions/node-install
+ with:
+ pnpm-version: ${{ inputs.pnpm_version }}
+ node-version: ${{ inputs.nodejs_version }}
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: "Run linting"
run: |
make test-lint
- - name: "Save the linting result"
- run: |
- echo "Nothing to save"
- test-coverage:
- name: "Test coverage"
- needs: [test-unit]
+ test-typecheck:
+ name: "Typecheck"
runs-on: ubuntu-latest
- timeout-minutes: 5
+ timeout-minutes: 6
+ permissions:
+ contents: read
+ packages: read
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- name: "Checkout code"
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- - name: "Run test coverage check"
- run: |
- make test-coverage
- - name: "Save the coverage check result"
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
+ - name: Node install and setup
+ uses: ./.github/actions/node-install
+ with:
+ pnpm-version: ${{ inputs.pnpm_version }}
+ node-version: ${{ inputs.nodejs_version }}
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ - name: "Run typecheck"
run: |
- echo "Nothing to save"
+ make test-typecheck
perform-static-analysis:
name: "Perform static analysis"
needs: [test-unit]
diff --git a/.gitignore b/.gitignore
index e97bd341..84771e91 100644
--- a/.gitignore
+++ b/.gitignore
@@ -22,3 +22,4 @@ node_modules
dist
.DS_Store
.reports
+.turbo
diff --git a/.tool-versions b/.tool-versions
index eb24f4c8..909a5ffd 100644
--- a/.tool-versions
+++ b/.tool-versions
@@ -2,8 +2,8 @@ act 0.2.64
gitleaks 8.24.0
jq 1.6
nodejs 22.15.1
-pnpm 10.33.0
pre-commit 3.6.0
+pnpm 11.15.1
terraform 1.10.1
terraform-docs 0.19.0
trivy 0.69.2
diff --git a/Makefile b/Makefile
index ea70e95e..573e1b98 100644
--- a/Makefile
+++ b/Makefile
@@ -8,7 +8,7 @@ include scripts/init.mk
# Example CI/CD targets are: dependencies, build, publish, deploy, clean, etc.
dependencies: # Install dependencies needed to build and test the project @Pipeline
- # TODO: Implement installation of your project dependencies
+ pnpm install
build: # Build the project artefact @Pipeline
(cd docs && make build)
@@ -21,6 +21,7 @@ deploy: # Deploy the project artefact to the target environment @Pipeline
clean:: # Clean-up project resources (main) @Operations
rm -f .version
+ pnpm run clean
# TODO: Implement project resources clean-up step
config:: _install-dependencies version # Configure development environment (main) @Configuration
@@ -30,6 +31,7 @@ version:
rm -f .version
make version-create-effective-file dir=.
echo "{ \"schemaVersion\": 1, \"label\": \"version\", \"message\": \"$$(head -n 1 .version 2> /dev/null || echo unknown)\", \"color\": \"orange\" }" > version.json
+
# ==============================================================================
${VERBOSE}.SILENT: \
diff --git a/eslint.config.mjs b/eslint.config.mjs
new file mode 100644
index 00000000..228b0b0e
--- /dev/null
+++ b/eslint.config.mjs
@@ -0,0 +1,261 @@
+import jest from 'eslint-plugin-jest';
+import jsxA11y from 'eslint-plugin-jsx-a11y';
+import prettierRecommended from 'eslint-plugin-prettier/recommended';
+import { importX } from 'eslint-plugin-import-x';
+import * as eslintImportResolverTypescript from 'eslint-import-resolver-typescript';
+import noRelativeImportPaths from 'eslint-plugin-no-relative-import-paths';
+import react from 'eslint-plugin-react';
+import security from 'eslint-plugin-security';
+import sonarjs from 'eslint-plugin-sonarjs';
+import json from 'eslint-plugin-json';
+import unicorn from 'eslint-plugin-unicorn';
+import { defineConfig, globalIgnores } from 'eslint/config';
+import js from '@eslint/js';
+import html from 'eslint-plugin-html';
+import tseslint from 'typescript-eslint';
+import sortDestructureKeys from 'eslint-plugin-sort-destructure-keys';
+import {
+ configs as airbnbConfigs,
+ plugins as airbnbPlugins,
+} from 'eslint-config-airbnb-extended';
+import { rules as prettierConfigRules } from 'eslint-config-prettier';
+
+export default defineConfig([
+ globalIgnores([
+ '**/*/coverage/*',
+ '**/.build',
+ '**/node_modules',
+ '**/dist',
+ '**/test-results',
+ '**/playwright-report*',
+ 'eslint.config.mjs',
+ ]),
+
+ //imports
+ importX.flatConfigs.recommended,
+ { rules: { ...airbnbPlugins.importX.rules } },
+
+ // js
+ js.configs.recommended,
+ airbnbPlugins.stylistic,
+ airbnbConfigs.base.recommended,
+
+ // ts
+ tseslint.configs.strictTypeChecked,
+ tseslint.configs.stylisticTypeChecked,
+ airbnbConfigs.base.typescript,
+ airbnbPlugins.typescriptEslint,
+
+ {
+ ignores: ['**/*.json'],
+ languageOptions: {
+ parserOptions: {
+ projectService: true,
+ tsconfigRootDir: import.meta.dirname,
+ },
+ },
+ },
+
+ {
+ files: ['**/*.json'],
+ extends: [tseslint.configs.disableTypeChecked],
+ },
+
+ {
+ settings: {
+ 'import-x/resolver-next': [
+ eslintImportResolverTypescript.createTypeScriptImportResolver({
+ project: [
+ 'src/lambdas/*/tsconfig.json',
+ 'src/utils/tsconfig.json',
+ ],
+ }),
+ ],
+ },
+ },
+
+ {
+ rules: {
+ '@typescript-eslint/no-unused-vars': [
+ 2,
+ {
+ argsIgnorePattern: '^_',
+ varsIgnorePattern: '^_',
+ },
+ ],
+ '@typescript-eslint/consistent-type-definitions': 0,
+ },
+ },
+
+ // unicorn
+ unicorn.configs['recommended'],
+ {
+ rules: {
+ 'unicorn/prevent-abbreviations': 0,
+ 'unicorn/filename-case': [
+ 2,
+ {
+ case: 'kebabCase',
+ ignore: ['.tsx'],
+ },
+ ],
+ 'unicorn/no-null': 0,
+ 'unicorn/prefer-module': 0,
+ 'unicorn/import-style': [
+ 2,
+ {
+ styles: {
+ path: {
+ named: true,
+ },
+ },
+ },
+ ],
+ },
+ },
+
+ // react
+ react.configs.flat.recommended,
+ airbnbConfigs.react.recommended,
+ airbnbConfigs.react.typescript,
+ airbnbPlugins.react,
+ airbnbPlugins.reactHooks,
+ airbnbPlugins.reactA11y,
+
+ // jest
+ jest.configs['flat/recommended'],
+
+ // prettier
+ prettierRecommended,
+ { rules: { ...prettierConfigRules, 'prettier/prettier': ['error', { singleQuote: true }] } },
+
+ // jsxA11y
+ {
+ files: ['**/*.{js,mjs,cjs,jsx,mjsx,ts,tsx,mtsx}'],
+ plugins: {
+ 'jsx-a11y': jsxA11y,
+ },
+ languageOptions: {
+ parserOptions: {
+ ecmaFeatures: {
+ jsx: true,
+ },
+ },
+ },
+ },
+
+ // security
+ security.configs.recommended,
+
+ // sonar
+ sonarjs.configs.recommended,
+
+ // html
+ {
+ files: ['**/*.html'],
+ plugins: { html },
+ },
+
+ // json
+ {
+ files: ['**/*.json'],
+ ...json.configs['recommended'],
+ },
+
+ // destructure sorting
+ {
+ name: 'eslint-plugin-sort-destructure-keys',
+ plugins: {
+ 'sort-destructure-keys': sortDestructureKeys,
+ },
+ rules: {
+ 'sort-destructure-keys/sort-destructure-keys': 2,
+ },
+ },
+
+ // imports
+ {
+ rules: {
+ 'sort-imports': [
+ 2,
+ {
+ ignoreDeclarationSort: true,
+ },
+ ],
+ 'import-x/extensions': 0,
+ },
+ },
+ {
+ files: ['**/*.ts', '**/*.tsx'],
+ rules: {
+ 'import-x/no-unresolved': 0, // trust the typescript compiler to catch unresolved imports
+ },
+ },
+ {
+ files: ['tests/test-team/**'],
+ rules: {
+ 'import-x/no-extraneous-dependencies': [
+ 2,
+ {
+ devDependencies: true,
+ },
+ ],
+ },
+ },
+ {
+ files: ['**/utils/**', 'tests/test-team/**'],
+ rules: {
+ 'import-x/prefer-default-export': 0,
+ },
+ },
+ {
+ plugins: {
+ 'no-relative-import-paths': noRelativeImportPaths,
+ },
+ rules: {
+ 'no-relative-import-paths/no-relative-import-paths': 2,
+ },
+ },
+ {
+ files: ['src/utils/**', '**/jest.config.ts'],
+ rules: {
+ 'no-relative-import-paths/no-relative-import-paths': 0,
+ 'import-x/no-relative-packages': 0,
+ },
+ },
+ {
+ files: ['tests/pact-tests/**'],
+ rules: {
+ 'no-relative-import-paths/no-relative-import-paths': 0,
+ },
+ },
+ {
+ files: ['scripts/**'],
+ rules: {
+ 'import-x/no-extraneous-dependencies': [
+ 'error',
+ { devDependencies: true },
+ ],
+ },
+ },
+
+ // test files - allow 'as any' for explicit type coercion in mocks
+ {
+ files: ['**/__tests__/**', '**/*.test.ts', '**/*.test.tsx', '**/*.spec.ts'],
+ rules: {
+ '@typescript-eslint/no-unnecessary-type-assertion': 0,
+ },
+ },
+
+ // misc rule overrides
+ {
+ rules: {
+ 'no-restricted-syntax': 0,
+ 'no-underscore-dangle': 0,
+ 'no-await-in-loop': 0,
+ 'no-plusplus': [2, { allowForLoopAfterthoughts: true }],
+ 'unicorn/prefer-top-level-await': 0, // top level await is not available in commonjs
+ 'import-x/prefer-default-export': "off"
+ },
+ },
+]);
diff --git a/infrastructure/terraform/modules/apim-authentication/README.md b/infrastructure/terraform/modules/apim-authentication/README.md
new file mode 100644
index 00000000..aa72f1fc
--- /dev/null
+++ b/infrastructure/terraform/modules/apim-authentication/README.md
@@ -0,0 +1,46 @@
+
+
+
+
+
+## Requirements
+
+| Name | Version |
+|------|---------|
+| [terraform](#requirement\_terraform) | >= 1.9.0 |
+
+## Inputs
+
+| Name | Description | Type | Default | Required |
+|------|-------------|------|---------|:--------:|
+| [apim\_auth\_token\_schedule](#input\_apim\_auth\_token\_schedule) | Schedule to renew the APIM auth token | `string` | `"rate(9 minutes)"` | no |
+| [apim\_auth\_token\_url](#input\_apim\_auth\_token\_url) | URL to generate an APIM auth token | `string` | n/a | yes |
+| [apim\_keygen\_schedule](#input\_apim\_keygen\_schedule) | Schedule to refresh key pairs if necessary | `string` | `"cron(0 14 * * ? *)"` | no |
+| [aws\_account\_id](#input\_aws\_account\_id) | The AWS Account ID (numeric) | `string` | n/a | yes |
+| [component](#input\_component) | The name of the terraformscaffold component calling this module | `string` | n/a | yes |
+| [default\_tags](#input\_default\_tags) | A map of default tags to apply to all taggable resources within the component | `map(string)` | `{}` | no |
+| [environment](#input\_environment) | The name of the terraformscaffold environment the module is called for | `string` | n/a | yes |
+| [force\_destroy](#input\_force\_destroy) | Flag to force deletion of S3 buckets | `bool` | `false` | no |
+| [force\_lambda\_code\_deploy](#input\_force\_lambda\_code\_deploy) | If the lambda package in s3 has the same commit id tag as the terraform build branch, the lambda will not update automatically. Set to True if making changes to Lambda code from on the same commit for example during development | `bool` | `false` | no |
+| [group](#input\_group) | The name of the tfscaffold group | `string` | `null` | no |
+| [kms\_key\_arn](#input\_kms\_key\_arn) | KMS key arn to use for this function | `string` | n/a | yes |
+| [lambda\_timeout\_seconds](#input\_lambda\_timeout\_seconds) | The timeout of the lambdas that are triggered by SQS. | `string` | `"45"` | no |
+| [log\_level](#input\_log\_level) | The log level to be used in lambda functions within the component. Any log with a lower severity than the configured value will not be logged: https://docs.python.org/3/library/logging.html#levels | `string` | `"INFO"` | no |
+| [log\_retention\_in\_days](#input\_log\_retention\_in\_days) | The retention period in days for the Cloudwatch Logs events to be retained, default of 0 is indefinite | `number` | `0` | no |
+| [name](#input\_name) | A unique name to distinguish this module invocation from others within the same CSI scope | `string` | n/a | yes |
+| [parent\_acct\_environment](#input\_parent\_acct\_environment) | Name of the environment responsible for the acct resources used, affects things like DNS zone. Useful for named dev environments | `string` | `"main"` | no |
+| [project](#input\_project) | The name of the terraformscaffold project calling the module | `string` | n/a | yes |
+| [region](#input\_region) | The AWS Region | `string` | n/a | yes |
+| [root\_domain\_id](#input\_root\_domain\_id) | Root domain ID to host the APIM public key | `string` | n/a | yes |
+| [root\_domain\_name](#input\_root\_domain\_name) | Root domain name to host the APIM public key | `string` | n/a | yes |
+| [shared\_infra\_account\_id](#input\_shared\_infra\_account\_id) | The AWS Shared Infra Account ID (numeric) | `string` | n/a | yes |
+
+## Outputs
+
+| Name | Description |
+|------|-------------|
+| [apim\_access\_token\_ssm\_parameter](#output\_apim\_access\_token\_ssm\_parameter) | APIM Access Token SSM parameter details |
+
+
+
+
diff --git a/infrastructure/terraform/modules/apim-authentication/acm_certificate_static_assets_hosting.tf b/infrastructure/terraform/modules/apim-authentication/acm_certificate_static_assets_hosting.tf
new file mode 100644
index 00000000..8d348b6d
--- /dev/null
+++ b/infrastructure/terraform/modules/apim-authentication/acm_certificate_static_assets_hosting.tf
@@ -0,0 +1,14 @@
+resource "aws_acm_certificate" "static_assets_hosting" {
+ provider = aws.us-east-1
+ domain_name = var.root_domain_name
+ validation_method = "DNS"
+
+ lifecycle {
+ create_before_destroy = true
+ }
+}
+
+resource "aws_acm_certificate_validation" "static_assets_hosting" {
+ provider = aws.us-east-1
+ certificate_arn = aws_acm_certificate.static_assets_hosting.arn
+}
diff --git a/infrastructure/terraform/modules/apim-authentication/cloudfront_distribution_static_assets_hosting.tf b/infrastructure/terraform/modules/apim-authentication/cloudfront_distribution_static_assets_hosting.tf
new file mode 100644
index 00000000..4c72d1ea
--- /dev/null
+++ b/infrastructure/terraform/modules/apim-authentication/cloudfront_distribution_static_assets_hosting.tf
@@ -0,0 +1,50 @@
+resource "aws_cloudfront_distribution" "static_assets_hosting" {
+ enabled = true
+ is_ipv6_enabled = true
+ comment = "Static asset hosting for APIM Public Key"
+ price_class = "PriceClass_100"
+
+ restrictions {
+ geo_restriction {
+ restriction_type = "whitelist"
+ locations = ["GB"]
+ }
+ }
+
+ aliases = [var.root_domain_name]
+
+ viewer_certificate {
+ cloudfront_default_certificate = false
+ acm_certificate_arn = aws_acm_certificate.static_assets_hosting.arn
+ minimum_protocol_version = "TLSv1.2_2021"
+ ssl_support_method = "sni-only"
+ }
+
+ origin {
+ domain_name = module.s3bucket_static_assets.bucket_regional_domain_name
+ origin_id = "${local.csi}-origin-static-assets"
+ s3_origin_config {
+ origin_access_identity = aws_cloudfront_origin_access_identity.static_assets.cloudfront_access_identity_path
+ }
+ }
+
+ default_cache_behavior {
+ allowed_methods = ["GET", "HEAD"]
+ cached_methods = ["GET", "HEAD"]
+ target_origin_id = "${local.csi}-origin-static-assets"
+
+ forwarded_values {
+ query_string = false
+ headers = ["Origin"]
+ cookies {
+ forward = "none"
+ }
+ }
+
+ viewer_protocol_policy = "redirect-to-https"
+ min_ttl = 0
+ default_ttl = 0
+ max_ttl = 86400
+ compress = true
+ }
+}
diff --git a/infrastructure/terraform/modules/apim-authentication/lambda_apim_access_token_refresher.tf b/infrastructure/terraform/modules/apim-authentication/lambda_apim_access_token_refresher.tf
new file mode 100644
index 00000000..665d5dd3
--- /dev/null
+++ b/infrastructure/terraform/modules/apim-authentication/lambda_apim_access_token_refresher.tf
@@ -0,0 +1,64 @@
+module "lambda_lambda_apim_refresh_token" {
+ source = "https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/5.0.7/terraform-lambda.zip"
+
+ function_name = "apim-access-token-refresher"
+ description = "A function to generate APIM access tokens"
+
+ aws_account_id = var.aws_account_id
+ component = var.component
+ environment = var.environment
+ project = var.project
+ region = var.region
+ group = var.group
+
+ log_retention_in_days = var.log_retention_in_days
+ kms_key_arn = var.kms_key_arn
+
+ iam_policy_document = {
+ body = data.aws_iam_policy_document.apim_access_token_refresher.json
+ }
+
+ function_s3_bucket = local.acct.s3_buckets["lambda_function_artefacts"]["id"]
+ function_code_base_path = "${path.module}/dist"
+ function_code_dir = "apim-access-token-refresher"
+ function_include_common = true
+ handler_function_name = "handler"
+ runtime = "nodejs22.x"
+ memory = 256
+ timeout = var.lambda_timeout_seconds
+ log_level = var.log_level
+ schedule = var.apim_auth_token_schedule
+
+ force_lambda_code_deploy = var.force_lambda_code_deploy
+ enable_lambda_insights = false
+
+ log_destination_arn = local.log_destination_arn
+ log_subscription_role_arn = local.acct.log_subscription_role_arn
+
+ lambda_env_vars = {
+ APIM_AUTH_TOKEN_URL = var.apim_auth_token_url
+ APIM_ACCESS_TOKEN_SSM_PARAMETER_NAME = local.apim_access_token_ssm_parameter_name
+ APIM_API_KEY_SSM_PARAMETER_NAME = local.apim_api_key_ssm_parameter_name
+ APIM_PRIVATE_KEY_SSM_PARAMETER_NAME = local.apim_private_key_ssm_parameter_name
+ ENVIRONMENT = var.environment
+ }
+}
+
+data "aws_iam_policy_document" "apim_access_token_refresher" {
+ statement {
+ sid = "AllowSSMParam"
+ effect = "Allow"
+
+ actions = [
+ "ssm:DeleteParameter",
+ "ssm:GetParameter",
+ "ssm:GetParameters",
+ "ssm:GetParametersByPath",
+ "ssm:PutParameter",
+ ]
+
+ resources = [
+ "arn:aws:ssm:${var.region}:${var.aws_account_id}:parameter/${var.component}/${var.environment}/apim/*"
+ ]
+ }
+}
diff --git a/infrastructure/terraform/modules/apim-authentication/lambda_apim_key_generator.tf b/infrastructure/terraform/modules/apim-authentication/lambda_apim_key_generator.tf
new file mode 100644
index 00000000..93756848
--- /dev/null
+++ b/infrastructure/terraform/modules/apim-authentication/lambda_apim_key_generator.tf
@@ -0,0 +1,77 @@
+module "lambda_apim_key_generation" {
+ source = "https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/5.0.7/terraform-lambda.zip"
+
+ function_name = "apim-key-generator"
+ description = "A function to generate APIM public and private keys"
+
+ aws_account_id = var.aws_account_id
+ component = var.component
+ environment = var.environment
+ project = var.project
+ region = var.region
+ group = var.group
+
+ log_retention_in_days = var.log_retention_in_days
+ kms_key_arn = var.kms_key_arn
+
+ iam_policy_document = {
+ body = data.aws_iam_policy_document.lambda_apim_key_generator.json
+ }
+
+ function_s3_bucket = local.acct.s3_buckets["lambda_function_artefacts"]["id"]
+ function_code_base_path = "${path.module}/dist"
+ function_code_dir = "apim-key-generator"
+ function_include_common = true
+ function_module_name = "lambda"
+ handler_function_name = "handler"
+ runtime = "nodejs22.x"
+ memory = 512
+ timeout = 300
+ log_level = var.log_level
+ schedule = var.apim_keygen_schedule
+
+ force_lambda_code_deploy = var.force_lambda_code_deploy
+ enable_lambda_insights = false
+
+ log_destination_arn = local.log_destination_arn
+ log_subscription_role_arn = local.acct.log_subscription_role_arn
+
+ lambda_env_vars = {
+ SSM_PRIVATE_KEY_PARAMETER_NAME = local.apim_private_key_ssm_parameter_name
+ KEYSTORE_S3_BUCKET = local.apim_keystore_s3_bucket
+ ENVIRONMENT = var.environment
+ }
+}
+
+data "aws_iam_policy_document" "lambda_apim_key_generator" {
+ statement {
+ sid = "AllowS3List"
+ effect = "Allow"
+
+ actions = [
+ "s3:ListBucket",
+ "s3:PutObject"
+ ]
+
+ resources = [
+ "arn:aws:s3:::${local.apim_keystore_s3_bucket}/*"
+ ]
+ }
+
+ statement {
+ sid = "AllowSSMParam"
+ effect = "Allow"
+
+ actions = [
+ "ssm:DeleteParameter",
+ "ssm:GetParameter",
+ "ssm:GetParameters",
+ "ssm:GetParametersByPath",
+ "ssm:PutParameter",
+ ]
+
+ resources = [
+ "arn:aws:ssm:${var.region}:${var.aws_account_id}:parameter/${var.component}/${var.environment}/apim/*"
+ ]
+ }
+}
diff --git a/infrastructure/terraform/modules/apim-authentication/locals.tf b/infrastructure/terraform/modules/apim-authentication/locals.tf
new file mode 100644
index 00000000..6ced5b90
--- /dev/null
+++ b/infrastructure/terraform/modules/apim-authentication/locals.tf
@@ -0,0 +1,28 @@
+locals {
+ module = "apim-authentication"
+
+ csi = replace(
+ format(
+ "%s-%s-%s-%s",
+ var.project,
+ var.environment,
+ var.component,
+ var.name,
+ ),
+ "_",
+ "",
+ )
+ default_tags = merge(
+ var.default_tags,
+ {
+ Module = local.module
+ Name = local.csi
+ },
+ )
+ log_destination_arn = "arn:aws:logs:${var.region}:${var.shared_infra_account_id}:destination:nhs-main-obs-firehose-logs"
+ apim_access_token_ssm_parameter_name = "/${var.component}/${var.environment}/apim/access_token"
+ apim_api_key_ssm_parameter_name = "/${var.component}/${var.environment}/apim/api_key"
+ apim_keystore_s3_bucket = "nhs-${var.aws_account_id}-${var.region}-${var.environment}-${var.component}-static-assets"
+ apim_private_key_ssm_parameter_name = "/${var.component}/${var.environment}/apim/private_key"
+
+}
diff --git a/infrastructure/terraform/modules/apim-authentication/locals_remote_state.tf b/infrastructure/terraform/modules/apim-authentication/locals_remote_state.tf
new file mode 100644
index 00000000..7f87c1fa
--- /dev/null
+++ b/infrastructure/terraform/modules/apim-authentication/locals_remote_state.tf
@@ -0,0 +1,40 @@
+locals {
+ bootstrap = data.terraform_remote_state.bootstrap.outputs
+ acct = data.terraform_remote_state.acct.outputs
+}
+
+data "terraform_remote_state" "bootstrap" {
+ backend = "s3"
+
+ config = {
+ bucket = local.terraform_state_bucket
+
+ key = format(
+ "%s/%s/%s/%s/bootstrap.tfstate",
+ var.project,
+ var.aws_account_id,
+ "eu-west-2",
+ "bootstrap"
+ )
+
+ region = "eu-west-2"
+ }
+}
+
+data "terraform_remote_state" "acct" {
+ backend = "s3"
+
+ config = {
+ bucket = local.terraform_state_bucket
+
+ key = format(
+ "%s/%s/%s/%s/acct.tfstate",
+ var.project,
+ var.aws_account_id,
+ "eu-west-2",
+ var.parent_acct_environment
+ )
+
+ region = "eu-west-2"
+ }
+}
diff --git a/infrastructure/terraform/modules/apim-authentication/locals_tfscaffold.tf b/infrastructure/terraform/modules/apim-authentication/locals_tfscaffold.tf
new file mode 100644
index 00000000..ccfd79c9
--- /dev/null
+++ b/infrastructure/terraform/modules/apim-authentication/locals_tfscaffold.tf
@@ -0,0 +1,24 @@
+locals {
+ component = "dl"
+
+ terraform_state_bucket = format(
+ "%s-tfscaffold-%s-%s",
+ var.project,
+ var.aws_account_id,
+ var.region,
+ )
+
+ # CSI for use in resources with a global namespace, i.e. S3 Buckets
+ csi_global = replace(
+ format(
+ "%s-%s-%s-%s-%s",
+ var.project,
+ var.aws_account_id,
+ var.region,
+ var.environment,
+ local.component,
+ ),
+ "_",
+ "",
+ )
+}
diff --git a/infrastructure/terraform/modules/apim-authentication/outputs.tf b/infrastructure/terraform/modules/apim-authentication/outputs.tf
new file mode 100644
index 00000000..c9c31a2d
--- /dev/null
+++ b/infrastructure/terraform/modules/apim-authentication/outputs.tf
@@ -0,0 +1,7 @@
+output "apim_access_token_ssm_parameter" {
+ description = "APIM Access Token SSM parameter details"
+ value = {
+ name = aws_ssm_parameter.access_token.name
+ arn = aws_ssm_parameter.access_token.arn
+ }
+}
diff --git a/infrastructure/terraform/modules/apim-authentication/pre.sh b/infrastructure/terraform/modules/apim-authentication/pre.sh
new file mode 100755
index 00000000..60bbd73b
--- /dev/null
+++ b/infrastructure/terraform/modules/apim-authentication/pre.sh
@@ -0,0 +1,15 @@
+#!/bin/bash
+
+# This script is run before the module is packaged into a zip archive.
+# It builds the lambda functions and copies the distribution files to the module directory.
+
+echo "Running Pre.sh"
+
+ROOT_DIR="$(git rev-parse --show-toplevel)"
+
+(cd "$ROOT_DIR" && pnpm -r --filter "./src/lambdas/apim*" run --if-present lambda-build)
+
+# move distribution files to the module directory so that they can be zipped as part of a release
+mkdir dist || true
+cp -r "$ROOT_DIR/src/lambdas/apim-access-token-refresher/dist" dist/apim-access-token-refresher
+cp -r "$ROOT_DIR/src/lambdas/apim-key-generator/dist" dist/apim-key-generator
diff --git a/infrastructure/terraform/modules/apim-authentication/provider_aws.tf b/infrastructure/terraform/modules/apim-authentication/provider_aws.tf
new file mode 100644
index 00000000..d694811e
--- /dev/null
+++ b/infrastructure/terraform/modules/apim-authentication/provider_aws.tf
@@ -0,0 +1,24 @@
+provider "aws" {
+ region = var.region
+
+ allowed_account_ids = [
+ var.aws_account_id,
+ ]
+
+ default_tags {
+ tags = local.default_tags
+ }
+}
+
+provider "aws" {
+ alias = "us-east-1"
+ region = "us-east-1"
+
+ default_tags {
+ tags = local.default_tags
+ }
+
+ allowed_account_ids = [
+ var.aws_account_id,
+ ]
+}
diff --git a/infrastructure/terraform/modules/apim-authentication/route53_record_acm_validation.tf b/infrastructure/terraform/modules/apim-authentication/route53_record_acm_validation.tf
new file mode 100644
index 00000000..4ebd052b
--- /dev/null
+++ b/infrastructure/terraform/modules/apim-authentication/route53_record_acm_validation.tf
@@ -0,0 +1,17 @@
+resource "aws_route53_record" "acm_validation" {
+ for_each = {
+ for dvo in aws_acm_certificate.static_assets_hosting.domain_validation_options :
+ dvo.domain_name => {
+ name = dvo.resource_record_name
+ record = dvo.resource_record_value
+ type = dvo.resource_record_type
+ } if dvo.domain_name == var.root_domain_name
+ }
+
+ allow_overwrite = true
+ name = each.value.name
+ records = [each.value.record]
+ type = each.value.type
+ zone_id = var.root_domain_id
+ ttl = 60
+}
diff --git a/infrastructure/terraform/modules/apim-authentication/route53_record_static_assets_hosting.tf b/infrastructure/terraform/modules/apim-authentication/route53_record_static_assets_hosting.tf
new file mode 100644
index 00000000..030d3f98
--- /dev/null
+++ b/infrastructure/terraform/modules/apim-authentication/route53_record_static_assets_hosting.tf
@@ -0,0 +1,7 @@
+resource "aws_route53_record" "static_assets_hosting" {
+ name = var.root_domain_name
+ zone_id = var.root_domain_id
+ type = "CNAME"
+ ttl = 5
+ records = [aws_cloudfront_distribution.static_assets_hosting.domain_name]
+}
diff --git a/infrastructure/terraform/modules/apim-authentication/s3_bucket_static_assets.tf b/infrastructure/terraform/modules/apim-authentication/s3_bucket_static_assets.tf
new file mode 100644
index 00000000..1ff9e1a2
--- /dev/null
+++ b/infrastructure/terraform/modules/apim-authentication/s3_bucket_static_assets.tf
@@ -0,0 +1,117 @@
+module "s3bucket_static_assets" {
+ source = "https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/5.0.7/terraform-s3bucket.zip"
+
+ name = "static-assets"
+
+ aws_account_id = var.aws_account_id
+ region = "eu-west-2"
+ project = var.project
+ environment = var.environment
+ component = var.component
+
+ acl = "private"
+ force_destroy = var.force_destroy
+ versioning = true
+
+ lifecycle_rules = [
+ {
+ enabled = true
+
+ noncurrent_version_transition = [
+ {
+ noncurrent_days = "30"
+ storage_class = "STANDARD_IA"
+ }
+ ]
+
+ noncurrent_version_expiration = {
+ noncurrent_days = "90"
+ }
+
+ abort_incomplete_multipart_upload = {
+ days = "1"
+ }
+ }
+ ]
+
+ bucket_logging_target = {
+ bucket = local.acct.s3_buckets["access_logs"]["id"]
+ }
+
+ policy_documents = [
+ data.aws_iam_policy_document.static_assets_bucket_policy.json
+ ]
+
+ public_access = {
+ block_public_acls = true
+ block_public_policy = true
+ ignore_public_acls = true
+ restrict_public_buckets = true
+ }
+
+ default_tags = var.default_tags
+}
+
+data "aws_iam_policy_document" "static_assets_bucket_policy" {
+ statement {
+ actions = ["s3:GetObject"]
+ resources = [
+ "${module.s3bucket_static_assets.arn}/*"
+ ]
+
+ principals {
+ type = "AWS"
+ identifiers = [aws_cloudfront_origin_access_identity.static_assets.iam_arn]
+ }
+ }
+
+ statement {
+ actions = ["s3:ListBucket"]
+ resources = [
+ module.s3bucket_static_assets.arn
+ ]
+
+ principals {
+ type = "AWS"
+ identifiers = [aws_cloudfront_origin_access_identity.static_assets.iam_arn]
+ }
+ }
+
+ statement {
+ effect = "Deny"
+ actions = ["s3:*"]
+ resources = [
+ module.s3bucket_static_assets.arn,
+ "${module.s3bucket_static_assets.arn}/*",
+ ]
+
+ principals {
+ type = "AWS"
+ identifiers = ["*"]
+ }
+
+ condition {
+ test = "Bool"
+ variable = "aws:SecureTransport"
+ values = [
+ false
+ ]
+ }
+ }
+}
+
+resource "aws_s3_bucket_cors_configuration" "static_assets" {
+ bucket = module.s3bucket_static_assets.bucket
+
+ cors_rule {
+ allowed_headers = ["Authorization"]
+ allowed_methods = ["GET"]
+ allowed_origins = ["*"]
+ expose_headers = ["ETag"]
+ max_age_seconds = 300
+ }
+}
+
+resource "aws_cloudfront_origin_access_identity" "static_assets" {
+ comment = "Used to access the s3 content for the ${module.s3bucket_static_assets.bucket} bucket"
+}
diff --git a/infrastructure/terraform/modules/apim-authentication/ssm_parameter_access_token.tf b/infrastructure/terraform/modules/apim-authentication/ssm_parameter_access_token.tf
new file mode 100644
index 00000000..8bc51498
--- /dev/null
+++ b/infrastructure/terraform/modules/apim-authentication/ssm_parameter_access_token.tf
@@ -0,0 +1,14 @@
+resource "aws_ssm_parameter" "access_token" {
+ name = local.apim_access_token_ssm_parameter_name
+ description = "Access token for APIM"
+ type = "SecureString"
+ value = jsonencode({})
+
+ tags = merge(local.default_tags, { Backup = "true" })
+
+ lifecycle {
+ ignore_changes = [
+ value
+ ]
+ }
+}
diff --git a/infrastructure/terraform/modules/apim-authentication/ssm_parameter_api_key.tf b/infrastructure/terraform/modules/apim-authentication/ssm_parameter_api_key.tf
new file mode 100644
index 00000000..fe3bcc8e
--- /dev/null
+++ b/infrastructure/terraform/modules/apim-authentication/ssm_parameter_api_key.tf
@@ -0,0 +1,13 @@
+resource "aws_ssm_parameter" "api_key" {
+ name = local.apim_api_key_ssm_parameter_name
+ description = "API Key for APIM"
+ type = "SecureString"
+ value = "unset"
+ tags = merge(local.default_tags, { Backup = "true" })
+
+ lifecycle {
+ ignore_changes = [
+ value
+ ]
+ }
+}
diff --git a/infrastructure/terraform/modules/apim-authentication/variables.tf b/infrastructure/terraform/modules/apim-authentication/variables.tf
new file mode 100644
index 00000000..d64fc752
--- /dev/null
+++ b/infrastructure/terraform/modules/apim-authentication/variables.tf
@@ -0,0 +1,127 @@
+##
+# Basic inherited variables for terraformscaffold modules
+##
+
+variable "project" {
+ type = string
+ description = "The name of the terraformscaffold project calling the module"
+}
+
+variable "environment" {
+ type = string
+ description = "The name of the terraformscaffold environment the module is called for"
+}
+
+variable "component" {
+ type = string
+ description = "The name of the terraformscaffold component calling this module"
+}
+
+variable "aws_account_id" {
+ type = string
+ description = "The AWS Account ID (numeric)"
+}
+
+variable "group" {
+ type = string
+ description = "The name of the tfscaffold group"
+ default = null
+}
+
+variable "region" {
+ type = string
+ description = "The AWS Region"
+}
+
+variable "default_tags" {
+ type = map(string)
+ description = "A map of default tags to apply to all taggable resources within the component"
+ default = {}
+}
+
+##
+# Variable specific to the module
+##
+
+variable "log_retention_in_days" {
+ type = number
+ description = "The retention period in days for the Cloudwatch Logs events to be retained, default of 0 is indefinite"
+ default = 0
+}
+
+variable "kms_key_arn" {
+ type = string
+ description = "KMS key arn to use for this function"
+}
+
+variable "log_level" {
+ type = string
+ description = "The log level to be used in lambda functions within the component. Any log with a lower severity than the configured value will not be logged: https://docs.python.org/3/library/logging.html#levels"
+ default = "INFO"
+}
+
+variable "apim_keygen_schedule" {
+ type = string
+ description = "Schedule to refresh key pairs if necessary"
+ default = "cron(0 14 * * ? *)"
+}
+
+variable "apim_auth_token_schedule" {
+ type = string
+ description = "Schedule to renew the APIM auth token"
+ default = "rate(9 minutes)"
+}
+
+variable "force_lambda_code_deploy" {
+ type = bool
+ description = "If the lambda package in s3 has the same commit id tag as the terraform build branch, the lambda will not update automatically. Set to True if making changes to Lambda code from on the same commit for example during development"
+ default = false
+}
+
+variable "lambda_timeout_seconds" {
+ type = string
+ description = "The timeout of the lambdas that are triggered by SQS. "
+ default = "45"
+}
+
+variable "apim_auth_token_url" {
+ type = string
+ description = "URL to generate an APIM auth token"
+}
+
+variable "name" {
+ type = string
+ description = "A unique name to distinguish this module invocation from others within the same CSI scope"
+}
+
+variable "shared_infra_account_id" {
+ type = string
+ description = "The AWS Shared Infra Account ID (numeric)"
+}
+
+variable "force_destroy" {
+ type = bool
+ description = "Flag to force deletion of S3 buckets"
+ default = false
+
+ validation {
+ condition = !(var.force_destroy && var.environment == "prod")
+ error_message = "force_destroy must not be set to true when environment is 'prod'."
+ }
+}
+
+variable "parent_acct_environment" {
+ type = string
+ description = "Name of the environment responsible for the acct resources used, affects things like DNS zone. Useful for named dev environments"
+ default = "main"
+}
+
+variable "root_domain_id" {
+ type = string
+ description = "Root domain ID to host the APIM public key"
+}
+
+variable "root_domain_name" {
+ type = string
+ description = "Root domain name to host the APIM public key"
+}
diff --git a/infrastructure/terraform/modules/apim-authentication/versions.tf b/infrastructure/terraform/modules/apim-authentication/versions.tf
new file mode 100644
index 00000000..f8dc86e9
--- /dev/null
+++ b/infrastructure/terraform/modules/apim-authentication/versions.tf
@@ -0,0 +1,9 @@
+
+terraform {
+ required_providers {
+ aws = {
+ source = "hashicorp/aws"
+ }
+ }
+ required_version = ">= 1.9.0"
+}
diff --git a/jest.config.base.ts b/jest.config.base.ts
new file mode 100644
index 00000000..61d9d7f3
--- /dev/null
+++ b/jest.config.base.ts
@@ -0,0 +1,61 @@
+import type { Config } from 'jest';
+
+export const baseJestConfig: Config = {
+ preset: 'ts-jest',
+
+ // Automatically clear mock calls, instances, contexts and results before every test
+ clearMocks: true,
+
+ // Indicates whether the coverage information should be collected while executing the test
+ collectCoverage: true,
+
+ // An array of glob patterns indicating a set of files for which coverage information should be collected
+ collectCoverageFrom: [
+ 'src/**/*.{ts,tsx}',
+ '!src/**/*.d.ts',
+ '!src/**/__tests__/**',
+ '!src/**/*.test.{ts,tsx}',
+ '!src/**/*.spec.{ts,tsx}',
+ ],
+
+ // The directory where Jest should output its coverage files
+ coverageDirectory: './.reports/unit/coverage',
+
+ // Indicates which provider should be used to instrument code for coverage
+ coverageProvider: 'babel',
+
+ coverageThreshold: {
+ global: {
+ branches: 100,
+ functions: 100,
+ lines: 100,
+ statements: -10,
+ },
+ },
+
+ coveragePathIgnorePatterns: ['/__tests__/'],
+ transform: { '^.+\\.ts$': 'ts-jest' },
+ testPathIgnorePatterns: ['.build'],
+ testMatch: ['**/?(*.)+(spec|test).[jt]s?(x)'],
+
+ // Use this configuration option to add custom reporters to Jest
+ reporters: [
+ 'default',
+ [
+ 'jest-html-reporter',
+ {
+ pageTitle: 'Test Report',
+ outputPath: './.reports/unit/test-report.html',
+ includeFailureMsg: true,
+ },
+ ],
+ ],
+
+ // The test environment that will be used for testing
+ testEnvironment: 'node',
+
+ moduleDirectories: ['node_modules', 'src'],
+
+ // Turbo now handles parallel running
+ maxWorkers: 1,
+};
diff --git a/package.json b/package.json
new file mode 100644
index 00000000..5488a124
--- /dev/null
+++ b/package.json
@@ -0,0 +1,49 @@
+{
+ "devDependencies": {
+ "@eslint/js": "catalog:lint",
+ "@stylistic/eslint-plugin": "catalog:lint",
+ "@stylistic/eslint-plugin-ts": "catalog:lint",
+ "@tsconfig/node22": "catalog:tools",
+ "@types/jest": "catalog:test",
+ "@typescript-eslint/eslint-plugin": "catalog:lint",
+ "@typescript-eslint/parser": "catalog:lint",
+ "esbuild": "catalog:tools",
+ "eslint": "catalog:lint",
+ "eslint-config-airbnb-extended": "catalog:lint",
+ "eslint-config-prettier": "catalog:lint",
+ "eslint-import-resolver-typescript": "catalog:lint",
+ "eslint-plugin-html": "catalog:lint",
+ "eslint-plugin-import-x": "catalog:lint",
+ "eslint-plugin-jest": "catalog:lint",
+ "eslint-plugin-json": "catalog:lint",
+ "eslint-plugin-jsx-a11y": "catalog:lint",
+ "eslint-plugin-no-relative-import-paths": "catalog:lint",
+ "eslint-plugin-prettier": "catalog:lint",
+ "eslint-plugin-react": "catalog:lint",
+ "eslint-plugin-react-hooks": "^7.1.1",
+ "eslint-plugin-security": "catalog:lint",
+ "eslint-plugin-sonarjs": "catalog:lint",
+ "eslint-plugin-sort-destructure-keys": "catalog:lint",
+ "eslint-plugin-unicorn": "catalog:lint",
+ "jest": "catalog:test",
+ "jest-environment-jsdom": "catalog:test",
+ "jest-html-reporter": "catalog:test",
+ "jest-mock-extended": "catalog:test",
+ "lcov-result-merger": "catalog:test",
+ "ts-jest": "catalog:test",
+ "ts-node": "catalog:tools",
+ "tsx": "catalog:tools",
+ "turbo": "catalog:build",
+ "typescript-eslint": "catalog:lint"
+ },
+ "name": "nhs-notify-digital-letters",
+ "packageManager": "pnpm@11.15.1",
+ "scripts": {
+ "clean": "pnpm -r run --if-present clean",
+ "lint": "turbo run lint",
+ "lint:fix": "turbo run lint:fix",
+ "test:unit": "turbo run test:unit",
+ "typecheck": "turbo run typecheck"
+ },
+ "version": "0.0.1"
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
new file mode 100644
index 00000000..dc817fe5
--- /dev/null
+++ b/pnpm-lock.yaml
@@ -0,0 +1,9398 @@
+lockfileVersion: '9.0'
+
+settings:
+ autoInstallPeers: true
+ excludeLinksFromLockfile: false
+
+catalogs:
+ build:
+ turbo:
+ specifier: ^2.9.6
+ version: 2.10.8
+ lint:
+ '@eslint/js':
+ specifier: ^9.39.4
+ version: 9.39.5
+ '@stylistic/eslint-plugin':
+ specifier: ^3.1.0
+ version: 3.1.0
+ '@stylistic/eslint-plugin-ts':
+ specifier: ^4.4.1
+ version: 4.4.1
+ '@typescript-eslint/eslint-plugin':
+ specifier: ^8.46.1
+ version: 8.65.0
+ '@typescript-eslint/parser':
+ specifier: ^8.46.1
+ version: 8.65.0
+ eslint:
+ specifier: ^9.37.0
+ version: 9.39.5
+ eslint-config-airbnb-extended:
+ specifier: ^2.3.2
+ version: 2.3.3
+ eslint-config-prettier:
+ specifier: ^10.1.8
+ version: 10.1.8
+ eslint-import-resolver-typescript:
+ specifier: ^4.4.2
+ version: 4.4.5
+ eslint-plugin-html:
+ specifier: ^8.1.3
+ version: 8.1.4
+ eslint-plugin-import-x:
+ specifier: ^4.13.3
+ version: 4.17.1
+ eslint-plugin-jest:
+ specifier: ^29.0.1
+ version: 29.16.0
+ eslint-plugin-json:
+ specifier: ^4.0.1
+ version: 4.0.1
+ eslint-plugin-jsx-a11y:
+ specifier: ^6.10.2
+ version: 6.10.2
+ eslint-plugin-no-relative-import-paths:
+ specifier: ^1.6.1
+ version: 1.6.1
+ eslint-plugin-prettier:
+ specifier: ^5.5.4
+ version: 5.5.6
+ eslint-plugin-react:
+ specifier: ^7.37.5
+ version: 7.37.5
+ eslint-plugin-security:
+ specifier: ^3.0.1
+ version: 3.0.1
+ eslint-plugin-sonarjs:
+ specifier: ^3.0.5
+ version: 3.0.7
+ eslint-plugin-sort-destructure-keys:
+ specifier: ^2.0.0
+ version: 2.0.0
+ eslint-plugin-unicorn:
+ specifier: ^61.0.2
+ version: 61.0.2
+ typescript-eslint:
+ specifier: ^8.46.1
+ version: 8.65.0
+ test:
+ '@types/jest':
+ specifier: ^30.0.0
+ version: 30.0.0
+ '@types/mock-fs':
+ specifier: ^4.13.4
+ version: 4.13.4
+ jest:
+ specifier: ^30.2.0
+ version: 30.4.2
+ jest-environment-jsdom:
+ specifier: ^30.2.0
+ version: 30.4.1
+ jest-html-reporter:
+ specifier: ^4.3.0
+ version: 4.4.0
+ jest-mock-extended:
+ specifier: ^4.0.0
+ version: 4.0.1
+ lcov-result-merger:
+ specifier: ^5.0.1
+ version: 5.0.1
+ mock-fs:
+ specifier: ^5.5.0
+ version: 5.5.0
+ ts-jest:
+ specifier: ^29.4.11
+ version: 29.4.12
+ tools:
+ '@tsconfig/node22':
+ specifier: ^22.0.5
+ version: 22.0.5
+ '@types/aws-lambda':
+ specifier: ^8.10.161
+ version: 8.10.162
+ esbuild:
+ specifier: ^0.25.11
+ version: 0.25.12
+ ts-node:
+ specifier: ^10.9.2
+ version: 10.9.2
+ tsx:
+ specifier: ^4.20.6
+ version: 4.23.5
+ typescript:
+ specifier: ^5.9.3
+ version: 5.9.3
+
+overrides:
+ '@auth/core@>=0.1.0 <0.41.3': ^0.41.3
+ esbuild@>=0.27.3 <0.28.1: '>=0.28.1'
+ minimatch@>=10.0.0 <10.2.3: '>=10.2.3'
+ prismjs@<1.30.0: '>=1.30.0'
+ uuid@<11.1.1: '>=11.1.1'
+ yaml@>=2.0.0 <2.8.3: '>=2.8.3'
+
+importers:
+
+ .:
+ devDependencies:
+ '@eslint/js':
+ specifier: catalog:lint
+ version: 9.39.5
+ '@stylistic/eslint-plugin':
+ specifier: catalog:lint
+ version: 3.1.0(eslint@9.39.5(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.3)
+ '@stylistic/eslint-plugin-ts':
+ specifier: catalog:lint
+ version: 4.4.1(eslint@9.39.5(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.3)
+ '@tsconfig/node22':
+ specifier: catalog:tools
+ version: 22.0.5
+ '@types/jest':
+ specifier: catalog:test
+ version: 30.0.0
+ '@typescript-eslint/eslint-plugin':
+ specifier: catalog:lint
+ version: 8.65.0(@typescript-eslint/parser@8.65.0(eslint@9.39.5(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.3))(eslint@9.39.5(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.3)
+ '@typescript-eslint/parser':
+ specifier: catalog:lint
+ version: 8.65.0(eslint@9.39.5(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.3)
+ esbuild:
+ specifier: catalog:tools
+ version: 0.25.12
+ eslint:
+ specifier: catalog:lint
+ version: 9.39.5(supports-color@8.1.1)
+ eslint-config-airbnb-extended:
+ specifier: catalog:lint
+ version: 2.3.3(@stylistic/eslint-plugin@3.1.0(eslint@9.39.5(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.3))(eslint-import-resolver-typescript@4.4.5(eslint-plugin-import-x@4.17.1(@typescript-eslint/utils@8.65.0(eslint@9.39.5(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.3))(eslint@9.39.5(supports-color@8.1.1))(supports-color@8.1.1))(eslint@9.39.5(supports-color@8.1.1))(supports-color@8.1.1))(eslint-plugin-import-x@4.17.1(@typescript-eslint/utils@8.65.0(eslint@9.39.5(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.3))(eslint@9.39.5(supports-color@8.1.1))(supports-color@8.1.1))(eslint-plugin-jsx-a11y@6.10.2(eslint@9.39.5(supports-color@8.1.1)))(eslint-plugin-react-hooks@7.1.1(eslint@9.39.5(supports-color@8.1.1))(supports-color@8.1.1))(eslint-plugin-react@7.37.5(eslint@9.39.5(supports-color@8.1.1)))(eslint@9.39.5(supports-color@8.1.1))(typescript-eslint@8.65.0(eslint@9.39.5(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.3))
+ eslint-config-prettier:
+ specifier: catalog:lint
+ version: 10.1.8(eslint@9.39.5(supports-color@8.1.1))
+ eslint-import-resolver-typescript:
+ specifier: catalog:lint
+ version: 4.4.5(eslint-plugin-import-x@4.17.1(@typescript-eslint/utils@8.65.0(eslint@9.39.5(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.3))(eslint@9.39.5(supports-color@8.1.1))(supports-color@8.1.1))(eslint@9.39.5(supports-color@8.1.1))(supports-color@8.1.1)
+ eslint-plugin-html:
+ specifier: catalog:lint
+ version: 8.1.4
+ eslint-plugin-import-x:
+ specifier: catalog:lint
+ version: 4.17.1(@typescript-eslint/utils@8.65.0(eslint@9.39.5(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.3))(eslint@9.39.5(supports-color@8.1.1))(supports-color@8.1.1)
+ eslint-plugin-jest:
+ specifier: catalog:lint
+ version: 29.16.0(@typescript-eslint/eslint-plugin@8.65.0(@typescript-eslint/parser@8.65.0(eslint@9.39.5(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.3))(eslint@9.39.5(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.3))(eslint@9.39.5(supports-color@8.1.1))(jest@30.4.2(@types/node@24.13.3)(supports-color@8.1.1)(ts-node@10.9.2(@types/node@24.13.3)(typescript@5.9.3)))(supports-color@8.1.1)(typescript@5.9.3)
+ eslint-plugin-json:
+ specifier: catalog:lint
+ version: 4.0.1
+ eslint-plugin-jsx-a11y:
+ specifier: catalog:lint
+ version: 6.10.2(eslint@9.39.5(supports-color@8.1.1))
+ eslint-plugin-no-relative-import-paths:
+ specifier: catalog:lint
+ version: 1.6.1
+ eslint-plugin-prettier:
+ specifier: catalog:lint
+ version: 5.5.6(eslint-config-prettier@10.1.8(eslint@9.39.5(supports-color@8.1.1)))(eslint@9.39.5(supports-color@8.1.1))(prettier@3.9.6)
+ eslint-plugin-react:
+ specifier: catalog:lint
+ version: 7.37.5(eslint@9.39.5(supports-color@8.1.1))
+ eslint-plugin-react-hooks:
+ specifier: ^7.1.1
+ version: 7.1.1(eslint@9.39.5(supports-color@8.1.1))(supports-color@8.1.1)
+ eslint-plugin-security:
+ specifier: catalog:lint
+ version: 3.0.1
+ eslint-plugin-sonarjs:
+ specifier: catalog:lint
+ version: 3.0.7(eslint@9.39.5(supports-color@8.1.1))
+ eslint-plugin-sort-destructure-keys:
+ specifier: catalog:lint
+ version: 2.0.0(eslint@9.39.5(supports-color@8.1.1))
+ eslint-plugin-unicorn:
+ specifier: catalog:lint
+ version: 61.0.2(eslint@9.39.5(supports-color@8.1.1))
+ jest:
+ specifier: catalog:test
+ version: 30.4.2(@types/node@24.13.3)(supports-color@8.1.1)(ts-node@10.9.2(@types/node@24.13.3)(typescript@5.9.3))
+ jest-environment-jsdom:
+ specifier: catalog:test
+ version: 30.4.1(supports-color@8.1.1)
+ jest-html-reporter:
+ specifier: catalog:test
+ version: 4.4.0(jest@30.4.2(@types/node@24.13.3)(supports-color@8.1.1)(ts-node@10.9.2(@types/node@24.13.3)(typescript@5.9.3)))(supports-color@8.1.1)
+ jest-mock-extended:
+ specifier: catalog:test
+ version: 4.0.1(@jest/globals@30.4.1(supports-color@8.1.1))(jest@30.4.2(@types/node@24.13.3)(supports-color@8.1.1)(ts-node@10.9.2(@types/node@24.13.3)(typescript@5.9.3)))(typescript@5.9.3)
+ lcov-result-merger:
+ specifier: catalog:test
+ version: 5.0.1
+ ts-jest:
+ specifier: catalog:test
+ version: 29.4.12(@babel/core@7.29.7(supports-color@8.1.1))(@jest/transform@30.4.1(supports-color@8.1.1))(@jest/types@30.4.1)(babel-jest@30.4.1(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(esbuild@0.25.12)(jest-util@30.4.1)(jest@30.4.2(@types/node@24.13.3)(supports-color@8.1.1)(ts-node@10.9.2(@types/node@24.13.3)(typescript@5.9.3)))(typescript@5.9.3)
+ ts-node:
+ specifier: catalog:tools
+ version: 10.9.2(@types/node@24.13.3)(typescript@5.9.3)
+ tsx:
+ specifier: catalog:tools
+ version: 4.23.5
+ turbo:
+ specifier: catalog:build
+ version: 2.10.8
+ typescript-eslint:
+ specifier: catalog:lint
+ version: 8.65.0(eslint@9.39.5(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.3)
+
+ src/lambdas/apim-access-token-refresher:
+ dependencies:
+ '@aws-sdk/client-ssm':
+ specifier: ^3.840.0
+ version: 3.1101.0
+ axios:
+ specifier: ^1.18.1
+ version: 1.19.0(debug@4.4.3(supports-color@8.1.1))(supports-color@8.1.1)
+ esbuild:
+ specifier: ^0.25.9
+ version: 0.25.12
+ jsonwebtoken:
+ specifier: ^9.0.2
+ version: 9.0.3
+ qs:
+ specifier: ^6.14.1
+ version: 6.15.3
+ utils:
+ specifier: workspace:*
+ version: link:../../utils
+ devDependencies:
+ '@tsconfig/node22':
+ specifier: ^22.0.2
+ version: 22.0.5
+ '@types/jest':
+ specifier: ^29.5.14
+ version: 29.5.14
+ '@types/jsonwebtoken':
+ specifier: ^9.0.10
+ version: 9.0.10
+ '@types/node':
+ specifier: ^24.0.10
+ version: 24.13.3
+ '@types/qs':
+ specifier: ^6.14.0
+ version: 6.15.1
+ jest:
+ specifier: ^29.7.0
+ version: 29.7.0(@types/node@24.13.3)(supports-color@8.1.1)(ts-node@10.9.2(@types/node@24.13.3)(typescript@5.9.3))
+ jest-mock-extended:
+ specifier: ^3.0.7
+ version: 3.0.7(jest@29.7.0(@types/node@24.13.3)(supports-color@8.1.1)(ts-node@10.9.2(@types/node@24.13.3)(typescript@5.9.3)))(typescript@5.9.3)
+ typescript:
+ specifier: ^5.8.2
+ version: 5.9.3
+
+ src/lambdas/apim-key-generator:
+ dependencies:
+ date-fns:
+ specifier: ^4.1.0
+ version: 4.4.0
+ esbuild:
+ specifier: ^0.25.9
+ version: 0.25.12
+ jose:
+ specifier: ^5.10.0
+ version: 5.10.0
+ utils:
+ specifier: workspace:*
+ version: link:../../utils
+ devDependencies:
+ '@tsconfig/node22':
+ specifier: ^22.0.2
+ version: 22.0.5
+ '@types/aws-lambda':
+ specifier: ^8.10.148
+ version: 8.10.162
+ '@types/jest':
+ specifier: ^29.5.14
+ version: 29.5.14
+ '@types/node':
+ specifier: ^24.0.10
+ version: 24.13.3
+ jest:
+ specifier: ^29.7.0
+ version: 29.7.0(@types/node@24.13.3)(supports-color@8.1.1)(ts-node@10.9.2(@types/node@24.13.3)(typescript@5.9.3))
+ jest-mock-extended:
+ specifier: ^3.0.7
+ version: 3.0.7(jest@29.7.0(@types/node@24.13.3)(supports-color@8.1.1)(ts-node@10.9.2(@types/node@24.13.3)(typescript@5.9.3)))(typescript@5.9.3)
+ typescript:
+ specifier: ^5.8.2
+ version: 5.9.3
+
+ src/utils:
+ dependencies:
+ '@aws-sdk/client-athena':
+ specifier: ^3.984.0
+ version: 3.1101.0
+ '@aws-sdk/client-cloudwatch':
+ specifier: ^3.984.0
+ version: 3.1101.0
+ '@aws-sdk/client-dynamodb':
+ specifier: ^3.984.0
+ version: 3.1101.0
+ '@aws-sdk/client-eventbridge':
+ specifier: ^3.984.0
+ version: 3.1101.0
+ '@aws-sdk/client-lambda':
+ specifier: ^3.984.0
+ version: 3.1101.0
+ '@aws-sdk/client-s3':
+ specifier: ^3.984.0
+ version: 3.1101.0
+ '@aws-sdk/client-sqs':
+ specifier: ^3.984.0
+ version: 3.1101.0
+ '@aws-sdk/client-ssm':
+ specifier: ^3.984.0
+ version: 3.1101.0
+ '@aws-sdk/lib-dynamodb':
+ specifier: ^3.984.0
+ version: 3.1101.0(@aws-sdk/client-dynamodb@3.1101.0)
+ '@aws-sdk/lib-storage':
+ specifier: ^3.984.0
+ version: 3.1101.0(@aws-sdk/client-s3@3.1101.0)
+ async-mutex:
+ specifier: ^0.4.0
+ version: 0.4.1
+ axios:
+ specifier: ^1.18.1
+ version: 1.19.0(debug@4.4.3(supports-color@8.1.1))(supports-color@8.1.1)
+ date-fns:
+ specifier: ^4.1.0
+ version: 4.4.0
+ jose:
+ specifier: ^5.10.0
+ version: 5.10.0
+ winston:
+ specifier: ^3.17.0
+ version: 3.19.0
+ zod:
+ specifier: ^4.1.12
+ version: 4.4.3
+ devDependencies:
+ '@aws-sdk/types':
+ specifier: ^3.914.0
+ version: 3.974.2
+ '@tsconfig/node22':
+ specifier: catalog:tools
+ version: 22.0.5
+ '@types/aws-lambda':
+ specifier: catalog:tools
+ version: 8.10.162
+ '@types/jest':
+ specifier: ^29.5.14
+ version: 29.5.14
+ '@types/mock-fs':
+ specifier: catalog:test
+ version: 4.13.4
+ '@types/node':
+ specifier: ^24.0.10
+ version: 24.13.3
+ aws-sdk-client-mock:
+ specifier: ^4.1.0
+ version: 4.1.0
+ aws-sdk-client-mock-jest:
+ specifier: ^4.1.0
+ version: 4.1.0(aws-sdk-client-mock@4.1.0)
+ jest:
+ specifier: ^29.7.0
+ version: 29.7.0(@types/node@24.13.3)(supports-color@8.1.1)(ts-node@10.9.2(@types/node@24.13.3)(typescript@5.9.3))
+ jest-mock-extended:
+ specifier: ^3.0.7
+ version: 3.0.7(jest@29.7.0(@types/node@24.13.3)(supports-color@8.1.1)(ts-node@10.9.2(@types/node@24.13.3)(typescript@5.9.3)))(typescript@5.9.3)
+ mock-fs:
+ specifier: catalog:test
+ version: 5.5.0
+ typescript:
+ specifier: catalog:tools
+ version: 5.9.3
+
+packages:
+
+ '@asamuzakjp/css-color@3.2.0':
+ resolution: {integrity: sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==}
+
+ '@aws-sdk/checksums@3.1000.24':
+ resolution: {integrity: sha512-7TWLjypP8kk3savsDBRuhZJx7mBuFFA2136BQhwwLllsAnO4Tmq/p+SXZaNxbuulkzUFz3BZzj0bb4YzexZcNQ==}
+ engines: {node: '>=20.0.0'}
+
+ '@aws-sdk/client-athena@3.1101.0':
+ resolution: {integrity: sha512-aXsyZU4tOXGvQ5XKd5kJtUlVp0/2/9+pPT4C0NV9XjFXUAgd73C70wPavwcC1Qce1E9J+ohnXd8vpOGIYPW2cQ==}
+ engines: {node: '>=20.0.0'}
+
+ '@aws-sdk/client-cloudwatch@3.1101.0':
+ resolution: {integrity: sha512-ZPqPMfrTB1P6sraLBYUpebyaWY3losWaUwjDtGNBcbQ4kcoO83TXKGg5DeOO7Yu4hghznguyRpF/R4N/zPn6LQ==}
+ engines: {node: '>=20.0.0'}
+
+ '@aws-sdk/client-dynamodb@3.1101.0':
+ resolution: {integrity: sha512-3tlBfiRdksyqFggcMsN3AOA9uaqEbt5OK6Ym0MfUxKqLIOwdAlfCKL2GvE8qC0xa2dwPegIDs+grTRpBtzNILQ==}
+ engines: {node: '>=20.0.0'}
+
+ '@aws-sdk/client-eventbridge@3.1101.0':
+ resolution: {integrity: sha512-BZwGOvV+FcPcQqcnBHL11+qgHsaLAz+x01m+ppDXHpI6Tzhy4zsgp2LXESGzkLDrSk4DnzItkF38q1j9VX0ZVA==}
+ engines: {node: '>=20.0.0'}
+
+ '@aws-sdk/client-lambda@3.1101.0':
+ resolution: {integrity: sha512-OiMqyOfqWBMqh0Ov33uICt5ZXT/PQLUEDGXg9M+MinndyIEuJRuSONrmxUShQn6xjcjohCugpUSTjTrMUAt1UQ==}
+ engines: {node: '>=20.0.0'}
+
+ '@aws-sdk/client-s3@3.1101.0':
+ resolution: {integrity: sha512-16EFb1aTEBgPcfUAWAjjlB57IZCyn7B3rlfT+xqE7M6WoH8AMMU3vFZO0UOitwh/xvvzVx73YED1/n0PU4qBMw==}
+ engines: {node: '>=20.0.0'}
+
+ '@aws-sdk/client-sqs@3.1101.0':
+ resolution: {integrity: sha512-Ui4QpE1EII3CfTKxGHN4egx2GpowSb+r8o4g9cqcubc7j9fN3FOCKmQNCVMwQMxr33mXcM9yn8ZHrOiD2/ajeg==}
+ engines: {node: '>=20.0.0'}
+
+ '@aws-sdk/client-ssm@3.1101.0':
+ resolution: {integrity: sha512-8R5aywNT7ccoTFsbqKB+XIh5LA+XgqlB0PnICFplZUM8NfJIENip9LTXv6weFIuruqiAe2gMS0K2pjO2gP+gUQ==}
+ engines: {node: '>=20.0.0'}
+
+ '@aws-sdk/core@3.977.4':
+ resolution: {integrity: sha512-CEkcQlMOQJCvul60U7wdAOACjtdgFWDsfJI+6wUOGdhGNV2lGbuJpi/R50QLpFG3Tp+sQxa/RmzC3X7KHbhuTA==}
+ engines: {node: '>=20.0.0'}
+ deprecated: |-
+ Deprecated due to Document number parsing bug in JSON, see
+ https://github.com/aws/aws-sdk-js-v3/issues/8246. Newer version available.
+
+ '@aws-sdk/credential-provider-env@3.972.65':
+ resolution: {integrity: sha512-lJT2aRw9wCV8jPHyFJjdZLD4HTydL6/22AnCSOB8e/LqOc55nEJGLHkJQeSxhn8QiqyjFwPKQFtMw0ovjRUY/g==}
+ engines: {node: '>=20.0.0'}
+
+ '@aws-sdk/credential-provider-http@3.972.67':
+ resolution: {integrity: sha512-N7fw/15hSwI/CPxe5ohOyb7O4ge9f5me1gVIn8OIkBRB0squ8OJqQyDyH/HoL+Sb1W5xdC88jVC+bHkw73iu+Q==}
+ engines: {node: '>=20.0.0'}
+
+ '@aws-sdk/credential-provider-ini@3.973.10':
+ resolution: {integrity: sha512-Zh9XRaPnDN9buO7GfWBubS22R6Nq5D6hbyYEMN05LiOnXugm/8WDjUx6y756bSPbdn3aJB2qG4zFW3bN82QhoQ==}
+ engines: {node: '>=20.0.0'}
+
+ '@aws-sdk/credential-provider-login@3.972.72':
+ resolution: {integrity: sha512-zZapIKwaHp7TdTf9hbH1I3CVUdEupmt7FXO/BoTQGC+4h6NkXKWpqF2p5WyfpjurDLHCpSyh+BzMlAg8arqWLA==}
+ engines: {node: '>=20.0.0'}
+
+ '@aws-sdk/credential-provider-node@3.972.76':
+ resolution: {integrity: sha512-1yzLmRiYSgGC25v7ZZEwJn/auhHHTIHgFOmzL2f36hf1+7jSLcX+1QrAz4760WEzPiiQl8xmlpFhHfl2OoyVzA==}
+ engines: {node: '>=20.0.0'}
+
+ '@aws-sdk/credential-provider-process@3.972.65':
+ resolution: {integrity: sha512-e5DbbNteOSalN58U83G6kFa4ECLEuGbGqNBHIXE7zYXA/m4GHblIGjFbSH7wYv6gBV8iNSDcRZBKfQZF5vF9nw==}
+ engines: {node: '>=20.0.0'}
+
+ '@aws-sdk/credential-provider-sso@3.973.9':
+ resolution: {integrity: sha512-0V0u4t+KBku9fbh5CPCaC5hUWwSzDafp8nCuDy817zWbp2gz80jO44rMQkiwnZ+k54B+tjAtzRy00DJRGTKGBg==}
+ engines: {node: '>=20.0.0'}
+
+ '@aws-sdk/credential-provider-web-identity@3.972.71':
+ resolution: {integrity: sha512-e4dwiRltGAaQ+2yxw57Hj0l/BF3BHiG14+QpYE7bGYBlpAq/fkIri2BDhjWon8c0mhhtd2txQBAkQb9BcTStFg==}
+ engines: {node: '>=20.0.0'}
+
+ '@aws-sdk/dynamodb-codec@3.973.39':
+ resolution: {integrity: sha512-6n1ER8qEvfruOfVFHVYrAtfSOk0ELudJDWyUMMb+M+tiP4rWSU44zd9ilQcM2/ESlRPyzxc2h7dU3R+UT+sVEg==}
+ engines: {node: '>=20.0.0'}
+
+ '@aws-sdk/endpoint-cache@3.972.9':
+ resolution: {integrity: sha512-LFvdgq8SriaskUcjpBMDE7J2c9RmuT5v3gU36/znV71EU5DKUis4FmGFjCMelKCCViFeVrQADBAlIiOYRhEx6Q==}
+ engines: {node: '>=20.0.0'}
+
+ '@aws-sdk/lib-dynamodb@3.1101.0':
+ resolution: {integrity: sha512-ZqKo4lTrbphYbHzvZJox5ytFwXYlwgJooRzenNBv5bFjTK2m6PmLSIP5TCQm2wxpabVS4JDSiYd6fBzrXQBCzQ==}
+ engines: {node: '>=20.0.0'}
+ peerDependencies:
+ '@aws-sdk/client-dynamodb': ^3.1101.0
+
+ '@aws-sdk/lib-storage@3.1101.0':
+ resolution: {integrity: sha512-S2kYJzmX8a9SPgesOn8la1Ezuto8OqI4e85yNdiOJ/trLMrBxxEZoz8CtOk0uT3FjEJXVbYApzLNmVGVLvUwxA==}
+ engines: {node: '>=20.0.0'}
+ peerDependencies:
+ '@aws-sdk/client-s3': ^3.1101.0
+
+ '@aws-sdk/middleware-endpoint-discovery@3.972.27':
+ resolution: {integrity: sha512-5AJlxrsg27IGGiQauWOdVyqK55EN0EMIwXndkVHhBiPT4CtdSaz89/sAENSw+GP4KF+BOWcgycZFNjkOLmfo1A==}
+ engines: {node: '>=20.0.0'}
+
+ '@aws-sdk/middleware-sdk-s3@3.972.70':
+ resolution: {integrity: sha512-APdP0iODt39AkjCjzTFIoFrxDH/Cz3CpWRDKLcsJg7eOnfE1htkxL9BhDoe/xL7cXdoMwh2HBYv3DiT1uf64NQ==}
+ engines: {node: '>=20.0.0'}
+
+ '@aws-sdk/middleware-sdk-sqs@3.972.39':
+ resolution: {integrity: sha512-dlKLmJg1dLQVfFXUPS+f+SqXrRGHDVumY4gjM8sCLGao+zkDXEu8TObTyiaheKjT20ruok9Xs8oLocNItKQ0fw==}
+ engines: {node: '>=20.0.0'}
+
+ '@aws-sdk/nested-clients@3.997.39':
+ resolution: {integrity: sha512-wU5NPnj62Sb7A8xn/Zb+xThe05P3otNtDl37iOIi5DDMeCesNeCckaG+eXWGUs12Z9R34I8CD05TaTe6SIa61g==}
+ engines: {node: '>=20.0.0'}
+
+ '@aws-sdk/signature-v4-multi-region@3.996.43':
+ resolution: {integrity: sha512-lKekx8bLBXSv4O+cslk9Zfnw2XKSkWBs3uWL5QGhH2ZAQfNS7FE0vcSSN2vD/AhxX54ZTywWxR4STThoeOXlBA==}
+ engines: {node: '>=20.0.0'}
+
+ '@aws-sdk/token-providers@3.1100.0':
+ resolution: {integrity: sha512-THf3MkgY3fNJZ3zdgSenLqR7gSE68KccCj1RCKretlG73Ppszvues02VpCUO9NlB/tZDC483FvGCld+AiPCkvg==}
+ engines: {node: '>=20.0.0'}
+
+ '@aws-sdk/types@3.974.2':
+ resolution: {integrity: sha512-3W6IUtSxFbH6X7Wb7DzGCV5QiFQsd0g8bOfntpmDxQlzBoKWUMBu/JPQR0DwkE+Hpnxd6db1tXbOwdeHddG6cA==}
+ engines: {node: '>=20.0.0'}
+
+ '@aws-sdk/util-dynamodb@3.996.7':
+ resolution: {integrity: sha512-v+WJASG9yaW8qNM7pNSgH1PBYz5mVTf7gzKPi0NqGjLlaCtPdk6EjM1lmv03egA07iXUw6OToGYfW2w8D3kBrg==}
+ engines: {node: '>=20.0.0'}
+ peerDependencies:
+ '@aws-sdk/client-dynamodb': ^3.1088.0
+
+ '@aws-sdk/xml-builder@3.972.37':
+ resolution: {integrity: sha512-zKq4HQum8JwDyEuyfuI4bbiAcU0KxP6qy+9PR/IsR92IyE/DaBAikzAS50tjxip4bqIIANpCcG+Yyj6CVhXupg==}
+ engines: {node: '>=20.0.0'}
+
+ '@aws/lambda-invoke-store@0.3.0':
+ resolution: {integrity: sha512-sl4Bm6yiMNYrZKkqqDFWN0UfnWhlS8ivKxrYl+6t0gCLrqr8y3B2IqZZbFRkfaVVp7C/baApyh71P+LeE1A2sQ==}
+ engines: {node: '>=18.0.0'}
+
+ '@babel/code-frame@7.29.7':
+ resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/compat-data@7.29.7':
+ resolution: {integrity: sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/core@7.29.7':
+ resolution: {integrity: sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/generator@7.29.8':
+ resolution: {integrity: sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-compilation-targets@7.29.7':
+ resolution: {integrity: sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-globals@7.29.7':
+ resolution: {integrity: sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-module-imports@7.29.7':
+ resolution: {integrity: sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-module-transforms@7.29.7':
+ resolution: {integrity: sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0
+
+ '@babel/helper-plugin-utils@7.29.7':
+ resolution: {integrity: sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-string-parser@7.29.7':
+ resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-validator-identifier@7.29.7':
+ resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-validator-option@7.29.7':
+ resolution: {integrity: sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helpers@7.29.7':
+ resolution: {integrity: sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/parser@7.29.8':
+ resolution: {integrity: sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==}
+ engines: {node: '>=6.0.0'}
+ hasBin: true
+
+ '@babel/plugin-syntax-async-generators@7.8.4':
+ resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-syntax-bigint@7.8.3':
+ resolution: {integrity: sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-syntax-class-properties@7.12.13':
+ resolution: {integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-syntax-class-static-block@7.14.5':
+ resolution: {integrity: sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-syntax-import-attributes@7.29.7':
+ resolution: {integrity: sha512-zGYcYfq/WmZ4V+kBIXQon9dSSc8ircGZqw9ZaNhhGj9nZkeBu1jHLBDQqYYi5WA9uawvA2sIMbry2nCFhf5Djg==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-syntax-import-meta@7.10.4':
+ resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-syntax-json-strings@7.8.3':
+ resolution: {integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-syntax-jsx@7.29.7':
+ resolution: {integrity: sha512-TSu8+mHCoEaaCDEZ0I3+6mvTBYR4PCxQwf2z9/r5Tbztv6NaLR3B9thGTTxX2WGuGHJqRiAbKPeGTJ5XWXVg6A==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-syntax-logical-assignment-operators@7.10.4':
+ resolution: {integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3':
+ resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-syntax-numeric-separator@7.10.4':
+ resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-syntax-object-rest-spread@7.8.3':
+ resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-syntax-optional-catch-binding@7.8.3':
+ resolution: {integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-syntax-optional-chaining@7.8.3':
+ resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-syntax-private-property-in-object@7.14.5':
+ resolution: {integrity: sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-syntax-top-level-await@7.14.5':
+ resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-syntax-typescript@7.29.7':
+ resolution: {integrity: sha512-ngr+82Sh0xMz25TPCZi+nC2iTzjfCdWS2ONXTp/PtSCHCgaCNBpdMqgvJ2ccdLlClVZ7sisIgB914j/JFe+RZA==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/template@7.29.7':
+ resolution: {integrity: sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/traverse@7.29.8':
+ resolution: {integrity: sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/types@7.29.8':
+ resolution: {integrity: sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==}
+ engines: {node: '>=6.9.0'}
+
+ '@bcoe/v8-coverage@0.2.3':
+ resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==}
+
+ '@colors/colors@1.6.0':
+ resolution: {integrity: sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==}
+ engines: {node: '>=0.1.90'}
+
+ '@cspotcode/source-map-support@0.8.1':
+ resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==}
+ engines: {node: '>=12'}
+
+ '@csstools/color-helpers@5.1.0':
+ resolution: {integrity: sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==}
+ engines: {node: '>=18'}
+
+ '@csstools/css-calc@2.1.4':
+ resolution: {integrity: sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ '@csstools/css-parser-algorithms': ^3.0.5
+ '@csstools/css-tokenizer': ^3.0.4
+
+ '@csstools/css-color-parser@3.1.0':
+ resolution: {integrity: sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ '@csstools/css-parser-algorithms': ^3.0.5
+ '@csstools/css-tokenizer': ^3.0.4
+
+ '@csstools/css-parser-algorithms@3.0.5':
+ resolution: {integrity: sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ '@csstools/css-tokenizer': ^3.0.4
+
+ '@csstools/css-tokenizer@3.0.4':
+ resolution: {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==}
+ engines: {node: '>=18'}
+
+ '@dabh/diagnostics@2.0.8':
+ resolution: {integrity: sha512-R4MSXTVnuMzGD7bzHdW2ZhhdPC/igELENcq5IjEverBvq5hn1SXCWcsi6eSsdWP0/Ur+SItRRjAktmdoX/8R/Q==}
+
+ '@emnapi/core@1.10.0':
+ resolution: {integrity: sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==}
+
+ '@emnapi/runtime@1.10.0':
+ resolution: {integrity: sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==}
+
+ '@emnapi/wasi-threads@1.2.1':
+ resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==}
+
+ '@esbuild/aix-ppc64@0.25.12':
+ resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==}
+ engines: {node: '>=18'}
+ cpu: [ppc64]
+ os: [aix]
+
+ '@esbuild/aix-ppc64@0.28.1':
+ resolution: {integrity: sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==}
+ engines: {node: '>=18'}
+ cpu: [ppc64]
+ os: [aix]
+
+ '@esbuild/android-arm64@0.25.12':
+ resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [android]
+
+ '@esbuild/android-arm64@0.28.1':
+ resolution: {integrity: sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [android]
+
+ '@esbuild/android-arm@0.25.12':
+ resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==}
+ engines: {node: '>=18'}
+ cpu: [arm]
+ os: [android]
+
+ '@esbuild/android-arm@0.28.1':
+ resolution: {integrity: sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==}
+ engines: {node: '>=18'}
+ cpu: [arm]
+ os: [android]
+
+ '@esbuild/android-x64@0.25.12':
+ resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [android]
+
+ '@esbuild/android-x64@0.28.1':
+ resolution: {integrity: sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [android]
+
+ '@esbuild/darwin-arm64@0.25.12':
+ resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@esbuild/darwin-arm64@0.28.1':
+ resolution: {integrity: sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@esbuild/darwin-x64@0.25.12':
+ resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@esbuild/darwin-x64@0.28.1':
+ resolution: {integrity: sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@esbuild/freebsd-arm64@0.25.12':
+ resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [freebsd]
+
+ '@esbuild/freebsd-arm64@0.28.1':
+ resolution: {integrity: sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [freebsd]
+
+ '@esbuild/freebsd-x64@0.25.12':
+ resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@esbuild/freebsd-x64@0.28.1':
+ resolution: {integrity: sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@esbuild/linux-arm64@0.25.12':
+ resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@esbuild/linux-arm64@0.28.1':
+ resolution: {integrity: sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@esbuild/linux-arm@0.25.12':
+ resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==}
+ engines: {node: '>=18'}
+ cpu: [arm]
+ os: [linux]
+
+ '@esbuild/linux-arm@0.28.1':
+ resolution: {integrity: sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==}
+ engines: {node: '>=18'}
+ cpu: [arm]
+ os: [linux]
+
+ '@esbuild/linux-ia32@0.25.12':
+ resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==}
+ engines: {node: '>=18'}
+ cpu: [ia32]
+ os: [linux]
+
+ '@esbuild/linux-ia32@0.28.1':
+ resolution: {integrity: sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==}
+ engines: {node: '>=18'}
+ cpu: [ia32]
+ os: [linux]
+
+ '@esbuild/linux-loong64@0.25.12':
+ resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==}
+ engines: {node: '>=18'}
+ cpu: [loong64]
+ os: [linux]
+
+ '@esbuild/linux-loong64@0.28.1':
+ resolution: {integrity: sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==}
+ engines: {node: '>=18'}
+ cpu: [loong64]
+ os: [linux]
+
+ '@esbuild/linux-mips64el@0.25.12':
+ resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==}
+ engines: {node: '>=18'}
+ cpu: [mips64el]
+ os: [linux]
+
+ '@esbuild/linux-mips64el@0.28.1':
+ resolution: {integrity: sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==}
+ engines: {node: '>=18'}
+ cpu: [mips64el]
+ os: [linux]
+
+ '@esbuild/linux-ppc64@0.25.12':
+ resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==}
+ engines: {node: '>=18'}
+ cpu: [ppc64]
+ os: [linux]
+
+ '@esbuild/linux-ppc64@0.28.1':
+ resolution: {integrity: sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==}
+ engines: {node: '>=18'}
+ cpu: [ppc64]
+ os: [linux]
+
+ '@esbuild/linux-riscv64@0.25.12':
+ resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==}
+ engines: {node: '>=18'}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@esbuild/linux-riscv64@0.28.1':
+ resolution: {integrity: sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==}
+ engines: {node: '>=18'}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@esbuild/linux-s390x@0.25.12':
+ resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==}
+ engines: {node: '>=18'}
+ cpu: [s390x]
+ os: [linux]
+
+ '@esbuild/linux-s390x@0.28.1':
+ resolution: {integrity: sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==}
+ engines: {node: '>=18'}
+ cpu: [s390x]
+ os: [linux]
+
+ '@esbuild/linux-x64@0.25.12':
+ resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [linux]
+
+ '@esbuild/linux-x64@0.28.1':
+ resolution: {integrity: sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [linux]
+
+ '@esbuild/netbsd-arm64@0.25.12':
+ resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [netbsd]
+
+ '@esbuild/netbsd-arm64@0.28.1':
+ resolution: {integrity: sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [netbsd]
+
+ '@esbuild/netbsd-x64@0.25.12':
+ resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [netbsd]
+
+ '@esbuild/netbsd-x64@0.28.1':
+ resolution: {integrity: sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [netbsd]
+
+ '@esbuild/openbsd-arm64@0.25.12':
+ resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [openbsd]
+
+ '@esbuild/openbsd-arm64@0.28.1':
+ resolution: {integrity: sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [openbsd]
+
+ '@esbuild/openbsd-x64@0.25.12':
+ resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [openbsd]
+
+ '@esbuild/openbsd-x64@0.28.1':
+ resolution: {integrity: sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [openbsd]
+
+ '@esbuild/openharmony-arm64@0.25.12':
+ resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [openharmony]
+
+ '@esbuild/openharmony-arm64@0.28.1':
+ resolution: {integrity: sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [openharmony]
+
+ '@esbuild/sunos-x64@0.25.12':
+ resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [sunos]
+
+ '@esbuild/sunos-x64@0.28.1':
+ resolution: {integrity: sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [sunos]
+
+ '@esbuild/win32-arm64@0.25.12':
+ resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@esbuild/win32-arm64@0.28.1':
+ resolution: {integrity: sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@esbuild/win32-ia32@0.25.12':
+ resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==}
+ engines: {node: '>=18'}
+ cpu: [ia32]
+ os: [win32]
+
+ '@esbuild/win32-ia32@0.28.1':
+ resolution: {integrity: sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==}
+ engines: {node: '>=18'}
+ cpu: [ia32]
+ os: [win32]
+
+ '@esbuild/win32-x64@0.25.12':
+ resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [win32]
+
+ '@esbuild/win32-x64@0.28.1':
+ resolution: {integrity: sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [win32]
+
+ '@eslint-community/eslint-utils@4.10.1':
+ resolution: {integrity: sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==}
+ engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+ peerDependencies:
+ eslint: ^6.0.0 || ^7.0.0 || >=8.0.0
+
+ '@eslint-community/regexpp@4.12.2':
+ resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==}
+ engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
+
+ '@eslint/config-array@0.21.2':
+ resolution: {integrity: sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@eslint/config-helpers@0.4.2':
+ resolution: {integrity: sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@eslint/core@0.15.2':
+ resolution: {integrity: sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@eslint/core@0.17.0':
+ resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@eslint/eslintrc@3.3.6':
+ resolution: {integrity: sha512-l2Ul9PrHsPCKcEY/ac7VgFj9D80C7S68sOKc618SyHDPK36s1XcFebXY0iTzUVn4Yq+YbwvSnDmCz9yxjX+QrA==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@eslint/js@9.39.5':
+ resolution: {integrity: sha512-QywQuszQh77pIXCsq998c8hbhSTI/azTty1Z6N53dmAudKHhy573j3yvRLsX2BSp8YpLtoCEG8E9DJe+8zUh4A==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@eslint/object-schema@2.1.7':
+ resolution: {integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@eslint/plugin-kit@0.3.5':
+ resolution: {integrity: sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@eslint/plugin-kit@0.4.1':
+ resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@humanfs/core@0.19.2':
+ resolution: {integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==}
+ engines: {node: '>=18.18.0'}
+
+ '@humanfs/node@0.16.8':
+ resolution: {integrity: sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==}
+ engines: {node: '>=18.18.0'}
+
+ '@humanfs/types@0.15.0':
+ resolution: {integrity: sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==}
+ engines: {node: '>=18.18.0'}
+
+ '@humanwhocodes/module-importer@1.0.1':
+ resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==}
+ engines: {node: '>=12.22'}
+
+ '@humanwhocodes/retry@0.4.3':
+ resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==}
+ engines: {node: '>=18.18'}
+
+ '@isaacs/cliui@8.0.2':
+ resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
+ engines: {node: '>=12'}
+
+ '@istanbuljs/load-nyc-config@1.1.0':
+ resolution: {integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==}
+ engines: {node: '>=8'}
+
+ '@istanbuljs/schema@0.1.6':
+ resolution: {integrity: sha512-+Sg6GCR/wy1oSmQDFq4LQDAhm3ETKnorxN+y5nbLULOR3P0c14f2Wurzj3/xqPXtasLFfHd5iRFQ7AJt4KH2cw==}
+ engines: {node: '>=8'}
+
+ '@jest/console@29.7.0':
+ resolution: {integrity: sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+
+ '@jest/console@30.4.1':
+ resolution: {integrity: sha512-v3bhyxUh9Hgmo5p6hAOXe14/R3ZxZDOsvHleh4B07z3m/x4/ngPUXEm9XwK4sF4u+f+P2ORb0Ge+MgpaqRMVDA==}
+ engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
+
+ '@jest/core@29.7.0':
+ resolution: {integrity: sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+ peerDependencies:
+ node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0
+ peerDependenciesMeta:
+ node-notifier:
+ optional: true
+
+ '@jest/core@30.4.2':
+ resolution: {integrity: sha512-TZJA6cPJUFxoWhxaLo8t0VX/MZX2wPWr0uIDvLSHIvN4gu9h02vSzqI2kBADG1ExqQlC+cY09xKMSreivvrChQ==}
+ 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
+
+ '@jest/diff-sequences@30.4.0':
+ resolution: {integrity: sha512-zOpzlfUs45l6u7jm39qr87JCHUDsaeCtvL+kQe/Vn9jSnRB4/5IPXISm0h9I1vZW/o00Kn4UTJ2MOlhnUGwv3g==}
+ engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
+
+ '@jest/environment-jsdom-abstract@30.4.1':
+ resolution: {integrity: sha512-dSlKrqug3siYNHVnjwIldShY12wAH3spwRltO/+8VOjg0X+xEq7vOs3DbBs4LRKsu7OH+NUb9kuZUNBF9Ho3TA==}
+ engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
+ peerDependencies:
+ canvas: ^3.0.0
+ jsdom: '*'
+ peerDependenciesMeta:
+ canvas:
+ optional: true
+
+ '@jest/environment@29.7.0':
+ resolution: {integrity: sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+
+ '@jest/environment@30.4.1':
+ resolution: {integrity: sha512-AK9yNRqgKxiabqMoe4oW+3/TSSeV8vkdC7BGaxZdU0AFXfOpofTLqdru2GXKZghP3sdgwE9XXpnVwfZ8JnFV4w==}
+ engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
+
+ '@jest/expect-utils@29.7.0':
+ resolution: {integrity: sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+
+ '@jest/expect-utils@30.4.1':
+ resolution: {integrity: sha512-ZBn5CglH8fBsQsvs4VWNzD4aWfUYks+IdOOQU3MEK71ol/BcVm+P+rtb1KpiFBpSWSCE27uOahyyf1vfqOVbcQ==}
+ engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
+
+ '@jest/expect@29.7.0':
+ resolution: {integrity: sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+
+ '@jest/expect@30.4.1':
+ resolution: {integrity: sha512-ginrj6TMgh2GshLUGCjO94Ptx9HhdZA/I6A9iUfyeLKFtdAjnKzHDgzgP9HYQgbxM1lbXScQ2eUBz2lGeVDPWA==}
+ engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
+
+ '@jest/fake-timers@29.7.0':
+ resolution: {integrity: sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+
+ '@jest/fake-timers@30.4.1':
+ resolution: {integrity: sha512-iW5umdmfPeWzehrVhugFQZqCchSCud5S1l2YT0O9ZhjRR0ExclANDZkiSBwzqtnlOn0J1JXvO+HZ6rkuyOVOgQ==}
+ engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
+
+ '@jest/get-type@30.1.0':
+ resolution: {integrity: sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA==}
+ engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
+
+ '@jest/globals@29.7.0':
+ resolution: {integrity: sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+
+ '@jest/globals@30.4.1':
+ resolution: {integrity: sha512-ZbuY4cmXC8DkxYjfvT2DbcHWL2T6vmsMhXCDcmTB2T0y0gaezBI77ufq5ZAIdcRkYZ7NEQEDg1xFeKbxUJ5v5Q==}
+ engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
+
+ '@jest/pattern@30.4.0':
+ resolution: {integrity: sha512-RAWn3+f9u8BsHijKJ71uHcFp6vmyEt6VvoWXkl6hKF3qVIuWNmudVjg12DlBPGup/frIl5UcUlH5HfEuvHpEXg==}
+ engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
+
+ '@jest/reporters@29.7.0':
+ resolution: {integrity: sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+ peerDependencies:
+ node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0
+ peerDependenciesMeta:
+ node-notifier:
+ optional: true
+
+ '@jest/reporters@30.4.1':
+ resolution: {integrity: sha512-/SnkPCzEQpUaBH81kjdEdDdo2WZl5hxw+BmLDGWjRkm8o7XlhjwsU36cqwe5PGBE5WYpBvDzRSdXx9rbGuJtNA==}
+ 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
+
+ '@jest/schemas@29.6.3':
+ resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+
+ '@jest/schemas@30.4.1':
+ resolution: {integrity: sha512-i6b4qw5qnP8c5FEeBJg/uZQ4ddrkN6Ca8qISJh0pr7a5hfn3h3v5x60BEbOC7OYAGZNMs1LfFLwnW2CuK8F57Q==}
+ engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
+
+ '@jest/snapshot-utils@30.4.1':
+ resolution: {integrity: sha512-ObY4ljvQ95mt6iwKtVLetR/4yXiAgl3H4nJxhztr0MTjrN97TwDYrnCp/kF60Ec9HdhkWTHSu+Hg05aXfngpOA==}
+ engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
+
+ '@jest/source-map@29.6.3':
+ resolution: {integrity: sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+
+ '@jest/source-map@30.0.1':
+ resolution: {integrity: sha512-MIRWMUUR3sdbP36oyNyhbThLHyJ2eEDClPCiHVbrYAe5g3CHRArIVpBw7cdSB5fr+ofSfIb2Tnsw8iEHL0PYQg==}
+ engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
+
+ '@jest/test-result@29.7.0':
+ resolution: {integrity: sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+
+ '@jest/test-result@30.4.1':
+ resolution: {integrity: sha512-/ZG7pgEiOmmWkN9TplKbOu4id2N5lh7FHwRwlkgBVAzGdRH+OkkQ8wX/kIxg4zmd3ZQvAL1RwL2yWsvNYYECTw==}
+ engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
+
+ '@jest/test-sequencer@29.7.0':
+ resolution: {integrity: sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+
+ '@jest/test-sequencer@30.4.1':
+ resolution: {integrity: sha512-PeYE+4td5rKjoRPxztObrXU+H8hsjZfxKMXOcmrr34JerSyB/ROOxbbicz8B7A5j9R9VayDnVPvBmedqCsFCdw==}
+ engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
+
+ '@jest/transform@29.7.0':
+ resolution: {integrity: sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+
+ '@jest/transform@30.4.1':
+ resolution: {integrity: sha512-Wz0LyktlTvRefoymh+n64hQ84KNXsRGcwdoZ8CSa0Ea+fgYcHZlnk+hDP7v2MS7il2bQ5uTEIxf4/NNfhMN4KQ==}
+ engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
+
+ '@jest/types@29.6.3':
+ resolution: {integrity: sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+
+ '@jest/types@30.4.1':
+ resolution: {integrity: sha512-f1x/vJXIfjOlEmejYpbkbgw1gOqpPECwMvMEtBqe47j7H2Hg8h8w3o3ikhSXq3MI15kg+oQ0exWO0uCtTNJLoQ==}
+ engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
+
+ '@jridgewell/gen-mapping@0.3.13':
+ resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==}
+
+ '@jridgewell/remapping@2.3.5':
+ resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==}
+
+ '@jridgewell/resolve-uri@3.1.2':
+ resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
+ engines: {node: '>=6.0.0'}
+
+ '@jridgewell/sourcemap-codec@1.5.5':
+ resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
+
+ '@jridgewell/trace-mapping@0.3.31':
+ resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
+
+ '@jridgewell/trace-mapping@0.3.9':
+ resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==}
+
+ '@napi-rs/wasm-runtime@1.2.2':
+ resolution: {integrity: sha512-JfB4kuJQjaoHuCTseIINHtHWeJnvgEcxjwA5t/Y00ZgaOO1Crz3fjT/p8kT28zA/Caz7oiUMn3d6H2yOVCVwuw==}
+ engines: {node: ^20.19.0 || ^22.13.0 || >=23.5.0}
+ peerDependencies:
+ '@emnapi/core': ^1.7.1 || ^2.0.0-alpha.3
+ '@emnapi/runtime': ^1.7.1 || ^2.0.0-alpha.3
+
+ '@nodelib/fs.scandir@2.1.5':
+ resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
+ engines: {node: '>= 8'}
+
+ '@nodelib/fs.stat@2.0.5':
+ resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==}
+ engines: {node: '>= 8'}
+
+ '@nodelib/fs.walk@1.2.8':
+ resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
+ engines: {node: '>= 8'}
+
+ '@pkgjs/parseargs@0.11.0':
+ resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
+ engines: {node: '>=14'}
+
+ '@pkgr/core@0.3.6':
+ resolution: {integrity: sha512-SEeaJLb3qBNF/OaXnaR1NmmBbFYk1zC0ZH/52fATcRPLFg/p791YrcyFFy44Bo9sLaGuSuLp5Q6axbb/O+v/RA==}
+ engines: {node: ^14.18.0 || >=16.0.0}
+
+ '@sinclair/typebox@0.27.12':
+ resolution: {integrity: sha512-hhyNJ+nbR6ZR7pToHvllEFun9TL0sbL+tk/ON75lo+Xas054uez98qRbsuNt7MBCyZKK4+8Yli/OAGZhmfBZ/g==}
+
+ '@sinclair/typebox@0.34.52':
+ resolution: {integrity: sha512-XiMQh7qqVlxZzcVD+kkGMNGMzcTrDMLWI7S4x7z1MkCkbDPrekpZXEUK0eZqZFMuHQg2a2DZOcDIh9o5v3Gonw==}
+
+ '@sinonjs/commons@3.0.1':
+ resolution: {integrity: sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==}
+
+ '@sinonjs/fake-timers@10.3.0':
+ resolution: {integrity: sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==}
+
+ '@sinonjs/fake-timers@11.2.2':
+ resolution: {integrity: sha512-G2piCSxQ7oWOxwGSAyFHfPIsyeJGXYtc6mFbnFA+kRXkiEnTl8c/8jul2S329iFBnDI9HGoeWWAZvuvOkZccgw==}
+
+ '@sinonjs/fake-timers@15.4.0':
+ resolution: {integrity: sha512-DsG+8/LscQIQg68J6Ef3dv10u6nVyetYn923s3/sus5eaGfTo1of5WMZSLf0UJc9KDuKPilPH0UDJCjvNbDNCA==}
+
+ '@sinonjs/samsam@8.0.3':
+ resolution: {integrity: sha512-hw6HbX+GyVZzmaYNh82Ecj1vdGZrqVIn/keDTg63IgAwiQPO+xCz99uG6Woqgb4tM0mUiFENKZ4cqd7IX94AXQ==}
+
+ '@smithy/core@3.31.1':
+ resolution: {integrity: sha512-CyogUINxvi7C7LDsh8Syo6hVJOT9ckz4rG8dRZfTJ8r91HkMY59PnNooaj7WcHyxEkxPfBAmbgztZU+xTo76lg==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/credential-provider-imds@4.4.16':
+ resolution: {integrity: sha512-QfuLWAkLzptffFW980AFeHZFdqds2B64rpEd3uJ6lgs3xVn9QegGMUgUcj+4d7dRrAsya3r58ZKpku97WcFb4w==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/fetch-http-handler@5.6.13':
+ resolution: {integrity: sha512-4fW86pEUOMbrD5nkbyl/tTvPHHWJFbuB2odl6ps9lWfHoXf9HWh3Q/Smh59qH1g7+c/BSZghX6bbUk4gsiMs8A==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/middleware-compression@4.5.16':
+ resolution: {integrity: sha512-VwD+5B6lkieGIGxFx4pSXFZpevLJj3fl8JayvGHojnS5AblrvVJfz1DvCVJEg3XrMCBvs6HZa1znW9Is4xeFcg==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/node-http-handler@4.9.13':
+ resolution: {integrity: sha512-Nmd/Nl35zfYrd+a6OO2cDJb3GPh9bgTjIUhcM+JFfjpp8/osCgboDV5nCT1I01Pv6R13eSKDKLSoVa5ZB6Zsfw==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/signature-v4@5.6.12':
+ resolution: {integrity: sha512-I6KLtq3H0qqSuV9vLglfi8puHqzygzWHOnI4z/Rdoo+q50vvo18vBRdPAvvEtcaKROz7Zn6qnPa14kRfPH6PcQ==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/types@4.16.1':
+ resolution: {integrity: sha512-0JFs3V2y2M9tKW5na/qxe69Zv+uxLMO7QBbhxF/FHu/Gp2NFZAAL9tWl9PU02xxo07pb3G9FTyjNc6D5uZrJIg==}
+ engines: {node: '>=18.0.0'}
+
+ '@so-ric/colorspace@1.1.6':
+ resolution: {integrity: sha512-/KiKkpHNOBgkFJwu9sh48LkHSMYGyuTcSFK/qMBdnOAlrRJzRSXAOFB5qwzaVQuDl8wAvHVMkaASQDReTahxuw==}
+
+ '@standard-schema/spec@1.1.0':
+ resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==}
+
+ '@stylistic/eslint-plugin-ts@4.4.1':
+ resolution: {integrity: sha512-2r6cLcmdF6til66lx8esBYvBvsn7xCmLT50gw/n1rGGlTq/OxeNjBIh4c3VEaDGMa/5TybrZTia6sQUHdIWx1w==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ eslint: '>=9.0.0'
+
+ '@stylistic/eslint-plugin@3.1.0':
+ resolution: {integrity: sha512-pA6VOrOqk0+S8toJYhQGv2MWpQQR0QpeUo9AhNkC49Y26nxBQ/nH1rta9bUU1rPw2fJ1zZEMV5oCX5AazT7J2g==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ eslint: '>=8.40.0'
+
+ '@tsconfig/node10@1.0.12':
+ resolution: {integrity: sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==}
+
+ '@tsconfig/node12@1.0.11':
+ resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==}
+
+ '@tsconfig/node14@1.0.3':
+ resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==}
+
+ '@tsconfig/node16@1.0.4':
+ resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==}
+
+ '@tsconfig/node22@22.0.5':
+ resolution: {integrity: sha512-hLf2ld+sYN/BtOJjHUWOk568dvjFQkHnLNa6zce25GIH+vxKfvTgm3qpaH6ToF5tu/NN0IH66s+Bb5wElHrLcw==}
+
+ '@turbo/darwin-64@2.10.8':
+ resolution: {integrity: sha512-po+7rfJfUnFXjWlcoN2RwhErgzCdRtBc1T26vYPcywHlggmCQiQe1uWaE4j+BibI2uY9/2pDoFzMN0rmSaPFOw==}
+ cpu: [x64]
+ os: [darwin]
+
+ '@turbo/darwin-arm64@2.10.8':
+ resolution: {integrity: sha512-+zB2btDJ00lnPRuqOvpVvgl4x34k/djZQGZTTCfjn7JgNCl8QFY5Njo5+dqkY1g/+9gbbsnAvWm9CmJg9ebcXA==}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@turbo/linux-64@2.10.8':
+ resolution: {integrity: sha512-K1dxqiVisyN7cViVsfQLs6xscQbYuI8aO2nbUhFURDACgEDfZRdP/b4CCxeosBJpcMfhYyiibWqJorCnvz9kKg==}
+ cpu: [x64]
+ os: [android, linux]
+
+ '@turbo/linux-arm64@2.10.8':
+ resolution: {integrity: sha512-Gi77ibVnrE1fEmvr+/wBD/yvRqhwp/RQuCp2+//lv1U1wNFFyVg0V7Wj8FG9FXPFAw5QHReo8rxc9+wBSDZjzA==}
+ cpu: [arm64]
+ os: [android, linux]
+
+ '@turbo/windows-64@2.10.8':
+ resolution: {integrity: sha512-znnLO1haJPYTHoKMKwlAvlkjRiYbbhBzME6wIGaMd+fwir23U6jVd1ecaTWWi1fbnRVqxMfgDBKseQ/hLKb83g==}
+ cpu: [x64]
+ os: [win32]
+
+ '@turbo/windows-arm64@2.10.8':
+ resolution: {integrity: sha512-VN30vh3b3Czh2WzYHNTfF1FE0YMZ5aHsLO8dBMGHJewA6792wX6iJR8ZxlzFW6WdOu0gEAKIvlYhfyT81Wkm4Q==}
+ cpu: [arm64]
+ os: [win32]
+
+ '@tybys/wasm-util@0.10.3':
+ resolution: {integrity: sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==}
+
+ '@types/aws-lambda@8.10.162':
+ resolution: {integrity: sha512-Fn658grtLOci1oxi1391vvDWJRKNGWRSqfxRkmN/Iy3c0tQH1USMKEXcPYHLvope+ZgTFocx9FRQJx1muBL6qw==}
+
+ '@types/babel__core@7.20.5':
+ resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==}
+
+ '@types/babel__generator@7.27.0':
+ resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==}
+
+ '@types/babel__template@7.4.4':
+ resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==}
+
+ '@types/babel__traverse@7.28.0':
+ resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==}
+
+ '@types/chai@5.2.3':
+ resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==}
+
+ '@types/deep-eql@4.0.2':
+ resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==}
+
+ '@types/estree@1.0.9':
+ resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==}
+
+ '@types/graceful-fs@4.1.9':
+ resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==}
+
+ '@types/istanbul-lib-coverage@2.0.6':
+ resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==}
+
+ '@types/istanbul-lib-report@3.0.3':
+ resolution: {integrity: sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==}
+
+ '@types/istanbul-reports@3.0.4':
+ resolution: {integrity: sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==}
+
+ '@types/jest@29.5.14':
+ resolution: {integrity: sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==}
+
+ '@types/jest@30.0.0':
+ resolution: {integrity: sha512-XTYugzhuwqWjws0CVz8QpM36+T+Dz5mTEBKhNs/esGLnCIlGdRy+Dq78NRjd7ls7r8BC8ZRMOrKlkO1hU0JOwA==}
+
+ '@types/jsdom@21.1.7':
+ resolution: {integrity: sha512-yOriVnggzrnQ3a9OKOCxaVuSug3w3/SbOj5i7VwXWZEyUNl3bLF9V3MfxGbZKuwqJOQyRfqXyROBB1CoZLFWzA==}
+
+ '@types/json-schema@7.0.15':
+ resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
+
+ '@types/jsonwebtoken@9.0.10':
+ resolution: {integrity: sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==}
+
+ '@types/mock-fs@4.13.4':
+ resolution: {integrity: sha512-mXmM0o6lULPI8z3XNnQCpL0BGxPwx1Ul1wXYEPBGl4efShyxW2Rln0JOPEWGyZaYZMM6OVXM/15zUuFMY52ljg==}
+
+ '@types/ms@2.1.0':
+ resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==}
+
+ '@types/node@24.13.3':
+ resolution: {integrity: sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q==}
+
+ '@types/qs@6.15.1':
+ resolution: {integrity: sha512-GZHUBZR9hckSUhrxmp1nG6NwdpM9fCunJwyThLW1X3AyHgd9IlHb6VANpQQqDr2o/qQp6McZ3y/IA2rVzKzSbw==}
+
+ '@types/sinon@17.0.4':
+ resolution: {integrity: sha512-RHnIrhfPO3+tJT0s7cFaXGZvsL4bbR3/k7z3P312qMS4JaS2Tk+KiwiLx1S0rQ56ERj00u1/BtdyVd0FY+Pdew==}
+
+ '@types/sinonjs__fake-timers@15.0.1':
+ resolution: {integrity: sha512-Ko2tjWJq8oozHzHV+reuvS5KYIRAokHnGbDwGh/J64LntgpbuylF74ipEL24HCyRjf9FOlBiBHWBR1RlVKsI1w==}
+
+ '@types/stack-utils@2.0.3':
+ resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==}
+
+ '@types/tough-cookie@4.0.5':
+ resolution: {integrity: sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==}
+
+ '@types/triple-beam@1.3.5':
+ resolution: {integrity: sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==}
+
+ '@types/yargs-parser@21.0.3':
+ resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==}
+
+ '@types/yargs@17.0.35':
+ resolution: {integrity: sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==}
+
+ '@typescript-eslint/eslint-plugin@8.65.0':
+ resolution: {integrity: sha512-IEgob78X12rHpUmtcwFsXhZdVGJtwTVP8FiCLZkR6GlYVrl2PcuB+KhCE5BlVC/eQpQnu8WXRtkHZuPar+gCRA==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ '@typescript-eslint/parser': ^8.65.0
+ eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
+ typescript: '>=4.8.4 <6.1.0'
+
+ '@typescript-eslint/parser@8.65.0':
+ resolution: {integrity: sha512-CZ4nMxWwgu1HEEFNkeaCptra9QCtkmKdgf3sWh1rl1trIhmxLilgTV4cwcbQ4wemnT4sWQN8CaKOmdYx+g2gMA==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
+ typescript: '>=4.8.4 <6.1.0'
+
+ '@typescript-eslint/project-service@8.65.0':
+ resolution: {integrity: sha512-SxnPhbTsGahizDgbu7oqFH/xVtzIqMd/s+WtnSxNxJZJpLbdT5IPdzg8EZxO3+PoKahXmwJLeNQOpKJb3/bi7Q==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ typescript: '>=4.8.4 <6.1.0'
+
+ '@typescript-eslint/scope-manager@8.65.0':
+ resolution: {integrity: sha512-Esbl8OSYiVxBokYgWPf7VVWg/BE798wXhimnn9ML9Pt5qoDf8bfQlgjlKXR/k98+AcNzlLKYrpCcrcuZ9DZLgg==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@typescript-eslint/tsconfig-utils@8.65.0':
+ resolution: {integrity: sha512-j6GzGqCiRdA7Qhur2VVmKZAkBLfnHFQfx4TaJGL9RMveZqCo48jSHHO0DTgizEnGhtWnqmbtCUSrqSkdiY/0Hg==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ typescript: '>=4.8.4 <6.1.0'
+
+ '@typescript-eslint/type-utils@8.65.0':
+ resolution: {integrity: sha512-YjaZ7PRI5qY7ax2L3PbvX0rRyGtipAReCWs0mhhDBHjH/vl0g0BonaGXrKdKpMbIIsMIwDgbk/xzkBTyAltS5g==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
+ typescript: '>=4.8.4 <6.1.0'
+
+ '@typescript-eslint/types@8.65.0':
+ resolution: {integrity: sha512-JSSwWNy+H0E/01jJEM+hrX6N0OFDzFzeIhHFSAS01tlVaevpG8cFyYRPhS5yjGOvBUx3sqQHVMjCL1CAZZMxBg==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@typescript-eslint/typescript-estree@8.65.0':
+ resolution: {integrity: sha512-JboAE2swaYt4tb1fHhHTABE2K+OLy09XfcTbhnk4Pw96f9dd2e9iYsJ28gBggHlo5z5x1rkyWvcPoTuNTd4oGg==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ typescript: '>=4.8.4 <6.1.0'
+
+ '@typescript-eslint/utils@8.65.0':
+ resolution: {integrity: sha512-gXiwIHsYreboxeJucHKPvgwl7dXt50mF8s1/c00cP/WoVTyWKFdtfhRWwZiXYFU5H2O8vVoSLNrexFZjYS/SGA==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
+ typescript: '>=4.8.4 <6.1.0'
+
+ '@typescript-eslint/visitor-keys@8.65.0':
+ resolution: {integrity: sha512-8C71BQkGjiMmXtop7pHVJu1l2NNShFdkCyD6a2ezzs5vU/L3LRtb69EtcteFwz0mYMPzIgOw0n6OV4VBUWZd7A==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@ungap/structured-clone@1.3.3':
+ resolution: {integrity: sha512-60YRaenCQcVjYEKOcG824+DRGGIQ3VKErcBoAEDJZz5bKIs2ZG+X/H9Nk+Q6EVkwJk5QNApxbrc5QtBSwtrXAg==}
+
+ '@unrs/resolver-binding-android-arm-eabi@1.12.2':
+ resolution: {integrity: sha512-g5T90pqg1bo/7mytQx6F4iBNC0Wsh9cu+z9veDbFjc7HjpesJFWD7QMS0NGStXM075+7dJPPVvBbpZlnrdpi/w==}
+ cpu: [arm]
+ os: [android]
+
+ '@unrs/resolver-binding-android-arm64@1.12.2':
+ resolution: {integrity: sha512-YGCRZv/9GLhwmz6mYDeTsm/92BAyR28l6c2ReweVW5pWgfsitWLY8upvfRlGdoyD8HjeTHSYJWyZGD4KJA/nFQ==}
+ cpu: [arm64]
+ os: [android]
+
+ '@unrs/resolver-binding-darwin-arm64@1.12.2':
+ resolution: {integrity: sha512-u9DiNT1auQMO20A9SyTuG3wUgQWB9Z7KjAg0uFuCDR1FsAY8A0CG2S6JpHS1xwm/w1G08bjXZDcyOCjv1WAm2w==}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@unrs/resolver-binding-darwin-x64@1.12.2':
+ resolution: {integrity: sha512-f7rPLi/T1HVKZu/u6t87lroib16n8vrSzcyxI7lg4BGO9UF26KhQL44sd9eOUgrTYhvRXtWOIZT5PejdPyJfUA==}
+ cpu: [x64]
+ os: [darwin]
+
+ '@unrs/resolver-binding-freebsd-x64@1.12.2':
+ resolution: {integrity: sha512-BpcOjWCJub6nRZUS2zA20pmLvjtqAtGejETaIyRLiZiQf++cbrjltLA5NN/xaXfqeOBOSlMFbemIl5/S5tljmg==}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@unrs/resolver-binding-linux-arm-gnueabihf@1.12.2':
+ resolution: {integrity: sha512-vZTDvdSISZjJx66OzJqtsOhzifbqRjbmI1Mnu49fQDwog5GtDI4QidRiEAYbZCRj9C8YZEW+3ZjqsyS9GR4k2A==}
+ cpu: [arm]
+ os: [linux]
+
+ '@unrs/resolver-binding-linux-arm-musleabihf@1.12.2':
+ resolution: {integrity: sha512-BiPI+IrIlwcW4nLLMM21+B1dFPzd55yAVgVGrdgDjNef+ch03GdxrcyaIz8X9SsQirh/kCQ7mviyWlMxdh2D7g==}
+ cpu: [arm]
+ os: [linux]
+
+ '@unrs/resolver-binding-linux-arm64-gnu@1.12.2':
+ resolution: {integrity: sha512-zJc0H99FEPoFfSrNpa91HYfxzfAJCr502oxNK1cfdC9hlaFI43RT+JFCann9JUgZmLzzntChHyn13Sgn9ljHNg==}
+ cpu: [arm64]
+ os: [linux]
+ libc: [glibc]
+
+ '@unrs/resolver-binding-linux-arm64-musl@1.12.2':
+ resolution: {integrity: sha512-KQ3Lki6l+Pz1k/eBipN41ES+YUK30beLGb9YqcB1O542cyLCNE6GaxrfcY3T6EezmGGk84wb5XyO9loTM9tkcA==}
+ cpu: [arm64]
+ os: [linux]
+ libc: [musl]
+
+ '@unrs/resolver-binding-linux-loong64-gnu@1.12.2':
+ resolution: {integrity: sha512-3SJGEh1DborhG6pyxvhPzCT4bbSIVihsvgJc13P1bHG7KLdNDaF9T3gsTwFc7Jw/5Y5/iWOjkEx7Zy0NvCGX3Q==}
+ cpu: [loong64]
+ os: [linux]
+ libc: [glibc]
+
+ '@unrs/resolver-binding-linux-loong64-musl@1.12.2':
+ resolution: {integrity: sha512-jiuG/Obbel7uw1PwHNFfrkiKhLAF6mnyZ6aWlOAVN9WqKm8v0OFGnciJIHu8+CMvXLQ8AD51LPzAoUfT21D5Ew==}
+ cpu: [loong64]
+ os: [linux]
+ libc: [musl]
+
+ '@unrs/resolver-binding-linux-ppc64-gnu@1.12.2':
+ resolution: {integrity: sha512-q7xRvVpmcfeL+LlZg8Pbbo6QaTZwDU5BaGZbwfhkEsXJn3Was8xYfE0RBH266xZt0rM6B7i8xAYIvjthuUIWHg==}
+ cpu: [ppc64]
+ os: [linux]
+ libc: [glibc]
+
+ '@unrs/resolver-binding-linux-riscv64-gnu@1.12.2':
+ resolution: {integrity: sha512-0CVdx6lcnT3Q9inOH8tsMIOJ6ImndllMjqJHg8RLVdB7Vq4SfkEXl9mCSsVNuNA4MCYycRicCUxPCabVHJRr6A==}
+ cpu: [riscv64]
+ os: [linux]
+ libc: [glibc]
+
+ '@unrs/resolver-binding-linux-riscv64-musl@1.12.2':
+ resolution: {integrity: sha512-iOwlRo9vnp6R6ohHQS11n0NnfdXx/omhkocmIfaPRpQhKZ+3BDMkkdRVh53qjkFkpPddf+FETA28NwGN7l5l+w==}
+ cpu: [riscv64]
+ os: [linux]
+ libc: [musl]
+
+ '@unrs/resolver-binding-linux-s390x-gnu@1.12.2':
+ resolution: {integrity: sha512-HYJtLfXq94q8iZNFT1lknx258wlkkWhZeUXJRqzKBBUJ00CvZ+N33zgbCqimLjsyw5Va6uUxhVa12mI+kaveEw==}
+ cpu: [s390x]
+ os: [linux]
+ libc: [glibc]
+
+ '@unrs/resolver-binding-linux-x64-gnu@1.12.2':
+ resolution: {integrity: sha512-mPsUhunKKDih5O96Y6enDQyHc1SqBPlY1E/SfMWDM3EdJ95Z9CArPeCVwCCqbP45ljvivdEk8Fxn+SIb1rDAJQ==}
+ cpu: [x64]
+ os: [linux]
+ libc: [glibc]
+
+ '@unrs/resolver-binding-linux-x64-musl@1.12.2':
+ resolution: {integrity: sha512-azrt6+5ydLd8Vt210AAFis/lZevSfPw93EJRIJG+xPu4WCJ8K0kppCTpMyLPcKT7H15M4Jnt2tMp5bOvCkRC6A==}
+ cpu: [x64]
+ os: [linux]
+ libc: [musl]
+
+ '@unrs/resolver-binding-openharmony-arm64@1.12.2':
+ resolution: {integrity: sha512-YZ9hP4O0X9PQb8eO980qmLNGH4zT3I9+SZTdt0Pr0YyuGQhYKoOZkV02VzrzyOZJ5xIJ3UFIenKkUkGg8GjgWQ==}
+ cpu: [arm64]
+ os: [openharmony]
+
+ '@unrs/resolver-binding-wasm32-wasi@1.12.2':
+ resolution: {integrity: sha512-tYFDIkMxSflfEc/h92ZWNsZlHSwgimbNHSO3PL2JWQHfCuC2q316jMyYU9TIWZsFK2bQwyK5VAdYgn8ygPj69A==}
+ engines: {node: '>=14.0.0'}
+ cpu: [wasm32]
+
+ '@unrs/resolver-binding-win32-arm64-msvc@1.12.2':
+ resolution: {integrity: sha512-qzNyg3xL0VPQmCaUh+N5jSitce6k+uCBfMDesWRnlULOZaqUkaJ0ybdT+UqlAWJoQjuqfIU/0Ptx9bteN4D82g==}
+ cpu: [arm64]
+ os: [win32]
+
+ '@unrs/resolver-binding-win32-ia32-msvc@1.12.2':
+ resolution: {integrity: sha512-WD9sY00OfpHVGfsnHZoA8jVT+esS/Bg8z8jzxp5BnDCjjwsuKsPQrzswwpFy4J1AUJbXPRfkpcX0mXrzeXW79g==}
+ cpu: [ia32]
+ os: [win32]
+
+ '@unrs/resolver-binding-win32-x64-msvc@1.12.2':
+ resolution: {integrity: sha512-nAB74NfSNKknqQ1RrYj6uz8FcXEomu/MATJZxh/x+BArzN2U3JbOYC0APYzUIGhVY3m5hRxA8VPNdPBoG8txlA==}
+ cpu: [x64]
+ os: [win32]
+
+ '@vitest/expect@4.1.10':
+ resolution: {integrity: sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA==}
+
+ '@vitest/pretty-format@4.1.10':
+ resolution: {integrity: sha512-W1HsjSH4MXQ9YfmmhLAoIYf1HRfekQCGngeIgcei6MP5QQGWUe0gkopdZQaVCFO+JDJMrAJGwa5pRpNpvy4P8Q==}
+
+ '@vitest/spy@4.1.10':
+ resolution: {integrity: sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw==}
+
+ '@vitest/utils@4.1.10':
+ resolution: {integrity: sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA==}
+
+ acorn-jsx@5.3.2:
+ resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
+ peerDependencies:
+ acorn: ^6.0.0 || ^7.0.0 || ^8.0.0
+
+ acorn-walk@8.3.5:
+ resolution: {integrity: sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==}
+ engines: {node: '>=0.4.0'}
+
+ acorn@8.18.0:
+ resolution: {integrity: sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==}
+ engines: {node: '>=0.4.0'}
+ hasBin: true
+
+ agent-base@6.0.2:
+ resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==}
+ engines: {node: '>= 6.0.0'}
+
+ agent-base@7.1.4:
+ resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==}
+ engines: {node: '>= 14'}
+
+ ajv@6.15.0:
+ resolution: {integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==}
+
+ ansi-escapes@4.3.2:
+ resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==}
+ engines: {node: '>=8'}
+
+ ansi-regex@5.0.1:
+ resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
+ engines: {node: '>=8'}
+
+ ansi-regex@6.2.2:
+ resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==}
+ engines: {node: '>=12'}
+
+ ansi-styles@4.3.0:
+ resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
+ engines: {node: '>=8'}
+
+ ansi-styles@5.2.0:
+ resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==}
+ engines: {node: '>=10'}
+
+ ansi-styles@6.2.3:
+ resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==}
+ engines: {node: '>=12'}
+
+ anymatch@3.1.3:
+ resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==}
+ engines: {node: '>= 8'}
+
+ arg@4.1.3:
+ resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==}
+
+ argparse@1.0.10:
+ resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==}
+
+ argparse@2.0.1:
+ resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
+
+ aria-query@5.3.2:
+ resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==}
+ engines: {node: '>= 0.4'}
+
+ array-buffer-byte-length@1.0.2:
+ resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==}
+ engines: {node: '>= 0.4'}
+
+ array-includes@3.1.9:
+ resolution: {integrity: sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==}
+ engines: {node: '>= 0.4'}
+
+ array.prototype.findlast@1.2.5:
+ resolution: {integrity: sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==}
+ engines: {node: '>= 0.4'}
+
+ array.prototype.flat@1.3.3:
+ resolution: {integrity: sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==}
+ engines: {node: '>= 0.4'}
+
+ array.prototype.flatmap@1.3.3:
+ resolution: {integrity: sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==}
+ engines: {node: '>= 0.4'}
+
+ array.prototype.tosorted@1.1.4:
+ resolution: {integrity: sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==}
+ engines: {node: '>= 0.4'}
+
+ arraybuffer.prototype.slice@1.0.4:
+ resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==}
+ engines: {node: '>= 0.4'}
+
+ assertion-error@2.0.1:
+ resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==}
+ engines: {node: '>=12'}
+
+ ast-types-flow@0.0.8:
+ resolution: {integrity: sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==}
+
+ async-function@1.0.0:
+ resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==}
+ engines: {node: '>= 0.4'}
+
+ async-mutex@0.4.1:
+ resolution: {integrity: sha512-WfoBo4E/TbCX1G95XTjbWTE3X2XLG0m1Xbv2cwOtuPdyH9CZvnaA5nCt1ucjaKEgW2A5IF71hxrRhr83Je5xjA==}
+
+ async@3.2.6:
+ resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==}
+
+ asynckit@0.4.0:
+ resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
+
+ available-typed-arrays@1.0.7:
+ resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==}
+ engines: {node: '>= 0.4'}
+
+ aws-sdk-client-mock-jest@4.1.0:
+ resolution: {integrity: sha512-+g4a5Hp+MmPqqNnvwfLitByggrqf+xSbk1pm6fBYHNcon6+aQjL5iB+3YB6HuGPemY+/mUKN34iP62S14R61bA==}
+ peerDependencies:
+ aws-sdk-client-mock: 4.1.0
+ vitest: '>1.6.0'
+ peerDependenciesMeta:
+ vitest:
+ optional: true
+
+ aws-sdk-client-mock@4.1.0:
+ resolution: {integrity: sha512-h/tOYTkXEsAcV3//6C1/7U4ifSpKyJvb6auveAepqqNJl6TdZaPFEtKjBQNf8UxQdDP850knB2i/whq4zlsxJw==}
+
+ axe-core@4.12.1:
+ resolution: {integrity: sha512-s7iGf5GaVMxEG0ENN9x+xTr7GFZCb1ZP/1uATUpCEK2X78nDB3RwbtFCo9pGAf9ru+VwoQ464DkaLEeRM08wJA==}
+ engines: {node: '>=4'}
+
+ axios@1.19.0:
+ resolution: {integrity: sha512-ht/iuYZXEjFxLH/Hkezgd7m6JKlHHXEUSneaDz8uZe1Gj5QZtCnpyDsckvAiEnT89OEbCLmnte4R4sn7P0EKFw==}
+
+ axobject-query@4.1.0:
+ resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==}
+ engines: {node: '>= 0.4'}
+
+ babel-jest@29.7.0:
+ resolution: {integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+ peerDependencies:
+ '@babel/core': ^7.8.0
+
+ babel-jest@30.4.1:
+ resolution: {integrity: sha512-fATAbM8piYxkiXQp3RBXmZHxZVNJZAVXXfyeyCN2Tida3+qJ8ea9UxhiJ2y4fLO90ZImKt6k9FlcH2+rLkJGhw==}
+ engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
+ peerDependencies:
+ '@babel/core': ^7.11.0 || ^8.0.0-0
+
+ babel-plugin-istanbul@6.1.1:
+ resolution: {integrity: sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==}
+ engines: {node: '>=8'}
+
+ babel-plugin-istanbul@7.0.1:
+ resolution: {integrity: sha512-D8Z6Qm8jCvVXtIRkBnqNHX0zJ37rQcFJ9u8WOS6tkYOsRdHBzypCstaxWiu5ZIlqQtviRYbgnRLSoCEvjqcqbA==}
+ engines: {node: '>=12'}
+
+ babel-plugin-jest-hoist@29.6.3:
+ resolution: {integrity: sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+
+ babel-plugin-jest-hoist@30.4.0:
+ resolution: {integrity: sha512-9EdtWM/sSfXLOGLwSn+GS6pIXyBnL07/8gyJlwFXjWy4DxMOyItqyUT29d4lQiS380EZwYlX7/At4PgBS+m2aA==}
+ engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
+
+ babel-preset-current-node-syntax@1.2.0:
+ resolution: {integrity: sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==}
+ peerDependencies:
+ '@babel/core': ^7.0.0 || ^8.0.0-0
+
+ babel-preset-jest@29.6.3:
+ resolution: {integrity: sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+ peerDependencies:
+ '@babel/core': ^7.0.0
+
+ babel-preset-jest@30.4.0:
+ resolution: {integrity: sha512-lBY4jxsNmCnSiu7kquw8ZC9F4+XLMOKypT3RnNHPvU2Kpd4W0xaPuLr5ZkRyOsvLYAY4yaW1ZwTW4xB7NIiZzg==}
+ 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
+
+ balanced-match@1.0.2:
+ resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
+
+ balanced-match@4.0.4:
+ resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==}
+ engines: {node: 18 || 20 || >=22}
+
+ base64-js@1.5.1:
+ resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
+
+ baseline-browser-mapping@2.11.11:
+ resolution: {integrity: sha512-/yImnXwyTvgMkhgekLHok/Rx5vO6E0BmStWlSqKWMVm2a2ITuZ1Tn+9bgLS+gZRdZmWtd8nxuhHpdmCUOWsTQQ==}
+ engines: {node: '>=6.0.0'}
+ hasBin: true
+
+ bowser@2.14.1:
+ resolution: {integrity: sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==}
+
+ brace-expansion@1.1.18:
+ resolution: {integrity: sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==}
+
+ brace-expansion@2.1.4:
+ resolution: {integrity: sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==}
+
+ brace-expansion@5.0.9:
+ resolution: {integrity: sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==}
+ engines: {node: 20 || >=22}
+
+ braces@3.0.3:
+ resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
+ engines: {node: '>=8'}
+
+ browserslist@4.28.7:
+ resolution: {integrity: sha512-JxV13hNrFxqjOc8alRbq9dK1MM79NEXYpma2B2J4wAtpWS5zIEIKqWPGCl7N4o7Uc7B7itylh7SuDujATRyyTw==}
+ engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
+ hasBin: true
+
+ bs-logger@0.2.6:
+ resolution: {integrity: sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==}
+ engines: {node: '>= 6'}
+
+ bser@2.1.1:
+ resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==}
+
+ buffer-equal-constant-time@1.0.1:
+ resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==}
+
+ buffer-from@1.1.2:
+ resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
+
+ buffer@5.6.0:
+ resolution: {integrity: sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==}
+
+ builtin-modules@3.3.0:
+ resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==}
+ engines: {node: '>=6'}
+
+ builtin-modules@5.3.0:
+ resolution: {integrity: sha512-hMQUl2bUFG339QygPM97E+mc8OY1IAchORZxm4a/frcYwKzozMzRVDBwHW0NjOqGElLm2O37AVQE8ikxlZHrMQ==}
+ engines: {node: '>=18.20'}
+
+ bytes@3.1.2:
+ resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==}
+ engines: {node: '>= 0.8'}
+
+ call-bind-apply-helpers@1.0.2:
+ resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==}
+ engines: {node: '>= 0.4'}
+
+ call-bind@1.0.9:
+ resolution: {integrity: sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==}
+ engines: {node: '>= 0.4'}
+
+ call-bound@1.0.4:
+ resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==}
+ engines: {node: '>= 0.4'}
+
+ callsites@3.1.0:
+ resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
+ engines: {node: '>=6'}
+
+ camelcase@5.3.1:
+ resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==}
+ engines: {node: '>=6'}
+
+ camelcase@6.3.0:
+ resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==}
+ engines: {node: '>=10'}
+
+ caniuse-lite@1.0.30001806:
+ resolution: {integrity: sha512-72Cuvd95zbSYPKq6Fhg8eDJRlzgWDf7/mtoZv6Qe/DYNCEBdNxoA3+rZAU2ZhGCpZlns3EssFavaZomckT5Uuw==}
+
+ chai@6.2.2:
+ resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==}
+ engines: {node: '>=18'}
+
+ chalk@4.1.2:
+ resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
+ engines: {node: '>=10'}
+
+ change-case@5.4.4:
+ resolution: {integrity: sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==}
+
+ char-regex@1.0.2:
+ resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==}
+ engines: {node: '>=10'}
+
+ ci-info@3.9.0:
+ resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==}
+ engines: {node: '>=8'}
+
+ ci-info@4.4.0:
+ resolution: {integrity: sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==}
+ engines: {node: '>=8'}
+
+ cjs-module-lexer@1.4.3:
+ resolution: {integrity: sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==}
+
+ cjs-module-lexer@2.2.0:
+ resolution: {integrity: sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==}
+
+ clean-regexp@1.0.0:
+ resolution: {integrity: sha512-GfisEZEJvzKrmGWkvfhgzcz/BllN1USeqD2V6tg14OAOgaCD2Z/PUEuxnAZ/nPvmaHRG7a8y77p1T/IRQ4D1Hw==}
+ engines: {node: '>=4'}
+
+ cliui@7.0.4:
+ resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==}
+
+ cliui@8.0.1:
+ resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
+ engines: {node: '>=12'}
+
+ co@4.6.0:
+ resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==}
+ engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'}
+
+ collect-v8-coverage@1.0.3:
+ resolution: {integrity: sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==}
+
+ color-convert@2.0.1:
+ resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
+ engines: {node: '>=7.0.0'}
+
+ color-convert@3.1.3:
+ resolution: {integrity: sha512-fasDH2ont2GqF5HpyO4w0+BcewlhHEZOFn9c1ckZdHpJ56Qb7MHhH/IcJZbBGgvdtwdwNbLvxiBEdg336iA9Sg==}
+ engines: {node: '>=14.6'}
+
+ color-name@1.1.4:
+ resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
+
+ color-name@2.1.1:
+ resolution: {integrity: sha512-p2FdgwVx1a9yWBHP2wI0VgShkDpgN4kZISkxdNipGBJWpa5G6b04OINlVWCyJj0JmfvcPrgqt95E9k8yvaOJFg==}
+ engines: {node: '>=12.20'}
+
+ color-string@2.1.4:
+ resolution: {integrity: sha512-Bb6Cq8oq0IjDOe8wJmi4JeNn763Xs9cfrBcaylK1tPypWzyoy2G3l90v9k64kjphl/ZJjPIShFztenRomi8WTg==}
+ engines: {node: '>=18'}
+
+ color@5.0.3:
+ resolution: {integrity: sha512-ezmVcLR3xAVp8kYOm4GS45ZLLgIE6SPAFoduLr6hTDajwb3KZ2F46gulK3XpcwRFb5KKGCSezCBAY4Dw4HsyXA==}
+ engines: {node: '>=18'}
+
+ combined-stream@1.0.8:
+ resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
+ engines: {node: '>= 0.8'}
+
+ comment-parser@1.4.7:
+ resolution: {integrity: sha512-0h+uSNtQGW3D98eQt3jJ8L06Fves8hncB4V/PKdw/Qb8Hnk19VaKuTr55UNRYiSoVa7WwrFls+rh3ux9agmkeQ==}
+ engines: {node: '>= 12.0.0'}
+
+ concat-map@0.0.1:
+ resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
+
+ confusing-browser-globals@1.0.11:
+ resolution: {integrity: sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==}
+
+ convert-source-map@2.0.0:
+ resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
+
+ core-js-compat@3.49.0:
+ resolution: {integrity: sha512-VQXt1jr9cBz03b331DFDCCP90b3fanciLkgiOoy8SBHy06gNf+vQ1A3WFLqG7I8TipYIKeYK9wxd0tUrvHcOZA==}
+
+ create-jest@29.7.0:
+ resolution: {integrity: sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+ hasBin: true
+
+ create-require@1.1.1:
+ resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==}
+
+ cross-spawn@7.0.6:
+ resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
+ engines: {node: '>= 8'}
+
+ cssstyle@4.6.0:
+ resolution: {integrity: sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==}
+ engines: {node: '>=18'}
+
+ damerau-levenshtein@1.0.8:
+ resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==}
+
+ data-urls@5.0.0:
+ resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==}
+ engines: {node: '>=18'}
+
+ data-view-buffer@1.0.2:
+ resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==}
+ engines: {node: '>= 0.4'}
+
+ data-view-byte-length@1.0.2:
+ resolution: {integrity: sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==}
+ engines: {node: '>= 0.4'}
+
+ data-view-byte-offset@1.0.1:
+ resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==}
+ engines: {node: '>= 0.4'}
+
+ date-fns@4.4.0:
+ resolution: {integrity: sha512-+1UMbeh68lH1SegH83CGWwpb6OHHbpSgr3+s5Eww5M4CAgswBpoWS0AjTOfEJ33HiYKz1hdj/KTFprzXHmq/6w==}
+
+ dateformat@3.0.2:
+ resolution: {integrity: sha512-EelsCzH0gMC2YmXuMeaZ3c6md1sUJQxyb1XXc4xaisi/K6qKukqZhKPrEQyRkdNIncgYyLoDTReq0nNyuKerTg==}
+
+ debug@4.4.3:
+ resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
+ engines: {node: '>=6.0'}
+ peerDependencies:
+ supports-color: '*'
+ peerDependenciesMeta:
+ supports-color:
+ optional: true
+
+ decimal.js@10.6.0:
+ resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==}
+
+ dedent@1.7.2:
+ resolution: {integrity: sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA==}
+ peerDependencies:
+ babel-plugin-macros: ^3.1.0
+ peerDependenciesMeta:
+ babel-plugin-macros:
+ optional: true
+
+ deep-is@0.1.4:
+ resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
+
+ deepmerge@4.3.1:
+ resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==}
+ engines: {node: '>=0.10.0'}
+
+ define-data-property@1.1.4:
+ resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==}
+ engines: {node: '>= 0.4'}
+
+ define-properties@1.2.1:
+ resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==}
+ engines: {node: '>= 0.4'}
+
+ delayed-stream@1.0.0:
+ resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
+ engines: {node: '>=0.4.0'}
+
+ detect-newline@3.1.0:
+ resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==}
+ engines: {node: '>=8'}
+
+ diff-sequences@29.6.3:
+ resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+
+ diff@4.0.4:
+ resolution: {integrity: sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==}
+ engines: {node: '>=0.3.1'}
+
+ diff@5.2.2:
+ resolution: {integrity: sha512-vtcDfH3TOjP8UekytvnHH1o1P4FcUdt4eQ1Y+Abap1tk/OB2MWQvcwS2ClCd1zuIhc3JKOx6p3kod8Vfys3E+A==}
+ engines: {node: '>=0.3.1'}
+
+ doctrine@2.1.0:
+ resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==}
+ engines: {node: '>=0.10.0'}
+
+ dom-serializer@2.0.0:
+ resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==}
+
+ domelementtype@2.3.0:
+ resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==}
+
+ domhandler@5.0.3:
+ resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==}
+ engines: {node: '>= 4'}
+
+ domutils@3.2.2:
+ resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==}
+
+ dunder-proto@1.0.1:
+ resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
+ engines: {node: '>= 0.4'}
+
+ eastasianwidth@0.2.0:
+ resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
+
+ ecdsa-sig-formatter@1.0.11:
+ resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==}
+
+ electron-to-chromium@1.5.399:
+ resolution: {integrity: sha512-lEcqhErbHjXRvd41rnWLpzbyU/IXfIYo7QwaFWmxGeLiLyY2TBCdHnWY88vB+p3ubnihRypDm66panXl7TylLA==}
+
+ emittery@0.13.1:
+ resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==}
+ engines: {node: '>=12'}
+
+ emoji-regex@8.0.0:
+ resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
+
+ emoji-regex@9.2.2:
+ resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
+
+ enabled@2.0.0:
+ resolution: {integrity: sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==}
+
+ entities@4.5.0:
+ resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
+ engines: {node: '>=0.12'}
+
+ entities@6.0.1:
+ resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==}
+ engines: {node: '>=0.12'}
+
+ entities@7.0.1:
+ resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==}
+ engines: {node: '>=0.12'}
+
+ error-ex@1.3.4:
+ resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==}
+
+ es-abstract-get@1.0.0:
+ resolution: {integrity: sha512-6PMWXpdhshVvFp+FoWYs1EvG1Nj0tvk0dZM+XcK0xMEM1czRVcP6ohqPWHy6qPagSpC8j4+p89WXlT+xXJs/fg==}
+ engines: {node: '>= 0.4'}
+
+ es-abstract@1.24.2:
+ resolution: {integrity: sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==}
+ engines: {node: '>= 0.4'}
+
+ es-define-property@1.0.1:
+ resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==}
+ engines: {node: '>= 0.4'}
+
+ es-errors@1.3.0:
+ resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==}
+ engines: {node: '>= 0.4'}
+
+ es-iterator-helpers@1.4.0:
+ resolution: {integrity: sha512-c/A0P0oxkACDc+cKWw8evLXK83oBKgn0qPOqCYT4x9uolpCIJAcYvJC9QYKNDRPsTeGyCrQ326jrvgZWdCdK5Q==}
+ engines: {node: '>= 0.4'}
+
+ es-object-atoms@1.1.2:
+ resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==}
+ engines: {node: '>= 0.4'}
+
+ es-set-tostringtag@2.1.0:
+ resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==}
+ engines: {node: '>= 0.4'}
+
+ es-shim-unscopables@1.1.0:
+ resolution: {integrity: sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==}
+ engines: {node: '>= 0.4'}
+
+ es-to-primitive@1.3.4:
+ resolution: {integrity: sha512-yPDz7wqpg1/mmHLmS3tcfTfbw5f1eryXvyghYBffGdERwe+mV7ZcWzTR8LR17Kvqt3qfPurjlonmnq3MKXIOXw==}
+ engines: {node: '>= 0.4'}
+
+ esbuild@0.25.12:
+ resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==}
+ engines: {node: '>=18'}
+ hasBin: true
+
+ esbuild@0.28.1:
+ resolution: {integrity: sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==}
+ engines: {node: '>=18'}
+ hasBin: true
+
+ escalade@3.2.0:
+ resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
+ engines: {node: '>=6'}
+
+ escape-string-regexp@1.0.5:
+ resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==}
+ engines: {node: '>=0.8.0'}
+
+ escape-string-regexp@2.0.0:
+ resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==}
+ engines: {node: '>=8'}
+
+ escape-string-regexp@4.0.0:
+ resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
+ engines: {node: '>=10'}
+
+ eslint-config-airbnb-extended@2.3.3:
+ resolution: {integrity: sha512-1p/dQedg2lzPx/PlX5EpVlIY1UvZ5eflrO18rGs9DbhmVKCRv4/47irOekmPrrEGAZhbqrcmpzJIA+DE6yzHTQ==}
+ engines: {node: '>=16.0.0'}
+ peerDependencies:
+ '@next/eslint-plugin-next': ^15.0.0 || ^16.0.0
+ '@stylistic/eslint-plugin': ^3.0.0
+ '@types/eslint-plugin-jsx-a11y': ^6.0.0
+ eslint: ^9.0.0
+ eslint-import-resolver-typescript: ^4.0.0
+ eslint-plugin-import: ^2.0.0
+ eslint-plugin-import-x: ^4.0.0
+ eslint-plugin-jsx-a11y: ^6.0.0
+ eslint-plugin-n: ^17.0.0
+ eslint-plugin-react: ^7.0.0
+ eslint-plugin-react-hooks: ^5.0.0 || ^6.0.0 || ^7.0.0
+ typescript-eslint: ^8.0.0
+ peerDependenciesMeta:
+ '@next/eslint-plugin-next':
+ optional: true
+ '@stylistic/eslint-plugin':
+ optional: true
+ '@types/eslint-plugin-jsx-a11y':
+ optional: true
+ eslint-import-resolver-typescript:
+ optional: true
+ eslint-plugin-import:
+ optional: true
+ eslint-plugin-import-x:
+ optional: true
+ eslint-plugin-jsx-a11y:
+ optional: true
+ eslint-plugin-n:
+ optional: true
+ eslint-plugin-react:
+ optional: true
+ eslint-plugin-react-hooks:
+ optional: true
+ typescript-eslint:
+ optional: true
+
+ eslint-config-prettier@10.1.8:
+ resolution: {integrity: sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==}
+ hasBin: true
+ peerDependencies:
+ eslint: '>=7.0.0'
+
+ eslint-import-context@0.1.9:
+ resolution: {integrity: sha512-K9Hb+yRaGAGUbwjhFNHvSmmkZs9+zbuoe3kFQ4V1wYjrepUFYM2dZAfNtjbbj3qsPfUfsA68Bx/ICWQMi+C8Eg==}
+ engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
+ peerDependencies:
+ unrs-resolver: ^1.0.0
+ peerDependenciesMeta:
+ unrs-resolver:
+ optional: true
+
+ eslint-import-resolver-typescript@4.4.5:
+ resolution: {integrity: sha512-nbE5XLph6TLtGYcu/U6e6ZVXyKBhbDWK5cLGk76eJ7NdZpwf1P9EFkpt1Z01mNZNrrilsAYWKH6zUkL4reoXbw==}
+ engines: {node: ^16.17.0 || >=18.6.0}
+ peerDependencies:
+ eslint: '*'
+ eslint-plugin-import: '*'
+ eslint-plugin-import-x: '*'
+ peerDependenciesMeta:
+ eslint-plugin-import:
+ optional: true
+ eslint-plugin-import-x:
+ optional: true
+
+ eslint-plugin-html@8.1.4:
+ resolution: {integrity: sha512-Eno3oPEj3s6AhvDJ5zHhnHPDvXp6LNFXuy3w51fNebOKYuTrfjOHUGwP+mOrGFpR6eOJkO1xkB8ivtbfMjbMjg==}
+ engines: {node: '>=16.0.0'}
+
+ eslint-plugin-import-x@4.17.1:
+ resolution: {integrity: sha512-4cdstYkKCyjumM2Q9NSI03K8D2a9F4Ssz33K2lv2hQa4KmR9jPLwk3uWGtNvclfqBrPGfGuMBwsGMbe6dMRbfg==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ '@typescript-eslint/utils': ^8.56.0
+ eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
+ eslint-import-resolver-node: '*'
+ peerDependenciesMeta:
+ '@typescript-eslint/utils':
+ optional: true
+ eslint-import-resolver-node:
+ optional: true
+
+ eslint-plugin-jest@29.16.0:
+ resolution: {integrity: sha512-0WFBxDHlT2ratGQfnFQEVIsgQJ5cfd+0IV8Kc6U3X2onB8ATLG23voD2Ch5G9fCkEpCPmCMuzW0tbS0kYb8biw==}
+ engines: {node: ^20.12.0 || ^22.0.0 || >=24.0.0}
+ peerDependencies:
+ '@typescript-eslint/eslint-plugin': ^8.0.0
+ eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
+ jest: '*'
+ typescript: '>=4.8.4 <8.0.0'
+ peerDependenciesMeta:
+ '@typescript-eslint/eslint-plugin':
+ optional: true
+ jest:
+ optional: true
+ typescript:
+ optional: true
+
+ eslint-plugin-json@4.0.1:
+ resolution: {integrity: sha512-3An5ISV5dq/kHfXdNyY5TUe2ONC3yXFSkLX2gu+W8xAhKhfvrRvkSAeKXCxZqZ0KJLX15ojBuLPyj+UikQMkOA==}
+ engines: {node: '>=18.0'}
+
+ eslint-plugin-jsx-a11y@6.10.2:
+ resolution: {integrity: sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==}
+ engines: {node: '>=4.0'}
+ peerDependencies:
+ eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9
+
+ eslint-plugin-no-relative-import-paths@1.6.1:
+ resolution: {integrity: sha512-YZNeOnsOrJcwhFw0X29MXjIzu2P/f5X2BZDPWw1R3VUYBRFxNIh77lyoL/XrMU9ewZNQPcEvAgL/cBOT1P330A==}
+
+ eslint-plugin-prettier@5.5.6:
+ resolution: {integrity: sha512-ifetmTcxWfz+4qRW3pH/ujdTq2jQIj59AxJMIN26K5avYgU8dxycUETQonWiW+wPrYXA0j3Try0l1CnwVQtDqQ==}
+ engines: {node: ^14.18.0 || >=16.0.0}
+ peerDependencies:
+ '@types/eslint': '>=8.0.0'
+ eslint: '>=8.0.0'
+ eslint-config-prettier: '>= 7.0.0 <10.0.0 || >=10.1.0'
+ prettier: '>=3.0.0'
+ peerDependenciesMeta:
+ '@types/eslint':
+ optional: true
+ eslint-config-prettier:
+ optional: true
+
+ eslint-plugin-react-hooks@7.1.1:
+ resolution: {integrity: sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0
+
+ eslint-plugin-react@7.37.5:
+ resolution: {integrity: sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==}
+ engines: {node: '>=4'}
+ peerDependencies:
+ eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7
+
+ eslint-plugin-security@3.0.1:
+ resolution: {integrity: sha512-XjVGBhtDZJfyuhIxnQ/WMm385RbX3DBu7H1J7HNNhmB2tnGxMeqVSnYv79oAj992ayvIBZghsymwkYFS6cGH4Q==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ eslint-plugin-sonarjs@3.0.7:
+ resolution: {integrity: sha512-62jB20krIPvcwBLAyG3VVKa2ce2j2lL1yCb8Y0ylMRR/dLvCCTiQx8gQbXb+G81k1alPZ2/I3muZinqWQdBbzw==}
+ peerDependencies:
+ eslint: ^8.0.0 || ^9.0.0
+
+ eslint-plugin-sort-destructure-keys@2.0.0:
+ resolution: {integrity: sha512-4w1UQCa3o/YdfWaLr9jY8LfGowwjwjmwClyFLxIsToiyIdZMq3x9Ti44nDn34DtTPP7PWg96tUONKVmATKhYGQ==}
+ engines: {node: '>=12'}
+ peerDependencies:
+ eslint: 5 - 9
+
+ eslint-plugin-unicorn@61.0.2:
+ resolution: {integrity: sha512-zLihukvneYT7f74GNbVJXfWIiNQmkc/a9vYBTE4qPkQZswolWNdu+Wsp9sIXno1JOzdn6OUwLPd19ekXVkahRA==}
+ engines: {node: ^20.10.0 || >=21.0.0}
+ peerDependencies:
+ eslint: '>=9.29.0'
+
+ eslint-scope@8.4.0:
+ resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ eslint-visitor-keys@3.4.3:
+ resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==}
+ engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+
+ eslint-visitor-keys@4.2.1:
+ resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ eslint-visitor-keys@5.0.1:
+ resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==}
+ engines: {node: ^20.19.0 || ^22.13.0 || >=24}
+
+ eslint@9.39.5:
+ resolution: {integrity: sha512-DgZS62aPLXKlnxILS/AYCoRvHaZeXceIzlXPkkGGzJWSow1aEk0lbTlxUSlyjC8jcaKxAdOnTDz+o1JFSBsyjw==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ hasBin: true
+ peerDependencies:
+ jiti: '*'
+ peerDependenciesMeta:
+ jiti:
+ optional: true
+
+ espree@10.4.0:
+ resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ esprima@4.0.1:
+ resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==}
+ engines: {node: '>=4'}
+ hasBin: true
+
+ esquery@1.7.0:
+ resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==}
+ engines: {node: '>=0.10'}
+
+ esrecurse@4.3.0:
+ resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==}
+ engines: {node: '>=4.0'}
+
+ estraverse@5.3.0:
+ resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==}
+ engines: {node: '>=4.0'}
+
+ esutils@2.0.3:
+ resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
+ engines: {node: '>=0.10.0'}
+
+ events@3.3.0:
+ resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==}
+ engines: {node: '>=0.8.x'}
+
+ execa@5.1.1:
+ resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==}
+ engines: {node: '>=10'}
+
+ exit-x@0.2.2:
+ resolution: {integrity: sha512-+I6B/IkJc1o/2tiURyz/ivu/O0nKNEArIUB5O7zBrlDVJr22SCLH3xTeEry428LvFhRzIA1g8izguxJ/gbNcVQ==}
+ engines: {node: '>= 0.8.0'}
+
+ exit@0.1.2:
+ resolution: {integrity: sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==}
+ engines: {node: '>= 0.8.0'}
+
+ expect@29.7.0:
+ resolution: {integrity: sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+
+ expect@30.4.1:
+ resolution: {integrity: sha512-PMARsyh/JtqC20HoGqlFcIlQAyqUtW4PlI1rup1uhYJtKuwAjbvWi3GQMAn+STdHum/dk8xrKfUM1+5SAwpolA==}
+ engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
+
+ fast-deep-equal@3.1.3:
+ resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
+
+ fast-diff@1.3.0:
+ resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==}
+
+ fast-glob@3.3.3:
+ resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==}
+ engines: {node: '>=8.6.0'}
+
+ fast-json-stable-stringify@2.1.0:
+ resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==}
+
+ fast-levenshtein@2.0.6:
+ resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==}
+
+ fastq@1.20.1:
+ resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==}
+
+ fb-watchman@2.0.2:
+ resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==}
+
+ fdir@6.5.0:
+ resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
+ engines: {node: '>=12.0.0'}
+ peerDependencies:
+ picomatch: ^3 || ^4
+ peerDependenciesMeta:
+ picomatch:
+ optional: true
+
+ fecha@4.2.3:
+ resolution: {integrity: sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==}
+
+ fflate@0.8.1:
+ resolution: {integrity: sha512-/exOvEuc+/iaUm105QIiOt4LpBdMTWsXxqR0HDF35vx3fmaKzw7354gTilCh5rkzEt8WYyG//ku3h3nRmd7CHQ==}
+
+ file-entry-cache@8.0.0:
+ resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==}
+ engines: {node: '>=16.0.0'}
+
+ fill-range@7.1.1:
+ resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
+ engines: {node: '>=8'}
+
+ find-up-simple@1.0.1:
+ resolution: {integrity: sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ==}
+ engines: {node: '>=18'}
+
+ find-up@4.1.0:
+ resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==}
+ engines: {node: '>=8'}
+
+ find-up@5.0.0:
+ resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==}
+ engines: {node: '>=10'}
+
+ flat-cache@4.0.1:
+ resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==}
+ engines: {node: '>=16'}
+
+ flatted@3.4.4:
+ resolution: {integrity: sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==}
+
+ fn.name@1.1.0:
+ resolution: {integrity: sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==}
+
+ follow-redirects@1.16.0:
+ resolution: {integrity: sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==}
+ engines: {node: '>=4.0'}
+ peerDependencies:
+ debug: '*'
+ peerDependenciesMeta:
+ debug:
+ optional: true
+
+ for-each@0.3.5:
+ resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==}
+ engines: {node: '>= 0.4'}
+
+ foreground-child@3.3.1:
+ resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==}
+ engines: {node: '>=14'}
+
+ form-data@4.0.6:
+ resolution: {integrity: sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==}
+ engines: {node: '>= 6'}
+
+ fs.realpath@1.0.0:
+ resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
+
+ fsevents@2.3.3:
+ resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
+ engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
+ os: [darwin]
+
+ function-bind@1.1.2:
+ resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
+
+ function.prototype.name@1.2.0:
+ resolution: {integrity: sha512-jObKIik1P2QjPHP5nz5BaOtUlfgS0fWo8IUByNXkM+o+02sJOi94em77GwJKQSJ3gfPHdgzLNrHc1uokV4P/ew==}
+ engines: {node: '>= 0.4'}
+
+ functional-red-black-tree@1.0.1:
+ resolution: {integrity: sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==}
+
+ functions-have-names@1.2.3:
+ resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==}
+
+ generator-function@2.0.1:
+ resolution: {integrity: sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==}
+ engines: {node: '>= 0.4'}
+
+ gensync@1.0.0-beta.2:
+ resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
+ engines: {node: '>=6.9.0'}
+
+ get-caller-file@2.0.5:
+ resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
+ engines: {node: 6.* || 8.* || >= 10.*}
+
+ get-intrinsic@1.3.0:
+ resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==}
+ engines: {node: '>= 0.4'}
+
+ get-package-type@0.1.0:
+ resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==}
+ engines: {node: '>=8.0.0'}
+
+ get-proto@1.0.1:
+ resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==}
+ engines: {node: '>= 0.4'}
+
+ get-stream@6.0.1:
+ resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==}
+ engines: {node: '>=10'}
+
+ get-symbol-description@1.1.0:
+ resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==}
+ engines: {node: '>= 0.4'}
+
+ get-tsconfig@4.14.1:
+ resolution: {integrity: sha512-Dz/6HxkrxgNehhxLVeyv8sad9UzF2xBVeaKBQNDfJ5XiSXmp2gTR0eO0RWiT2NCKS5aGP9jjkOMggTN90qU50A==}
+
+ glob-parent@5.1.2:
+ resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
+ engines: {node: '>= 6'}
+
+ glob-parent@6.0.2:
+ resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==}
+ engines: {node: '>=10.13.0'}
+
+ glob@10.5.0:
+ resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==}
+ deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
+ hasBin: true
+
+ glob@7.2.3:
+ resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
+ deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
+
+ globals@14.0.0:
+ resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==}
+ engines: {node: '>=18'}
+
+ globals@16.5.0:
+ resolution: {integrity: sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==}
+ engines: {node: '>=18'}
+
+ globalthis@1.0.4:
+ resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==}
+ engines: {node: '>= 0.4'}
+
+ gopd@1.2.0:
+ resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
+ engines: {node: '>= 0.4'}
+
+ graceful-fs@4.2.11:
+ resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
+
+ handlebars@4.7.9:
+ resolution: {integrity: sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ==}
+ engines: {node: '>=0.4.7'}
+ hasBin: true
+
+ has-bigints@1.1.0:
+ resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==}
+ engines: {node: '>= 0.4'}
+
+ has-flag@4.0.0:
+ resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
+ engines: {node: '>=8'}
+
+ has-property-descriptors@1.0.2:
+ resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==}
+
+ has-proto@1.2.0:
+ resolution: {integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==}
+ engines: {node: '>= 0.4'}
+
+ has-symbols@1.1.0:
+ resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==}
+ engines: {node: '>= 0.4'}
+
+ has-tostringtag@1.0.2:
+ resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==}
+ engines: {node: '>= 0.4'}
+
+ hasown@2.0.4:
+ resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==}
+ engines: {node: '>= 0.4'}
+
+ hermes-estree@0.25.1:
+ resolution: {integrity: sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==}
+
+ hermes-parser@0.25.1:
+ resolution: {integrity: sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==}
+
+ html-encoding-sniffer@4.0.0:
+ resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==}
+ engines: {node: '>=18'}
+
+ html-escaper@2.0.2:
+ resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==}
+
+ htmlparser2@10.1.0:
+ resolution: {integrity: sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==}
+
+ http-proxy-agent@7.0.2:
+ resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==}
+ engines: {node: '>= 14'}
+
+ https-proxy-agent@5.0.1:
+ resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==}
+ engines: {node: '>= 6'}
+
+ https-proxy-agent@7.0.6:
+ resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==}
+ engines: {node: '>= 14'}
+
+ human-signals@2.1.0:
+ resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==}
+ engines: {node: '>=10.17.0'}
+
+ iconv-lite@0.6.3:
+ resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
+ engines: {node: '>=0.10.0'}
+
+ ieee754@1.2.1:
+ resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
+
+ ignore@5.3.2:
+ resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
+ engines: {node: '>= 4'}
+
+ ignore@7.0.6:
+ resolution: {integrity: sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==}
+ engines: {node: '>= 4'}
+
+ import-fresh@3.3.1:
+ resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==}
+ engines: {node: '>=6'}
+
+ import-local@3.2.0:
+ resolution: {integrity: sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==}
+ engines: {node: '>=8'}
+ hasBin: true
+
+ imurmurhash@0.1.4:
+ resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==}
+ engines: {node: '>=0.8.19'}
+
+ indent-string@5.0.0:
+ resolution: {integrity: sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==}
+ engines: {node: '>=12'}
+
+ inflight@1.0.6:
+ resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==}
+ deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.
+
+ inherits@2.0.4:
+ resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
+
+ internal-slot@1.1.0:
+ resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==}
+ engines: {node: '>= 0.4'}
+
+ is-array-buffer@3.0.5:
+ resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==}
+ engines: {node: '>= 0.4'}
+
+ is-arrayish@0.2.1:
+ resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==}
+
+ is-async-function@2.1.1:
+ resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==}
+ engines: {node: '>= 0.4'}
+
+ is-bigint@1.1.0:
+ resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==}
+ engines: {node: '>= 0.4'}
+
+ is-boolean-object@1.2.2:
+ resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==}
+ engines: {node: '>= 0.4'}
+
+ is-builtin-module@5.0.0:
+ resolution: {integrity: sha512-f4RqJKBUe5rQkJ2eJEJBXSticB3hGbN9j0yxxMQFqIW89Jp9WYFtzfTcRlstDKVUTRzSOTLKRfO9vIztenwtxA==}
+ engines: {node: '>=18.20'}
+
+ is-bun-module@2.0.0:
+ resolution: {integrity: sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==}
+
+ is-callable@1.2.7:
+ resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==}
+ engines: {node: '>= 0.4'}
+
+ is-core-module@2.16.2:
+ resolution: {integrity: sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==}
+ engines: {node: '>= 0.4'}
+
+ is-data-view@1.0.2:
+ resolution: {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==}
+ engines: {node: '>= 0.4'}
+
+ is-date-object@1.1.0:
+ resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==}
+ engines: {node: '>= 0.4'}
+
+ is-document.all@1.0.0:
+ resolution: {integrity: sha512-+XSoyS05OdBbhFuELhgTCpFNHkpBOJqtsZfUFFpe5QTw+9Sjbh8zitxhQkYAo6wV7e1Vb8cAPvpCk9jGam/82g==}
+ engines: {node: '>= 0.4'}
+
+ is-extglob@2.1.1:
+ resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
+ engines: {node: '>=0.10.0'}
+
+ is-finalizationregistry@1.1.1:
+ resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==}
+ engines: {node: '>= 0.4'}
+
+ is-fullwidth-code-point@3.0.0:
+ resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
+ engines: {node: '>=8'}
+
+ is-generator-fn@2.1.0:
+ resolution: {integrity: sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==}
+ engines: {node: '>=6'}
+
+ is-generator-function@1.1.2:
+ resolution: {integrity: sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==}
+ engines: {node: '>= 0.4'}
+
+ is-glob@4.0.3:
+ resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
+ engines: {node: '>=0.10.0'}
+
+ is-map@2.0.3:
+ resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==}
+ engines: {node: '>= 0.4'}
+
+ is-negative-zero@2.0.3:
+ resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==}
+ engines: {node: '>= 0.4'}
+
+ is-number-object@1.1.1:
+ resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==}
+ engines: {node: '>= 0.4'}
+
+ is-number@7.0.0:
+ resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
+ engines: {node: '>=0.12.0'}
+
+ is-potential-custom-element-name@1.0.1:
+ resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==}
+
+ is-regex@1.2.1:
+ resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==}
+ engines: {node: '>= 0.4'}
+
+ is-set@2.0.3:
+ resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==}
+ engines: {node: '>= 0.4'}
+
+ is-shared-array-buffer@1.0.4:
+ resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==}
+ engines: {node: '>= 0.4'}
+
+ is-stream@2.0.1:
+ resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==}
+ engines: {node: '>=8'}
+
+ is-string@1.1.1:
+ resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==}
+ engines: {node: '>= 0.4'}
+
+ is-symbol@1.1.1:
+ resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==}
+ engines: {node: '>= 0.4'}
+
+ is-typed-array@1.1.15:
+ resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==}
+ engines: {node: '>= 0.4'}
+
+ is-weakmap@2.0.2:
+ resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==}
+ engines: {node: '>= 0.4'}
+
+ is-weakref@1.1.1:
+ resolution: {integrity: sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==}
+ engines: {node: '>= 0.4'}
+
+ is-weakset@2.0.4:
+ resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==}
+ engines: {node: '>= 0.4'}
+
+ isarray@2.0.5:
+ resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==}
+
+ isexe@2.0.0:
+ resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
+
+ istanbul-lib-coverage@3.2.2:
+ resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==}
+ engines: {node: '>=8'}
+
+ istanbul-lib-instrument@5.2.1:
+ resolution: {integrity: sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==}
+ engines: {node: '>=8'}
+
+ istanbul-lib-instrument@6.0.3:
+ resolution: {integrity: sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==}
+ engines: {node: '>=10'}
+
+ istanbul-lib-report@3.0.1:
+ resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==}
+ engines: {node: '>=10'}
+
+ istanbul-lib-source-maps@4.0.1:
+ resolution: {integrity: sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==}
+ engines: {node: '>=10'}
+
+ istanbul-lib-source-maps@5.0.6:
+ resolution: {integrity: sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==}
+ engines: {node: '>=10'}
+
+ istanbul-reports@3.2.0:
+ resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==}
+ engines: {node: '>=8'}
+
+ iterator.prototype@1.1.5:
+ resolution: {integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==}
+ engines: {node: '>= 0.4'}
+
+ jackspeak@3.4.3:
+ resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==}
+
+ jest-changed-files@29.7.0:
+ resolution: {integrity: sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+
+ jest-changed-files@30.4.1:
+ resolution: {integrity: sha512-IuctmYrxi21iOSOaIXpJWalHyPAsVv0GeBHKDn8C1CA4W5htHn7INL+wdnL4Bo0+olEndvAFkmb++tIQJG+vvg==}
+ engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
+
+ jest-circus@29.7.0:
+ resolution: {integrity: sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+
+ jest-circus@30.4.2:
+ resolution: {integrity: sha512-rvHH7VlY6LgbJXJTQ87GW62g1FntOtbhh0zT+v04kC+pgL6aBKyYINXxWukCpj3dcIBMw5/XUbtDS9dU9JTXeQ==}
+ engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
+
+ jest-cli@29.7.0:
+ resolution: {integrity: sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+ hasBin: true
+ peerDependencies:
+ node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0
+ peerDependenciesMeta:
+ node-notifier:
+ optional: true
+
+ jest-cli@30.4.2:
+ resolution: {integrity: sha512-jfA2ocvVHMXS2QijrJ0d31ektP+d/W0T5RpcTX2Pq+3sVqHlsXVCM2+FmwpL+bdY8OfHpIg9xMxLF17Zg0U49Q==}
+ engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
+ hasBin: true
+ peerDependencies:
+ node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0
+ peerDependenciesMeta:
+ node-notifier:
+ optional: true
+
+ jest-config@29.7.0:
+ resolution: {integrity: sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+ peerDependencies:
+ '@types/node': '*'
+ ts-node: '>=9.0.0'
+ peerDependenciesMeta:
+ '@types/node':
+ optional: true
+ ts-node:
+ optional: true
+
+ jest-config@30.4.2:
+ resolution: {integrity: sha512-rNHAShJQqQwFNoL0hbf3BphSBOWnpOUAKvidLS/AjNVLPfoj5mSf4jQMfW3cYOs6hXeZC7nF7mDHaBnbxELOzg==}
+ 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
+
+ jest-diff@29.7.0:
+ resolution: {integrity: sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+
+ jest-diff@30.4.1:
+ resolution: {integrity: sha512-CRpFK0RtLriVDGcPPAnR6HMVI8bSR2jnUIgralhauzYQZIb4RH9AtEInTuQr65LmmGggGcRT6HIASxwqsVsmlA==}
+ engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
+
+ jest-docblock@29.7.0:
+ resolution: {integrity: sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+
+ jest-docblock@30.4.0:
+ resolution: {integrity: sha512-ZPMabUZCx5MpbZ2eBYSvZ0J8fvo3dR9oM+eeUpb3aKNQFuS2tu3Duw1TNlMoP8k3WQgKGJuhcMFvwcVuq6T7oA==}
+ engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
+
+ jest-each@29.7.0:
+ resolution: {integrity: sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+
+ jest-each@30.4.1:
+ resolution: {integrity: sha512-/8MJbH6fuj48TstjrMf+u/pd06Qezz5xOXvZA6442heNOWr8bdeoGZX2d9fCn028CoMgYmroH9//zky5GfyYmA==}
+ engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
+
+ jest-environment-jsdom@30.4.1:
+ resolution: {integrity: sha512-o3nfaN4zej7qgk2X0j8Jhq/S9nAVKs2xK3QeQxeHVvpkEPxaA1yxDGydR+iVI7zPy7Cp62Aq2h3Ja46QvfWHGA==}
+ engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
+ peerDependencies:
+ canvas: ^3.0.0
+ peerDependenciesMeta:
+ canvas:
+ optional: true
+
+ jest-environment-node@29.7.0:
+ resolution: {integrity: sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+
+ jest-environment-node@30.4.1:
+ resolution: {integrity: sha512-4FZYVOk85hz2AyT6BbarKy9u37g6DbrDyCdFhsnDdXqyrueYQvB+0zO4f/kqLCRD0BsPRXPMNJeQwihKZV8naw==}
+ engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
+
+ jest-get-type@29.6.3:
+ resolution: {integrity: sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+
+ jest-haste-map@29.7.0:
+ resolution: {integrity: sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+
+ jest-haste-map@30.4.1:
+ resolution: {integrity: sha512-rFrcONd8jeFsyw+Z9CrScJgglRf2+NFmNam8dKu7n+SoHqNYT47mn0DdEcVUZJpvh7Iz6/si7f7yUH7GJHVgnw==}
+ engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
+
+ jest-html-reporter@4.4.0:
+ resolution: {integrity: sha512-8aC5pzPOgsbiPwlvE686Gt3ZkUGHpafHtS0ffhCmKqTYdNwnrNX1WpmF7lbb3+3/TvZ9+UlACM811abivu5SWw==}
+ engines: {node: '>=14.0.0'}
+ peerDependencies:
+ jest: 19.x - 30.x
+
+ jest-leak-detector@29.7.0:
+ resolution: {integrity: sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+
+ jest-leak-detector@30.4.1:
+ resolution: {integrity: sha512-IpmyiioeHxiWDhesHnUFmOxcTzwCwKpgACgWajtAP+nYQXiY7DakTxB6Bx9JFiRMljr0AX1PvnQdaU1KFoz6NQ==}
+ engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
+
+ jest-matcher-utils@29.7.0:
+ resolution: {integrity: sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+
+ jest-matcher-utils@30.4.1:
+ resolution: {integrity: sha512-zvYfX5CaeEkFrrLS9suWe9rvJrm9J1Iv3ua8kIBv9GEPzcnsfBf0bob37la7s67fs0nlBC3EuvkOLnXQKxtx4A==}
+ engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
+
+ jest-message-util@29.7.0:
+ resolution: {integrity: sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+
+ jest-message-util@30.4.1:
+ resolution: {integrity: sha512-kwCKIvq0MCW1HzLoGola9Te6JUdzgV0loyKJ3Qghrkz9i5/RRIHsL95BMQc2HBBhlBKC4j22K9p11TGHH8RBpQ==}
+ engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
+
+ jest-mock-extended@3.0.7:
+ resolution: {integrity: sha512-7lsKdLFcW9B9l5NzZ66S/yTQ9k8rFtnwYdCNuRU/81fqDWicNDVhitTSPnrGmNeNm0xyw0JHexEOShrIKRCIRQ==}
+ peerDependencies:
+ jest: ^24.0.0 || ^25.0.0 || ^26.0.0 || ^27.0.0 || ^28.0.0 || ^29.0.0
+ typescript: ^3.0.0 || ^4.0.0 || ^5.0.0
+
+ jest-mock-extended@4.0.1:
+ resolution: {integrity: sha512-Q/4k/yefiv/Al3n755V9xDEwMiL+7LwkjRKjaORkgCdovZv00hF/D0QypLoqO+MVfrYkzCYa4BYlcEKA74iOgQ==}
+ peerDependencies:
+ '@jest/globals': ^28.0.0 || ^29.0.0 || ^30.0.0
+ jest: ^24.0.0 || ^25.0.0 || ^26.0.0 || ^27.0.0 || ^28.0.0 || ^29.0.0 || ^30.0.0
+ typescript: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0
+
+ jest-mock@29.7.0:
+ resolution: {integrity: sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+
+ jest-mock@30.4.1:
+ resolution: {integrity: sha512-/i8SVb8/NSB7RfNi8gfqu8gxLV23KaL5EpAttyb9iz8qWRIqXRLflycz/32wXsYkOnaUlx8NAKnJYtpsmXUmfw==}
+ engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
+
+ jest-pnp-resolver@1.2.3:
+ resolution: {integrity: sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==}
+ engines: {node: '>=6'}
+ peerDependencies:
+ jest-resolve: '*'
+ peerDependenciesMeta:
+ jest-resolve:
+ optional: true
+
+ jest-regex-util@29.6.3:
+ resolution: {integrity: sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+
+ jest-regex-util@30.4.0:
+ resolution: {integrity: sha512-mWlvLviKIgIQ8VCuM1xRdD0TWp3zlzionlmDBjuXVBs+VkmXq6FgW9T4Emr7oGz/Rk6feDCGyiugolcQEyp3mg==}
+ engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
+
+ jest-resolve-dependencies@29.7.0:
+ resolution: {integrity: sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+
+ jest-resolve-dependencies@30.4.2:
+ resolution: {integrity: sha512-gDiVh1I+GxYzz9oXlyw+1wv6VOYX1WYxMOfjsA3iGKePV2oxmbHhwxfkALxNxYy1ciw6APWwkW2zZONwP97aEQ==}
+ engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
+
+ jest-resolve@29.7.0:
+ resolution: {integrity: sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+
+ jest-resolve@30.4.1:
+ resolution: {integrity: sha512-Zry8Yq/yJcNAZ7dJ5F2heic8AheXvbFZ7XI5V+h28nrYZ7Qoyy4dItq8OodjnYD270mvX+ZudmrNV9cysqhW5Q==}
+ engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
+
+ jest-runner@29.7.0:
+ resolution: {integrity: sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+
+ jest-runner@30.4.2:
+ resolution: {integrity: sha512-2dw0PslVYXxffXGpLo+Ejad+KcI1Qkjn7f4X4619gf21oCUmL+SPfjqIa/losUem3yEOvfNZe/F1HWUcNpODcg==}
+ engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
+
+ jest-runtime@29.7.0:
+ resolution: {integrity: sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+
+ jest-runtime@30.4.2:
+ resolution: {integrity: sha512-3/5e8iPz2k/VLqlr8DgTftYyLUv8Su3FkCAO2/Od81UsUTpSxOrS6O5x5KkoQwyUjmpYyDJKeyAvg2T2nvpNkQ==}
+ engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
+
+ jest-snapshot@29.7.0:
+ resolution: {integrity: sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+
+ jest-snapshot@30.4.1:
+ resolution: {integrity: sha512-tEOkkfOMppUyeiHwjZswOQ3lcnoTnws/q5FnGIaeIh/jmoU0ZlgMYRR8sTlTj+nNGCoJ0RDq6SfxGxCsyMTPmw==}
+ engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
+
+ jest-util@29.7.0:
+ resolution: {integrity: sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+
+ jest-util@30.4.1:
+ resolution: {integrity: sha512-vjQb1sACEiv13DKJMDToJpzVW0joCsIQrmbg0fi7CyOOt+g9jTuQl2A216pWRBYhOVt53XbL/2LbMKg1BECWOw==}
+ engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
+
+ jest-validate@29.7.0:
+ resolution: {integrity: sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+
+ jest-validate@30.4.1:
+ resolution: {integrity: sha512-PDWi4SOwLnwqNDfHZjOcsEFyZ4fc/2W2gVL3DEoyqnB6jCQMLRtfBong8s6omIw3lI0HWOus12xfnFmQtjW3fw==}
+ engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
+
+ jest-watcher@29.7.0:
+ resolution: {integrity: sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+
+ jest-watcher@30.4.1:
+ resolution: {integrity: sha512-/l9UonmvCwjHH7d2h3iAwIloLc1H0S8mJZ/LNK3i86hqwPAz8otUJjP9MfYtz9Tt77Su5FD2xGjZn8d31IZHlw==}
+ engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
+
+ jest-worker@29.7.0:
+ resolution: {integrity: sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+
+ jest-worker@30.4.1:
+ resolution: {integrity: sha512-SHynN/q/QD++iNyvMdy+WMmbCGk8jIsNcRxycXbWubSOhvo6T+j2afcfUSl+3hYsiBebOTo0cT7c2H7CXugu1g==}
+ engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
+
+ jest@29.7.0:
+ resolution: {integrity: sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+ hasBin: true
+ peerDependencies:
+ node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0
+ peerDependenciesMeta:
+ node-notifier:
+ optional: true
+
+ jest@30.4.2:
+ resolution: {integrity: sha512-Yi1jqNC/Oq0N4hBgNH/YvBpP1P57QqundgytzYqy3yqAa7NZPNjSoi4SGbRAXDMdBzNE6xBCi5U7RgfrvMEUVQ==}
+ engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
+ hasBin: true
+ peerDependencies:
+ node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0
+ peerDependenciesMeta:
+ node-notifier:
+ optional: true
+
+ jose@5.10.0:
+ resolution: {integrity: sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg==}
+
+ js-tokens@4.0.0:
+ resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
+
+ js-yaml@3.15.1:
+ resolution: {integrity: sha512-S99WuO3HlhO3XN41EtYUNl9zzXjoJx7QvmipxsJVxtCBT0YHEFy+iOJhjSvrmV12nYhWpZaM8lPHkJm0yUMbag==}
+ hasBin: true
+
+ js-yaml@4.3.1:
+ resolution: {integrity: sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==}
+ hasBin: true
+
+ jsdom@26.1.0:
+ resolution: {integrity: sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ canvas: ^3.0.0
+ peerDependenciesMeta:
+ canvas:
+ optional: true
+
+ jsesc@3.0.2:
+ resolution: {integrity: sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==}
+ engines: {node: '>=6'}
+ hasBin: true
+
+ jsesc@3.1.0:
+ resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==}
+ engines: {node: '>=6'}
+ hasBin: true
+
+ json-buffer@3.0.1:
+ resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==}
+
+ json-parse-even-better-errors@2.3.1:
+ resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==}
+
+ json-schema-traverse@0.4.1:
+ resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==}
+
+ json-stable-stringify-without-jsonify@1.0.1:
+ resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
+
+ json5@2.2.3:
+ resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==}
+ engines: {node: '>=6'}
+ hasBin: true
+
+ jsonc-parser@3.3.1:
+ resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==}
+
+ jsonwebtoken@9.0.3:
+ resolution: {integrity: sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==}
+ engines: {node: '>=12', npm: '>=6'}
+
+ jsx-ast-utils-x@0.1.0:
+ resolution: {integrity: sha512-eQQBjBnsVtGacsG9uJNB8qOr3yA8rga4wAaGG1qRcBzSIvfhERLrWxMAM1hp5fcS6Abo8M4+bUBTekYR0qTPQw==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ jsx-ast-utils@3.3.5:
+ resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==}
+ engines: {node: '>=4.0'}
+
+ just-extend@6.2.0:
+ resolution: {integrity: sha512-cYofQu2Xpom82S6qD778jBDpwvvy39s1l/hrYij2u9AMdQcGRpaBu6kY4mVhuno5kJVi1DAz4aiphA2WI1/OAw==}
+
+ jwa@2.0.1:
+ resolution: {integrity: sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==}
+
+ jws@4.0.1:
+ resolution: {integrity: sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==}
+
+ keyv@4.5.4:
+ resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
+
+ kleur@3.0.3:
+ resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==}
+ engines: {node: '>=6'}
+
+ kuler@2.0.0:
+ resolution: {integrity: sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==}
+
+ language-subtag-registry@0.3.23:
+ resolution: {integrity: sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==}
+
+ language-tags@1.0.9:
+ resolution: {integrity: sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==}
+ engines: {node: '>=0.10'}
+
+ lcov-result-merger@5.0.1:
+ resolution: {integrity: sha512-i53RjTYfqbHgerqGtuJjDfARDU340zNxXrJudQZU3o8ak9rrx8FDQUKf38Cjm6MtbqonqiDFmoKuUe++uZbvOg==}
+ engines: {node: '>=14'}
+ hasBin: true
+
+ leven@3.1.0:
+ resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==}
+ engines: {node: '>=6'}
+
+ levn@0.4.1:
+ resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
+ engines: {node: '>= 0.8.0'}
+
+ lines-and-columns@1.2.4:
+ resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
+
+ locate-path@5.0.0:
+ resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==}
+ engines: {node: '>=8'}
+
+ locate-path@6.0.0:
+ resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
+ engines: {node: '>=10'}
+
+ lodash.includes@4.3.0:
+ resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==}
+
+ lodash.isboolean@3.0.3:
+ resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==}
+
+ lodash.isequal@4.5.0:
+ resolution: {integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==}
+ deprecated: This package is deprecated. Use require('node:util').isDeepStrictEqual instead.
+
+ lodash.isinteger@4.0.4:
+ resolution: {integrity: sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==}
+
+ lodash.isnumber@3.0.3:
+ resolution: {integrity: sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==}
+
+ lodash.isplainobject@4.0.6:
+ resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==}
+
+ lodash.isstring@4.0.1:
+ resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==}
+
+ lodash.memoize@4.1.2:
+ resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==}
+
+ lodash.merge@4.6.2:
+ resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
+
+ lodash.once@4.1.1:
+ resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==}
+
+ lodash@4.18.1:
+ resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==}
+
+ logform@2.7.0:
+ resolution: {integrity: sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==}
+ engines: {node: '>= 12.0.0'}
+
+ loose-envify@1.4.0:
+ resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
+ hasBin: true
+
+ lru-cache@10.4.3:
+ resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
+
+ lru-cache@5.1.1:
+ resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
+
+ make-dir@4.0.0:
+ resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==}
+ engines: {node: '>=10'}
+
+ make-error@1.3.6:
+ resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==}
+
+ makeerror@1.0.12:
+ resolution: {integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==}
+
+ math-intrinsics@1.1.0:
+ resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
+ engines: {node: '>= 0.4'}
+
+ merge-stream@2.0.0:
+ resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==}
+
+ merge2@1.4.1:
+ resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
+ engines: {node: '>= 8'}
+
+ micromatch@4.0.8:
+ resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
+ engines: {node: '>=8.6'}
+
+ mime-db@1.52.0:
+ resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
+ engines: {node: '>= 0.6'}
+
+ mime-types@2.1.35:
+ resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
+ engines: {node: '>= 0.6'}
+
+ mimic-fn@2.1.0:
+ resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==}
+ engines: {node: '>=6'}
+
+ minimatch@10.2.6:
+ resolution: {integrity: sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==}
+ engines: {node: 18 || 20 || >=22}
+
+ minimatch@3.1.5:
+ resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==}
+
+ minimatch@9.0.9:
+ resolution: {integrity: sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==}
+ engines: {node: '>=16 || 14 >=14.17'}
+
+ minimist@1.2.8:
+ resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
+
+ minipass@7.1.3:
+ resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==}
+ engines: {node: '>=16 || 14 >=14.17'}
+
+ mkdirp@1.0.4:
+ resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==}
+ engines: {node: '>=10'}
+ hasBin: true
+
+ mnemonist@0.38.3:
+ resolution: {integrity: sha512-2K9QYubXx/NAjv4VLq1d1Ly8pWNC5L3BrixtdkyTegXWJIqY+zLNDhhX/A+ZwWt70tB1S8H4BE8FLYEFyNoOBw==}
+
+ mock-fs@5.5.0:
+ resolution: {integrity: sha512-d/P1M/RacgM3dB0sJ8rjeRNXxtapkPCUnMGmIN0ixJ16F/E4GUZCvWcSGfWGz8eaXYvn1s9baUwNjI4LOPEjiA==}
+ engines: {node: '>=12.0.0'}
+
+ ms@2.1.3:
+ resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
+
+ napi-postinstall@0.3.4:
+ resolution: {integrity: sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==}
+ engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
+ hasBin: true
+
+ natural-compare-lite@1.4.0:
+ resolution: {integrity: sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==}
+
+ natural-compare@1.4.0:
+ resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
+
+ neo-async@2.6.2:
+ resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==}
+
+ nise@6.1.5:
+ resolution: {integrity: sha512-SnRDPDBjxZZoU2n0+gzzLtSvo1OZo7j6jnbXsoh3AFxEGhaFU7ZF0TmefuKERq79wxR2U+MPn7ArW+Tl+clC3A==}
+
+ node-exports-info@1.6.2:
+ resolution: {integrity: sha512-kXs9Go0cah0qHVV2v389IXQLdLCeE1xfFtjOAF+iobu0OIoG1pje8At2vMHyaPMiPMnG/LWP50twML21eMcAag==}
+ engines: {node: '>= 0.4'}
+
+ node-int64@0.4.0:
+ resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==}
+
+ node-releases@2.0.51:
+ resolution: {integrity: sha512-wRNIrw4DmVLKQlbgOMdkMx27Wrpzes2hh5Jtbi2bjPd+4wJstWIqP5A+lscnqbm0xxmT5Bpg8Lec5ItEBwx6BQ==}
+ engines: {node: '>=18'}
+
+ normalize-path@3.0.0:
+ resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
+ engines: {node: '>=0.10.0'}
+
+ npm-run-path@4.0.1:
+ resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==}
+ engines: {node: '>=8'}
+
+ nwsapi@2.2.24:
+ resolution: {integrity: sha512-7YRhZ3jS45LwmSCT4b2sVFHt/WuovaktDU07QrtOBY2PXskss5a9jfmR9jptyumwXST+rFjrmppMY1KT/yn35A==}
+
+ object-assign@4.1.1:
+ resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
+ engines: {node: '>=0.10.0'}
+
+ object-inspect@1.13.4:
+ resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==}
+ engines: {node: '>= 0.4'}
+
+ object-keys@1.1.1:
+ resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==}
+ engines: {node: '>= 0.4'}
+
+ object.assign@4.1.7:
+ resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==}
+ engines: {node: '>= 0.4'}
+
+ object.entries@1.1.9:
+ resolution: {integrity: sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==}
+ engines: {node: '>= 0.4'}
+
+ object.fromentries@2.0.8:
+ resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==}
+ engines: {node: '>= 0.4'}
+
+ object.values@1.2.1:
+ resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==}
+ engines: {node: '>= 0.4'}
+
+ obliterator@1.6.1:
+ resolution: {integrity: sha512-9WXswnqINnnhOG/5SLimUlzuU1hFJUc8zkwyD59Sd+dPOMf05PmnYG/d6Q7HZ+KmgkZJa1PxRso6QdM3sTNHig==}
+
+ once@1.4.0:
+ resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
+
+ one-time@1.0.0:
+ resolution: {integrity: sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==}
+
+ onetime@5.1.2:
+ resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==}
+ engines: {node: '>=6'}
+
+ optionator@0.9.4:
+ resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
+ engines: {node: '>= 0.8.0'}
+
+ own-keys@1.0.2:
+ resolution: {integrity: sha512-19YVAg7T+WTrxggPukVq7DjTv6+PJ867TmhCvBsYwmbFCsZd344rq2Ld1p0wo8f8Qrrhgp82c6FJRqdXWtSEhg==}
+ engines: {node: '>= 0.4'}
+
+ p-limit@2.3.0:
+ resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==}
+ engines: {node: '>=6'}
+
+ p-limit@3.1.0:
+ resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
+ engines: {node: '>=10'}
+
+ p-locate@4.1.0:
+ resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==}
+ engines: {node: '>=8'}
+
+ p-locate@5.0.0:
+ resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==}
+ engines: {node: '>=10'}
+
+ p-try@2.2.0:
+ resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==}
+ engines: {node: '>=6'}
+
+ package-json-from-dist@1.0.1:
+ resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==}
+
+ parent-module@1.0.1:
+ resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
+ engines: {node: '>=6'}
+
+ parse-json@5.2.0:
+ resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==}
+ engines: {node: '>=8'}
+
+ parse5@7.3.0:
+ resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==}
+
+ path-exists@4.0.0:
+ resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
+ engines: {node: '>=8'}
+
+ path-is-absolute@1.0.1:
+ resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==}
+ engines: {node: '>=0.10.0'}
+
+ path-key@3.1.1:
+ resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
+ engines: {node: '>=8'}
+
+ path-parse@1.0.7:
+ resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
+
+ path-scurry@1.11.1:
+ resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==}
+ engines: {node: '>=16 || 14 >=14.18'}
+
+ path-to-regexp@8.4.2:
+ resolution: {integrity: sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==}
+
+ picocolors@1.1.1:
+ resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
+
+ picomatch@2.3.2:
+ resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==}
+ engines: {node: '>=8.6'}
+
+ picomatch@4.0.5:
+ resolution: {integrity: sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==}
+ engines: {node: '>=12'}
+
+ pirates@4.0.7:
+ resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==}
+ engines: {node: '>= 6'}
+
+ pkg-dir@4.2.0:
+ resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==}
+ engines: {node: '>=8'}
+
+ pluralize@8.0.0:
+ resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==}
+ engines: {node: '>=4'}
+
+ possible-typed-array-names@1.1.0:
+ resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==}
+ engines: {node: '>= 0.4'}
+
+ prelude-ls@1.2.1:
+ resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
+ engines: {node: '>= 0.8.0'}
+
+ prettier-linter-helpers@1.0.1:
+ resolution: {integrity: sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg==}
+ engines: {node: '>=6.0.0'}
+
+ prettier@3.9.6:
+ resolution: {integrity: sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g==}
+ engines: {node: '>=14'}
+ hasBin: true
+
+ pretty-format@29.7.0:
+ resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+
+ pretty-format@30.4.1:
+ resolution: {integrity: sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw==}
+ engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
+
+ prompts@2.4.2:
+ resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==}
+ engines: {node: '>= 6'}
+
+ prop-types@15.8.1:
+ resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
+
+ proxy-from-env@2.1.0:
+ resolution: {integrity: sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==}
+ engines: {node: '>=10'}
+
+ punycode@2.3.1:
+ resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
+ engines: {node: '>=6'}
+
+ pure-rand@6.1.0:
+ resolution: {integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==}
+
+ pure-rand@7.0.1:
+ resolution: {integrity: sha512-oTUZM/NAZS8p7ANR3SHh30kXB+zK2r2BPcEn/awJIbOvq82WoMN4p62AWWp3Hhw50G0xMsw1mhIBLqHw64EcNQ==}
+
+ qs@6.15.3:
+ resolution: {integrity: sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==}
+ engines: {node: '>=0.6'}
+
+ queue-microtask@1.2.3:
+ resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
+
+ react-is@16.13.1:
+ resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
+
+ react-is@18.3.1:
+ resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==}
+
+ react-is@19.2.8:
+ resolution: {integrity: sha512-s5un28nYxKJw5gvUHyW5PCC28CvBqLu9r3cWgzHT4Vo/5fqqkFcdRYsGcKf50WMPpjjFZS5d76fn3YCo2njKwQ==}
+
+ readable-stream@3.6.2:
+ resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==}
+ engines: {node: '>= 6'}
+
+ refa@0.12.1:
+ resolution: {integrity: sha512-J8rn6v4DBb2nnFqkqwy6/NnTYMcgLA+sLr0iIO41qpv0n+ngb7ksag2tMRl0inb1bbO/esUwzW1vbJi7K0sI0g==}
+ engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
+
+ reflect.getprototypeof@1.0.10:
+ resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==}
+ engines: {node: '>= 0.4'}
+
+ regexp-ast-analysis@0.7.1:
+ resolution: {integrity: sha512-sZuz1dYW/ZsfG17WSAG7eS85r5a0dDsvg+7BiiYR5o6lKCAtUrEwdmRmaGF6rwVj3LcmAeYkOWKEPlbPzN3Y3A==}
+ engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
+
+ regexp-tree@0.1.27:
+ resolution: {integrity: sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA==}
+ hasBin: true
+
+ regexp.prototype.flags@1.5.4:
+ resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==}
+ engines: {node: '>= 0.4'}
+
+ regjsparser@0.12.0:
+ resolution: {integrity: sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ==}
+ hasBin: true
+
+ require-directory@2.1.1:
+ resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
+ engines: {node: '>=0.10.0'}
+
+ resolve-cwd@3.0.0:
+ resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==}
+ engines: {node: '>=8'}
+
+ resolve-from@4.0.0:
+ resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
+ engines: {node: '>=4'}
+
+ resolve-from@5.0.0:
+ resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==}
+ engines: {node: '>=8'}
+
+ resolve-pkg-maps@1.0.0:
+ resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==}
+
+ resolve.exports@2.0.3:
+ resolution: {integrity: sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==}
+ engines: {node: '>=10'}
+
+ resolve@1.22.12:
+ resolution: {integrity: sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==}
+ engines: {node: '>= 0.4'}
+ hasBin: true
+
+ resolve@2.0.0-next.7:
+ resolution: {integrity: sha512-tqt+NBWwyaMgw3zDsnygx4CByWjQEJHOPMdslYhppaQSJUtL/D4JO9CcBBlhPoI8lz9oJIDXkwXfhF4aWqP8xQ==}
+ engines: {node: '>= 0.4'}
+ hasBin: true
+
+ reusify@1.1.0:
+ resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==}
+ engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
+
+ rrweb-cssom@0.8.0:
+ resolution: {integrity: sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==}
+
+ run-parallel@1.2.0:
+ resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
+
+ safe-array-concat@1.1.4:
+ resolution: {integrity: sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg==}
+ engines: {node: '>=0.4'}
+
+ safe-buffer@5.2.1:
+ resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
+
+ safe-push-apply@1.0.0:
+ resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==}
+ engines: {node: '>= 0.4'}
+
+ safe-regex-test@1.1.0:
+ resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==}
+ engines: {node: '>= 0.4'}
+
+ safe-regex@2.1.1:
+ resolution: {integrity: sha512-rx+x8AMzKb5Q5lQ95Zoi6ZbJqwCLkqi3XuJXp5P3rT8OEc6sZCJG5AE5dU3lsgRr/F4Bs31jSlVN+j5KrsGu9A==}
+
+ safe-stable-stringify@2.5.0:
+ resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==}
+ engines: {node: '>=10'}
+
+ safer-buffer@2.1.2:
+ resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
+
+ saxes@6.0.0:
+ resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==}
+ engines: {node: '>=v12.22.7'}
+
+ scslre@0.3.0:
+ resolution: {integrity: sha512-3A6sD0WYP7+QrjbfNA2FN3FsOaGGFoekCVgTyypy53gPxhbkCIjtO6YWgdrfM+n/8sI8JeXZOIxsHjMTNxQ4nQ==}
+ engines: {node: ^14.0.0 || >=16.0.0}
+
+ semver@6.3.1:
+ resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
+ hasBin: true
+
+ semver@7.7.4:
+ resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==}
+ engines: {node: '>=10'}
+ hasBin: true
+
+ semver@7.8.5:
+ resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==}
+ engines: {node: '>=10'}
+ hasBin: true
+
+ set-function-length@1.2.2:
+ resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==}
+ engines: {node: '>= 0.4'}
+
+ set-function-name@2.0.2:
+ resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==}
+ engines: {node: '>= 0.4'}
+
+ set-proto@1.0.0:
+ resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==}
+ engines: {node: '>= 0.4'}
+
+ shebang-command@2.0.0:
+ resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
+ engines: {node: '>=8'}
+
+ shebang-regex@3.0.0:
+ resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
+ engines: {node: '>=8'}
+
+ side-channel-list@1.0.1:
+ resolution: {integrity: sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==}
+ engines: {node: '>= 0.4'}
+
+ side-channel-map@1.0.1:
+ resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==}
+ engines: {node: '>= 0.4'}
+
+ side-channel-weakmap@1.0.2:
+ resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==}
+ engines: {node: '>= 0.4'}
+
+ side-channel@1.1.1:
+ resolution: {integrity: sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==}
+ engines: {node: '>= 0.4'}
+
+ signal-exit@3.0.7:
+ resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==}
+
+ signal-exit@4.1.0:
+ resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==}
+ engines: {node: '>=14'}
+
+ sinon@18.0.1:
+ resolution: {integrity: sha512-a2N2TDY1uGviajJ6r4D1CyRAkzE9NNVlYOV1wX5xQDuAk0ONgzgRl0EjCQuRCPxOwp13ghsMwt9Gdldujs39qw==}
+
+ sisteransi@1.0.5:
+ resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==}
+
+ slash@3.0.0:
+ resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==}
+ engines: {node: '>=8'}
+
+ source-map-support@0.5.13:
+ resolution: {integrity: sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==}
+
+ source-map@0.6.1:
+ resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
+ engines: {node: '>=0.10.0'}
+
+ sprintf-js@1.0.3:
+ resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==}
+
+ stable-hash-x@0.2.0:
+ resolution: {integrity: sha512-o3yWv49B/o4QZk5ZcsALc6t0+eCelPc44zZsLtCQnZPDwFpDYSWcDnrv2TtMmMbQ7uKo3J0HTURCqckw23czNQ==}
+ engines: {node: '>=12.0.0'}
+
+ stack-trace@0.0.10:
+ resolution: {integrity: sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==}
+
+ stack-utils@2.0.6:
+ resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==}
+ engines: {node: '>=10'}
+
+ stop-iteration-iterator@1.1.0:
+ resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==}
+ engines: {node: '>= 0.4'}
+
+ stream-browserify@3.0.0:
+ resolution: {integrity: sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==}
+
+ string-length@4.0.2:
+ resolution: {integrity: sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==}
+ engines: {node: '>=10'}
+
+ string-width@4.2.3:
+ resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
+ engines: {node: '>=8'}
+
+ string-width@5.1.2:
+ resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==}
+ engines: {node: '>=12'}
+
+ string.prototype.includes@2.0.1:
+ resolution: {integrity: sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==}
+ engines: {node: '>= 0.4'}
+
+ string.prototype.matchall@4.0.12:
+ resolution: {integrity: sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==}
+ engines: {node: '>= 0.4'}
+
+ string.prototype.repeat@1.0.0:
+ resolution: {integrity: sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==}
+
+ string.prototype.trim@1.2.11:
+ resolution: {integrity: sha512-PwvK7BU+CMTJGYQCTZb5RWXIML92lftJLhQz1tBzgKiqGxJaMlBAa48POXaNAC2s4y8jr3EFqrkF9+44neS46w==}
+ engines: {node: '>= 0.4'}
+
+ string.prototype.trimend@1.0.10:
+ resolution: {integrity: sha512-2+3aDAOmPTmuFwjDnmJG2ctEkQKVki7vOSqaxkv42Mowj1V6PnvuwFCRrR5lChUux1TBskPjfkeTOhqczDMxTw==}
+ engines: {node: '>= 0.4'}
+
+ string.prototype.trimstart@1.0.8:
+ resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==}
+ engines: {node: '>= 0.4'}
+
+ string_decoder@1.3.0:
+ resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==}
+
+ strip-ansi@6.0.1:
+ resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
+ engines: {node: '>=8'}
+
+ strip-ansi@7.2.0:
+ resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==}
+ engines: {node: '>=12'}
+
+ strip-bom@4.0.0:
+ resolution: {integrity: sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==}
+ engines: {node: '>=8'}
+
+ strip-final-newline@2.0.0:
+ resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==}
+ engines: {node: '>=6'}
+
+ strip-indent@4.1.1:
+ resolution: {integrity: sha512-SlyRoSkdh1dYP0PzclLE7r0M9sgbFKKMFXpFRUMNuKhQSbC6VQIGzq3E0qsfvGJaUFJPGv6Ws1NZ/haTAjfbMA==}
+ engines: {node: '>=12'}
+
+ strip-json-comments@3.1.1:
+ resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
+ engines: {node: '>=8'}
+
+ supports-color@7.2.0:
+ resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
+ engines: {node: '>=8'}
+
+ supports-color@8.1.1:
+ resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==}
+ engines: {node: '>=10'}
+
+ supports-preserve-symlinks-flag@1.0.0:
+ resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
+ engines: {node: '>= 0.4'}
+
+ symbol-tree@3.2.4:
+ resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==}
+
+ synckit@0.11.13:
+ resolution: {integrity: sha512-eNRKgb3z66Yp3D2CixVujOUvXLFUTij/zVnV8KRyvFdQwpz7I5DS8UfRkTeLzb64u+dkzDSdelE24izu+zSSUg==}
+ engines: {node: ^14.18.0 || >=16.0.0}
+
+ test-exclude@6.0.0:
+ resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==}
+ engines: {node: '>=8'}
+
+ text-hex@1.0.0:
+ resolution: {integrity: sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==}
+
+ tinyglobby@0.2.17:
+ resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==}
+ engines: {node: '>=12.0.0'}
+
+ tinyrainbow@3.1.1:
+ resolution: {integrity: sha512-yau8yJdTt989Mm0Bd/236QnzEiPf2xLLTqUZRUJOo/3CB078LSwzei343DgtJVmfJKJE3TMINY1u42SQsP6mXw==}
+ engines: {node: '>=14.0.0'}
+
+ tldts-core@6.1.86:
+ resolution: {integrity: sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==}
+
+ tldts@6.1.86:
+ resolution: {integrity: sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==}
+ hasBin: true
+
+ tmpl@1.0.5:
+ resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==}
+
+ to-regex-range@5.0.1:
+ resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
+ engines: {node: '>=8.0'}
+
+ tough-cookie@5.1.2:
+ resolution: {integrity: sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==}
+ engines: {node: '>=16'}
+
+ tr46@5.1.1:
+ resolution: {integrity: sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==}
+ engines: {node: '>=18'}
+
+ triple-beam@1.4.1:
+ resolution: {integrity: sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==}
+ engines: {node: '>= 14.0.0'}
+
+ ts-api-utils@2.5.0:
+ resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==}
+ engines: {node: '>=18.12'}
+ peerDependencies:
+ typescript: '>=4.8.4'
+
+ ts-essentials@10.2.1:
+ resolution: {integrity: sha512-+Id1fRkuir+CsgK2x04/icS2b4V1hQmq7ObzIrDjhN0ozfRYivnP7aaKMVJfLApQm0trjR39A6NIMVchiB9Erw==}
+ peerDependencies:
+ typescript: '>=4.5.0'
+ peerDependenciesMeta:
+ typescript:
+ optional: true
+
+ ts-jest@29.4.12:
+ resolution: {integrity: sha512-Ov6ClY53Fflh6BGAnY2DlTq1hYDrTycz2PVTXBWFW2CU+9zrEqAp9fWdGXl42EXO5RLSFAcAZ2JFKbP+zBTFfw==}
+ engines: {node: ^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0}
+ hasBin: true
+ 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
+ esbuild: '*'
+ 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
+
+ ts-node@10.9.2:
+ resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==}
+ hasBin: true
+ peerDependencies:
+ '@swc/core': '>=1.2.50'
+ '@swc/wasm': '>=1.2.50'
+ '@types/node': '*'
+ typescript: '>=2.7'
+ peerDependenciesMeta:
+ '@swc/core':
+ optional: true
+ '@swc/wasm':
+ optional: true
+
+ tslib@2.8.1:
+ resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
+
+ tsx@4.23.5:
+ resolution: {integrity: sha512-rw55FUaqOoI7RvlQwLbhO4nSDApnQ4/CykPuiQ/EPvtrX3WA9Ig55jIt9VvbBJbzJuj12ueRu4PMZ2SxPVbihg==}
+ engines: {node: '>=18.0.0'}
+ hasBin: true
+
+ turbo@2.10.8:
+ resolution: {integrity: sha512-9+8YX5QOkGXzZxcIykTHgaooRHGMWO+jfdyRK0o+rN0U7hBIig2MrJ8r/aNzIPDPhdA73SGb0O+tIztaModTMg==}
+ hasBin: true
+
+ type-check@0.4.0:
+ resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
+ engines: {node: '>= 0.8.0'}
+
+ type-detect@4.0.8:
+ resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==}
+ engines: {node: '>=4'}
+
+ type-detect@4.1.0:
+ resolution: {integrity: sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==}
+ engines: {node: '>=4'}
+
+ type-fest@0.21.3:
+ resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==}
+ engines: {node: '>=10'}
+
+ type-fest@4.41.0:
+ resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==}
+ engines: {node: '>=16'}
+
+ typed-array-buffer@1.0.3:
+ resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==}
+ engines: {node: '>= 0.4'}
+
+ typed-array-byte-length@1.0.3:
+ resolution: {integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==}
+ engines: {node: '>= 0.4'}
+
+ typed-array-byte-offset@1.0.4:
+ resolution: {integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==}
+ engines: {node: '>= 0.4'}
+
+ typed-array-length@1.0.8:
+ resolution: {integrity: sha512-phPGCwqr2+Qo0fwniCE8e4pKnGu/yFb5nD5Y8bf0EEeiI5GklnACYA9GFy/DrAeRrKHXvHn+1SUsOWgJp6RO+g==}
+ engines: {node: '>= 0.4'}
+
+ typescript-eslint@8.65.0:
+ resolution: {integrity: sha512-/ggrHAwyjENDusvyxbuqxAC2dTnZg/Z8F+fgQtYIz+L6n/9HfSlEZcFGV/NsMNa6CkGk0xUjUAFwC0vHOflvIA==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
+ typescript: '>=4.8.4 <6.1.0'
+
+ typescript@5.9.3:
+ resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
+ engines: {node: '>=14.17'}
+ hasBin: true
+
+ uglify-js@3.19.3:
+ resolution: {integrity: sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==}
+ engines: {node: '>=0.8.0'}
+ hasBin: true
+
+ unbox-primitive@1.1.0:
+ resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==}
+ engines: {node: '>= 0.4'}
+
+ undici-types@7.18.2:
+ resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==}
+
+ unrs-resolver@1.12.2:
+ resolution: {integrity: sha512-dmlRxBJJayXjqTwC+JtF1HhJmgf3ftQ3YejFcZrf4+KKtJv0qDsK1pjqaaVjG7wJ5NJ6UVP1OqRMQ71Z4C3rxQ==}
+
+ update-browserslist-db@1.2.3:
+ resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==}
+ hasBin: true
+ peerDependencies:
+ browserslist: '>= 4.21.0'
+
+ uri-js@4.4.1:
+ resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
+
+ util-deprecate@1.0.2:
+ resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
+
+ v8-compile-cache-lib@3.0.1:
+ resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==}
+
+ v8-to-istanbul@9.3.0:
+ resolution: {integrity: sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==}
+ engines: {node: '>=10.12.0'}
+
+ vscode-json-languageservice@4.2.1:
+ resolution: {integrity: sha512-xGmv9QIWs2H8obGbWg+sIPI/3/pFgj/5OWBhNzs00BkYQ9UaB2F6JJaGB/2/YOZJ3BvLXQTC4Q7muqU25QgAhA==}
+
+ vscode-languageserver-textdocument@1.0.12:
+ resolution: {integrity: sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==}
+
+ vscode-languageserver-types@3.18.0:
+ resolution: {integrity: sha512-8TsGPNMIMiiBdkORgRSvLjuiEIiAFtO+KssmYWxQ+uSVvlf7RjK8YKCOjPzZ+YA04jXEV7+7LvkSmHkhpNS99g==}
+
+ vscode-nls@5.2.0:
+ resolution: {integrity: sha512-RAaHx7B14ZU04EU31pT+rKz2/zSl7xMsfIZuo8pd+KZO6PXtQmpevpq3vxvWNcrGbdmhM/rr5Uw5Mz+NBfhVng==}
+
+ vscode-uri@3.1.0:
+ resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==}
+
+ w3c-xmlserializer@5.0.0:
+ resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==}
+ engines: {node: '>=18'}
+
+ walker@1.0.8:
+ resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==}
+
+ webidl-conversions@7.0.0:
+ resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==}
+ engines: {node: '>=12'}
+
+ whatwg-encoding@3.1.1:
+ resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==}
+ engines: {node: '>=18'}
+ deprecated: Use @exodus/bytes instead for a more spec-conformant and faster implementation
+
+ whatwg-mimetype@4.0.0:
+ resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==}
+ engines: {node: '>=18'}
+
+ whatwg-url@14.2.0:
+ resolution: {integrity: sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==}
+ engines: {node: '>=18'}
+
+ which-boxed-primitive@1.1.1:
+ resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==}
+ engines: {node: '>= 0.4'}
+
+ which-builtin-type@1.2.1:
+ resolution: {integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==}
+ engines: {node: '>= 0.4'}
+
+ which-collection@1.0.2:
+ resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==}
+ engines: {node: '>= 0.4'}
+
+ which-typed-array@1.1.22:
+ resolution: {integrity: sha512-fvO4ExWMFsqyhG3AiPAObMuY1lxaqgYcxbc49CNdWDDECOJNgQyvsOWVwbZc+qf3rzRtxojBK+CMEv0Ld5CYpw==}
+ engines: {node: '>= 0.4'}
+
+ which@2.0.2:
+ resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
+ engines: {node: '>= 8'}
+ hasBin: true
+
+ winston-transport@4.9.0:
+ resolution: {integrity: sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==}
+ engines: {node: '>= 12.0.0'}
+
+ winston@3.19.0:
+ resolution: {integrity: sha512-LZNJgPzfKR+/J3cHkxcpHKpKKvGfDZVPS4hfJCc4cCG0CgYzvlD6yE/S3CIL/Yt91ak327YCpiF/0MyeZHEHKA==}
+ engines: {node: '>= 12.0.0'}
+
+ word-wrap@1.2.5:
+ resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
+ engines: {node: '>=0.10.0'}
+
+ wordwrap@1.0.0:
+ resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==}
+
+ wrap-ansi@7.0.0:
+ resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
+ engines: {node: '>=10'}
+
+ wrap-ansi@8.1.0:
+ resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==}
+ engines: {node: '>=12'}
+
+ wrappy@1.0.2:
+ resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
+
+ write-file-atomic@4.0.2:
+ resolution: {integrity: sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==}
+ engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0}
+
+ write-file-atomic@5.0.1:
+ resolution: {integrity: sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==}
+ engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
+
+ ws@8.21.1:
+ resolution: {integrity: sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw==}
+ engines: {node: '>=10.0.0'}
+ peerDependencies:
+ bufferutil: ^4.0.1
+ utf-8-validate: '>=5.0.2'
+ peerDependenciesMeta:
+ bufferutil:
+ optional: true
+ utf-8-validate:
+ optional: true
+
+ xml-name-validator@5.0.0:
+ resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==}
+ engines: {node: '>=18'}
+
+ xmlbuilder@15.0.0:
+ resolution: {integrity: sha512-KLu/G0DoWhkncQ9eHSI6s0/w+T4TM7rQaLhtCaL6tORv8jFlJPlnGumsgTcGfYeS1qZ/IHqrvDG7zJZ4d7e+nw==}
+ engines: {node: '>=8.0'}
+
+ xmlchars@2.2.0:
+ resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==}
+
+ y18n@5.0.8:
+ resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
+ engines: {node: '>=10'}
+
+ yallist@3.1.1:
+ resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
+
+ yargs-parser@20.2.9:
+ resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==}
+ engines: {node: '>=10'}
+
+ yargs-parser@21.1.1:
+ resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
+ engines: {node: '>=12'}
+
+ yargs@16.2.2:
+ resolution: {integrity: sha512-Nt9ZJjXTv5R8MHbqby/wXQ6Gi0Bb3TcYZkR1bzuL4yB2OxWPkXknz513gEF0GoA6tn00UpbPvERW8rzCuWCA6w==}
+ engines: {node: '>=10'}
+
+ yargs@17.7.3:
+ resolution: {integrity: sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==}
+ engines: {node: '>=12'}
+
+ yn@3.1.1:
+ resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==}
+ engines: {node: '>=6'}
+
+ yocto-queue@0.1.0:
+ resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
+ engines: {node: '>=10'}
+
+ zod-validation-error@4.0.2:
+ resolution: {integrity: sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==}
+ engines: {node: '>=18.0.0'}
+ peerDependencies:
+ zod: ^3.25.0 || ^4.0.0
+
+ zod@4.4.3:
+ resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==}
+
+snapshots:
+
+ '@asamuzakjp/css-color@3.2.0':
+ dependencies:
+ '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
+ '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
+ '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
+ '@csstools/css-tokenizer': 3.0.4
+ lru-cache: 10.4.3
+
+ '@aws-sdk/checksums@3.1000.24':
+ dependencies:
+ '@aws-sdk/core': 3.977.4
+ '@aws-sdk/types': 3.974.2
+ '@smithy/core': 3.31.1
+ '@smithy/types': 4.16.1
+ tslib: 2.8.1
+
+ '@aws-sdk/client-athena@3.1101.0':
+ dependencies:
+ '@aws-sdk/core': 3.977.4
+ '@aws-sdk/credential-provider-node': 3.972.76
+ '@aws-sdk/types': 3.974.2
+ '@smithy/core': 3.31.1
+ '@smithy/fetch-http-handler': 5.6.13
+ '@smithy/node-http-handler': 4.9.13
+ '@smithy/types': 4.16.1
+ tslib: 2.8.1
+
+ '@aws-sdk/client-cloudwatch@3.1101.0':
+ dependencies:
+ '@aws-sdk/core': 3.977.4
+ '@aws-sdk/credential-provider-node': 3.972.76
+ '@aws-sdk/types': 3.974.2
+ '@smithy/core': 3.31.1
+ '@smithy/fetch-http-handler': 5.6.13
+ '@smithy/middleware-compression': 4.5.16
+ '@smithy/node-http-handler': 4.9.13
+ '@smithy/types': 4.16.1
+ tslib: 2.8.1
+
+ '@aws-sdk/client-dynamodb@3.1101.0':
+ dependencies:
+ '@aws-sdk/core': 3.977.4
+ '@aws-sdk/credential-provider-node': 3.972.76
+ '@aws-sdk/dynamodb-codec': 3.973.39
+ '@aws-sdk/middleware-endpoint-discovery': 3.972.27
+ '@aws-sdk/types': 3.974.2
+ '@smithy/core': 3.31.1
+ '@smithy/fetch-http-handler': 5.6.13
+ '@smithy/node-http-handler': 4.9.13
+ '@smithy/types': 4.16.1
+ tslib: 2.8.1
+
+ '@aws-sdk/client-eventbridge@3.1101.0':
+ dependencies:
+ '@aws-sdk/core': 3.977.4
+ '@aws-sdk/credential-provider-node': 3.972.76
+ '@aws-sdk/signature-v4-multi-region': 3.996.43
+ '@aws-sdk/types': 3.974.2
+ '@smithy/core': 3.31.1
+ '@smithy/fetch-http-handler': 5.6.13
+ '@smithy/node-http-handler': 4.9.13
+ '@smithy/types': 4.16.1
+ tslib: 2.8.1
+
+ '@aws-sdk/client-lambda@3.1101.0':
+ dependencies:
+ '@aws-sdk/core': 3.977.4
+ '@aws-sdk/credential-provider-node': 3.972.76
+ '@aws-sdk/types': 3.974.2
+ '@smithy/core': 3.31.1
+ '@smithy/fetch-http-handler': 5.6.13
+ '@smithy/node-http-handler': 4.9.13
+ '@smithy/types': 4.16.1
+ tslib: 2.8.1
+
+ '@aws-sdk/client-s3@3.1101.0':
+ dependencies:
+ '@aws-sdk/checksums': 3.1000.24
+ '@aws-sdk/core': 3.977.4
+ '@aws-sdk/credential-provider-node': 3.972.76
+ '@aws-sdk/middleware-sdk-s3': 3.972.70
+ '@aws-sdk/signature-v4-multi-region': 3.996.43
+ '@aws-sdk/types': 3.974.2
+ '@smithy/core': 3.31.1
+ '@smithy/fetch-http-handler': 5.6.13
+ '@smithy/node-http-handler': 4.9.13
+ '@smithy/types': 4.16.1
+ tslib: 2.8.1
+
+ '@aws-sdk/client-sqs@3.1101.0':
+ dependencies:
+ '@aws-sdk/core': 3.977.4
+ '@aws-sdk/credential-provider-node': 3.972.76
+ '@aws-sdk/middleware-sdk-sqs': 3.972.39
+ '@aws-sdk/types': 3.974.2
+ '@smithy/core': 3.31.1
+ '@smithy/fetch-http-handler': 5.6.13
+ '@smithy/node-http-handler': 4.9.13
+ '@smithy/types': 4.16.1
+ tslib: 2.8.1
+
+ '@aws-sdk/client-ssm@3.1101.0':
+ dependencies:
+ '@aws-sdk/core': 3.977.4
+ '@aws-sdk/credential-provider-node': 3.972.76
+ '@aws-sdk/types': 3.974.2
+ '@smithy/core': 3.31.1
+ '@smithy/fetch-http-handler': 5.6.13
+ '@smithy/node-http-handler': 4.9.13
+ '@smithy/types': 4.16.1
+ tslib: 2.8.1
+
+ '@aws-sdk/core@3.977.4':
+ dependencies:
+ '@aws-sdk/types': 3.974.2
+ '@aws-sdk/xml-builder': 3.972.37
+ '@aws/lambda-invoke-store': 0.3.0
+ '@smithy/core': 3.31.1
+ '@smithy/signature-v4': 5.6.12
+ '@smithy/types': 4.16.1
+ bowser: 2.14.1
+ tslib: 2.8.1
+
+ '@aws-sdk/credential-provider-env@3.972.65':
+ dependencies:
+ '@aws-sdk/core': 3.977.4
+ '@aws-sdk/types': 3.974.2
+ '@smithy/core': 3.31.1
+ '@smithy/types': 4.16.1
+ tslib: 2.8.1
+
+ '@aws-sdk/credential-provider-http@3.972.67':
+ dependencies:
+ '@aws-sdk/core': 3.977.4
+ '@aws-sdk/types': 3.974.2
+ '@smithy/core': 3.31.1
+ '@smithy/fetch-http-handler': 5.6.13
+ '@smithy/node-http-handler': 4.9.13
+ '@smithy/types': 4.16.1
+ tslib: 2.8.1
+
+ '@aws-sdk/credential-provider-ini@3.973.10':
+ dependencies:
+ '@aws-sdk/core': 3.977.4
+ '@aws-sdk/credential-provider-env': 3.972.65
+ '@aws-sdk/credential-provider-http': 3.972.67
+ '@aws-sdk/credential-provider-login': 3.972.72
+ '@aws-sdk/credential-provider-process': 3.972.65
+ '@aws-sdk/credential-provider-sso': 3.973.9
+ '@aws-sdk/credential-provider-web-identity': 3.972.71
+ '@aws-sdk/nested-clients': 3.997.39
+ '@aws-sdk/types': 3.974.2
+ '@smithy/core': 3.31.1
+ '@smithy/credential-provider-imds': 4.4.16
+ '@smithy/types': 4.16.1
+ tslib: 2.8.1
+
+ '@aws-sdk/credential-provider-login@3.972.72':
+ dependencies:
+ '@aws-sdk/core': 3.977.4
+ '@aws-sdk/nested-clients': 3.997.39
+ '@aws-sdk/types': 3.974.2
+ '@smithy/core': 3.31.1
+ '@smithy/types': 4.16.1
+ tslib: 2.8.1
+
+ '@aws-sdk/credential-provider-node@3.972.76':
+ dependencies:
+ '@aws-sdk/credential-provider-env': 3.972.65
+ '@aws-sdk/credential-provider-http': 3.972.67
+ '@aws-sdk/credential-provider-ini': 3.973.10
+ '@aws-sdk/credential-provider-process': 3.972.65
+ '@aws-sdk/credential-provider-sso': 3.973.9
+ '@aws-sdk/credential-provider-web-identity': 3.972.71
+ '@aws-sdk/types': 3.974.2
+ '@smithy/core': 3.31.1
+ '@smithy/credential-provider-imds': 4.4.16
+ '@smithy/types': 4.16.1
+ tslib: 2.8.1
+
+ '@aws-sdk/credential-provider-process@3.972.65':
+ dependencies:
+ '@aws-sdk/core': 3.977.4
+ '@aws-sdk/types': 3.974.2
+ '@smithy/core': 3.31.1
+ '@smithy/types': 4.16.1
+ tslib: 2.8.1
+
+ '@aws-sdk/credential-provider-sso@3.973.9':
+ dependencies:
+ '@aws-sdk/core': 3.977.4
+ '@aws-sdk/nested-clients': 3.997.39
+ '@aws-sdk/token-providers': 3.1100.0
+ '@aws-sdk/types': 3.974.2
+ '@smithy/core': 3.31.1
+ '@smithy/types': 4.16.1
+ tslib: 2.8.1
+
+ '@aws-sdk/credential-provider-web-identity@3.972.71':
+ dependencies:
+ '@aws-sdk/core': 3.977.4
+ '@aws-sdk/nested-clients': 3.997.39
+ '@aws-sdk/types': 3.974.2
+ '@smithy/core': 3.31.1
+ '@smithy/types': 4.16.1
+ tslib: 2.8.1
+
+ '@aws-sdk/dynamodb-codec@3.973.39':
+ dependencies:
+ '@aws-sdk/core': 3.977.4
+ '@smithy/core': 3.31.1
+ '@smithy/types': 4.16.1
+ tslib: 2.8.1
+
+ '@aws-sdk/endpoint-cache@3.972.9':
+ dependencies:
+ mnemonist: 0.38.3
+ tslib: 2.8.1
+
+ '@aws-sdk/lib-dynamodb@3.1101.0(@aws-sdk/client-dynamodb@3.1101.0)':
+ dependencies:
+ '@aws-sdk/client-dynamodb': 3.1101.0
+ '@aws-sdk/core': 3.977.4
+ '@aws-sdk/util-dynamodb': 3.996.7(@aws-sdk/client-dynamodb@3.1101.0)
+ '@smithy/core': 3.31.1
+ '@smithy/types': 4.16.1
+ tslib: 2.8.1
+
+ '@aws-sdk/lib-storage@3.1101.0(@aws-sdk/client-s3@3.1101.0)':
+ dependencies:
+ '@aws-sdk/client-s3': 3.1101.0
+ '@smithy/core': 3.31.1
+ '@smithy/types': 4.16.1
+ buffer: 5.6.0
+ events: 3.3.0
+ stream-browserify: 3.0.0
+ tslib: 2.8.1
+
+ '@aws-sdk/middleware-endpoint-discovery@3.972.27':
+ dependencies:
+ '@aws-sdk/endpoint-cache': 3.972.9
+ '@aws-sdk/types': 3.974.2
+ '@smithy/core': 3.31.1
+ '@smithy/types': 4.16.1
+ tslib: 2.8.1
+
+ '@aws-sdk/middleware-sdk-s3@3.972.70':
+ dependencies:
+ '@aws-sdk/core': 3.977.4
+ '@aws-sdk/signature-v4-multi-region': 3.996.43
+ '@aws-sdk/types': 3.974.2
+ '@smithy/core': 3.31.1
+ '@smithy/types': 4.16.1
+ tslib: 2.8.1
+
+ '@aws-sdk/middleware-sdk-sqs@3.972.39':
+ dependencies:
+ '@aws-sdk/types': 3.974.2
+ '@smithy/core': 3.31.1
+ '@smithy/types': 4.16.1
+ tslib: 2.8.1
+
+ '@aws-sdk/nested-clients@3.997.39':
+ dependencies:
+ '@aws-sdk/core': 3.977.4
+ '@aws-sdk/signature-v4-multi-region': 3.996.43
+ '@aws-sdk/types': 3.974.2
+ '@smithy/core': 3.31.1
+ '@smithy/fetch-http-handler': 5.6.13
+ '@smithy/node-http-handler': 4.9.13
+ '@smithy/types': 4.16.1
+ tslib: 2.8.1
+
+ '@aws-sdk/signature-v4-multi-region@3.996.43':
+ dependencies:
+ '@aws-sdk/types': 3.974.2
+ '@smithy/signature-v4': 5.6.12
+ '@smithy/types': 4.16.1
+ tslib: 2.8.1
+
+ '@aws-sdk/token-providers@3.1100.0':
+ dependencies:
+ '@aws-sdk/core': 3.977.4
+ '@aws-sdk/nested-clients': 3.997.39
+ '@aws-sdk/types': 3.974.2
+ '@smithy/core': 3.31.1
+ '@smithy/types': 4.16.1
+ tslib: 2.8.1
+
+ '@aws-sdk/types@3.974.2':
+ dependencies:
+ '@smithy/types': 4.16.1
+ tslib: 2.8.1
+
+ '@aws-sdk/util-dynamodb@3.996.7(@aws-sdk/client-dynamodb@3.1101.0)':
+ dependencies:
+ '@aws-sdk/client-dynamodb': 3.1101.0
+ tslib: 2.8.1
+
+ '@aws-sdk/xml-builder@3.972.37':
+ dependencies:
+ '@smithy/types': 4.16.1
+ tslib: 2.8.1
+
+ '@aws/lambda-invoke-store@0.3.0': {}
+
+ '@babel/code-frame@7.29.7':
+ dependencies:
+ '@babel/helper-validator-identifier': 7.29.7
+ js-tokens: 4.0.0
+ picocolors: 1.1.1
+
+ '@babel/compat-data@7.29.7': {}
+
+ '@babel/core@7.29.7(supports-color@8.1.1)':
+ dependencies:
+ '@babel/code-frame': 7.29.7
+ '@babel/generator': 7.29.8
+ '@babel/helper-compilation-targets': 7.29.7
+ '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)
+ '@babel/helpers': 7.29.7
+ '@babel/parser': 7.29.8
+ '@babel/template': 7.29.7
+ '@babel/traverse': 7.29.8(supports-color@8.1.1)
+ '@babel/types': 7.29.8
+ '@jridgewell/remapping': 2.3.5
+ convert-source-map: 2.0.0
+ debug: 4.4.3(supports-color@8.1.1)
+ gensync: 1.0.0-beta.2
+ json5: 2.2.3
+ semver: 6.3.1
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/generator@7.29.8':
+ dependencies:
+ '@babel/parser': 7.29.8
+ '@babel/types': 7.29.8
+ '@jridgewell/gen-mapping': 0.3.13
+ '@jridgewell/trace-mapping': 0.3.31
+ jsesc: 3.1.0
+
+ '@babel/helper-compilation-targets@7.29.7':
+ dependencies:
+ '@babel/compat-data': 7.29.7
+ '@babel/helper-validator-option': 7.29.7
+ browserslist: 4.28.7
+ lru-cache: 5.1.1
+ semver: 6.3.1
+
+ '@babel/helper-globals@7.29.7': {}
+
+ '@babel/helper-module-imports@7.29.7(supports-color@8.1.1)':
+ dependencies:
+ '@babel/traverse': 7.29.8(supports-color@8.1.1)
+ '@babel/types': 7.29.8
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/helper-module-transforms@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)':
+ dependencies:
+ '@babel/core': 7.29.7(supports-color@8.1.1)
+ '@babel/helper-module-imports': 7.29.7(supports-color@8.1.1)
+ '@babel/helper-validator-identifier': 7.29.7
+ '@babel/traverse': 7.29.8(supports-color@8.1.1)
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/helper-plugin-utils@7.29.7': {}
+
+ '@babel/helper-string-parser@7.29.7': {}
+
+ '@babel/helper-validator-identifier@7.29.7': {}
+
+ '@babel/helper-validator-option@7.29.7': {}
+
+ '@babel/helpers@7.29.7':
+ dependencies:
+ '@babel/template': 7.29.7
+ '@babel/types': 7.29.8
+
+ '@babel/parser@7.29.8':
+ dependencies:
+ '@babel/types': 7.29.8
+
+ '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.29.7(supports-color@8.1.1))':
+ dependencies:
+ '@babel/core': 7.29.7(supports-color@8.1.1)
+ '@babel/helper-plugin-utils': 7.29.7
+
+ '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.29.7(supports-color@8.1.1))':
+ dependencies:
+ '@babel/core': 7.29.7(supports-color@8.1.1)
+ '@babel/helper-plugin-utils': 7.29.7
+
+ '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.29.7(supports-color@8.1.1))':
+ dependencies:
+ '@babel/core': 7.29.7(supports-color@8.1.1)
+ '@babel/helper-plugin-utils': 7.29.7
+
+ '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.29.7(supports-color@8.1.1))':
+ dependencies:
+ '@babel/core': 7.29.7(supports-color@8.1.1)
+ '@babel/helper-plugin-utils': 7.29.7
+
+ '@babel/plugin-syntax-import-attributes@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))':
+ dependencies:
+ '@babel/core': 7.29.7(supports-color@8.1.1)
+ '@babel/helper-plugin-utils': 7.29.7
+
+ '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.29.7(supports-color@8.1.1))':
+ dependencies:
+ '@babel/core': 7.29.7(supports-color@8.1.1)
+ '@babel/helper-plugin-utils': 7.29.7
+
+ '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.29.7(supports-color@8.1.1))':
+ dependencies:
+ '@babel/core': 7.29.7(supports-color@8.1.1)
+ '@babel/helper-plugin-utils': 7.29.7
+
+ '@babel/plugin-syntax-jsx@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))':
+ dependencies:
+ '@babel/core': 7.29.7(supports-color@8.1.1)
+ '@babel/helper-plugin-utils': 7.29.7
+
+ '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.29.7(supports-color@8.1.1))':
+ dependencies:
+ '@babel/core': 7.29.7(supports-color@8.1.1)
+ '@babel/helper-plugin-utils': 7.29.7
+
+ '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.29.7(supports-color@8.1.1))':
+ dependencies:
+ '@babel/core': 7.29.7(supports-color@8.1.1)
+ '@babel/helper-plugin-utils': 7.29.7
+
+ '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.29.7(supports-color@8.1.1))':
+ dependencies:
+ '@babel/core': 7.29.7(supports-color@8.1.1)
+ '@babel/helper-plugin-utils': 7.29.7
+
+ '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.29.7(supports-color@8.1.1))':
+ dependencies:
+ '@babel/core': 7.29.7(supports-color@8.1.1)
+ '@babel/helper-plugin-utils': 7.29.7
+
+ '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.29.7(supports-color@8.1.1))':
+ dependencies:
+ '@babel/core': 7.29.7(supports-color@8.1.1)
+ '@babel/helper-plugin-utils': 7.29.7
+
+ '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.29.7(supports-color@8.1.1))':
+ dependencies:
+ '@babel/core': 7.29.7(supports-color@8.1.1)
+ '@babel/helper-plugin-utils': 7.29.7
+
+ '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.29.7(supports-color@8.1.1))':
+ dependencies:
+ '@babel/core': 7.29.7(supports-color@8.1.1)
+ '@babel/helper-plugin-utils': 7.29.7
+
+ '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.29.7(supports-color@8.1.1))':
+ dependencies:
+ '@babel/core': 7.29.7(supports-color@8.1.1)
+ '@babel/helper-plugin-utils': 7.29.7
+
+ '@babel/plugin-syntax-typescript@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))':
+ dependencies:
+ '@babel/core': 7.29.7(supports-color@8.1.1)
+ '@babel/helper-plugin-utils': 7.29.7
+
+ '@babel/template@7.29.7':
+ dependencies:
+ '@babel/code-frame': 7.29.7
+ '@babel/parser': 7.29.8
+ '@babel/types': 7.29.8
+
+ '@babel/traverse@7.29.8(supports-color@8.1.1)':
+ dependencies:
+ '@babel/code-frame': 7.29.7
+ '@babel/generator': 7.29.8
+ '@babel/helper-globals': 7.29.7
+ '@babel/parser': 7.29.8
+ '@babel/template': 7.29.7
+ '@babel/types': 7.29.8
+ debug: 4.4.3(supports-color@8.1.1)
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/types@7.29.8':
+ dependencies:
+ '@babel/helper-string-parser': 7.29.7
+ '@babel/helper-validator-identifier': 7.29.7
+
+ '@bcoe/v8-coverage@0.2.3': {}
+
+ '@colors/colors@1.6.0': {}
+
+ '@cspotcode/source-map-support@0.8.1':
+ dependencies:
+ '@jridgewell/trace-mapping': 0.3.9
+
+ '@csstools/color-helpers@5.1.0': {}
+
+ '@csstools/css-calc@2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)':
+ dependencies:
+ '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
+ '@csstools/css-tokenizer': 3.0.4
+
+ '@csstools/css-color-parser@3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)':
+ dependencies:
+ '@csstools/color-helpers': 5.1.0
+ '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
+ '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
+ '@csstools/css-tokenizer': 3.0.4
+
+ '@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4)':
+ dependencies:
+ '@csstools/css-tokenizer': 3.0.4
+
+ '@csstools/css-tokenizer@3.0.4': {}
+
+ '@dabh/diagnostics@2.0.8':
+ dependencies:
+ '@so-ric/colorspace': 1.1.6
+ enabled: 2.0.0
+ kuler: 2.0.0
+
+ '@emnapi/core@1.10.0':
+ dependencies:
+ '@emnapi/wasi-threads': 1.2.1
+ tslib: 2.8.1
+ optional: true
+
+ '@emnapi/runtime@1.10.0':
+ dependencies:
+ tslib: 2.8.1
+ optional: true
+
+ '@emnapi/wasi-threads@1.2.1':
+ dependencies:
+ tslib: 2.8.1
+ optional: true
+
+ '@esbuild/aix-ppc64@0.25.12':
+ optional: true
+
+ '@esbuild/aix-ppc64@0.28.1':
+ optional: true
+
+ '@esbuild/android-arm64@0.25.12':
+ optional: true
+
+ '@esbuild/android-arm64@0.28.1':
+ optional: true
+
+ '@esbuild/android-arm@0.25.12':
+ optional: true
+
+ '@esbuild/android-arm@0.28.1':
+ optional: true
+
+ '@esbuild/android-x64@0.25.12':
+ optional: true
+
+ '@esbuild/android-x64@0.28.1':
+ optional: true
+
+ '@esbuild/darwin-arm64@0.25.12':
+ optional: true
+
+ '@esbuild/darwin-arm64@0.28.1':
+ optional: true
+
+ '@esbuild/darwin-x64@0.25.12':
+ optional: true
+
+ '@esbuild/darwin-x64@0.28.1':
+ optional: true
+
+ '@esbuild/freebsd-arm64@0.25.12':
+ optional: true
+
+ '@esbuild/freebsd-arm64@0.28.1':
+ optional: true
+
+ '@esbuild/freebsd-x64@0.25.12':
+ optional: true
+
+ '@esbuild/freebsd-x64@0.28.1':
+ optional: true
+
+ '@esbuild/linux-arm64@0.25.12':
+ optional: true
+
+ '@esbuild/linux-arm64@0.28.1':
+ optional: true
+
+ '@esbuild/linux-arm@0.25.12':
+ optional: true
+
+ '@esbuild/linux-arm@0.28.1':
+ optional: true
+
+ '@esbuild/linux-ia32@0.25.12':
+ optional: true
+
+ '@esbuild/linux-ia32@0.28.1':
+ optional: true
+
+ '@esbuild/linux-loong64@0.25.12':
+ optional: true
+
+ '@esbuild/linux-loong64@0.28.1':
+ optional: true
+
+ '@esbuild/linux-mips64el@0.25.12':
+ optional: true
+
+ '@esbuild/linux-mips64el@0.28.1':
+ optional: true
+
+ '@esbuild/linux-ppc64@0.25.12':
+ optional: true
+
+ '@esbuild/linux-ppc64@0.28.1':
+ optional: true
+
+ '@esbuild/linux-riscv64@0.25.12':
+ optional: true
+
+ '@esbuild/linux-riscv64@0.28.1':
+ optional: true
+
+ '@esbuild/linux-s390x@0.25.12':
+ optional: true
+
+ '@esbuild/linux-s390x@0.28.1':
+ optional: true
+
+ '@esbuild/linux-x64@0.25.12':
+ optional: true
+
+ '@esbuild/linux-x64@0.28.1':
+ optional: true
+
+ '@esbuild/netbsd-arm64@0.25.12':
+ optional: true
+
+ '@esbuild/netbsd-arm64@0.28.1':
+ optional: true
+
+ '@esbuild/netbsd-x64@0.25.12':
+ optional: true
+
+ '@esbuild/netbsd-x64@0.28.1':
+ optional: true
+
+ '@esbuild/openbsd-arm64@0.25.12':
+ optional: true
+
+ '@esbuild/openbsd-arm64@0.28.1':
+ optional: true
+
+ '@esbuild/openbsd-x64@0.25.12':
+ optional: true
+
+ '@esbuild/openbsd-x64@0.28.1':
+ optional: true
+
+ '@esbuild/openharmony-arm64@0.25.12':
+ optional: true
+
+ '@esbuild/openharmony-arm64@0.28.1':
+ optional: true
+
+ '@esbuild/sunos-x64@0.25.12':
+ optional: true
+
+ '@esbuild/sunos-x64@0.28.1':
+ optional: true
+
+ '@esbuild/win32-arm64@0.25.12':
+ optional: true
+
+ '@esbuild/win32-arm64@0.28.1':
+ optional: true
+
+ '@esbuild/win32-ia32@0.25.12':
+ optional: true
+
+ '@esbuild/win32-ia32@0.28.1':
+ optional: true
+
+ '@esbuild/win32-x64@0.25.12':
+ optional: true
+
+ '@esbuild/win32-x64@0.28.1':
+ optional: true
+
+ '@eslint-community/eslint-utils@4.10.1(eslint@9.39.5(supports-color@8.1.1))':
+ dependencies:
+ eslint: 9.39.5(supports-color@8.1.1)
+ eslint-visitor-keys: 3.4.3
+
+ '@eslint-community/regexpp@4.12.2': {}
+
+ '@eslint/config-array@0.21.2(supports-color@8.1.1)':
+ dependencies:
+ '@eslint/object-schema': 2.1.7
+ debug: 4.4.3(supports-color@8.1.1)
+ minimatch: 3.1.5
+ transitivePeerDependencies:
+ - supports-color
+
+ '@eslint/config-helpers@0.4.2':
+ dependencies:
+ '@eslint/core': 0.17.0
+
+ '@eslint/core@0.15.2':
+ dependencies:
+ '@types/json-schema': 7.0.15
+
+ '@eslint/core@0.17.0':
+ dependencies:
+ '@types/json-schema': 7.0.15
+
+ '@eslint/eslintrc@3.3.6(supports-color@8.1.1)':
+ dependencies:
+ ajv: 6.15.0
+ debug: 4.4.3(supports-color@8.1.1)
+ espree: 10.4.0
+ globals: 14.0.0
+ ignore: 5.3.2
+ import-fresh: 3.3.1
+ js-yaml: 4.3.1
+ minimatch: 3.1.5
+ strip-json-comments: 3.1.1
+ transitivePeerDependencies:
+ - supports-color
+
+ '@eslint/js@9.39.5': {}
+
+ '@eslint/object-schema@2.1.7': {}
+
+ '@eslint/plugin-kit@0.3.5':
+ dependencies:
+ '@eslint/core': 0.15.2
+ levn: 0.4.1
+
+ '@eslint/plugin-kit@0.4.1':
+ dependencies:
+ '@eslint/core': 0.17.0
+ levn: 0.4.1
+
+ '@humanfs/core@0.19.2':
+ dependencies:
+ '@humanfs/types': 0.15.0
+
+ '@humanfs/node@0.16.8':
+ dependencies:
+ '@humanfs/core': 0.19.2
+ '@humanfs/types': 0.15.0
+ '@humanwhocodes/retry': 0.4.3
+
+ '@humanfs/types@0.15.0': {}
+
+ '@humanwhocodes/module-importer@1.0.1': {}
+
+ '@humanwhocodes/retry@0.4.3': {}
+
+ '@isaacs/cliui@8.0.2':
+ dependencies:
+ string-width: 5.1.2
+ string-width-cjs: string-width@4.2.3
+ strip-ansi: 7.2.0
+ strip-ansi-cjs: strip-ansi@6.0.1
+ wrap-ansi: 8.1.0
+ wrap-ansi-cjs: wrap-ansi@7.0.0
+
+ '@istanbuljs/load-nyc-config@1.1.0':
+ dependencies:
+ camelcase: 5.3.1
+ find-up: 4.1.0
+ get-package-type: 0.1.0
+ js-yaml: 3.15.1
+ resolve-from: 5.0.0
+
+ '@istanbuljs/schema@0.1.6': {}
+
+ '@jest/console@29.7.0':
+ dependencies:
+ '@jest/types': 29.6.3
+ '@types/node': 24.13.3
+ chalk: 4.1.2
+ jest-message-util: 29.7.0
+ jest-util: 29.7.0
+ slash: 3.0.0
+
+ '@jest/console@30.4.1':
+ dependencies:
+ '@jest/types': 30.4.1
+ '@types/node': 24.13.3
+ chalk: 4.1.2
+ jest-message-util: 30.4.1
+ jest-util: 30.4.1
+ slash: 3.0.0
+
+ '@jest/core@29.7.0(supports-color@8.1.1)(ts-node@10.9.2(@types/node@24.13.3)(typescript@5.9.3))':
+ dependencies:
+ '@jest/console': 29.7.0
+ '@jest/reporters': 29.7.0(supports-color@8.1.1)
+ '@jest/test-result': 29.7.0
+ '@jest/transform': 29.7.0(supports-color@8.1.1)
+ '@jest/types': 29.6.3
+ '@types/node': 24.13.3
+ ansi-escapes: 4.3.2
+ chalk: 4.1.2
+ ci-info: 3.9.0
+ exit: 0.1.2
+ graceful-fs: 4.2.11
+ jest-changed-files: 29.7.0
+ jest-config: 29.7.0(@types/node@24.13.3)(supports-color@8.1.1)(ts-node@10.9.2(@types/node@24.13.3)(typescript@5.9.3))
+ jest-haste-map: 29.7.0
+ jest-message-util: 29.7.0
+ jest-regex-util: 29.6.3
+ jest-resolve: 29.7.0
+ jest-resolve-dependencies: 29.7.0(supports-color@8.1.1)
+ jest-runner: 29.7.0(supports-color@8.1.1)
+ jest-runtime: 29.7.0(supports-color@8.1.1)
+ jest-snapshot: 29.7.0(supports-color@8.1.1)
+ jest-util: 29.7.0
+ jest-validate: 29.7.0
+ jest-watcher: 29.7.0
+ micromatch: 4.0.8
+ pretty-format: 29.7.0
+ slash: 3.0.0
+ strip-ansi: 6.0.1
+ transitivePeerDependencies:
+ - babel-plugin-macros
+ - supports-color
+ - ts-node
+
+ '@jest/core@30.4.2(supports-color@8.1.1)(ts-node@10.9.2(@types/node@24.13.3)(typescript@5.9.3))':
+ dependencies:
+ '@jest/console': 30.4.1
+ '@jest/pattern': 30.4.0
+ '@jest/reporters': 30.4.1(supports-color@8.1.1)
+ '@jest/test-result': 30.4.1
+ '@jest/transform': 30.4.1(supports-color@8.1.1)
+ '@jest/types': 30.4.1
+ '@types/node': 24.13.3
+ ansi-escapes: 4.3.2
+ chalk: 4.1.2
+ ci-info: 4.4.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(@types/node@24.13.3)(supports-color@8.1.1)(ts-node@10.9.2(@types/node@24.13.3)(typescript@5.9.3))
+ 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(supports-color@8.1.1)
+ jest-runner: 30.4.2(supports-color@8.1.1)
+ jest-runtime: 30.4.2(supports-color@8.1.1)
+ jest-snapshot: 30.4.1(supports-color@8.1.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
+ transitivePeerDependencies:
+ - babel-plugin-macros
+ - esbuild-register
+ - supports-color
+ - ts-node
+
+ '@jest/diff-sequences@30.4.0': {}
+
+ '@jest/environment-jsdom-abstract@30.4.1(jsdom@26.1.0(supports-color@8.1.1))':
+ dependencies:
+ '@jest/environment': 30.4.1
+ '@jest/fake-timers': 30.4.1
+ '@jest/types': 30.4.1
+ '@types/jsdom': 21.1.7
+ '@types/node': 24.13.3
+ jest-mock: 30.4.1
+ jest-util: 30.4.1
+ jsdom: 26.1.0(supports-color@8.1.1)
+
+ '@jest/environment@29.7.0':
+ dependencies:
+ '@jest/fake-timers': 29.7.0
+ '@jest/types': 29.6.3
+ '@types/node': 24.13.3
+ jest-mock: 29.7.0
+
+ '@jest/environment@30.4.1':
+ dependencies:
+ '@jest/fake-timers': 30.4.1
+ '@jest/types': 30.4.1
+ '@types/node': 24.13.3
+ jest-mock: 30.4.1
+
+ '@jest/expect-utils@29.7.0':
+ dependencies:
+ jest-get-type: 29.6.3
+
+ '@jest/expect-utils@30.4.1':
+ dependencies:
+ '@jest/get-type': 30.1.0
+
+ '@jest/expect@29.7.0(supports-color@8.1.1)':
+ dependencies:
+ expect: 29.7.0
+ jest-snapshot: 29.7.0(supports-color@8.1.1)
+ transitivePeerDependencies:
+ - supports-color
+
+ '@jest/expect@30.4.1(supports-color@8.1.1)':
+ dependencies:
+ expect: 30.4.1
+ jest-snapshot: 30.4.1(supports-color@8.1.1)
+ transitivePeerDependencies:
+ - supports-color
+
+ '@jest/fake-timers@29.7.0':
+ dependencies:
+ '@jest/types': 29.6.3
+ '@sinonjs/fake-timers': 10.3.0
+ '@types/node': 24.13.3
+ jest-message-util: 29.7.0
+ jest-mock: 29.7.0
+ jest-util: 29.7.0
+
+ '@jest/fake-timers@30.4.1':
+ dependencies:
+ '@jest/types': 30.4.1
+ '@sinonjs/fake-timers': 15.4.0
+ '@types/node': 24.13.3
+ jest-message-util: 30.4.1
+ jest-mock: 30.4.1
+ jest-util: 30.4.1
+
+ '@jest/get-type@30.1.0': {}
+
+ '@jest/globals@29.7.0(supports-color@8.1.1)':
+ dependencies:
+ '@jest/environment': 29.7.0
+ '@jest/expect': 29.7.0(supports-color@8.1.1)
+ '@jest/types': 29.6.3
+ jest-mock: 29.7.0
+ transitivePeerDependencies:
+ - supports-color
+
+ '@jest/globals@30.4.1(supports-color@8.1.1)':
+ dependencies:
+ '@jest/environment': 30.4.1
+ '@jest/expect': 30.4.1(supports-color@8.1.1)
+ '@jest/types': 30.4.1
+ jest-mock: 30.4.1
+ transitivePeerDependencies:
+ - supports-color
+
+ '@jest/pattern@30.4.0':
+ dependencies:
+ '@types/node': 24.13.3
+ jest-regex-util: 30.4.0
+
+ '@jest/reporters@29.7.0(supports-color@8.1.1)':
+ dependencies:
+ '@bcoe/v8-coverage': 0.2.3
+ '@jest/console': 29.7.0
+ '@jest/test-result': 29.7.0
+ '@jest/transform': 29.7.0(supports-color@8.1.1)
+ '@jest/types': 29.6.3
+ '@jridgewell/trace-mapping': 0.3.31
+ '@types/node': 24.13.3
+ chalk: 4.1.2
+ collect-v8-coverage: 1.0.3
+ exit: 0.1.2
+ glob: 7.2.3
+ graceful-fs: 4.2.11
+ istanbul-lib-coverage: 3.2.2
+ istanbul-lib-instrument: 6.0.3(supports-color@8.1.1)
+ istanbul-lib-report: 3.0.1
+ istanbul-lib-source-maps: 4.0.1(supports-color@8.1.1)
+ istanbul-reports: 3.2.0
+ jest-message-util: 29.7.0
+ jest-util: 29.7.0
+ jest-worker: 29.7.0
+ slash: 3.0.0
+ string-length: 4.0.2
+ strip-ansi: 6.0.1
+ v8-to-istanbul: 9.3.0
+ transitivePeerDependencies:
+ - supports-color
+
+ '@jest/reporters@30.4.1(supports-color@8.1.1)':
+ dependencies:
+ '@bcoe/v8-coverage': 0.2.3
+ '@jest/console': 30.4.1
+ '@jest/test-result': 30.4.1
+ '@jest/transform': 30.4.1(supports-color@8.1.1)
+ '@jest/types': 30.4.1
+ '@jridgewell/trace-mapping': 0.3.31
+ '@types/node': 24.13.3
+ chalk: 4.1.2
+ collect-v8-coverage: 1.0.3
+ exit-x: 0.2.2
+ glob: 10.5.0
+ graceful-fs: 4.2.11
+ istanbul-lib-coverage: 3.2.2
+ istanbul-lib-instrument: 6.0.3(supports-color@8.1.1)
+ istanbul-lib-report: 3.0.1
+ istanbul-lib-source-maps: 5.0.6(supports-color@8.1.1)
+ istanbul-reports: 3.2.0
+ 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.3.0
+ transitivePeerDependencies:
+ - supports-color
+
+ '@jest/schemas@29.6.3':
+ dependencies:
+ '@sinclair/typebox': 0.27.12
+
+ '@jest/schemas@30.4.1':
+ dependencies:
+ '@sinclair/typebox': 0.34.52
+
+ '@jest/snapshot-utils@30.4.1':
+ dependencies:
+ '@jest/types': 30.4.1
+ chalk: 4.1.2
+ graceful-fs: 4.2.11
+ natural-compare: 1.4.0
+
+ '@jest/source-map@29.6.3':
+ dependencies:
+ '@jridgewell/trace-mapping': 0.3.31
+ callsites: 3.1.0
+ graceful-fs: 4.2.11
+
+ '@jest/source-map@30.0.1':
+ dependencies:
+ '@jridgewell/trace-mapping': 0.3.31
+ callsites: 3.1.0
+ graceful-fs: 4.2.11
+
+ '@jest/test-result@29.7.0':
+ dependencies:
+ '@jest/console': 29.7.0
+ '@jest/types': 29.6.3
+ '@types/istanbul-lib-coverage': 2.0.6
+ collect-v8-coverage: 1.0.3
+
+ '@jest/test-result@30.4.1':
+ dependencies:
+ '@jest/console': 30.4.1
+ '@jest/types': 30.4.1
+ '@types/istanbul-lib-coverage': 2.0.6
+ collect-v8-coverage: 1.0.3
+
+ '@jest/test-sequencer@29.7.0':
+ dependencies:
+ '@jest/test-result': 29.7.0
+ graceful-fs: 4.2.11
+ jest-haste-map: 29.7.0
+ slash: 3.0.0
+
+ '@jest/test-sequencer@30.4.1':
+ dependencies:
+ '@jest/test-result': 30.4.1
+ graceful-fs: 4.2.11
+ jest-haste-map: 30.4.1
+ slash: 3.0.0
+
+ '@jest/transform@29.7.0(supports-color@8.1.1)':
+ dependencies:
+ '@babel/core': 7.29.7(supports-color@8.1.1)
+ '@jest/types': 29.6.3
+ '@jridgewell/trace-mapping': 0.3.31
+ babel-plugin-istanbul: 6.1.1(supports-color@8.1.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: 29.7.0
+ jest-regex-util: 29.6.3
+ jest-util: 29.7.0
+ micromatch: 4.0.8
+ pirates: 4.0.7
+ slash: 3.0.0
+ write-file-atomic: 4.0.2
+ transitivePeerDependencies:
+ - supports-color
+
+ '@jest/transform@30.4.1(supports-color@8.1.1)':
+ dependencies:
+ '@babel/core': 7.29.7(supports-color@8.1.1)
+ '@jest/types': 30.4.1
+ '@jridgewell/trace-mapping': 0.3.31
+ babel-plugin-istanbul: 7.0.1(supports-color@8.1.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
+ transitivePeerDependencies:
+ - supports-color
+
+ '@jest/types@29.6.3':
+ dependencies:
+ '@jest/schemas': 29.6.3
+ '@types/istanbul-lib-coverage': 2.0.6
+ '@types/istanbul-reports': 3.0.4
+ '@types/node': 24.13.3
+ '@types/yargs': 17.0.35
+ chalk: 4.1.2
+
+ '@jest/types@30.4.1':
+ 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': 24.13.3
+ '@types/yargs': 17.0.35
+ chalk: 4.1.2
+
+ '@jridgewell/gen-mapping@0.3.13':
+ dependencies:
+ '@jridgewell/sourcemap-codec': 1.5.5
+ '@jridgewell/trace-mapping': 0.3.31
+
+ '@jridgewell/remapping@2.3.5':
+ dependencies:
+ '@jridgewell/gen-mapping': 0.3.13
+ '@jridgewell/trace-mapping': 0.3.31
+
+ '@jridgewell/resolve-uri@3.1.2': {}
+
+ '@jridgewell/sourcemap-codec@1.5.5': {}
+
+ '@jridgewell/trace-mapping@0.3.31':
+ dependencies:
+ '@jridgewell/resolve-uri': 3.1.2
+ '@jridgewell/sourcemap-codec': 1.5.5
+
+ '@jridgewell/trace-mapping@0.3.9':
+ dependencies:
+ '@jridgewell/resolve-uri': 3.1.2
+ '@jridgewell/sourcemap-codec': 1.5.5
+
+ '@napi-rs/wasm-runtime@1.2.2(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)':
+ dependencies:
+ '@emnapi/core': 1.10.0
+ '@emnapi/runtime': 1.10.0
+ '@tybys/wasm-util': 0.10.3
+ optional: true
+
+ '@nodelib/fs.scandir@2.1.5':
+ dependencies:
+ '@nodelib/fs.stat': 2.0.5
+ run-parallel: 1.2.0
+
+ '@nodelib/fs.stat@2.0.5': {}
+
+ '@nodelib/fs.walk@1.2.8':
+ dependencies:
+ '@nodelib/fs.scandir': 2.1.5
+ fastq: 1.20.1
+
+ '@pkgjs/parseargs@0.11.0':
+ optional: true
+
+ '@pkgr/core@0.3.6': {}
+
+ '@sinclair/typebox@0.27.12': {}
+
+ '@sinclair/typebox@0.34.52': {}
+
+ '@sinonjs/commons@3.0.1':
+ dependencies:
+ type-detect: 4.0.8
+
+ '@sinonjs/fake-timers@10.3.0':
+ dependencies:
+ '@sinonjs/commons': 3.0.1
+
+ '@sinonjs/fake-timers@11.2.2':
+ dependencies:
+ '@sinonjs/commons': 3.0.1
+
+ '@sinonjs/fake-timers@15.4.0':
+ dependencies:
+ '@sinonjs/commons': 3.0.1
+
+ '@sinonjs/samsam@8.0.3':
+ dependencies:
+ '@sinonjs/commons': 3.0.1
+ type-detect: 4.1.0
+
+ '@smithy/core@3.31.1':
+ dependencies:
+ '@smithy/types': 4.16.1
+ tslib: 2.8.1
+
+ '@smithy/credential-provider-imds@4.4.16':
+ dependencies:
+ '@smithy/core': 3.31.1
+ '@smithy/types': 4.16.1
+ tslib: 2.8.1
+
+ '@smithy/fetch-http-handler@5.6.13':
+ dependencies:
+ '@smithy/core': 3.31.1
+ '@smithy/types': 4.16.1
+ tslib: 2.8.1
+
+ '@smithy/middleware-compression@4.5.16':
+ dependencies:
+ '@smithy/core': 3.31.1
+ '@smithy/types': 4.16.1
+ fflate: 0.8.1
+ tslib: 2.8.1
+
+ '@smithy/node-http-handler@4.9.13':
+ dependencies:
+ '@smithy/core': 3.31.1
+ '@smithy/types': 4.16.1
+ tslib: 2.8.1
+
+ '@smithy/signature-v4@5.6.12':
+ dependencies:
+ '@smithy/core': 3.31.1
+ '@smithy/types': 4.16.1
+ tslib: 2.8.1
+
+ '@smithy/types@4.16.1':
+ dependencies:
+ tslib: 2.8.1
+
+ '@so-ric/colorspace@1.1.6':
+ dependencies:
+ color: 5.0.3
+ text-hex: 1.0.0
+
+ '@standard-schema/spec@1.1.0': {}
+
+ '@stylistic/eslint-plugin-ts@4.4.1(eslint@9.39.5(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.3)':
+ dependencies:
+ '@typescript-eslint/utils': 8.65.0(eslint@9.39.5(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.3)
+ eslint: 9.39.5(supports-color@8.1.1)
+ eslint-visitor-keys: 4.2.1
+ espree: 10.4.0
+ transitivePeerDependencies:
+ - supports-color
+ - typescript
+
+ '@stylistic/eslint-plugin@3.1.0(eslint@9.39.5(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.3)':
+ dependencies:
+ '@typescript-eslint/utils': 8.65.0(eslint@9.39.5(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.3)
+ eslint: 9.39.5(supports-color@8.1.1)
+ eslint-visitor-keys: 4.2.1
+ espree: 10.4.0
+ estraverse: 5.3.0
+ picomatch: 4.0.5
+ transitivePeerDependencies:
+ - supports-color
+ - typescript
+
+ '@tsconfig/node10@1.0.12': {}
+
+ '@tsconfig/node12@1.0.11': {}
+
+ '@tsconfig/node14@1.0.3': {}
+
+ '@tsconfig/node16@1.0.4': {}
+
+ '@tsconfig/node22@22.0.5': {}
+
+ '@turbo/darwin-64@2.10.8':
+ optional: true
+
+ '@turbo/darwin-arm64@2.10.8':
+ optional: true
+
+ '@turbo/linux-64@2.10.8':
+ optional: true
+
+ '@turbo/linux-arm64@2.10.8':
+ optional: true
+
+ '@turbo/windows-64@2.10.8':
+ optional: true
+
+ '@turbo/windows-arm64@2.10.8':
+ optional: true
+
+ '@tybys/wasm-util@0.10.3':
+ dependencies:
+ tslib: 2.8.1
+ optional: true
+
+ '@types/aws-lambda@8.10.162': {}
+
+ '@types/babel__core@7.20.5':
+ dependencies:
+ '@babel/parser': 7.29.8
+ '@babel/types': 7.29.8
+ '@types/babel__generator': 7.27.0
+ '@types/babel__template': 7.4.4
+ '@types/babel__traverse': 7.28.0
+
+ '@types/babel__generator@7.27.0':
+ dependencies:
+ '@babel/types': 7.29.8
+
+ '@types/babel__template@7.4.4':
+ dependencies:
+ '@babel/parser': 7.29.8
+ '@babel/types': 7.29.8
+
+ '@types/babel__traverse@7.28.0':
+ dependencies:
+ '@babel/types': 7.29.8
+
+ '@types/chai@5.2.3':
+ dependencies:
+ '@types/deep-eql': 4.0.2
+ assertion-error: 2.0.1
+
+ '@types/deep-eql@4.0.2': {}
+
+ '@types/estree@1.0.9': {}
+
+ '@types/graceful-fs@4.1.9':
+ dependencies:
+ '@types/node': 24.13.3
+
+ '@types/istanbul-lib-coverage@2.0.6': {}
+
+ '@types/istanbul-lib-report@3.0.3':
+ dependencies:
+ '@types/istanbul-lib-coverage': 2.0.6
+
+ '@types/istanbul-reports@3.0.4':
+ dependencies:
+ '@types/istanbul-lib-report': 3.0.3
+
+ '@types/jest@29.5.14':
+ dependencies:
+ expect: 29.7.0
+ pretty-format: 29.7.0
+
+ '@types/jest@30.0.0':
+ dependencies:
+ expect: 30.4.1
+ pretty-format: 30.4.1
+
+ '@types/jsdom@21.1.7':
+ dependencies:
+ '@types/node': 24.13.3
+ '@types/tough-cookie': 4.0.5
+ parse5: 7.3.0
+
+ '@types/json-schema@7.0.15': {}
+
+ '@types/jsonwebtoken@9.0.10':
+ dependencies:
+ '@types/ms': 2.1.0
+ '@types/node': 24.13.3
+
+ '@types/mock-fs@4.13.4':
+ dependencies:
+ '@types/node': 24.13.3
+
+ '@types/ms@2.1.0': {}
+
+ '@types/node@24.13.3':
+ dependencies:
+ undici-types: 7.18.2
+
+ '@types/qs@6.15.1': {}
+
+ '@types/sinon@17.0.4':
+ dependencies:
+ '@types/sinonjs__fake-timers': 15.0.1
+
+ '@types/sinonjs__fake-timers@15.0.1': {}
+
+ '@types/stack-utils@2.0.3': {}
+
+ '@types/tough-cookie@4.0.5': {}
+
+ '@types/triple-beam@1.3.5': {}
+
+ '@types/yargs-parser@21.0.3': {}
+
+ '@types/yargs@17.0.35':
+ dependencies:
+ '@types/yargs-parser': 21.0.3
+
+ '@typescript-eslint/eslint-plugin@8.65.0(@typescript-eslint/parser@8.65.0(eslint@9.39.5(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.3))(eslint@9.39.5(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.3)':
+ dependencies:
+ '@eslint-community/regexpp': 4.12.2
+ '@typescript-eslint/parser': 8.65.0(eslint@9.39.5(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.3)
+ '@typescript-eslint/scope-manager': 8.65.0
+ '@typescript-eslint/type-utils': 8.65.0(eslint@9.39.5(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.3)
+ '@typescript-eslint/utils': 8.65.0(eslint@9.39.5(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.3)
+ '@typescript-eslint/visitor-keys': 8.65.0
+ eslint: 9.39.5(supports-color@8.1.1)
+ ignore: 7.0.6
+ natural-compare: 1.4.0
+ ts-api-utils: 2.5.0(typescript@5.9.3)
+ typescript: 5.9.3
+ transitivePeerDependencies:
+ - supports-color
+
+ '@typescript-eslint/parser@8.65.0(eslint@9.39.5(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.3)':
+ dependencies:
+ '@typescript-eslint/scope-manager': 8.65.0
+ '@typescript-eslint/types': 8.65.0
+ '@typescript-eslint/typescript-estree': 8.65.0(supports-color@8.1.1)(typescript@5.9.3)
+ '@typescript-eslint/visitor-keys': 8.65.0
+ debug: 4.4.3(supports-color@8.1.1)
+ eslint: 9.39.5(supports-color@8.1.1)
+ typescript: 5.9.3
+ transitivePeerDependencies:
+ - supports-color
+
+ '@typescript-eslint/project-service@8.65.0(supports-color@8.1.1)(typescript@5.9.3)':
+ dependencies:
+ '@typescript-eslint/tsconfig-utils': 8.65.0(typescript@5.9.3)
+ '@typescript-eslint/types': 8.65.0
+ debug: 4.4.3(supports-color@8.1.1)
+ typescript: 5.9.3
+ transitivePeerDependencies:
+ - supports-color
+
+ '@typescript-eslint/scope-manager@8.65.0':
+ dependencies:
+ '@typescript-eslint/types': 8.65.0
+ '@typescript-eslint/visitor-keys': 8.65.0
+
+ '@typescript-eslint/tsconfig-utils@8.65.0(typescript@5.9.3)':
+ dependencies:
+ typescript: 5.9.3
+
+ '@typescript-eslint/type-utils@8.65.0(eslint@9.39.5(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.3)':
+ dependencies:
+ '@typescript-eslint/types': 8.65.0
+ '@typescript-eslint/typescript-estree': 8.65.0(supports-color@8.1.1)(typescript@5.9.3)
+ '@typescript-eslint/utils': 8.65.0(eslint@9.39.5(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.3)
+ debug: 4.4.3(supports-color@8.1.1)
+ eslint: 9.39.5(supports-color@8.1.1)
+ ts-api-utils: 2.5.0(typescript@5.9.3)
+ typescript: 5.9.3
+ transitivePeerDependencies:
+ - supports-color
+
+ '@typescript-eslint/types@8.65.0': {}
+
+ '@typescript-eslint/typescript-estree@8.65.0(supports-color@8.1.1)(typescript@5.9.3)':
+ dependencies:
+ '@typescript-eslint/project-service': 8.65.0(supports-color@8.1.1)(typescript@5.9.3)
+ '@typescript-eslint/tsconfig-utils': 8.65.0(typescript@5.9.3)
+ '@typescript-eslint/types': 8.65.0
+ '@typescript-eslint/visitor-keys': 8.65.0
+ debug: 4.4.3(supports-color@8.1.1)
+ minimatch: 10.2.6
+ semver: 7.8.5
+ tinyglobby: 0.2.17
+ ts-api-utils: 2.5.0(typescript@5.9.3)
+ typescript: 5.9.3
+ transitivePeerDependencies:
+ - supports-color
+
+ '@typescript-eslint/utils@8.65.0(eslint@9.39.5(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.3)':
+ dependencies:
+ '@eslint-community/eslint-utils': 4.10.1(eslint@9.39.5(supports-color@8.1.1))
+ '@typescript-eslint/scope-manager': 8.65.0
+ '@typescript-eslint/types': 8.65.0
+ '@typescript-eslint/typescript-estree': 8.65.0(supports-color@8.1.1)(typescript@5.9.3)
+ eslint: 9.39.5(supports-color@8.1.1)
+ typescript: 5.9.3
+ transitivePeerDependencies:
+ - supports-color
+
+ '@typescript-eslint/visitor-keys@8.65.0':
+ dependencies:
+ '@typescript-eslint/types': 8.65.0
+ eslint-visitor-keys: 5.0.1
+
+ '@ungap/structured-clone@1.3.3': {}
+
+ '@unrs/resolver-binding-android-arm-eabi@1.12.2':
+ optional: true
+
+ '@unrs/resolver-binding-android-arm64@1.12.2':
+ optional: true
+
+ '@unrs/resolver-binding-darwin-arm64@1.12.2':
+ optional: true
+
+ '@unrs/resolver-binding-darwin-x64@1.12.2':
+ optional: true
+
+ '@unrs/resolver-binding-freebsd-x64@1.12.2':
+ optional: true
+
+ '@unrs/resolver-binding-linux-arm-gnueabihf@1.12.2':
+ optional: true
+
+ '@unrs/resolver-binding-linux-arm-musleabihf@1.12.2':
+ optional: true
+
+ '@unrs/resolver-binding-linux-arm64-gnu@1.12.2':
+ optional: true
+
+ '@unrs/resolver-binding-linux-arm64-musl@1.12.2':
+ optional: true
+
+ '@unrs/resolver-binding-linux-loong64-gnu@1.12.2':
+ optional: true
+
+ '@unrs/resolver-binding-linux-loong64-musl@1.12.2':
+ optional: true
+
+ '@unrs/resolver-binding-linux-ppc64-gnu@1.12.2':
+ optional: true
+
+ '@unrs/resolver-binding-linux-riscv64-gnu@1.12.2':
+ optional: true
+
+ '@unrs/resolver-binding-linux-riscv64-musl@1.12.2':
+ optional: true
+
+ '@unrs/resolver-binding-linux-s390x-gnu@1.12.2':
+ optional: true
+
+ '@unrs/resolver-binding-linux-x64-gnu@1.12.2':
+ optional: true
+
+ '@unrs/resolver-binding-linux-x64-musl@1.12.2':
+ optional: true
+
+ '@unrs/resolver-binding-openharmony-arm64@1.12.2':
+ optional: true
+
+ '@unrs/resolver-binding-wasm32-wasi@1.12.2':
+ dependencies:
+ '@emnapi/core': 1.10.0
+ '@emnapi/runtime': 1.10.0
+ '@napi-rs/wasm-runtime': 1.2.2(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)
+ optional: true
+
+ '@unrs/resolver-binding-win32-arm64-msvc@1.12.2':
+ optional: true
+
+ '@unrs/resolver-binding-win32-ia32-msvc@1.12.2':
+ optional: true
+
+ '@unrs/resolver-binding-win32-x64-msvc@1.12.2':
+ optional: true
+
+ '@vitest/expect@4.1.10':
+ dependencies:
+ '@standard-schema/spec': 1.1.0
+ '@types/chai': 5.2.3
+ '@vitest/spy': 4.1.10
+ '@vitest/utils': 4.1.10
+ chai: 6.2.2
+ tinyrainbow: 3.1.1
+
+ '@vitest/pretty-format@4.1.10':
+ dependencies:
+ tinyrainbow: 3.1.1
+
+ '@vitest/spy@4.1.10': {}
+
+ '@vitest/utils@4.1.10':
+ dependencies:
+ '@vitest/pretty-format': 4.1.10
+ convert-source-map: 2.0.0
+ tinyrainbow: 3.1.1
+
+ acorn-jsx@5.3.2(acorn@8.18.0):
+ dependencies:
+ acorn: 8.18.0
+
+ acorn-walk@8.3.5:
+ dependencies:
+ acorn: 8.18.0
+
+ acorn@8.18.0: {}
+
+ agent-base@6.0.2(supports-color@8.1.1):
+ dependencies:
+ debug: 4.4.3(supports-color@8.1.1)
+ transitivePeerDependencies:
+ - supports-color
+
+ agent-base@7.1.4: {}
+
+ ajv@6.15.0:
+ dependencies:
+ fast-deep-equal: 3.1.3
+ fast-json-stable-stringify: 2.1.0
+ json-schema-traverse: 0.4.1
+ uri-js: 4.4.1
+
+ ansi-escapes@4.3.2:
+ dependencies:
+ type-fest: 0.21.3
+
+ ansi-regex@5.0.1: {}
+
+ ansi-regex@6.2.2: {}
+
+ ansi-styles@4.3.0:
+ dependencies:
+ color-convert: 2.0.1
+
+ ansi-styles@5.2.0: {}
+
+ ansi-styles@6.2.3: {}
+
+ anymatch@3.1.3:
+ dependencies:
+ normalize-path: 3.0.0
+ picomatch: 2.3.2
+
+ arg@4.1.3: {}
+
+ argparse@1.0.10:
+ dependencies:
+ sprintf-js: 1.0.3
+
+ argparse@2.0.1: {}
+
+ aria-query@5.3.2: {}
+
+ array-buffer-byte-length@1.0.2:
+ dependencies:
+ call-bound: 1.0.4
+ is-array-buffer: 3.0.5
+
+ array-includes@3.1.9:
+ dependencies:
+ call-bind: 1.0.9
+ call-bound: 1.0.4
+ define-properties: 1.2.1
+ es-abstract: 1.24.2
+ es-object-atoms: 1.1.2
+ get-intrinsic: 1.3.0
+ is-string: 1.1.1
+ math-intrinsics: 1.1.0
+
+ array.prototype.findlast@1.2.5:
+ dependencies:
+ call-bind: 1.0.9
+ define-properties: 1.2.1
+ es-abstract: 1.24.2
+ es-errors: 1.3.0
+ es-object-atoms: 1.1.2
+ es-shim-unscopables: 1.1.0
+
+ array.prototype.flat@1.3.3:
+ dependencies:
+ call-bind: 1.0.9
+ define-properties: 1.2.1
+ es-abstract: 1.24.2
+ es-shim-unscopables: 1.1.0
+
+ array.prototype.flatmap@1.3.3:
+ dependencies:
+ call-bind: 1.0.9
+ define-properties: 1.2.1
+ es-abstract: 1.24.2
+ es-shim-unscopables: 1.1.0
+
+ array.prototype.tosorted@1.1.4:
+ dependencies:
+ call-bind: 1.0.9
+ define-properties: 1.2.1
+ es-abstract: 1.24.2
+ es-errors: 1.3.0
+ es-shim-unscopables: 1.1.0
+
+ arraybuffer.prototype.slice@1.0.4:
+ dependencies:
+ array-buffer-byte-length: 1.0.2
+ call-bind: 1.0.9
+ define-properties: 1.2.1
+ es-abstract: 1.24.2
+ es-errors: 1.3.0
+ get-intrinsic: 1.3.0
+ is-array-buffer: 3.0.5
+
+ assertion-error@2.0.1: {}
+
+ ast-types-flow@0.0.8: {}
+
+ async-function@1.0.0: {}
+
+ async-mutex@0.4.1:
+ dependencies:
+ tslib: 2.8.1
+
+ async@3.2.6: {}
+
+ asynckit@0.4.0: {}
+
+ available-typed-arrays@1.0.7:
+ dependencies:
+ possible-typed-array-names: 1.1.0
+
+ aws-sdk-client-mock-jest@4.1.0(aws-sdk-client-mock@4.1.0):
+ dependencies:
+ '@vitest/expect': 4.1.10
+ aws-sdk-client-mock: 4.1.0
+ expect: 30.4.1
+ tslib: 2.8.1
+
+ aws-sdk-client-mock@4.1.0:
+ dependencies:
+ '@types/sinon': 17.0.4
+ sinon: 18.0.1
+ tslib: 2.8.1
+
+ axe-core@4.12.1: {}
+
+ axios@1.19.0(debug@4.4.3(supports-color@8.1.1))(supports-color@8.1.1):
+ dependencies:
+ follow-redirects: 1.16.0(debug@4.4.3(supports-color@8.1.1))
+ form-data: 4.0.6
+ https-proxy-agent: 5.0.1(supports-color@8.1.1)
+ proxy-from-env: 2.1.0
+ transitivePeerDependencies:
+ - debug
+ - supports-color
+
+ axobject-query@4.1.0: {}
+
+ babel-jest@29.7.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1):
+ dependencies:
+ '@babel/core': 7.29.7(supports-color@8.1.1)
+ '@jest/transform': 29.7.0(supports-color@8.1.1)
+ '@types/babel__core': 7.20.5
+ babel-plugin-istanbul: 6.1.1(supports-color@8.1.1)
+ babel-preset-jest: 29.6.3(@babel/core@7.29.7(supports-color@8.1.1))
+ chalk: 4.1.2
+ graceful-fs: 4.2.11
+ slash: 3.0.0
+ transitivePeerDependencies:
+ - supports-color
+
+ babel-jest@30.4.1(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1):
+ dependencies:
+ '@babel/core': 7.29.7(supports-color@8.1.1)
+ '@jest/transform': 30.4.1(supports-color@8.1.1)
+ '@types/babel__core': 7.20.5
+ babel-plugin-istanbul: 7.0.1(supports-color@8.1.1)
+ babel-preset-jest: 30.4.0(@babel/core@7.29.7(supports-color@8.1.1))
+ chalk: 4.1.2
+ graceful-fs: 4.2.11
+ slash: 3.0.0
+ transitivePeerDependencies:
+ - supports-color
+
+ babel-plugin-istanbul@6.1.1(supports-color@8.1.1):
+ dependencies:
+ '@babel/helper-plugin-utils': 7.29.7
+ '@istanbuljs/load-nyc-config': 1.1.0
+ '@istanbuljs/schema': 0.1.6
+ istanbul-lib-instrument: 5.2.1(supports-color@8.1.1)
+ test-exclude: 6.0.0
+ transitivePeerDependencies:
+ - supports-color
+
+ babel-plugin-istanbul@7.0.1(supports-color@8.1.1):
+ dependencies:
+ '@babel/helper-plugin-utils': 7.29.7
+ '@istanbuljs/load-nyc-config': 1.1.0
+ '@istanbuljs/schema': 0.1.6
+ istanbul-lib-instrument: 6.0.3(supports-color@8.1.1)
+ test-exclude: 6.0.0
+ transitivePeerDependencies:
+ - supports-color
+
+ babel-plugin-jest-hoist@29.6.3:
+ dependencies:
+ '@babel/template': 7.29.7
+ '@babel/types': 7.29.8
+ '@types/babel__core': 7.20.5
+ '@types/babel__traverse': 7.28.0
+
+ babel-plugin-jest-hoist@30.4.0:
+ dependencies:
+ '@types/babel__core': 7.20.5
+
+ babel-preset-current-node-syntax@1.2.0(@babel/core@7.29.7(supports-color@8.1.1)):
+ dependencies:
+ '@babel/core': 7.29.7(supports-color@8.1.1)
+ '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.29.7(supports-color@8.1.1))
+ '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.29.7(supports-color@8.1.1))
+ '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.29.7(supports-color@8.1.1))
+ '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.29.7(supports-color@8.1.1))
+ '@babel/plugin-syntax-import-attributes': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))
+ '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.29.7(supports-color@8.1.1))
+ '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.29.7(supports-color@8.1.1))
+ '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.29.7(supports-color@8.1.1))
+ '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.29.7(supports-color@8.1.1))
+ '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.29.7(supports-color@8.1.1))
+ '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.29.7(supports-color@8.1.1))
+ '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.29.7(supports-color@8.1.1))
+ '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.29.7(supports-color@8.1.1))
+ '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.29.7(supports-color@8.1.1))
+ '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.29.7(supports-color@8.1.1))
+
+ babel-preset-jest@29.6.3(@babel/core@7.29.7(supports-color@8.1.1)):
+ dependencies:
+ '@babel/core': 7.29.7(supports-color@8.1.1)
+ babel-plugin-jest-hoist: 29.6.3
+ babel-preset-current-node-syntax: 1.2.0(@babel/core@7.29.7(supports-color@8.1.1))
+
+ babel-preset-jest@30.4.0(@babel/core@7.29.7(supports-color@8.1.1)):
+ dependencies:
+ '@babel/core': 7.29.7(supports-color@8.1.1)
+ babel-plugin-jest-hoist: 30.4.0
+ babel-preset-current-node-syntax: 1.2.0(@babel/core@7.29.7(supports-color@8.1.1))
+
+ balanced-match@1.0.2: {}
+
+ balanced-match@4.0.4: {}
+
+ base64-js@1.5.1: {}
+
+ baseline-browser-mapping@2.11.11: {}
+
+ bowser@2.14.1: {}
+
+ brace-expansion@1.1.18:
+ dependencies:
+ balanced-match: 1.0.2
+ concat-map: 0.0.1
+
+ brace-expansion@2.1.4:
+ dependencies:
+ balanced-match: 1.0.2
+
+ brace-expansion@5.0.9:
+ dependencies:
+ balanced-match: 4.0.4
+
+ braces@3.0.3:
+ dependencies:
+ fill-range: 7.1.1
+
+ browserslist@4.28.7:
+ dependencies:
+ baseline-browser-mapping: 2.11.11
+ caniuse-lite: 1.0.30001806
+ electron-to-chromium: 1.5.399
+ node-releases: 2.0.51
+ update-browserslist-db: 1.2.3(browserslist@4.28.7)
+
+ bs-logger@0.2.6:
+ dependencies:
+ fast-json-stable-stringify: 2.1.0
+
+ bser@2.1.1:
+ dependencies:
+ node-int64: 0.4.0
+
+ buffer-equal-constant-time@1.0.1: {}
+
+ buffer-from@1.1.2: {}
+
+ buffer@5.6.0:
+ dependencies:
+ base64-js: 1.5.1
+ ieee754: 1.2.1
+
+ builtin-modules@3.3.0: {}
+
+ builtin-modules@5.3.0: {}
+
+ bytes@3.1.2: {}
+
+ call-bind-apply-helpers@1.0.2:
+ dependencies:
+ es-errors: 1.3.0
+ function-bind: 1.1.2
+
+ call-bind@1.0.9:
+ dependencies:
+ call-bind-apply-helpers: 1.0.2
+ es-define-property: 1.0.1
+ get-intrinsic: 1.3.0
+ set-function-length: 1.2.2
+
+ call-bound@1.0.4:
+ dependencies:
+ call-bind-apply-helpers: 1.0.2
+ get-intrinsic: 1.3.0
+
+ callsites@3.1.0: {}
+
+ camelcase@5.3.1: {}
+
+ camelcase@6.3.0: {}
+
+ caniuse-lite@1.0.30001806: {}
+
+ chai@6.2.2: {}
+
+ chalk@4.1.2:
+ dependencies:
+ ansi-styles: 4.3.0
+ supports-color: 7.2.0
+
+ change-case@5.4.4: {}
+
+ char-regex@1.0.2: {}
+
+ ci-info@3.9.0: {}
+
+ ci-info@4.4.0: {}
+
+ cjs-module-lexer@1.4.3: {}
+
+ cjs-module-lexer@2.2.0: {}
+
+ clean-regexp@1.0.0:
+ dependencies:
+ escape-string-regexp: 1.0.5
+
+ cliui@7.0.4:
+ dependencies:
+ string-width: 4.2.3
+ strip-ansi: 6.0.1
+ wrap-ansi: 7.0.0
+
+ cliui@8.0.1:
+ dependencies:
+ string-width: 4.2.3
+ strip-ansi: 6.0.1
+ wrap-ansi: 7.0.0
+
+ co@4.6.0: {}
+
+ collect-v8-coverage@1.0.3: {}
+
+ color-convert@2.0.1:
+ dependencies:
+ color-name: 1.1.4
+
+ color-convert@3.1.3:
+ dependencies:
+ color-name: 2.1.1
+
+ color-name@1.1.4: {}
+
+ color-name@2.1.1: {}
+
+ color-string@2.1.4:
+ dependencies:
+ color-name: 2.1.1
+
+ color@5.0.3:
+ dependencies:
+ color-convert: 3.1.3
+ color-string: 2.1.4
+
+ combined-stream@1.0.8:
+ dependencies:
+ delayed-stream: 1.0.0
+
+ comment-parser@1.4.7: {}
+
+ concat-map@0.0.1: {}
+
+ confusing-browser-globals@1.0.11: {}
+
+ convert-source-map@2.0.0: {}
+
+ core-js-compat@3.49.0:
+ dependencies:
+ browserslist: 4.28.7
+
+ create-jest@29.7.0(@types/node@24.13.3)(supports-color@8.1.1)(ts-node@10.9.2(@types/node@24.13.3)(typescript@5.9.3)):
+ dependencies:
+ '@jest/types': 29.6.3
+ chalk: 4.1.2
+ exit: 0.1.2
+ graceful-fs: 4.2.11
+ jest-config: 29.7.0(@types/node@24.13.3)(supports-color@8.1.1)(ts-node@10.9.2(@types/node@24.13.3)(typescript@5.9.3))
+ jest-util: 29.7.0
+ prompts: 2.4.2
+ transitivePeerDependencies:
+ - '@types/node'
+ - babel-plugin-macros
+ - supports-color
+ - ts-node
+
+ create-require@1.1.1: {}
+
+ cross-spawn@7.0.6:
+ dependencies:
+ path-key: 3.1.1
+ shebang-command: 2.0.0
+ which: 2.0.2
+
+ cssstyle@4.6.0:
+ dependencies:
+ '@asamuzakjp/css-color': 3.2.0
+ rrweb-cssom: 0.8.0
+
+ damerau-levenshtein@1.0.8: {}
+
+ data-urls@5.0.0:
+ dependencies:
+ whatwg-mimetype: 4.0.0
+ whatwg-url: 14.2.0
+
+ data-view-buffer@1.0.2:
+ dependencies:
+ call-bound: 1.0.4
+ es-errors: 1.3.0
+ is-data-view: 1.0.2
+
+ data-view-byte-length@1.0.2:
+ dependencies:
+ call-bound: 1.0.4
+ es-errors: 1.3.0
+ is-data-view: 1.0.2
+
+ data-view-byte-offset@1.0.1:
+ dependencies:
+ call-bound: 1.0.4
+ es-errors: 1.3.0
+ is-data-view: 1.0.2
+
+ date-fns@4.4.0: {}
+
+ dateformat@3.0.2: {}
+
+ debug@4.4.3(supports-color@8.1.1):
+ dependencies:
+ ms: 2.1.3
+ optionalDependencies:
+ supports-color: 8.1.1
+
+ decimal.js@10.6.0: {}
+
+ dedent@1.7.2: {}
+
+ deep-is@0.1.4: {}
+
+ deepmerge@4.3.1: {}
+
+ define-data-property@1.1.4:
+ dependencies:
+ es-define-property: 1.0.1
+ es-errors: 1.3.0
+ gopd: 1.2.0
+
+ define-properties@1.2.1:
+ dependencies:
+ define-data-property: 1.1.4
+ has-property-descriptors: 1.0.2
+ object-keys: 1.1.1
+
+ delayed-stream@1.0.0: {}
+
+ detect-newline@3.1.0: {}
+
+ diff-sequences@29.6.3: {}
+
+ diff@4.0.4: {}
+
+ diff@5.2.2: {}
+
+ doctrine@2.1.0:
+ dependencies:
+ esutils: 2.0.3
+
+ dom-serializer@2.0.0:
+ dependencies:
+ domelementtype: 2.3.0
+ domhandler: 5.0.3
+ entities: 4.5.0
+
+ domelementtype@2.3.0: {}
+
+ domhandler@5.0.3:
+ dependencies:
+ domelementtype: 2.3.0
+
+ domutils@3.2.2:
+ dependencies:
+ dom-serializer: 2.0.0
+ domelementtype: 2.3.0
+ domhandler: 5.0.3
+
+ dunder-proto@1.0.1:
+ dependencies:
+ call-bind-apply-helpers: 1.0.2
+ es-errors: 1.3.0
+ gopd: 1.2.0
+
+ eastasianwidth@0.2.0: {}
+
+ ecdsa-sig-formatter@1.0.11:
+ dependencies:
+ safe-buffer: 5.2.1
+
+ electron-to-chromium@1.5.399: {}
+
+ emittery@0.13.1: {}
+
+ emoji-regex@8.0.0: {}
+
+ emoji-regex@9.2.2: {}
+
+ enabled@2.0.0: {}
+
+ entities@4.5.0: {}
+
+ entities@6.0.1: {}
+
+ entities@7.0.1: {}
+
+ error-ex@1.3.4:
+ dependencies:
+ is-arrayish: 0.2.1
+
+ es-abstract-get@1.0.0:
+ dependencies:
+ es-errors: 1.3.0
+ es-object-atoms: 1.1.2
+ is-callable: 1.2.7
+ object-inspect: 1.13.4
+
+ es-abstract@1.24.2:
+ dependencies:
+ array-buffer-byte-length: 1.0.2
+ arraybuffer.prototype.slice: 1.0.4
+ available-typed-arrays: 1.0.7
+ call-bind: 1.0.9
+ call-bound: 1.0.4
+ data-view-buffer: 1.0.2
+ data-view-byte-length: 1.0.2
+ data-view-byte-offset: 1.0.1
+ es-define-property: 1.0.1
+ es-errors: 1.3.0
+ es-object-atoms: 1.1.2
+ es-set-tostringtag: 2.1.0
+ es-to-primitive: 1.3.4
+ function.prototype.name: 1.2.0
+ get-intrinsic: 1.3.0
+ get-proto: 1.0.1
+ get-symbol-description: 1.1.0
+ globalthis: 1.0.4
+ gopd: 1.2.0
+ has-property-descriptors: 1.0.2
+ has-proto: 1.2.0
+ has-symbols: 1.1.0
+ hasown: 2.0.4
+ internal-slot: 1.1.0
+ is-array-buffer: 3.0.5
+ is-callable: 1.2.7
+ is-data-view: 1.0.2
+ is-negative-zero: 2.0.3
+ is-regex: 1.2.1
+ is-set: 2.0.3
+ is-shared-array-buffer: 1.0.4
+ is-string: 1.1.1
+ is-typed-array: 1.1.15
+ is-weakref: 1.1.1
+ math-intrinsics: 1.1.0
+ object-inspect: 1.13.4
+ object-keys: 1.1.1
+ object.assign: 4.1.7
+ own-keys: 1.0.2
+ regexp.prototype.flags: 1.5.4
+ safe-array-concat: 1.1.4
+ safe-push-apply: 1.0.0
+ safe-regex-test: 1.1.0
+ set-proto: 1.0.0
+ stop-iteration-iterator: 1.1.0
+ string.prototype.trim: 1.2.11
+ string.prototype.trimend: 1.0.10
+ string.prototype.trimstart: 1.0.8
+ typed-array-buffer: 1.0.3
+ typed-array-byte-length: 1.0.3
+ typed-array-byte-offset: 1.0.4
+ typed-array-length: 1.0.8
+ unbox-primitive: 1.1.0
+ which-typed-array: 1.1.22
+
+ es-define-property@1.0.1: {}
+
+ es-errors@1.3.0: {}
+
+ es-iterator-helpers@1.4.0:
+ dependencies:
+ call-bind: 1.0.9
+ call-bound: 1.0.4
+ define-properties: 1.2.1
+ es-abstract: 1.24.2
+ es-errors: 1.3.0
+ es-set-tostringtag: 2.1.0
+ function-bind: 1.1.2
+ get-intrinsic: 1.3.0
+ globalthis: 1.0.4
+ gopd: 1.2.0
+ has-property-descriptors: 1.0.2
+ has-proto: 1.2.0
+ has-symbols: 1.1.0
+ internal-slot: 1.1.0
+ iterator.prototype: 1.1.5
+ math-intrinsics: 1.1.0
+
+ es-object-atoms@1.1.2:
+ dependencies:
+ es-errors: 1.3.0
+
+ es-set-tostringtag@2.1.0:
+ dependencies:
+ es-errors: 1.3.0
+ get-intrinsic: 1.3.0
+ has-tostringtag: 1.0.2
+ hasown: 2.0.4
+
+ es-shim-unscopables@1.1.0:
+ dependencies:
+ hasown: 2.0.4
+
+ es-to-primitive@1.3.4:
+ dependencies:
+ es-abstract-get: 1.0.0
+ es-define-property: 1.0.1
+ es-errors: 1.3.0
+ is-callable: 1.2.7
+ is-date-object: 1.1.0
+ is-symbol: 1.1.1
+
+ esbuild@0.25.12:
+ optionalDependencies:
+ '@esbuild/aix-ppc64': 0.25.12
+ '@esbuild/android-arm': 0.25.12
+ '@esbuild/android-arm64': 0.25.12
+ '@esbuild/android-x64': 0.25.12
+ '@esbuild/darwin-arm64': 0.25.12
+ '@esbuild/darwin-x64': 0.25.12
+ '@esbuild/freebsd-arm64': 0.25.12
+ '@esbuild/freebsd-x64': 0.25.12
+ '@esbuild/linux-arm': 0.25.12
+ '@esbuild/linux-arm64': 0.25.12
+ '@esbuild/linux-ia32': 0.25.12
+ '@esbuild/linux-loong64': 0.25.12
+ '@esbuild/linux-mips64el': 0.25.12
+ '@esbuild/linux-ppc64': 0.25.12
+ '@esbuild/linux-riscv64': 0.25.12
+ '@esbuild/linux-s390x': 0.25.12
+ '@esbuild/linux-x64': 0.25.12
+ '@esbuild/netbsd-arm64': 0.25.12
+ '@esbuild/netbsd-x64': 0.25.12
+ '@esbuild/openbsd-arm64': 0.25.12
+ '@esbuild/openbsd-x64': 0.25.12
+ '@esbuild/openharmony-arm64': 0.25.12
+ '@esbuild/sunos-x64': 0.25.12
+ '@esbuild/win32-arm64': 0.25.12
+ '@esbuild/win32-ia32': 0.25.12
+ '@esbuild/win32-x64': 0.25.12
+
+ esbuild@0.28.1:
+ optionalDependencies:
+ '@esbuild/aix-ppc64': 0.28.1
+ '@esbuild/android-arm': 0.28.1
+ '@esbuild/android-arm64': 0.28.1
+ '@esbuild/android-x64': 0.28.1
+ '@esbuild/darwin-arm64': 0.28.1
+ '@esbuild/darwin-x64': 0.28.1
+ '@esbuild/freebsd-arm64': 0.28.1
+ '@esbuild/freebsd-x64': 0.28.1
+ '@esbuild/linux-arm': 0.28.1
+ '@esbuild/linux-arm64': 0.28.1
+ '@esbuild/linux-ia32': 0.28.1
+ '@esbuild/linux-loong64': 0.28.1
+ '@esbuild/linux-mips64el': 0.28.1
+ '@esbuild/linux-ppc64': 0.28.1
+ '@esbuild/linux-riscv64': 0.28.1
+ '@esbuild/linux-s390x': 0.28.1
+ '@esbuild/linux-x64': 0.28.1
+ '@esbuild/netbsd-arm64': 0.28.1
+ '@esbuild/netbsd-x64': 0.28.1
+ '@esbuild/openbsd-arm64': 0.28.1
+ '@esbuild/openbsd-x64': 0.28.1
+ '@esbuild/openharmony-arm64': 0.28.1
+ '@esbuild/sunos-x64': 0.28.1
+ '@esbuild/win32-arm64': 0.28.1
+ '@esbuild/win32-ia32': 0.28.1
+ '@esbuild/win32-x64': 0.28.1
+
+ escalade@3.2.0: {}
+
+ escape-string-regexp@1.0.5: {}
+
+ escape-string-regexp@2.0.0: {}
+
+ escape-string-regexp@4.0.0: {}
+
+ eslint-config-airbnb-extended@2.3.3(@stylistic/eslint-plugin@3.1.0(eslint@9.39.5(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.3))(eslint-import-resolver-typescript@4.4.5(eslint-plugin-import-x@4.17.1(@typescript-eslint/utils@8.65.0(eslint@9.39.5(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.3))(eslint@9.39.5(supports-color@8.1.1))(supports-color@8.1.1))(eslint@9.39.5(supports-color@8.1.1))(supports-color@8.1.1))(eslint-plugin-import-x@4.17.1(@typescript-eslint/utils@8.65.0(eslint@9.39.5(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.3))(eslint@9.39.5(supports-color@8.1.1))(supports-color@8.1.1))(eslint-plugin-jsx-a11y@6.10.2(eslint@9.39.5(supports-color@8.1.1)))(eslint-plugin-react-hooks@7.1.1(eslint@9.39.5(supports-color@8.1.1))(supports-color@8.1.1))(eslint-plugin-react@7.37.5(eslint@9.39.5(supports-color@8.1.1)))(eslint@9.39.5(supports-color@8.1.1))(typescript-eslint@8.65.0(eslint@9.39.5(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.3)):
+ dependencies:
+ confusing-browser-globals: 1.0.11
+ eslint: 9.39.5(supports-color@8.1.1)
+ globals: 16.5.0
+ optionalDependencies:
+ '@stylistic/eslint-plugin': 3.1.0(eslint@9.39.5(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.3)
+ eslint-import-resolver-typescript: 4.4.5(eslint-plugin-import-x@4.17.1(@typescript-eslint/utils@8.65.0(eslint@9.39.5(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.3))(eslint@9.39.5(supports-color@8.1.1))(supports-color@8.1.1))(eslint@9.39.5(supports-color@8.1.1))(supports-color@8.1.1)
+ eslint-plugin-import-x: 4.17.1(@typescript-eslint/utils@8.65.0(eslint@9.39.5(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.3))(eslint@9.39.5(supports-color@8.1.1))(supports-color@8.1.1)
+ eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.5(supports-color@8.1.1))
+ eslint-plugin-react: 7.37.5(eslint@9.39.5(supports-color@8.1.1))
+ eslint-plugin-react-hooks: 7.1.1(eslint@9.39.5(supports-color@8.1.1))(supports-color@8.1.1)
+ typescript-eslint: 8.65.0(eslint@9.39.5(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.3)
+
+ eslint-config-prettier@10.1.8(eslint@9.39.5(supports-color@8.1.1)):
+ dependencies:
+ eslint: 9.39.5(supports-color@8.1.1)
+
+ eslint-import-context@0.1.9(unrs-resolver@1.12.2):
+ dependencies:
+ get-tsconfig: 4.14.1
+ stable-hash-x: 0.2.0
+ optionalDependencies:
+ unrs-resolver: 1.12.2
+
+ eslint-import-resolver-typescript@4.4.5(eslint-plugin-import-x@4.17.1(@typescript-eslint/utils@8.65.0(eslint@9.39.5(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.3))(eslint@9.39.5(supports-color@8.1.1))(supports-color@8.1.1))(eslint@9.39.5(supports-color@8.1.1))(supports-color@8.1.1):
+ dependencies:
+ debug: 4.4.3(supports-color@8.1.1)
+ eslint: 9.39.5(supports-color@8.1.1)
+ eslint-import-context: 0.1.9(unrs-resolver@1.12.2)
+ get-tsconfig: 4.14.1
+ is-bun-module: 2.0.0
+ stable-hash-x: 0.2.0
+ tinyglobby: 0.2.17
+ unrs-resolver: 1.12.2
+ optionalDependencies:
+ eslint-plugin-import-x: 4.17.1(@typescript-eslint/utils@8.65.0(eslint@9.39.5(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.3))(eslint@9.39.5(supports-color@8.1.1))(supports-color@8.1.1)
+ transitivePeerDependencies:
+ - supports-color
+
+ eslint-plugin-html@8.1.4:
+ dependencies:
+ htmlparser2: 10.1.0
+
+ eslint-plugin-import-x@4.17.1(@typescript-eslint/utils@8.65.0(eslint@9.39.5(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.3))(eslint@9.39.5(supports-color@8.1.1))(supports-color@8.1.1):
+ dependencies:
+ '@typescript-eslint/types': 8.65.0
+ comment-parser: 1.4.7
+ debug: 4.4.3(supports-color@8.1.1)
+ eslint: 9.39.5(supports-color@8.1.1)
+ eslint-import-context: 0.1.9(unrs-resolver@1.12.2)
+ is-glob: 4.0.3
+ minimatch: 10.2.6
+ semver: 7.8.5
+ stable-hash-x: 0.2.0
+ unrs-resolver: 1.12.2
+ optionalDependencies:
+ '@typescript-eslint/utils': 8.65.0(eslint@9.39.5(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.3)
+ transitivePeerDependencies:
+ - supports-color
+
+ eslint-plugin-jest@29.16.0(@typescript-eslint/eslint-plugin@8.65.0(@typescript-eslint/parser@8.65.0(eslint@9.39.5(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.3))(eslint@9.39.5(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.3))(eslint@9.39.5(supports-color@8.1.1))(jest@30.4.2(@types/node@24.13.3)(supports-color@8.1.1)(ts-node@10.9.2(@types/node@24.13.3)(typescript@5.9.3)))(supports-color@8.1.1)(typescript@5.9.3):
+ dependencies:
+ '@typescript-eslint/utils': 8.65.0(eslint@9.39.5(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.3)
+ eslint: 9.39.5(supports-color@8.1.1)
+ optionalDependencies:
+ '@typescript-eslint/eslint-plugin': 8.65.0(@typescript-eslint/parser@8.65.0(eslint@9.39.5(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.3))(eslint@9.39.5(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.3)
+ jest: 30.4.2(@types/node@24.13.3)(supports-color@8.1.1)(ts-node@10.9.2(@types/node@24.13.3)(typescript@5.9.3))
+ typescript: 5.9.3
+ transitivePeerDependencies:
+ - supports-color
+
+ eslint-plugin-json@4.0.1:
+ dependencies:
+ lodash: 4.18.1
+ vscode-json-languageservice: 4.2.1
+
+ eslint-plugin-jsx-a11y@6.10.2(eslint@9.39.5(supports-color@8.1.1)):
+ dependencies:
+ aria-query: 5.3.2
+ array-includes: 3.1.9
+ array.prototype.flatmap: 1.3.3
+ ast-types-flow: 0.0.8
+ axe-core: 4.12.1
+ axobject-query: 4.1.0
+ damerau-levenshtein: 1.0.8
+ emoji-regex: 9.2.2
+ eslint: 9.39.5(supports-color@8.1.1)
+ hasown: 2.0.4
+ jsx-ast-utils: 3.3.5
+ language-tags: 1.0.9
+ minimatch: 3.1.5
+ object.fromentries: 2.0.8
+ safe-regex-test: 1.1.0
+ string.prototype.includes: 2.0.1
+
+ eslint-plugin-no-relative-import-paths@1.6.1: {}
+
+ eslint-plugin-prettier@5.5.6(eslint-config-prettier@10.1.8(eslint@9.39.5(supports-color@8.1.1)))(eslint@9.39.5(supports-color@8.1.1))(prettier@3.9.6):
+ dependencies:
+ eslint: 9.39.5(supports-color@8.1.1)
+ prettier: 3.9.6
+ prettier-linter-helpers: 1.0.1
+ synckit: 0.11.13
+ optionalDependencies:
+ eslint-config-prettier: 10.1.8(eslint@9.39.5(supports-color@8.1.1))
+
+ eslint-plugin-react-hooks@7.1.1(eslint@9.39.5(supports-color@8.1.1))(supports-color@8.1.1):
+ dependencies:
+ '@babel/core': 7.29.7(supports-color@8.1.1)
+ '@babel/parser': 7.29.8
+ eslint: 9.39.5(supports-color@8.1.1)
+ hermes-parser: 0.25.1
+ zod: 4.4.3
+ zod-validation-error: 4.0.2(zod@4.4.3)
+ transitivePeerDependencies:
+ - supports-color
+
+ eslint-plugin-react@7.37.5(eslint@9.39.5(supports-color@8.1.1)):
+ dependencies:
+ array-includes: 3.1.9
+ array.prototype.findlast: 1.2.5
+ array.prototype.flatmap: 1.3.3
+ array.prototype.tosorted: 1.1.4
+ doctrine: 2.1.0
+ es-iterator-helpers: 1.4.0
+ eslint: 9.39.5(supports-color@8.1.1)
+ estraverse: 5.3.0
+ hasown: 2.0.4
+ jsx-ast-utils: 3.3.5
+ minimatch: 3.1.5
+ object.entries: 1.1.9
+ object.fromentries: 2.0.8
+ object.values: 1.2.1
+ prop-types: 15.8.1
+ resolve: 2.0.0-next.7
+ semver: 6.3.1
+ string.prototype.matchall: 4.0.12
+ string.prototype.repeat: 1.0.0
+
+ eslint-plugin-security@3.0.1:
+ dependencies:
+ safe-regex: 2.1.1
+
+ eslint-plugin-sonarjs@3.0.7(eslint@9.39.5(supports-color@8.1.1)):
+ dependencies:
+ '@eslint-community/regexpp': 4.12.2
+ builtin-modules: 3.3.0
+ bytes: 3.1.2
+ eslint: 9.39.5(supports-color@8.1.1)
+ functional-red-black-tree: 1.0.1
+ jsx-ast-utils-x: 0.1.0
+ lodash.merge: 4.6.2
+ minimatch: 10.2.6
+ scslre: 0.3.0
+ semver: 7.7.4
+ typescript: 5.9.3
+
+ eslint-plugin-sort-destructure-keys@2.0.0(eslint@9.39.5(supports-color@8.1.1)):
+ dependencies:
+ eslint: 9.39.5(supports-color@8.1.1)
+ natural-compare-lite: 1.4.0
+
+ eslint-plugin-unicorn@61.0.2(eslint@9.39.5(supports-color@8.1.1)):
+ dependencies:
+ '@babel/helper-validator-identifier': 7.29.7
+ '@eslint-community/eslint-utils': 4.10.1(eslint@9.39.5(supports-color@8.1.1))
+ '@eslint/plugin-kit': 0.3.5
+ change-case: 5.4.4
+ ci-info: 4.4.0
+ clean-regexp: 1.0.0
+ core-js-compat: 3.49.0
+ eslint: 9.39.5(supports-color@8.1.1)
+ esquery: 1.7.0
+ find-up-simple: 1.0.1
+ globals: 16.5.0
+ indent-string: 5.0.0
+ is-builtin-module: 5.0.0
+ jsesc: 3.1.0
+ pluralize: 8.0.0
+ regexp-tree: 0.1.27
+ regjsparser: 0.12.0
+ semver: 7.8.5
+ strip-indent: 4.1.1
+
+ eslint-scope@8.4.0:
+ dependencies:
+ esrecurse: 4.3.0
+ estraverse: 5.3.0
+
+ eslint-visitor-keys@3.4.3: {}
+
+ eslint-visitor-keys@4.2.1: {}
+
+ eslint-visitor-keys@5.0.1: {}
+
+ eslint@9.39.5(supports-color@8.1.1):
+ dependencies:
+ '@eslint-community/eslint-utils': 4.10.1(eslint@9.39.5(supports-color@8.1.1))
+ '@eslint-community/regexpp': 4.12.2
+ '@eslint/config-array': 0.21.2(supports-color@8.1.1)
+ '@eslint/config-helpers': 0.4.2
+ '@eslint/core': 0.17.0
+ '@eslint/eslintrc': 3.3.6(supports-color@8.1.1)
+ '@eslint/js': 9.39.5
+ '@eslint/plugin-kit': 0.4.1
+ '@humanfs/node': 0.16.8
+ '@humanwhocodes/module-importer': 1.0.1
+ '@humanwhocodes/retry': 0.4.3
+ '@types/estree': 1.0.9
+ ajv: 6.15.0
+ chalk: 4.1.2
+ cross-spawn: 7.0.6
+ debug: 4.4.3(supports-color@8.1.1)
+ escape-string-regexp: 4.0.0
+ eslint-scope: 8.4.0
+ eslint-visitor-keys: 4.2.1
+ espree: 10.4.0
+ esquery: 1.7.0
+ esutils: 2.0.3
+ fast-deep-equal: 3.1.3
+ file-entry-cache: 8.0.0
+ find-up: 5.0.0
+ glob-parent: 6.0.2
+ ignore: 5.3.2
+ imurmurhash: 0.1.4
+ is-glob: 4.0.3
+ json-stable-stringify-without-jsonify: 1.0.1
+ lodash.merge: 4.6.2
+ minimatch: 3.1.5
+ natural-compare: 1.4.0
+ optionator: 0.9.4
+ transitivePeerDependencies:
+ - supports-color
+
+ espree@10.4.0:
+ dependencies:
+ acorn: 8.18.0
+ acorn-jsx: 5.3.2(acorn@8.18.0)
+ eslint-visitor-keys: 4.2.1
+
+ esprima@4.0.1: {}
+
+ esquery@1.7.0:
+ dependencies:
+ estraverse: 5.3.0
+
+ esrecurse@4.3.0:
+ dependencies:
+ estraverse: 5.3.0
+
+ estraverse@5.3.0: {}
+
+ esutils@2.0.3: {}
+
+ events@3.3.0: {}
+
+ execa@5.1.1:
+ dependencies:
+ cross-spawn: 7.0.6
+ get-stream: 6.0.1
+ human-signals: 2.1.0
+ is-stream: 2.0.1
+ merge-stream: 2.0.0
+ npm-run-path: 4.0.1
+ onetime: 5.1.2
+ signal-exit: 3.0.7
+ strip-final-newline: 2.0.0
+
+ exit-x@0.2.2: {}
+
+ exit@0.1.2: {}
+
+ expect@29.7.0:
+ dependencies:
+ '@jest/expect-utils': 29.7.0
+ jest-get-type: 29.6.3
+ jest-matcher-utils: 29.7.0
+ jest-message-util: 29.7.0
+ jest-util: 29.7.0
+
+ expect@30.4.1:
+ 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
+
+ fast-deep-equal@3.1.3: {}
+
+ fast-diff@1.3.0: {}
+
+ fast-glob@3.3.3:
+ dependencies:
+ '@nodelib/fs.stat': 2.0.5
+ '@nodelib/fs.walk': 1.2.8
+ glob-parent: 5.1.2
+ merge2: 1.4.1
+ micromatch: 4.0.8
+
+ fast-json-stable-stringify@2.1.0: {}
+
+ fast-levenshtein@2.0.6: {}
+
+ fastq@1.20.1:
+ dependencies:
+ reusify: 1.1.0
+
+ fb-watchman@2.0.2:
+ dependencies:
+ bser: 2.1.1
+
+ fdir@6.5.0(picomatch@4.0.5):
+ optionalDependencies:
+ picomatch: 4.0.5
+
+ fecha@4.2.3: {}
+
+ fflate@0.8.1: {}
+
+ file-entry-cache@8.0.0:
+ dependencies:
+ flat-cache: 4.0.1
+
+ fill-range@7.1.1:
+ dependencies:
+ to-regex-range: 5.0.1
+
+ find-up-simple@1.0.1: {}
+
+ find-up@4.1.0:
+ dependencies:
+ locate-path: 5.0.0
+ path-exists: 4.0.0
+
+ find-up@5.0.0:
+ dependencies:
+ locate-path: 6.0.0
+ path-exists: 4.0.0
+
+ flat-cache@4.0.1:
+ dependencies:
+ flatted: 3.4.4
+ keyv: 4.5.4
+
+ flatted@3.4.4: {}
+
+ fn.name@1.1.0: {}
+
+ follow-redirects@1.16.0(debug@4.4.3(supports-color@8.1.1)):
+ optionalDependencies:
+ debug: 4.4.3(supports-color@8.1.1)
+
+ for-each@0.3.5:
+ dependencies:
+ is-callable: 1.2.7
+
+ foreground-child@3.3.1:
+ dependencies:
+ cross-spawn: 7.0.6
+ signal-exit: 4.1.0
+
+ form-data@4.0.6:
+ dependencies:
+ asynckit: 0.4.0
+ combined-stream: 1.0.8
+ es-set-tostringtag: 2.1.0
+ hasown: 2.0.4
+ mime-types: 2.1.35
+
+ fs.realpath@1.0.0: {}
+
+ fsevents@2.3.3:
+ optional: true
+
+ function-bind@1.1.2: {}
+
+ function.prototype.name@1.2.0:
+ dependencies:
+ call-bind: 1.0.9
+ call-bound: 1.0.4
+ es-define-property: 1.0.1
+ es-errors: 1.3.0
+ functions-have-names: 1.2.3
+ has-property-descriptors: 1.0.2
+ hasown: 2.0.4
+ is-callable: 1.2.7
+ is-document.all: 1.0.0
+
+ functional-red-black-tree@1.0.1: {}
+
+ functions-have-names@1.2.3: {}
+
+ generator-function@2.0.1: {}
+
+ gensync@1.0.0-beta.2: {}
+
+ get-caller-file@2.0.5: {}
+
+ get-intrinsic@1.3.0:
+ dependencies:
+ call-bind-apply-helpers: 1.0.2
+ es-define-property: 1.0.1
+ es-errors: 1.3.0
+ es-object-atoms: 1.1.2
+ function-bind: 1.1.2
+ get-proto: 1.0.1
+ gopd: 1.2.0
+ has-symbols: 1.1.0
+ hasown: 2.0.4
+ math-intrinsics: 1.1.0
+
+ get-package-type@0.1.0: {}
+
+ get-proto@1.0.1:
+ dependencies:
+ dunder-proto: 1.0.1
+ es-object-atoms: 1.1.2
+
+ get-stream@6.0.1: {}
+
+ get-symbol-description@1.1.0:
+ dependencies:
+ call-bound: 1.0.4
+ es-errors: 1.3.0
+ get-intrinsic: 1.3.0
+
+ get-tsconfig@4.14.1:
+ dependencies:
+ resolve-pkg-maps: 1.0.0
+
+ glob-parent@5.1.2:
+ dependencies:
+ is-glob: 4.0.3
+
+ glob-parent@6.0.2:
+ dependencies:
+ is-glob: 4.0.3
+
+ glob@10.5.0:
+ dependencies:
+ foreground-child: 3.3.1
+ jackspeak: 3.4.3
+ minimatch: 9.0.9
+ minipass: 7.1.3
+ package-json-from-dist: 1.0.1
+ path-scurry: 1.11.1
+
+ glob@7.2.3:
+ dependencies:
+ fs.realpath: 1.0.0
+ inflight: 1.0.6
+ inherits: 2.0.4
+ minimatch: 3.1.5
+ once: 1.4.0
+ path-is-absolute: 1.0.1
+
+ globals@14.0.0: {}
+
+ globals@16.5.0: {}
+
+ globalthis@1.0.4:
+ dependencies:
+ define-properties: 1.2.1
+ gopd: 1.2.0
+
+ gopd@1.2.0: {}
+
+ graceful-fs@4.2.11: {}
+
+ handlebars@4.7.9:
+ dependencies:
+ minimist: 1.2.8
+ neo-async: 2.6.2
+ source-map: 0.6.1
+ wordwrap: 1.0.0
+ optionalDependencies:
+ uglify-js: 3.19.3
+
+ has-bigints@1.1.0: {}
+
+ has-flag@4.0.0: {}
+
+ has-property-descriptors@1.0.2:
+ dependencies:
+ es-define-property: 1.0.1
+
+ has-proto@1.2.0:
+ dependencies:
+ dunder-proto: 1.0.1
+
+ has-symbols@1.1.0: {}
+
+ has-tostringtag@1.0.2:
+ dependencies:
+ has-symbols: 1.1.0
+
+ hasown@2.0.4:
+ dependencies:
+ function-bind: 1.1.2
+
+ hermes-estree@0.25.1: {}
+
+ hermes-parser@0.25.1:
+ dependencies:
+ hermes-estree: 0.25.1
+
+ html-encoding-sniffer@4.0.0:
+ dependencies:
+ whatwg-encoding: 3.1.1
+
+ html-escaper@2.0.2: {}
+
+ htmlparser2@10.1.0:
+ dependencies:
+ domelementtype: 2.3.0
+ domhandler: 5.0.3
+ domutils: 3.2.2
+ entities: 7.0.1
+
+ http-proxy-agent@7.0.2(supports-color@8.1.1):
+ dependencies:
+ agent-base: 7.1.4
+ debug: 4.4.3(supports-color@8.1.1)
+ transitivePeerDependencies:
+ - supports-color
+
+ https-proxy-agent@5.0.1(supports-color@8.1.1):
+ dependencies:
+ agent-base: 6.0.2(supports-color@8.1.1)
+ debug: 4.4.3(supports-color@8.1.1)
+ transitivePeerDependencies:
+ - supports-color
+
+ https-proxy-agent@7.0.6(supports-color@8.1.1):
+ dependencies:
+ agent-base: 7.1.4
+ debug: 4.4.3(supports-color@8.1.1)
+ transitivePeerDependencies:
+ - supports-color
+
+ human-signals@2.1.0: {}
+
+ iconv-lite@0.6.3:
+ dependencies:
+ safer-buffer: 2.1.2
+
+ ieee754@1.2.1: {}
+
+ ignore@5.3.2: {}
+
+ ignore@7.0.6: {}
+
+ import-fresh@3.3.1:
+ dependencies:
+ parent-module: 1.0.1
+ resolve-from: 4.0.0
+
+ import-local@3.2.0:
+ dependencies:
+ pkg-dir: 4.2.0
+ resolve-cwd: 3.0.0
+
+ imurmurhash@0.1.4: {}
+
+ indent-string@5.0.0: {}
+
+ inflight@1.0.6:
+ dependencies:
+ once: 1.4.0
+ wrappy: 1.0.2
+
+ inherits@2.0.4: {}
+
+ internal-slot@1.1.0:
+ dependencies:
+ es-errors: 1.3.0
+ hasown: 2.0.4
+ side-channel: 1.1.1
+
+ is-array-buffer@3.0.5:
+ dependencies:
+ call-bind: 1.0.9
+ call-bound: 1.0.4
+ get-intrinsic: 1.3.0
+
+ is-arrayish@0.2.1: {}
+
+ is-async-function@2.1.1:
+ dependencies:
+ async-function: 1.0.0
+ call-bound: 1.0.4
+ get-proto: 1.0.1
+ has-tostringtag: 1.0.2
+ safe-regex-test: 1.1.0
+
+ is-bigint@1.1.0:
+ dependencies:
+ has-bigints: 1.1.0
+
+ is-boolean-object@1.2.2:
+ dependencies:
+ call-bound: 1.0.4
+ has-tostringtag: 1.0.2
+
+ is-builtin-module@5.0.0:
+ dependencies:
+ builtin-modules: 5.3.0
+
+ is-bun-module@2.0.0:
+ dependencies:
+ semver: 7.8.5
+
+ is-callable@1.2.7: {}
+
+ is-core-module@2.16.2:
+ dependencies:
+ hasown: 2.0.4
+
+ is-data-view@1.0.2:
+ dependencies:
+ call-bound: 1.0.4
+ get-intrinsic: 1.3.0
+ is-typed-array: 1.1.15
+
+ is-date-object@1.1.0:
+ dependencies:
+ call-bound: 1.0.4
+ has-tostringtag: 1.0.2
+
+ is-document.all@1.0.0:
+ dependencies:
+ call-bound: 1.0.4
+
+ is-extglob@2.1.1: {}
+
+ is-finalizationregistry@1.1.1:
+ dependencies:
+ call-bound: 1.0.4
+
+ is-fullwidth-code-point@3.0.0: {}
+
+ is-generator-fn@2.1.0: {}
+
+ is-generator-function@1.1.2:
+ dependencies:
+ call-bound: 1.0.4
+ generator-function: 2.0.1
+ get-proto: 1.0.1
+ has-tostringtag: 1.0.2
+ safe-regex-test: 1.1.0
+
+ is-glob@4.0.3:
+ dependencies:
+ is-extglob: 2.1.1
+
+ is-map@2.0.3: {}
+
+ is-negative-zero@2.0.3: {}
+
+ is-number-object@1.1.1:
+ dependencies:
+ call-bound: 1.0.4
+ has-tostringtag: 1.0.2
+
+ is-number@7.0.0: {}
+
+ is-potential-custom-element-name@1.0.1: {}
+
+ is-regex@1.2.1:
+ dependencies:
+ call-bound: 1.0.4
+ gopd: 1.2.0
+ has-tostringtag: 1.0.2
+ hasown: 2.0.4
+
+ is-set@2.0.3: {}
+
+ is-shared-array-buffer@1.0.4:
+ dependencies:
+ call-bound: 1.0.4
+
+ is-stream@2.0.1: {}
+
+ is-string@1.1.1:
+ dependencies:
+ call-bound: 1.0.4
+ has-tostringtag: 1.0.2
+
+ is-symbol@1.1.1:
+ dependencies:
+ call-bound: 1.0.4
+ has-symbols: 1.1.0
+ safe-regex-test: 1.1.0
+
+ is-typed-array@1.1.15:
+ dependencies:
+ which-typed-array: 1.1.22
+
+ is-weakmap@2.0.2: {}
+
+ is-weakref@1.1.1:
+ dependencies:
+ call-bound: 1.0.4
+
+ is-weakset@2.0.4:
+ dependencies:
+ call-bound: 1.0.4
+ get-intrinsic: 1.3.0
+
+ isarray@2.0.5: {}
+
+ isexe@2.0.0: {}
+
+ istanbul-lib-coverage@3.2.2: {}
+
+ istanbul-lib-instrument@5.2.1(supports-color@8.1.1):
+ dependencies:
+ '@babel/core': 7.29.7(supports-color@8.1.1)
+ '@babel/parser': 7.29.8
+ '@istanbuljs/schema': 0.1.6
+ istanbul-lib-coverage: 3.2.2
+ semver: 6.3.1
+ transitivePeerDependencies:
+ - supports-color
+
+ istanbul-lib-instrument@6.0.3(supports-color@8.1.1):
+ dependencies:
+ '@babel/core': 7.29.7(supports-color@8.1.1)
+ '@babel/parser': 7.29.8
+ '@istanbuljs/schema': 0.1.6
+ istanbul-lib-coverage: 3.2.2
+ semver: 7.8.5
+ transitivePeerDependencies:
+ - supports-color
+
+ istanbul-lib-report@3.0.1:
+ dependencies:
+ istanbul-lib-coverage: 3.2.2
+ make-dir: 4.0.0
+ supports-color: 7.2.0
+
+ istanbul-lib-source-maps@4.0.1(supports-color@8.1.1):
+ dependencies:
+ debug: 4.4.3(supports-color@8.1.1)
+ istanbul-lib-coverage: 3.2.2
+ source-map: 0.6.1
+ transitivePeerDependencies:
+ - supports-color
+
+ istanbul-lib-source-maps@5.0.6(supports-color@8.1.1):
+ dependencies:
+ '@jridgewell/trace-mapping': 0.3.31
+ debug: 4.4.3(supports-color@8.1.1)
+ istanbul-lib-coverage: 3.2.2
+ transitivePeerDependencies:
+ - supports-color
+
+ istanbul-reports@3.2.0:
+ dependencies:
+ html-escaper: 2.0.2
+ istanbul-lib-report: 3.0.1
+
+ iterator.prototype@1.1.5:
+ dependencies:
+ define-data-property: 1.1.4
+ es-object-atoms: 1.1.2
+ get-intrinsic: 1.3.0
+ get-proto: 1.0.1
+ has-symbols: 1.1.0
+ set-function-name: 2.0.2
+
+ jackspeak@3.4.3:
+ dependencies:
+ '@isaacs/cliui': 8.0.2
+ optionalDependencies:
+ '@pkgjs/parseargs': 0.11.0
+
+ jest-changed-files@29.7.0:
+ dependencies:
+ execa: 5.1.1
+ jest-util: 29.7.0
+ p-limit: 3.1.0
+
+ jest-changed-files@30.4.1:
+ dependencies:
+ execa: 5.1.1
+ jest-util: 30.4.1
+ p-limit: 3.1.0
+
+ jest-circus@29.7.0(supports-color@8.1.1):
+ dependencies:
+ '@jest/environment': 29.7.0
+ '@jest/expect': 29.7.0(supports-color@8.1.1)
+ '@jest/test-result': 29.7.0
+ '@jest/types': 29.6.3
+ '@types/node': 24.13.3
+ chalk: 4.1.2
+ co: 4.6.0
+ dedent: 1.7.2
+ is-generator-fn: 2.1.0
+ jest-each: 29.7.0
+ jest-matcher-utils: 29.7.0
+ jest-message-util: 29.7.0
+ jest-runtime: 29.7.0(supports-color@8.1.1)
+ jest-snapshot: 29.7.0(supports-color@8.1.1)
+ jest-util: 29.7.0
+ p-limit: 3.1.0
+ pretty-format: 29.7.0
+ pure-rand: 6.1.0
+ slash: 3.0.0
+ stack-utils: 2.0.6
+ transitivePeerDependencies:
+ - babel-plugin-macros
+ - supports-color
+
+ jest-circus@30.4.2(supports-color@8.1.1):
+ dependencies:
+ '@jest/environment': 30.4.1
+ '@jest/expect': 30.4.1(supports-color@8.1.1)
+ '@jest/test-result': 30.4.1
+ '@jest/types': 30.4.1
+ '@types/node': 24.13.3
+ chalk: 4.1.2
+ co: 4.6.0
+ dedent: 1.7.2
+ 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(supports-color@8.1.1)
+ jest-snapshot: 30.4.1(supports-color@8.1.1)
+ jest-util: 30.4.1
+ p-limit: 3.1.0
+ pretty-format: 30.4.1
+ pure-rand: 7.0.1
+ slash: 3.0.0
+ stack-utils: 2.0.6
+ transitivePeerDependencies:
+ - babel-plugin-macros
+ - supports-color
+
+ jest-cli@29.7.0(@types/node@24.13.3)(supports-color@8.1.1)(ts-node@10.9.2(@types/node@24.13.3)(typescript@5.9.3)):
+ dependencies:
+ '@jest/core': 29.7.0(supports-color@8.1.1)(ts-node@10.9.2(@types/node@24.13.3)(typescript@5.9.3))
+ '@jest/test-result': 29.7.0
+ '@jest/types': 29.6.3
+ chalk: 4.1.2
+ create-jest: 29.7.0(@types/node@24.13.3)(supports-color@8.1.1)(ts-node@10.9.2(@types/node@24.13.3)(typescript@5.9.3))
+ exit: 0.1.2
+ import-local: 3.2.0
+ jest-config: 29.7.0(@types/node@24.13.3)(supports-color@8.1.1)(ts-node@10.9.2(@types/node@24.13.3)(typescript@5.9.3))
+ jest-util: 29.7.0
+ jest-validate: 29.7.0
+ yargs: 17.7.3
+ transitivePeerDependencies:
+ - '@types/node'
+ - babel-plugin-macros
+ - supports-color
+ - ts-node
+
+ jest-cli@30.4.2(@types/node@24.13.3)(supports-color@8.1.1)(ts-node@10.9.2(@types/node@24.13.3)(typescript@5.9.3)):
+ dependencies:
+ '@jest/core': 30.4.2(supports-color@8.1.1)(ts-node@10.9.2(@types/node@24.13.3)(typescript@5.9.3))
+ '@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(@types/node@24.13.3)(supports-color@8.1.1)(ts-node@10.9.2(@types/node@24.13.3)(typescript@5.9.3))
+ jest-util: 30.4.1
+ jest-validate: 30.4.1
+ yargs: 17.7.3
+ transitivePeerDependencies:
+ - '@types/node'
+ - babel-plugin-macros
+ - esbuild-register
+ - supports-color
+ - ts-node
+
+ jest-config@29.7.0(@types/node@24.13.3)(supports-color@8.1.1)(ts-node@10.9.2(@types/node@24.13.3)(typescript@5.9.3)):
+ dependencies:
+ '@babel/core': 7.29.7(supports-color@8.1.1)
+ '@jest/test-sequencer': 29.7.0
+ '@jest/types': 29.6.3
+ babel-jest: 29.7.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)
+ chalk: 4.1.2
+ ci-info: 3.9.0
+ deepmerge: 4.3.1
+ glob: 7.2.3
+ graceful-fs: 4.2.11
+ jest-circus: 29.7.0(supports-color@8.1.1)
+ jest-environment-node: 29.7.0
+ jest-get-type: 29.6.3
+ jest-regex-util: 29.6.3
+ jest-resolve: 29.7.0
+ jest-runner: 29.7.0(supports-color@8.1.1)
+ jest-util: 29.7.0
+ jest-validate: 29.7.0
+ micromatch: 4.0.8
+ parse-json: 5.2.0
+ pretty-format: 29.7.0
+ slash: 3.0.0
+ strip-json-comments: 3.1.1
+ optionalDependencies:
+ '@types/node': 24.13.3
+ ts-node: 10.9.2(@types/node@24.13.3)(typescript@5.9.3)
+ transitivePeerDependencies:
+ - babel-plugin-macros
+ - supports-color
+
+ jest-config@30.4.2(@types/node@24.13.3)(supports-color@8.1.1)(ts-node@10.9.2(@types/node@24.13.3)(typescript@5.9.3)):
+ dependencies:
+ '@babel/core': 7.29.7(supports-color@8.1.1)
+ '@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(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)
+ chalk: 4.1.2
+ ci-info: 4.4.0
+ deepmerge: 4.3.1
+ glob: 10.5.0
+ graceful-fs: 4.2.11
+ jest-circus: 30.4.2(supports-color@8.1.1)
+ 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(supports-color@8.1.1)
+ 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
+ optionalDependencies:
+ '@types/node': 24.13.3
+ ts-node: 10.9.2(@types/node@24.13.3)(typescript@5.9.3)
+ transitivePeerDependencies:
+ - babel-plugin-macros
+ - supports-color
+
+ jest-diff@29.7.0:
+ dependencies:
+ chalk: 4.1.2
+ diff-sequences: 29.6.3
+ jest-get-type: 29.6.3
+ pretty-format: 29.7.0
+
+ jest-diff@30.4.1:
+ dependencies:
+ '@jest/diff-sequences': 30.4.0
+ '@jest/get-type': 30.1.0
+ chalk: 4.1.2
+ pretty-format: 30.4.1
+
+ jest-docblock@29.7.0:
+ dependencies:
+ detect-newline: 3.1.0
+
+ jest-docblock@30.4.0:
+ dependencies:
+ detect-newline: 3.1.0
+
+ jest-each@29.7.0:
+ dependencies:
+ '@jest/types': 29.6.3
+ chalk: 4.1.2
+ jest-get-type: 29.6.3
+ jest-util: 29.7.0
+ pretty-format: 29.7.0
+
+ jest-each@30.4.1:
+ 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
+
+ jest-environment-jsdom@30.4.1(supports-color@8.1.1):
+ dependencies:
+ '@jest/environment': 30.4.1
+ '@jest/environment-jsdom-abstract': 30.4.1(jsdom@26.1.0(supports-color@8.1.1))
+ jsdom: 26.1.0(supports-color@8.1.1)
+ transitivePeerDependencies:
+ - bufferutil
+ - supports-color
+ - utf-8-validate
+
+ jest-environment-node@29.7.0:
+ dependencies:
+ '@jest/environment': 29.7.0
+ '@jest/fake-timers': 29.7.0
+ '@jest/types': 29.6.3
+ '@types/node': 24.13.3
+ jest-mock: 29.7.0
+ jest-util: 29.7.0
+
+ jest-environment-node@30.4.1:
+ dependencies:
+ '@jest/environment': 30.4.1
+ '@jest/fake-timers': 30.4.1
+ '@jest/types': 30.4.1
+ '@types/node': 24.13.3
+ jest-mock: 30.4.1
+ jest-util: 30.4.1
+ jest-validate: 30.4.1
+
+ jest-get-type@29.6.3: {}
+
+ jest-haste-map@29.7.0:
+ dependencies:
+ '@jest/types': 29.6.3
+ '@types/graceful-fs': 4.1.9
+ '@types/node': 24.13.3
+ anymatch: 3.1.3
+ fb-watchman: 2.0.2
+ graceful-fs: 4.2.11
+ jest-regex-util: 29.6.3
+ jest-util: 29.7.0
+ jest-worker: 29.7.0
+ micromatch: 4.0.8
+ walker: 1.0.8
+ optionalDependencies:
+ fsevents: 2.3.3
+
+ jest-haste-map@30.4.1:
+ dependencies:
+ '@jest/types': 30.4.1
+ '@types/node': 24.13.3
+ 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.5
+ walker: 1.0.8
+ optionalDependencies:
+ fsevents: 2.3.3
+
+ jest-html-reporter@4.4.0(jest@30.4.2(@types/node@24.13.3)(supports-color@8.1.1)(ts-node@10.9.2(@types/node@24.13.3)(typescript@5.9.3)))(supports-color@8.1.1):
+ dependencies:
+ '@jest/reporters': 30.4.1(supports-color@8.1.1)
+ '@jest/test-result': 30.4.1
+ '@jest/types': 30.4.1
+ dateformat: 3.0.2
+ jest: 30.4.2(@types/node@24.13.3)(supports-color@8.1.1)(ts-node@10.9.2(@types/node@24.13.3)(typescript@5.9.3))
+ mkdirp: 1.0.4
+ strip-ansi: 6.0.1
+ xmlbuilder: 15.0.0
+ transitivePeerDependencies:
+ - node-notifier
+ - supports-color
+
+ jest-leak-detector@29.7.0:
+ dependencies:
+ jest-get-type: 29.6.3
+ pretty-format: 29.7.0
+
+ jest-leak-detector@30.4.1:
+ dependencies:
+ '@jest/get-type': 30.1.0
+ pretty-format: 30.4.1
+
+ jest-matcher-utils@29.7.0:
+ dependencies:
+ chalk: 4.1.2
+ jest-diff: 29.7.0
+ jest-get-type: 29.6.3
+ pretty-format: 29.7.0
+
+ jest-matcher-utils@30.4.1:
+ dependencies:
+ '@jest/get-type': 30.1.0
+ chalk: 4.1.2
+ jest-diff: 30.4.1
+ pretty-format: 30.4.1
+
+ jest-message-util@29.7.0:
+ dependencies:
+ '@babel/code-frame': 7.29.7
+ '@jest/types': 29.6.3
+ '@types/stack-utils': 2.0.3
+ chalk: 4.1.2
+ graceful-fs: 4.2.11
+ micromatch: 4.0.8
+ pretty-format: 29.7.0
+ slash: 3.0.0
+ stack-utils: 2.0.6
+
+ jest-message-util@30.4.1:
+ dependencies:
+ '@babel/code-frame': 7.29.7
+ '@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.5
+ pretty-format: 30.4.1
+ slash: 3.0.0
+ stack-utils: 2.0.6
+
+ jest-mock-extended@3.0.7(jest@29.7.0(@types/node@24.13.3)(supports-color@8.1.1)(ts-node@10.9.2(@types/node@24.13.3)(typescript@5.9.3)))(typescript@5.9.3):
+ dependencies:
+ jest: 29.7.0(@types/node@24.13.3)(supports-color@8.1.1)(ts-node@10.9.2(@types/node@24.13.3)(typescript@5.9.3))
+ ts-essentials: 10.2.1(typescript@5.9.3)
+ typescript: 5.9.3
+
+ jest-mock-extended@4.0.1(@jest/globals@30.4.1(supports-color@8.1.1))(jest@30.4.2(@types/node@24.13.3)(supports-color@8.1.1)(ts-node@10.9.2(@types/node@24.13.3)(typescript@5.9.3)))(typescript@5.9.3):
+ dependencies:
+ '@jest/globals': 30.4.1(supports-color@8.1.1)
+ jest: 30.4.2(@types/node@24.13.3)(supports-color@8.1.1)(ts-node@10.9.2(@types/node@24.13.3)(typescript@5.9.3))
+ lodash.isequal: 4.5.0
+ ts-essentials: 10.2.1(typescript@5.9.3)
+ typescript: 5.9.3
+
+ jest-mock@29.7.0:
+ dependencies:
+ '@jest/types': 29.6.3
+ '@types/node': 24.13.3
+ jest-util: 29.7.0
+
+ jest-mock@30.4.1:
+ dependencies:
+ '@jest/types': 30.4.1
+ '@types/node': 24.13.3
+ jest-util: 30.4.1
+
+ jest-pnp-resolver@1.2.3(jest-resolve@29.7.0):
+ optionalDependencies:
+ jest-resolve: 29.7.0
+
+ jest-pnp-resolver@1.2.3(jest-resolve@30.4.1):
+ optionalDependencies:
+ jest-resolve: 30.4.1
+
+ jest-regex-util@29.6.3: {}
+
+ jest-regex-util@30.4.0: {}
+
+ jest-resolve-dependencies@29.7.0(supports-color@8.1.1):
+ dependencies:
+ jest-regex-util: 29.6.3
+ jest-snapshot: 29.7.0(supports-color@8.1.1)
+ transitivePeerDependencies:
+ - supports-color
+
+ jest-resolve-dependencies@30.4.2(supports-color@8.1.1):
+ dependencies:
+ jest-regex-util: 30.4.0
+ jest-snapshot: 30.4.1(supports-color@8.1.1)
+ transitivePeerDependencies:
+ - supports-color
+
+ jest-resolve@29.7.0:
+ dependencies:
+ chalk: 4.1.2
+ graceful-fs: 4.2.11
+ jest-haste-map: 29.7.0
+ jest-pnp-resolver: 1.2.3(jest-resolve@29.7.0)
+ jest-util: 29.7.0
+ jest-validate: 29.7.0
+ resolve: 1.22.12
+ resolve.exports: 2.0.3
+ slash: 3.0.0
+
+ jest-resolve@30.4.1:
+ dependencies:
+ chalk: 4.1.2
+ graceful-fs: 4.2.11
+ jest-haste-map: 30.4.1
+ jest-pnp-resolver: 1.2.3(jest-resolve@30.4.1)
+ jest-util: 30.4.1
+ jest-validate: 30.4.1
+ slash: 3.0.0
+ unrs-resolver: 1.12.2
+
+ jest-runner@29.7.0(supports-color@8.1.1):
+ dependencies:
+ '@jest/console': 29.7.0
+ '@jest/environment': 29.7.0
+ '@jest/test-result': 29.7.0
+ '@jest/transform': 29.7.0(supports-color@8.1.1)
+ '@jest/types': 29.6.3
+ '@types/node': 24.13.3
+ chalk: 4.1.2
+ emittery: 0.13.1
+ graceful-fs: 4.2.11
+ jest-docblock: 29.7.0
+ jest-environment-node: 29.7.0
+ jest-haste-map: 29.7.0
+ jest-leak-detector: 29.7.0
+ jest-message-util: 29.7.0
+ jest-resolve: 29.7.0
+ jest-runtime: 29.7.0(supports-color@8.1.1)
+ jest-util: 29.7.0
+ jest-watcher: 29.7.0
+ jest-worker: 29.7.0
+ p-limit: 3.1.0
+ source-map-support: 0.5.13
+ transitivePeerDependencies:
+ - supports-color
+
+ jest-runner@30.4.2(supports-color@8.1.1):
+ dependencies:
+ '@jest/console': 30.4.1
+ '@jest/environment': 30.4.1
+ '@jest/test-result': 30.4.1
+ '@jest/transform': 30.4.1(supports-color@8.1.1)
+ '@jest/types': 30.4.1
+ '@types/node': 24.13.3
+ 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(supports-color@8.1.1)
+ 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
+ transitivePeerDependencies:
+ - supports-color
+
+ jest-runtime@29.7.0(supports-color@8.1.1):
+ dependencies:
+ '@jest/environment': 29.7.0
+ '@jest/fake-timers': 29.7.0
+ '@jest/globals': 29.7.0(supports-color@8.1.1)
+ '@jest/source-map': 29.6.3
+ '@jest/test-result': 29.7.0
+ '@jest/transform': 29.7.0(supports-color@8.1.1)
+ '@jest/types': 29.6.3
+ '@types/node': 24.13.3
+ chalk: 4.1.2
+ cjs-module-lexer: 1.4.3
+ collect-v8-coverage: 1.0.3
+ glob: 7.2.3
+ graceful-fs: 4.2.11
+ jest-haste-map: 29.7.0
+ jest-message-util: 29.7.0
+ jest-mock: 29.7.0
+ jest-regex-util: 29.6.3
+ jest-resolve: 29.7.0
+ jest-snapshot: 29.7.0(supports-color@8.1.1)
+ jest-util: 29.7.0
+ slash: 3.0.0
+ strip-bom: 4.0.0
+ transitivePeerDependencies:
+ - supports-color
+
+ jest-runtime@30.4.2(supports-color@8.1.1):
+ dependencies:
+ '@jest/environment': 30.4.1
+ '@jest/fake-timers': 30.4.1
+ '@jest/globals': 30.4.1(supports-color@8.1.1)
+ '@jest/source-map': 30.0.1
+ '@jest/test-result': 30.4.1
+ '@jest/transform': 30.4.1(supports-color@8.1.1)
+ '@jest/types': 30.4.1
+ '@types/node': 24.13.3
+ chalk: 4.1.2
+ cjs-module-lexer: 2.2.0
+ collect-v8-coverage: 1.0.3
+ 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(supports-color@8.1.1)
+ jest-util: 30.4.1
+ slash: 3.0.0
+ strip-bom: 4.0.0
+ transitivePeerDependencies:
+ - supports-color
+
+ jest-snapshot@29.7.0(supports-color@8.1.1):
+ dependencies:
+ '@babel/core': 7.29.7(supports-color@8.1.1)
+ '@babel/generator': 7.29.8
+ '@babel/plugin-syntax-jsx': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))
+ '@babel/plugin-syntax-typescript': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))
+ '@babel/types': 7.29.8
+ '@jest/expect-utils': 29.7.0
+ '@jest/transform': 29.7.0(supports-color@8.1.1)
+ '@jest/types': 29.6.3
+ babel-preset-current-node-syntax: 1.2.0(@babel/core@7.29.7(supports-color@8.1.1))
+ chalk: 4.1.2
+ expect: 29.7.0
+ graceful-fs: 4.2.11
+ jest-diff: 29.7.0
+ jest-get-type: 29.6.3
+ jest-matcher-utils: 29.7.0
+ jest-message-util: 29.7.0
+ jest-util: 29.7.0
+ natural-compare: 1.4.0
+ pretty-format: 29.7.0
+ semver: 7.8.5
+ transitivePeerDependencies:
+ - supports-color
+
+ jest-snapshot@30.4.1(supports-color@8.1.1):
+ dependencies:
+ '@babel/core': 7.29.7(supports-color@8.1.1)
+ '@babel/generator': 7.29.8
+ '@babel/plugin-syntax-jsx': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))
+ '@babel/plugin-syntax-typescript': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))
+ '@babel/types': 7.29.8
+ '@jest/expect-utils': 30.4.1
+ '@jest/get-type': 30.1.0
+ '@jest/snapshot-utils': 30.4.1
+ '@jest/transform': 30.4.1(supports-color@8.1.1)
+ '@jest/types': 30.4.1
+ babel-preset-current-node-syntax: 1.2.0(@babel/core@7.29.7(supports-color@8.1.1))
+ 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.8.5
+ synckit: 0.11.13
+ transitivePeerDependencies:
+ - supports-color
+
+ jest-util@29.7.0:
+ dependencies:
+ '@jest/types': 29.6.3
+ '@types/node': 24.13.3
+ chalk: 4.1.2
+ ci-info: 3.9.0
+ graceful-fs: 4.2.11
+ picomatch: 2.3.2
+
+ jest-util@30.4.1:
+ dependencies:
+ '@jest/types': 30.4.1
+ '@types/node': 24.13.3
+ chalk: 4.1.2
+ ci-info: 4.4.0
+ graceful-fs: 4.2.11
+ picomatch: 4.0.5
+
+ jest-validate@29.7.0:
+ dependencies:
+ '@jest/types': 29.6.3
+ camelcase: 6.3.0
+ chalk: 4.1.2
+ jest-get-type: 29.6.3
+ leven: 3.1.0
+ pretty-format: 29.7.0
+
+ jest-validate@30.4.1:
+ 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
+
+ jest-watcher@29.7.0:
+ dependencies:
+ '@jest/test-result': 29.7.0
+ '@jest/types': 29.6.3
+ '@types/node': 24.13.3
+ ansi-escapes: 4.3.2
+ chalk: 4.1.2
+ emittery: 0.13.1
+ jest-util: 29.7.0
+ string-length: 4.0.2
+
+ jest-watcher@30.4.1:
+ dependencies:
+ '@jest/test-result': 30.4.1
+ '@jest/types': 30.4.1
+ '@types/node': 24.13.3
+ ansi-escapes: 4.3.2
+ chalk: 4.1.2
+ emittery: 0.13.1
+ jest-util: 30.4.1
+ string-length: 4.0.2
+
+ jest-worker@29.7.0:
+ dependencies:
+ '@types/node': 24.13.3
+ jest-util: 29.7.0
+ merge-stream: 2.0.0
+ supports-color: 8.1.1
+
+ jest-worker@30.4.1:
+ dependencies:
+ '@types/node': 24.13.3
+ '@ungap/structured-clone': 1.3.3
+ jest-util: 30.4.1
+ merge-stream: 2.0.0
+ supports-color: 8.1.1
+
+ jest@29.7.0(@types/node@24.13.3)(supports-color@8.1.1)(ts-node@10.9.2(@types/node@24.13.3)(typescript@5.9.3)):
+ dependencies:
+ '@jest/core': 29.7.0(supports-color@8.1.1)(ts-node@10.9.2(@types/node@24.13.3)(typescript@5.9.3))
+ '@jest/types': 29.6.3
+ import-local: 3.2.0
+ jest-cli: 29.7.0(@types/node@24.13.3)(supports-color@8.1.1)(ts-node@10.9.2(@types/node@24.13.3)(typescript@5.9.3))
+ transitivePeerDependencies:
+ - '@types/node'
+ - babel-plugin-macros
+ - supports-color
+ - ts-node
+
+ jest@30.4.2(@types/node@24.13.3)(supports-color@8.1.1)(ts-node@10.9.2(@types/node@24.13.3)(typescript@5.9.3)):
+ dependencies:
+ '@jest/core': 30.4.2(supports-color@8.1.1)(ts-node@10.9.2(@types/node@24.13.3)(typescript@5.9.3))
+ '@jest/types': 30.4.1
+ import-local: 3.2.0
+ jest-cli: 30.4.2(@types/node@24.13.3)(supports-color@8.1.1)(ts-node@10.9.2(@types/node@24.13.3)(typescript@5.9.3))
+ transitivePeerDependencies:
+ - '@types/node'
+ - babel-plugin-macros
+ - esbuild-register
+ - supports-color
+ - ts-node
+
+ jose@5.10.0: {}
+
+ js-tokens@4.0.0: {}
+
+ js-yaml@3.15.1:
+ dependencies:
+ argparse: 1.0.10
+ esprima: 4.0.1
+
+ js-yaml@4.3.1:
+ dependencies:
+ argparse: 2.0.1
+
+ jsdom@26.1.0(supports-color@8.1.1):
+ dependencies:
+ cssstyle: 4.6.0
+ data-urls: 5.0.0
+ decimal.js: 10.6.0
+ html-encoding-sniffer: 4.0.0
+ http-proxy-agent: 7.0.2(supports-color@8.1.1)
+ https-proxy-agent: 7.0.6(supports-color@8.1.1)
+ is-potential-custom-element-name: 1.0.1
+ nwsapi: 2.2.24
+ parse5: 7.3.0
+ rrweb-cssom: 0.8.0
+ saxes: 6.0.0
+ symbol-tree: 3.2.4
+ tough-cookie: 5.1.2
+ w3c-xmlserializer: 5.0.0
+ webidl-conversions: 7.0.0
+ whatwg-encoding: 3.1.1
+ whatwg-mimetype: 4.0.0
+ whatwg-url: 14.2.0
+ ws: 8.21.1
+ xml-name-validator: 5.0.0
+ transitivePeerDependencies:
+ - bufferutil
+ - supports-color
+ - utf-8-validate
+
+ jsesc@3.0.2: {}
+
+ jsesc@3.1.0: {}
+
+ json-buffer@3.0.1: {}
+
+ json-parse-even-better-errors@2.3.1: {}
+
+ json-schema-traverse@0.4.1: {}
+
+ json-stable-stringify-without-jsonify@1.0.1: {}
+
+ json5@2.2.3: {}
+
+ jsonc-parser@3.3.1: {}
+
+ jsonwebtoken@9.0.3:
+ dependencies:
+ jws: 4.0.1
+ lodash.includes: 4.3.0
+ lodash.isboolean: 3.0.3
+ lodash.isinteger: 4.0.4
+ lodash.isnumber: 3.0.3
+ lodash.isplainobject: 4.0.6
+ lodash.isstring: 4.0.1
+ lodash.once: 4.1.1
+ ms: 2.1.3
+ semver: 7.8.5
+
+ jsx-ast-utils-x@0.1.0: {}
+
+ jsx-ast-utils@3.3.5:
+ dependencies:
+ array-includes: 3.1.9
+ array.prototype.flat: 1.3.3
+ object.assign: 4.1.7
+ object.values: 1.2.1
+
+ just-extend@6.2.0: {}
+
+ jwa@2.0.1:
+ dependencies:
+ buffer-equal-constant-time: 1.0.1
+ ecdsa-sig-formatter: 1.0.11
+ safe-buffer: 5.2.1
+
+ jws@4.0.1:
+ dependencies:
+ jwa: 2.0.1
+ safe-buffer: 5.2.1
+
+ keyv@4.5.4:
+ dependencies:
+ json-buffer: 3.0.1
+
+ kleur@3.0.3: {}
+
+ kuler@2.0.0: {}
+
+ language-subtag-registry@0.3.23: {}
+
+ language-tags@1.0.9:
+ dependencies:
+ language-subtag-registry: 0.3.23
+
+ lcov-result-merger@5.0.1:
+ dependencies:
+ fast-glob: 3.3.3
+ yargs: 16.2.2
+
+ leven@3.1.0: {}
+
+ levn@0.4.1:
+ dependencies:
+ prelude-ls: 1.2.1
+ type-check: 0.4.0
+
+ lines-and-columns@1.2.4: {}
+
+ locate-path@5.0.0:
+ dependencies:
+ p-locate: 4.1.0
+
+ locate-path@6.0.0:
+ dependencies:
+ p-locate: 5.0.0
+
+ lodash.includes@4.3.0: {}
+
+ lodash.isboolean@3.0.3: {}
+
+ lodash.isequal@4.5.0: {}
+
+ lodash.isinteger@4.0.4: {}
+
+ lodash.isnumber@3.0.3: {}
+
+ lodash.isplainobject@4.0.6: {}
+
+ lodash.isstring@4.0.1: {}
+
+ lodash.memoize@4.1.2: {}
+
+ lodash.merge@4.6.2: {}
+
+ lodash.once@4.1.1: {}
+
+ lodash@4.18.1: {}
+
+ logform@2.7.0:
+ dependencies:
+ '@colors/colors': 1.6.0
+ '@types/triple-beam': 1.3.5
+ fecha: 4.2.3
+ ms: 2.1.3
+ safe-stable-stringify: 2.5.0
+ triple-beam: 1.4.1
+
+ loose-envify@1.4.0:
+ dependencies:
+ js-tokens: 4.0.0
+
+ lru-cache@10.4.3: {}
+
+ lru-cache@5.1.1:
+ dependencies:
+ yallist: 3.1.1
+
+ make-dir@4.0.0:
+ dependencies:
+ semver: 7.8.5
+
+ make-error@1.3.6: {}
+
+ makeerror@1.0.12:
+ dependencies:
+ tmpl: 1.0.5
+
+ math-intrinsics@1.1.0: {}
+
+ merge-stream@2.0.0: {}
+
+ merge2@1.4.1: {}
+
+ micromatch@4.0.8:
+ dependencies:
+ braces: 3.0.3
+ picomatch: 2.3.2
+
+ mime-db@1.52.0: {}
+
+ mime-types@2.1.35:
+ dependencies:
+ mime-db: 1.52.0
+
+ mimic-fn@2.1.0: {}
+
+ minimatch@10.2.6:
+ dependencies:
+ brace-expansion: 5.0.9
+
+ minimatch@3.1.5:
+ dependencies:
+ brace-expansion: 1.1.18
+
+ minimatch@9.0.9:
+ dependencies:
+ brace-expansion: 2.1.4
+
+ minimist@1.2.8: {}
+
+ minipass@7.1.3: {}
+
+ mkdirp@1.0.4: {}
+
+ mnemonist@0.38.3:
+ dependencies:
+ obliterator: 1.6.1
+
+ mock-fs@5.5.0: {}
+
+ ms@2.1.3: {}
+
+ napi-postinstall@0.3.4: {}
+
+ natural-compare-lite@1.4.0: {}
+
+ natural-compare@1.4.0: {}
+
+ neo-async@2.6.2: {}
+
+ nise@6.1.5:
+ dependencies:
+ '@sinonjs/commons': 3.0.1
+ '@sinonjs/fake-timers': 15.4.0
+ just-extend: 6.2.0
+ path-to-regexp: 8.4.2
+
+ node-exports-info@1.6.2:
+ dependencies:
+ array.prototype.flatmap: 1.3.3
+ es-errors: 1.3.0
+ object.entries: 1.1.9
+ semver: 6.3.1
+
+ node-int64@0.4.0: {}
+
+ node-releases@2.0.51: {}
+
+ normalize-path@3.0.0: {}
+
+ npm-run-path@4.0.1:
+ dependencies:
+ path-key: 3.1.1
+
+ nwsapi@2.2.24: {}
+
+ object-assign@4.1.1: {}
+
+ object-inspect@1.13.4: {}
+
+ object-keys@1.1.1: {}
+
+ object.assign@4.1.7:
+ dependencies:
+ call-bind: 1.0.9
+ call-bound: 1.0.4
+ define-properties: 1.2.1
+ es-object-atoms: 1.1.2
+ has-symbols: 1.1.0
+ object-keys: 1.1.1
+
+ object.entries@1.1.9:
+ dependencies:
+ call-bind: 1.0.9
+ call-bound: 1.0.4
+ define-properties: 1.2.1
+ es-object-atoms: 1.1.2
+
+ object.fromentries@2.0.8:
+ dependencies:
+ call-bind: 1.0.9
+ define-properties: 1.2.1
+ es-abstract: 1.24.2
+ es-object-atoms: 1.1.2
+
+ object.values@1.2.1:
+ dependencies:
+ call-bind: 1.0.9
+ call-bound: 1.0.4
+ define-properties: 1.2.1
+ es-object-atoms: 1.1.2
+
+ obliterator@1.6.1: {}
+
+ once@1.4.0:
+ dependencies:
+ wrappy: 1.0.2
+
+ one-time@1.0.0:
+ dependencies:
+ fn.name: 1.1.0
+
+ onetime@5.1.2:
+ dependencies:
+ mimic-fn: 2.1.0
+
+ optionator@0.9.4:
+ dependencies:
+ deep-is: 0.1.4
+ fast-levenshtein: 2.0.6
+ levn: 0.4.1
+ prelude-ls: 1.2.1
+ type-check: 0.4.0
+ word-wrap: 1.2.5
+
+ own-keys@1.0.2:
+ dependencies:
+ call-bound: 1.0.4
+ get-intrinsic: 1.3.0
+ object-keys: 1.1.1
+ safe-push-apply: 1.0.0
+
+ p-limit@2.3.0:
+ dependencies:
+ p-try: 2.2.0
+
+ p-limit@3.1.0:
+ dependencies:
+ yocto-queue: 0.1.0
+
+ p-locate@4.1.0:
+ dependencies:
+ p-limit: 2.3.0
+
+ p-locate@5.0.0:
+ dependencies:
+ p-limit: 3.1.0
+
+ p-try@2.2.0: {}
+
+ package-json-from-dist@1.0.1: {}
+
+ parent-module@1.0.1:
+ dependencies:
+ callsites: 3.1.0
+
+ parse-json@5.2.0:
+ dependencies:
+ '@babel/code-frame': 7.29.7
+ error-ex: 1.3.4
+ json-parse-even-better-errors: 2.3.1
+ lines-and-columns: 1.2.4
+
+ parse5@7.3.0:
+ dependencies:
+ entities: 6.0.1
+
+ path-exists@4.0.0: {}
+
+ path-is-absolute@1.0.1: {}
+
+ path-key@3.1.1: {}
+
+ path-parse@1.0.7: {}
+
+ path-scurry@1.11.1:
+ dependencies:
+ lru-cache: 10.4.3
+ minipass: 7.1.3
+
+ path-to-regexp@8.4.2: {}
+
+ picocolors@1.1.1: {}
+
+ picomatch@2.3.2: {}
+
+ picomatch@4.0.5: {}
+
+ pirates@4.0.7: {}
+
+ pkg-dir@4.2.0:
+ dependencies:
+ find-up: 4.1.0
+
+ pluralize@8.0.0: {}
+
+ possible-typed-array-names@1.1.0: {}
+
+ prelude-ls@1.2.1: {}
+
+ prettier-linter-helpers@1.0.1:
+ dependencies:
+ fast-diff: 1.3.0
+
+ prettier@3.9.6: {}
+
+ pretty-format@29.7.0:
+ dependencies:
+ '@jest/schemas': 29.6.3
+ ansi-styles: 5.2.0
+ react-is: 18.3.1
+
+ pretty-format@30.4.1:
+ dependencies:
+ '@jest/schemas': 30.4.1
+ ansi-styles: 5.2.0
+ react-is-18: react-is@18.3.1
+ react-is-19: react-is@19.2.8
+
+ prompts@2.4.2:
+ dependencies:
+ kleur: 3.0.3
+ sisteransi: 1.0.5
+
+ prop-types@15.8.1:
+ dependencies:
+ loose-envify: 1.4.0
+ object-assign: 4.1.1
+ react-is: 16.13.1
+
+ proxy-from-env@2.1.0: {}
+
+ punycode@2.3.1: {}
+
+ pure-rand@6.1.0: {}
+
+ pure-rand@7.0.1: {}
+
+ qs@6.15.3:
+ dependencies:
+ es-define-property: 1.0.1
+ side-channel: 1.1.1
+
+ queue-microtask@1.2.3: {}
+
+ react-is@16.13.1: {}
+
+ react-is@18.3.1: {}
+
+ react-is@19.2.8: {}
+
+ readable-stream@3.6.2:
+ dependencies:
+ inherits: 2.0.4
+ string_decoder: 1.3.0
+ util-deprecate: 1.0.2
+
+ refa@0.12.1:
+ dependencies:
+ '@eslint-community/regexpp': 4.12.2
+
+ reflect.getprototypeof@1.0.10:
+ dependencies:
+ call-bind: 1.0.9
+ define-properties: 1.2.1
+ es-abstract: 1.24.2
+ es-errors: 1.3.0
+ es-object-atoms: 1.1.2
+ get-intrinsic: 1.3.0
+ get-proto: 1.0.1
+ which-builtin-type: 1.2.1
+
+ regexp-ast-analysis@0.7.1:
+ dependencies:
+ '@eslint-community/regexpp': 4.12.2
+ refa: 0.12.1
+
+ regexp-tree@0.1.27: {}
+
+ regexp.prototype.flags@1.5.4:
+ dependencies:
+ call-bind: 1.0.9
+ define-properties: 1.2.1
+ es-errors: 1.3.0
+ get-proto: 1.0.1
+ gopd: 1.2.0
+ set-function-name: 2.0.2
+
+ regjsparser@0.12.0:
+ dependencies:
+ jsesc: 3.0.2
+
+ require-directory@2.1.1: {}
+
+ resolve-cwd@3.0.0:
+ dependencies:
+ resolve-from: 5.0.0
+
+ resolve-from@4.0.0: {}
+
+ resolve-from@5.0.0: {}
+
+ resolve-pkg-maps@1.0.0: {}
+
+ resolve.exports@2.0.3: {}
+
+ resolve@1.22.12:
+ dependencies:
+ es-errors: 1.3.0
+ is-core-module: 2.16.2
+ path-parse: 1.0.7
+ supports-preserve-symlinks-flag: 1.0.0
+
+ resolve@2.0.0-next.7:
+ dependencies:
+ es-errors: 1.3.0
+ is-core-module: 2.16.2
+ node-exports-info: 1.6.2
+ object-keys: 1.1.1
+ path-parse: 1.0.7
+ supports-preserve-symlinks-flag: 1.0.0
+
+ reusify@1.1.0: {}
+
+ rrweb-cssom@0.8.0: {}
+
+ run-parallel@1.2.0:
+ dependencies:
+ queue-microtask: 1.2.3
+
+ safe-array-concat@1.1.4:
+ dependencies:
+ call-bind: 1.0.9
+ call-bound: 1.0.4
+ get-intrinsic: 1.3.0
+ has-symbols: 1.1.0
+ isarray: 2.0.5
+
+ safe-buffer@5.2.1: {}
+
+ safe-push-apply@1.0.0:
+ dependencies:
+ es-errors: 1.3.0
+ isarray: 2.0.5
+
+ safe-regex-test@1.1.0:
+ dependencies:
+ call-bound: 1.0.4
+ es-errors: 1.3.0
+ is-regex: 1.2.1
+
+ safe-regex@2.1.1:
+ dependencies:
+ regexp-tree: 0.1.27
+
+ safe-stable-stringify@2.5.0: {}
+
+ safer-buffer@2.1.2: {}
+
+ saxes@6.0.0:
+ dependencies:
+ xmlchars: 2.2.0
+
+ scslre@0.3.0:
+ dependencies:
+ '@eslint-community/regexpp': 4.12.2
+ refa: 0.12.1
+ regexp-ast-analysis: 0.7.1
+
+ semver@6.3.1: {}
+
+ semver@7.7.4: {}
+
+ semver@7.8.5: {}
+
+ set-function-length@1.2.2:
+ dependencies:
+ define-data-property: 1.1.4
+ es-errors: 1.3.0
+ function-bind: 1.1.2
+ get-intrinsic: 1.3.0
+ gopd: 1.2.0
+ has-property-descriptors: 1.0.2
+
+ set-function-name@2.0.2:
+ dependencies:
+ define-data-property: 1.1.4
+ es-errors: 1.3.0
+ functions-have-names: 1.2.3
+ has-property-descriptors: 1.0.2
+
+ set-proto@1.0.0:
+ dependencies:
+ dunder-proto: 1.0.1
+ es-errors: 1.3.0
+ es-object-atoms: 1.1.2
+
+ shebang-command@2.0.0:
+ dependencies:
+ shebang-regex: 3.0.0
+
+ shebang-regex@3.0.0: {}
+
+ side-channel-list@1.0.1:
+ dependencies:
+ es-errors: 1.3.0
+ object-inspect: 1.13.4
+
+ side-channel-map@1.0.1:
+ dependencies:
+ call-bound: 1.0.4
+ es-errors: 1.3.0
+ get-intrinsic: 1.3.0
+ object-inspect: 1.13.4
+
+ side-channel-weakmap@1.0.2:
+ dependencies:
+ call-bound: 1.0.4
+ es-errors: 1.3.0
+ get-intrinsic: 1.3.0
+ object-inspect: 1.13.4
+ side-channel-map: 1.0.1
+
+ side-channel@1.1.1:
+ dependencies:
+ es-errors: 1.3.0
+ object-inspect: 1.13.4
+ side-channel-list: 1.0.1
+ side-channel-map: 1.0.1
+ side-channel-weakmap: 1.0.2
+
+ signal-exit@3.0.7: {}
+
+ signal-exit@4.1.0: {}
+
+ sinon@18.0.1:
+ dependencies:
+ '@sinonjs/commons': 3.0.1
+ '@sinonjs/fake-timers': 11.2.2
+ '@sinonjs/samsam': 8.0.3
+ diff: 5.2.2
+ nise: 6.1.5
+ supports-color: 7.2.0
+
+ sisteransi@1.0.5: {}
+
+ slash@3.0.0: {}
+
+ source-map-support@0.5.13:
+ dependencies:
+ buffer-from: 1.1.2
+ source-map: 0.6.1
+
+ source-map@0.6.1: {}
+
+ sprintf-js@1.0.3: {}
+
+ stable-hash-x@0.2.0: {}
+
+ stack-trace@0.0.10: {}
+
+ stack-utils@2.0.6:
+ dependencies:
+ escape-string-regexp: 2.0.0
+
+ stop-iteration-iterator@1.1.0:
+ dependencies:
+ es-errors: 1.3.0
+ internal-slot: 1.1.0
+
+ stream-browserify@3.0.0:
+ dependencies:
+ inherits: 2.0.4
+ readable-stream: 3.6.2
+
+ string-length@4.0.2:
+ dependencies:
+ char-regex: 1.0.2
+ strip-ansi: 6.0.1
+
+ string-width@4.2.3:
+ dependencies:
+ emoji-regex: 8.0.0
+ is-fullwidth-code-point: 3.0.0
+ strip-ansi: 6.0.1
+
+ string-width@5.1.2:
+ dependencies:
+ eastasianwidth: 0.2.0
+ emoji-regex: 9.2.2
+ strip-ansi: 7.2.0
+
+ string.prototype.includes@2.0.1:
+ dependencies:
+ call-bind: 1.0.9
+ define-properties: 1.2.1
+ es-abstract: 1.24.2
+
+ string.prototype.matchall@4.0.12:
+ dependencies:
+ call-bind: 1.0.9
+ call-bound: 1.0.4
+ define-properties: 1.2.1
+ es-abstract: 1.24.2
+ es-errors: 1.3.0
+ es-object-atoms: 1.1.2
+ get-intrinsic: 1.3.0
+ gopd: 1.2.0
+ has-symbols: 1.1.0
+ internal-slot: 1.1.0
+ regexp.prototype.flags: 1.5.4
+ set-function-name: 2.0.2
+ side-channel: 1.1.1
+
+ string.prototype.repeat@1.0.0:
+ dependencies:
+ define-properties: 1.2.1
+ es-abstract: 1.24.2
+
+ string.prototype.trim@1.2.11:
+ dependencies:
+ call-bind: 1.0.9
+ call-bound: 1.0.4
+ define-data-property: 1.1.4
+ define-properties: 1.2.1
+ es-abstract: 1.24.2
+ es-object-atoms: 1.1.2
+ has-property-descriptors: 1.0.2
+ safe-regex-test: 1.1.0
+
+ string.prototype.trimend@1.0.10:
+ dependencies:
+ call-bind: 1.0.9
+ call-bound: 1.0.4
+ define-properties: 1.2.1
+ es-object-atoms: 1.1.2
+
+ string.prototype.trimstart@1.0.8:
+ dependencies:
+ call-bind: 1.0.9
+ define-properties: 1.2.1
+ es-object-atoms: 1.1.2
+
+ string_decoder@1.3.0:
+ dependencies:
+ safe-buffer: 5.2.1
+
+ strip-ansi@6.0.1:
+ dependencies:
+ ansi-regex: 5.0.1
+
+ strip-ansi@7.2.0:
+ dependencies:
+ ansi-regex: 6.2.2
+
+ strip-bom@4.0.0: {}
+
+ strip-final-newline@2.0.0: {}
+
+ strip-indent@4.1.1: {}
+
+ strip-json-comments@3.1.1: {}
+
+ supports-color@7.2.0:
+ dependencies:
+ has-flag: 4.0.0
+
+ supports-color@8.1.1:
+ dependencies:
+ has-flag: 4.0.0
+
+ supports-preserve-symlinks-flag@1.0.0: {}
+
+ symbol-tree@3.2.4: {}
+
+ synckit@0.11.13:
+ dependencies:
+ '@pkgr/core': 0.3.6
+
+ test-exclude@6.0.0:
+ dependencies:
+ '@istanbuljs/schema': 0.1.6
+ glob: 7.2.3
+ minimatch: 3.1.5
+
+ text-hex@1.0.0: {}
+
+ tinyglobby@0.2.17:
+ dependencies:
+ fdir: 6.5.0(picomatch@4.0.5)
+ picomatch: 4.0.5
+
+ tinyrainbow@3.1.1: {}
+
+ tldts-core@6.1.86: {}
+
+ tldts@6.1.86:
+ dependencies:
+ tldts-core: 6.1.86
+
+ tmpl@1.0.5: {}
+
+ to-regex-range@5.0.1:
+ dependencies:
+ is-number: 7.0.0
+
+ tough-cookie@5.1.2:
+ dependencies:
+ tldts: 6.1.86
+
+ tr46@5.1.1:
+ dependencies:
+ punycode: 2.3.1
+
+ triple-beam@1.4.1: {}
+
+ ts-api-utils@2.5.0(typescript@5.9.3):
+ dependencies:
+ typescript: 5.9.3
+
+ ts-essentials@10.2.1(typescript@5.9.3):
+ optionalDependencies:
+ typescript: 5.9.3
+
+ ts-jest@29.4.12(@babel/core@7.29.7(supports-color@8.1.1))(@jest/transform@30.4.1(supports-color@8.1.1))(@jest/types@30.4.1)(babel-jest@30.4.1(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(esbuild@0.25.12)(jest-util@30.4.1)(jest@30.4.2(@types/node@24.13.3)(supports-color@8.1.1)(ts-node@10.9.2(@types/node@24.13.3)(typescript@5.9.3)))(typescript@5.9.3):
+ dependencies:
+ bs-logger: 0.2.6
+ fast-json-stable-stringify: 2.1.0
+ handlebars: 4.7.9
+ jest: 30.4.2(@types/node@24.13.3)(supports-color@8.1.1)(ts-node@10.9.2(@types/node@24.13.3)(typescript@5.9.3))
+ json5: 2.2.3
+ lodash.memoize: 4.1.2
+ make-error: 1.3.6
+ semver: 7.8.5
+ type-fest: 4.41.0
+ typescript: 5.9.3
+ yargs-parser: 21.1.1
+ optionalDependencies:
+ '@babel/core': 7.29.7(supports-color@8.1.1)
+ '@jest/transform': 30.4.1(supports-color@8.1.1)
+ '@jest/types': 30.4.1
+ babel-jest: 30.4.1(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)
+ esbuild: 0.25.12
+ jest-util: 30.4.1
+
+ ts-node@10.9.2(@types/node@24.13.3)(typescript@5.9.3):
+ dependencies:
+ '@cspotcode/source-map-support': 0.8.1
+ '@tsconfig/node10': 1.0.12
+ '@tsconfig/node12': 1.0.11
+ '@tsconfig/node14': 1.0.3
+ '@tsconfig/node16': 1.0.4
+ '@types/node': 24.13.3
+ acorn: 8.18.0
+ acorn-walk: 8.3.5
+ arg: 4.1.3
+ create-require: 1.1.1
+ diff: 4.0.4
+ make-error: 1.3.6
+ typescript: 5.9.3
+ v8-compile-cache-lib: 3.0.1
+ yn: 3.1.1
+
+ tslib@2.8.1: {}
+
+ tsx@4.23.5:
+ dependencies:
+ esbuild: 0.28.1
+ optionalDependencies:
+ fsevents: 2.3.3
+
+ turbo@2.10.8:
+ optionalDependencies:
+ '@turbo/darwin-64': 2.10.8
+ '@turbo/darwin-arm64': 2.10.8
+ '@turbo/linux-64': 2.10.8
+ '@turbo/linux-arm64': 2.10.8
+ '@turbo/windows-64': 2.10.8
+ '@turbo/windows-arm64': 2.10.8
+
+ type-check@0.4.0:
+ dependencies:
+ prelude-ls: 1.2.1
+
+ type-detect@4.0.8: {}
+
+ type-detect@4.1.0: {}
+
+ type-fest@0.21.3: {}
+
+ type-fest@4.41.0: {}
+
+ typed-array-buffer@1.0.3:
+ dependencies:
+ call-bound: 1.0.4
+ es-errors: 1.3.0
+ is-typed-array: 1.1.15
+
+ typed-array-byte-length@1.0.3:
+ dependencies:
+ call-bind: 1.0.9
+ for-each: 0.3.5
+ gopd: 1.2.0
+ has-proto: 1.2.0
+ is-typed-array: 1.1.15
+
+ typed-array-byte-offset@1.0.4:
+ dependencies:
+ available-typed-arrays: 1.0.7
+ call-bind: 1.0.9
+ for-each: 0.3.5
+ gopd: 1.2.0
+ has-proto: 1.2.0
+ is-typed-array: 1.1.15
+ reflect.getprototypeof: 1.0.10
+
+ typed-array-length@1.0.8:
+ dependencies:
+ call-bind: 1.0.9
+ for-each: 0.3.5
+ gopd: 1.2.0
+ is-typed-array: 1.1.15
+ possible-typed-array-names: 1.1.0
+ reflect.getprototypeof: 1.0.10
+
+ typescript-eslint@8.65.0(eslint@9.39.5(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.3):
+ dependencies:
+ '@typescript-eslint/eslint-plugin': 8.65.0(@typescript-eslint/parser@8.65.0(eslint@9.39.5(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.3))(eslint@9.39.5(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.3)
+ '@typescript-eslint/parser': 8.65.0(eslint@9.39.5(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.3)
+ '@typescript-eslint/typescript-estree': 8.65.0(supports-color@8.1.1)(typescript@5.9.3)
+ '@typescript-eslint/utils': 8.65.0(eslint@9.39.5(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.3)
+ eslint: 9.39.5(supports-color@8.1.1)
+ typescript: 5.9.3
+ transitivePeerDependencies:
+ - supports-color
+
+ typescript@5.9.3: {}
+
+ uglify-js@3.19.3:
+ optional: true
+
+ unbox-primitive@1.1.0:
+ dependencies:
+ call-bound: 1.0.4
+ has-bigints: 1.1.0
+ has-symbols: 1.1.0
+ which-boxed-primitive: 1.1.1
+
+ undici-types@7.18.2: {}
+
+ unrs-resolver@1.12.2:
+ dependencies:
+ napi-postinstall: 0.3.4
+ 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
+
+ update-browserslist-db@1.2.3(browserslist@4.28.7):
+ dependencies:
+ browserslist: 4.28.7
+ escalade: 3.2.0
+ picocolors: 1.1.1
+
+ uri-js@4.4.1:
+ dependencies:
+ punycode: 2.3.1
+
+ util-deprecate@1.0.2: {}
+
+ v8-compile-cache-lib@3.0.1: {}
+
+ v8-to-istanbul@9.3.0:
+ dependencies:
+ '@jridgewell/trace-mapping': 0.3.31
+ '@types/istanbul-lib-coverage': 2.0.6
+ convert-source-map: 2.0.0
+
+ vscode-json-languageservice@4.2.1:
+ dependencies:
+ jsonc-parser: 3.3.1
+ vscode-languageserver-textdocument: 1.0.12
+ vscode-languageserver-types: 3.18.0
+ vscode-nls: 5.2.0
+ vscode-uri: 3.1.0
+
+ vscode-languageserver-textdocument@1.0.12: {}
+
+ vscode-languageserver-types@3.18.0: {}
+
+ vscode-nls@5.2.0: {}
+
+ vscode-uri@3.1.0: {}
+
+ w3c-xmlserializer@5.0.0:
+ dependencies:
+ xml-name-validator: 5.0.0
+
+ walker@1.0.8:
+ dependencies:
+ makeerror: 1.0.12
+
+ webidl-conversions@7.0.0: {}
+
+ whatwg-encoding@3.1.1:
+ dependencies:
+ iconv-lite: 0.6.3
+
+ whatwg-mimetype@4.0.0: {}
+
+ whatwg-url@14.2.0:
+ dependencies:
+ tr46: 5.1.1
+ webidl-conversions: 7.0.0
+
+ which-boxed-primitive@1.1.1:
+ dependencies:
+ is-bigint: 1.1.0
+ is-boolean-object: 1.2.2
+ is-number-object: 1.1.1
+ is-string: 1.1.1
+ is-symbol: 1.1.1
+
+ which-builtin-type@1.2.1:
+ dependencies:
+ call-bound: 1.0.4
+ function.prototype.name: 1.2.0
+ has-tostringtag: 1.0.2
+ is-async-function: 2.1.1
+ is-date-object: 1.1.0
+ is-finalizationregistry: 1.1.1
+ is-generator-function: 1.1.2
+ is-regex: 1.2.1
+ is-weakref: 1.1.1
+ isarray: 2.0.5
+ which-boxed-primitive: 1.1.1
+ which-collection: 1.0.2
+ which-typed-array: 1.1.22
+
+ which-collection@1.0.2:
+ dependencies:
+ is-map: 2.0.3
+ is-set: 2.0.3
+ is-weakmap: 2.0.2
+ is-weakset: 2.0.4
+
+ which-typed-array@1.1.22:
+ dependencies:
+ available-typed-arrays: 1.0.7
+ call-bind: 1.0.9
+ call-bound: 1.0.4
+ for-each: 0.3.5
+ get-proto: 1.0.1
+ gopd: 1.2.0
+ has-tostringtag: 1.0.2
+
+ which@2.0.2:
+ dependencies:
+ isexe: 2.0.0
+
+ winston-transport@4.9.0:
+ dependencies:
+ logform: 2.7.0
+ readable-stream: 3.6.2
+ triple-beam: 1.4.1
+
+ winston@3.19.0:
+ dependencies:
+ '@colors/colors': 1.6.0
+ '@dabh/diagnostics': 2.0.8
+ async: 3.2.6
+ is-stream: 2.0.1
+ logform: 2.7.0
+ one-time: 1.0.0
+ readable-stream: 3.6.2
+ safe-stable-stringify: 2.5.0
+ stack-trace: 0.0.10
+ triple-beam: 1.4.1
+ winston-transport: 4.9.0
+
+ word-wrap@1.2.5: {}
+
+ wordwrap@1.0.0: {}
+
+ wrap-ansi@7.0.0:
+ dependencies:
+ ansi-styles: 4.3.0
+ string-width: 4.2.3
+ strip-ansi: 6.0.1
+
+ wrap-ansi@8.1.0:
+ dependencies:
+ ansi-styles: 6.2.3
+ string-width: 5.1.2
+ strip-ansi: 7.2.0
+
+ wrappy@1.0.2: {}
+
+ write-file-atomic@4.0.2:
+ dependencies:
+ imurmurhash: 0.1.4
+ signal-exit: 3.0.7
+
+ write-file-atomic@5.0.1:
+ dependencies:
+ imurmurhash: 0.1.4
+ signal-exit: 4.1.0
+
+ ws@8.21.1: {}
+
+ xml-name-validator@5.0.0: {}
+
+ xmlbuilder@15.0.0: {}
+
+ xmlchars@2.2.0: {}
+
+ y18n@5.0.8: {}
+
+ yallist@3.1.1: {}
+
+ yargs-parser@20.2.9: {}
+
+ yargs-parser@21.1.1: {}
+
+ yargs@16.2.2:
+ dependencies:
+ cliui: 7.0.4
+ escalade: 3.2.0
+ get-caller-file: 2.0.5
+ require-directory: 2.1.1
+ string-width: 4.2.3
+ y18n: 5.0.8
+ yargs-parser: 20.2.9
+
+ yargs@17.7.3:
+ dependencies:
+ cliui: 8.0.1
+ escalade: 3.2.0
+ get-caller-file: 2.0.5
+ require-directory: 2.1.1
+ string-width: 4.2.3
+ y18n: 5.0.8
+ yargs-parser: 21.1.1
+
+ yn@3.1.1: {}
+
+ yocto-queue@0.1.0: {}
+
+ zod-validation-error@4.0.2(zod@4.4.3):
+ dependencies:
+ zod: 4.4.3
+
+ zod@4.4.3: {}
+
+time:
+ '@aws-sdk/client-athena@3.1101.0': '2026-07-31T18:50:30.517Z'
+ '@aws-sdk/client-cloudwatch@3.1101.0': '2026-07-31T18:53:29.405Z'
+ '@aws-sdk/client-dynamodb@3.1101.0': '2026-07-31T18:54:33.369Z'
+ '@aws-sdk/client-eventbridge@3.1101.0': '2026-07-31T18:54:01.860Z'
+ '@aws-sdk/client-lambda@3.1101.0': '2026-07-31T18:55:13.820Z'
+ '@aws-sdk/client-s3@3.1101.0': '2026-07-31T18:51:01.862Z'
+ '@aws-sdk/client-sqs@3.1101.0': '2026-07-31T19:00:13.767Z'
+ '@aws-sdk/client-ssm@3.1101.0': '2026-07-31T18:58:13.556Z'
+ '@aws-sdk/lib-dynamodb@3.1101.0': '2026-07-31T18:50:54.195Z'
+ '@aws-sdk/lib-storage@3.1101.0': '2026-07-31T18:50:53.721Z'
+ '@aws-sdk/types@3.974.2': '2026-07-15T18:50:46.412Z'
+ '@eslint/js@9.39.5': '2026-07-10T20:16:17.272Z'
+ '@stylistic/eslint-plugin-ts@4.4.1': '2025-06-04T05:15:11.157Z'
+ '@stylistic/eslint-plugin@3.1.0': '2025-02-08T03:32:25.887Z'
+ '@tsconfig/node22@22.0.5': '2025-11-18T04:18:10.669Z'
+ '@types/aws-lambda@8.10.162': '2026-06-06T12:54:37.547Z'
+ '@types/jest@29.5.14': '2024-10-23T03:43:49.927Z'
+ '@types/jest@30.0.0': '2025-06-16T07:35:50.850Z'
+ '@types/jsonwebtoken@9.0.10': '2025-06-16T07:36:00.187Z'
+ '@types/mock-fs@4.13.4': '2023-11-07T11:15:00.220Z'
+ '@types/node@24.13.3': '2026-07-08T06:48:03.261Z'
+ '@types/qs@6.15.1': '2026-05-06T23:46:01.024Z'
+ '@typescript-eslint/eslint-plugin@8.65.0': '2026-07-20T17:39:25.625Z'
+ '@typescript-eslint/parser@8.65.0': '2026-07-20T17:39:03.395Z'
+ async-mutex@0.4.1: '2024-01-17T21:30:34.828Z'
+ aws-sdk-client-mock-jest@4.1.0: '2024-10-15T12:49:36.193Z'
+ aws-sdk-client-mock@4.1.0: '2024-10-15T12:49:34.458Z'
+ axios@1.19.0: '2026-07-29T17:07:54.912Z'
+ date-fns@4.4.0: '2026-05-29T23:23:42.628Z'
+ esbuild@0.25.12: '2025-11-01T23:36:22.605Z'
+ eslint-config-airbnb-extended@2.3.3: '2025-11-29T18:21:46.393Z'
+ eslint-config-prettier@10.1.8: '2025-07-18T18:40:08.244Z'
+ eslint-import-resolver-typescript@4.4.5: '2026-06-01T04:17:50.360Z'
+ eslint-plugin-html@8.1.4: '2026-01-23T14:05:26.428Z'
+ eslint-plugin-import-x@4.17.1: '2026-06-28T07:00:54.891Z'
+ eslint-plugin-jest@29.16.0: '2026-07-24T05:07:19.397Z'
+ eslint-plugin-json@4.0.1: '2024-08-07T22:51:29.877Z'
+ eslint-plugin-jsx-a11y@6.10.2: '2024-10-26T04:45:18.067Z'
+ eslint-plugin-no-relative-import-paths@1.6.1: '2025-01-07T15:01:05.204Z'
+ eslint-plugin-prettier@5.5.6: '2026-05-28T10:52:57.100Z'
+ eslint-plugin-react-hooks@7.1.1: '2026-04-17T18:03:19.591Z'
+ eslint-plugin-react@7.37.5: '2025-04-03T20:01:15.958Z'
+ eslint-plugin-security@3.0.1: '2024-06-14T11:35:49.853Z'
+ eslint-plugin-sonarjs@3.0.7: '2026-02-11T10:59:09.363Z'
+ eslint-plugin-sort-destructure-keys@2.0.0: '2024-04-24T05:18:05.644Z'
+ eslint-plugin-unicorn@61.0.2: '2025-09-08T09:24:37.952Z'
+ eslint@9.39.5: '2026-07-10T20:41:47.507Z'
+ jest-environment-jsdom@30.4.1: '2026-05-08T08:34:58.404Z'
+ jest-html-reporter@4.4.0: '2026-03-28T08:26:31.721Z'
+ jest-mock-extended@3.0.7: '2024-05-02T09:32:24.366Z'
+ jest-mock-extended@4.0.1: '2026-04-20T16:17:09.360Z'
+ jest@29.7.0: '2023-09-12T06:44:08.561Z'
+ jest@30.4.2: '2026-05-09T00:31:21.036Z'
+ jose@5.10.0: '2025-02-17T15:07:27.617Z'
+ jsonwebtoken@9.0.3: '2025-12-04T10:27:57.257Z'
+ lcov-result-merger@5.0.1: '2024-05-17T08:48:24.166Z'
+ mock-fs@5.5.0: '2025-02-06T16:36:54.369Z'
+ qs@6.15.3: '2026-06-24T20:03:49.752Z'
+ ts-jest@29.4.12: '2026-07-22T07:47:19.343Z'
+ ts-node@10.9.2: '2023-12-08T12:04:46.154Z'
+ tsx@4.23.5: '2026-08-02T23:18:23.595Z'
+ turbo@2.10.8: '2026-07-31T14:23:18.916Z'
+ typescript-eslint@8.65.0: '2026-07-20T17:39:32.402Z'
+ typescript@5.9.3: '2025-09-30T21:19:38.784Z'
+ winston@3.19.0: '2025-12-07T07:37:16.009Z'
+ zod@4.4.3: '2026-05-04T07:06:40.819Z'
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
new file mode 100644
index 00000000..5887b297
--- /dev/null
+++ b/pnpm-workspace.yaml
@@ -0,0 +1,83 @@
+# PLEASE UPDATE THE PACKAGES LIST APPROPRIATELY.
+packages:
+ - "src/lambdas/apim-access-token-refresher"
+ - "src/lambdas/apim-key-generator"
+ - "src/utils"
+
+allowBuilds:
+ '@parcel/watcher': false
+ esbuild: false
+ protobufjs: false
+ sharp: false
+ unrs-resolver: false
+ vue-demi: false
+blockExoticSubdeps: true
+
+catalogs:
+ build:
+ turbo: "^2.9.6"
+ lint:
+ "@eslint/js": "^9.39.4"
+ "@stylistic/eslint-plugin": "^3.1.0"
+ "@stylistic/eslint-plugin-ts": "^4.4.1"
+ "@typescript-eslint/eslint-plugin": "^8.46.1"
+ "@typescript-eslint/parser": "^8.46.1"
+ eslint: "^9.37.0"
+ eslint-config-airbnb-extended: "^2.3.2"
+ eslint-config-next: "^15.3.2"
+ eslint-config-prettier: "^10.1.8"
+ eslint-import-resolver-typescript: "^4.4.2"
+ eslint-plugin-html: "^8.1.3"
+ eslint-plugin-import-x: "^4.13.3"
+ eslint-plugin-jest: "^29.0.1"
+ eslint-plugin-json: "^4.0.1"
+ eslint-plugin-jsx-a11y: "^6.10.2"
+ eslint-plugin-no-relative-import-paths: "^1.6.1"
+ eslint-plugin-prettier: "^5.5.4"
+ eslint-plugin-react: "^7.37.5"
+ eslint-plugin-security: "^3.0.1"
+ eslint-plugin-sonarjs: "^3.0.5"
+ eslint-plugin-sort-destructure-keys: "^2.0.0"
+ eslint-plugin-unicorn: "^61.0.2"
+ typescript-eslint: "^8.46.1"
+ test:
+ "@types/jest": "^30.0.0"
+ "@types/mock-fs": "^4.13.4"
+ jest: "^30.2.0"
+ jest-environment-jsdom: "^30.2.0"
+ jest-html-reporter: "^4.3.0"
+ jest-mock-extended: "^4.0.0"
+ lcov-result-merger: "^5.0.1"
+ mock-fs: "^5.5.0"
+ ts-jest: "^29.4.11"
+ test-legacy:
+ "@jest/globals": "^29.7.0"
+ "@types/jest": "^29.5.14"
+ jest: "^29.7.0"
+ tools:
+ "@tsconfig/node22": "^22.0.5"
+ "@types/aws-lambda": "^8.10.161"
+ "@types/node": "^24.12.0"
+ cross-env: "^10.1.0"
+ esbuild: "^0.25.11"
+ rimraf: "^6.1.3"
+ ts-node: "^10.9.2"
+ tsx: "^4.20.6"
+ typescript: "^5.9.3"
+
+engineStrict: true
+minimumReleaseAge: 2880
+nodeOptions: "${NODE_OPTIONS:- } --experimental-vm-modules"
+
+overrides:
+ '@auth/core@>=0.1.0 <0.41.3': ^0.41.3
+ esbuild@>=0.27.3 <0.28.1: '>=0.28.1'
+ minimatch@>=10.0.0 <10.2.3: '>=10.2.3'
+ prismjs@<1.30.0: '>=1.30.0'
+ uuid@<11.1.1: '>=11.1.1'
+ yaml@>=2.0.0 <2.8.3: '>=2.8.3'
+resolutionMode: time-based
+trustPolicy: no-downgrade
+trustPolicyExclude:
+ - "semver@5.7.2"
+ - "semver@6.3.1"
diff --git a/scripts/tests/lint.sh b/scripts/tests/lint.sh
new file mode 100755
index 00000000..6205f783
--- /dev/null
+++ b/scripts/tests/lint.sh
@@ -0,0 +1,8 @@
+#!/bin/bash
+
+set -euo pipefail
+
+cd "$(git rev-parse --show-toplevel)"
+
+pnpm install --frozen-lockfile
+pnpm run lint
diff --git a/scripts/tests/test.mk b/scripts/tests/test.mk
index a1f16907..e4831657 100644
--- a/scripts/tests/test.mk
+++ b/scripts/tests/test.mk
@@ -14,6 +14,9 @@ test-unit: # Run your unit tests from scripts/test/unit @Testing
test-lint: # Lint your code from scripts/test/lint @Testing
make _test name="lint"
+test-typecheck: # Typecheck your code from scripts/test/typecheck @Testing
+ make _test name="typecheck"
+
test-coverage: # Evaluate code coverage from scripts/test/coverage @Testing
make _test name="coverage"
diff --git a/scripts/tests/typecheck.sh b/scripts/tests/typecheck.sh
new file mode 100755
index 00000000..66cae045
--- /dev/null
+++ b/scripts/tests/typecheck.sh
@@ -0,0 +1,8 @@
+#!/bin/bash
+
+set -euo pipefail
+
+cd "$(git rev-parse --show-toplevel)"
+
+pnpm install --frozen-lockfile
+pnpm run typecheck
diff --git a/scripts/tests/unit.sh b/scripts/tests/unit.sh
index c589be5b..d6cb4092 100755
--- a/scripts/tests/unit.sh
+++ b/scripts/tests/unit.sh
@@ -4,17 +4,107 @@ set -euo pipefail
cd "$(git rev-parse --show-toplevel)"
-# This file is for you! Edit it to call your unit test suite. Note that the same
-# file will be called if you run it locally as if you run it on CI.
-
-# Replace the following line with something like:
-#
-# rails test:unit
-# python manage.py test
-# npm run test
-#
-# or whatever is appropriate to your project. You should *only* run your fast
-# tests from here. If you want to run other test suites, see the predefined
-# tasks in scripts/test.mk.
-
-echo "Unit tests are not yet implemented. See scripts/tests/unit.sh for more."
+_timer_labels=()
+_timer_seconds=()
+
+run_timed() {
+ local label="$1"
+ shift
+ local start
+ start=$(date +%s)
+ local rc=0
+ "$@" || rc=$?
+ local end
+ end=$(date +%s)
+ _timer_labels+=("$label")
+ _timer_seconds+=("$((end - start))")
+ return "$rc"
+}
+
+print_timing_summary() {
+ echo ""
+ echo "===== Timing Summary ====="
+ local total=0
+ for i in "${!_timer_labels[@]}"; do
+ printf " %-55s %4ds\n" "${_timer_labels[$i]}" "${_timer_seconds[$i]}"
+ total=$((total + _timer_seconds[$i]))
+ done
+ echo " ---------------------------------------------------------"
+ printf " %-55s %4ds\n" "TOTAL" "$total"
+ echo "=========================="
+}
+
+trap print_timing_summary EXIT
+
+run_timed "Node unit tests (parallel)" pnpm run test:unit || jest_exit=$?
+
+# ---- Phase 1: install all Python dev dependencies (sequential) ----
+# Discover Python projects dynamically: any directory under src/, utils/, or
+# lambdas/ whose Makefile defines both an `install-dev` target (Python deps)
+# and a `coverage` target (pytest). This avoids maintaining a hardcoded list.
+echo "Installing Python dev dependencies..."
+_python_projects=()
+while IFS= read -r _proj; do
+ _python_projects+=("$_proj")
+done < <(
+ grep -rl "^install-dev:" src/ utils/ lambdas/ --include="Makefile" 2>/dev/null \
+ | xargs grep -l "^coverage:" \
+ | xargs -I{} dirname {} \
+ | sort
+)
+for proj in "${_python_projects[@]}"; do
+ run_timed "${proj}: install-dev" make -C "$proj" install-dev
+done
+
+# ---- Phase 2: run all coverage steps in parallel ----
+echo "Running Python coverage in parallel..."
+
+_py_pids=()
+_py_labels=()
+_py_logs=()
+_py_exits=()
+
+for proj in "${_python_projects[@]}"; do
+ label="${proj}: coverage"
+ logfile=$(mktemp)
+ make -C "$proj" coverage >"$logfile" 2>&1 &
+ _py_pids+=("$!")
+ _py_labels+=("$label")
+ _py_logs+=("$logfile")
+done
+
+# Collect results in launch order (preserves deterministic output)
+_py_start=$(date +%s)
+for i in "${!_py_pids[@]}"; do
+ if wait "${_py_pids[$i]}"; then
+ _py_exit=0
+ else
+ _py_exit=$?
+ fi
+ _py_exits+=("${_py_exit}")
+ echo ""
+ echo "--- ${_py_labels[$i]} ---"
+ cat "${_py_logs[$i]}"
+ rm -f "${_py_logs[$i]}"
+done
+_py_end=$(date +%s)
+_timer_labels+=("Python unit tests (parallel)")
+_timer_seconds+=("$((_py_end - _py_start))")
+
+# Propagate any Jest failure now that all other test suites have completed
+if [ "${jest_exit:-0}" -ne 0 ]; then
+ echo "Jest tests failed with exit code ${jest_exit}"
+ exit "${jest_exit}"
+fi
+
+# Propagate any Python coverage failure
+for i in "${!_py_exits[@]}"; do
+ if [ "${_py_exits[$i]}" -ne 0 ]; then
+ echo "${_py_labels[$i]} failed with exit code ${_py_exits[$i]}"
+ exit "${_py_exits[$i]}"
+ fi
+done
+
+# merge coverage reports
+mkdir -p .reports
+TMPDIR="./.reports" ./node_modules/.bin/lcov-result-merger "**/.reports/unit/coverage/lcov.info" ".reports/lcov.info" --ignore "node_modules" --prepend-source-files --prepend-path-fix "../../.."
diff --git a/src/lambdas/apim-access-token-refresher/README.md b/src/lambdas/apim-access-token-refresher/README.md
new file mode 100644
index 00000000..3b2f71e6
--- /dev/null
+++ b/src/lambdas/apim-access-token-refresher/README.md
@@ -0,0 +1,49 @@
+# Refresh APIM Access Tokens Lambda
+
+We store an access token for the APIM applications in SSM Parameter Store for shared access.
+
+Access Tokens for APIM applications have a lifespan of 10 minutes. Every 9 minutes, this Lambda function will be invoked by a EventBridge scheduled event to refresh the access token stored in SSM.
+
+The integration follows the instructions [found here](https://digital.nhs.uk/developer/guides-and-documentation/security-and-authorisation/application-restricted-restful-apis-signed-jwt-authentication#step-1-create-an-application).
+
+It will do the following:
+
+- Read all of the private keys uploaded to SSM Parameter Store with the path prefix `/comms/{ENVIRONMENT}/pds/keys` (these are created by the KeyGen Lambda).
+- It will figure out the youngest key using the key name, and use this key to sign a JWT.
+- This JWT is sent to the PDS authorization server and exchanged for an access token with a lifespan of 10 minutes.
+- This token is uploaded to SSM Parameter store with the name `/comms/{ENVIRONMENT}/apim/access-token`.
+
+## Setup instructions
+
+Each environment will need an application creating through the [NHS Developer Portal](https://onboarding.prod.api.platform.nhs.uk/MyApplications) (including dynamic environments).
+
+Once you have an environment created, you will need to do the following:
+
+- Enable the relevant API for your application (e.g. PDS FHIR API)
+- Store your application API key in SSM Parameter store (`/comms//apim/api-key`)
+- Configure to use your environment's JWKS file (ideally using the public endpoint, don't upload a static file).
+
+## Access token data
+
+The access token data stored in SSM has the following structure:
+
+```json
+{
+ "access_token": "the access token",
+ "expires_at": 0, // "unix timestamp (in seconds) at which the access token will expire",
+ "token_type": "Bearer"
+}
+```
+
+## CLI
+
+The application is also exposed via a CLI which is useful for local testing. The entrypoint for this is at `src/apis/cli.ts`.
+
+Ensure you have the following environment variables set:
+
+- `APIM_AUTH_TOKEN_URL`
+- `APIM_ACCESS_TOKEN_SSM_PARAMETER_NAME`
+- `APIM_API_KEY_SSM_PARAMETER_NAME`
+- `APIM_PRIVATE_KEY_SSM_PARAMETER_NAME`
+
+Then run `pnpm run cli`.
diff --git a/src/lambdas/apim-access-token-refresher/jest.config.ts b/src/lambdas/apim-access-token-refresher/jest.config.ts
new file mode 100644
index 00000000..1a1510f9
--- /dev/null
+++ b/src/lambdas/apim-access-token-refresher/jest.config.ts
@@ -0,0 +1,15 @@
+import { baseJestConfig } from '../../../jest.config.base';
+
+const config = baseJestConfig;
+
+config.coveragePathIgnorePatterns = ['/__tests__/', 'cli.ts'];
+config.coverageThreshold = {
+ global: {
+ branches: 100,
+ functions: 100,
+ lines: 90,
+ statements: -10,
+ },
+};
+
+export default config;
diff --git a/src/lambdas/apim-access-token-refresher/package.json b/src/lambdas/apim-access-token-refresher/package.json
new file mode 100644
index 00000000..bb4df5fd
--- /dev/null
+++ b/src/lambdas/apim-access-token-refresher/package.json
@@ -0,0 +1,33 @@
+{
+ "dependencies": {
+ "@aws-sdk/client-ssm": "^3.840.0",
+ "axios": "^1.18.1",
+ "esbuild": "^0.25.9",
+ "jsonwebtoken": "^9.0.2",
+ "qs": "^6.14.1",
+ "utils": "workspace:*"
+ },
+ "devDependencies": {
+ "@tsconfig/node22": "^22.0.2",
+ "@types/jest": "^29.5.14",
+ "@types/jsonwebtoken": "^9.0.10",
+ "@types/node": "^24.0.10",
+ "@types/qs": "^6.14.0",
+ "jest": "^29.7.0",
+ "jest-mock-extended": "^3.0.7",
+ "typescript": "^5.8.2"
+ },
+ "exports": {
+ ".": "./src/index.ts"
+ },
+ "name": "apim-access-token-refresher",
+ "private": true,
+ "scripts": {
+ "lambda-build": "rm -rf dist && npx esbuild --bundle --minify --sourcemap --target=es2020 --platform=node --loader:.node=file --entry-names=[name] --outdir=dist src/index.ts",
+ "lint": "eslint .",
+ "lint:fix": "eslint . --fix",
+ "test:unit": "jest",
+ "typecheck": "tsc --noEmit"
+ },
+ "version": "0.0.1"
+}
diff --git a/src/lambdas/apim-access-token-refresher/src/__tests__/app/refresh-apim-access-token.test.ts b/src/lambdas/apim-access-token-refresher/src/__tests__/app/refresh-apim-access-token.test.ts
new file mode 100644
index 00000000..57a58c0a
--- /dev/null
+++ b/src/lambdas/apim-access-token-refresher/src/__tests__/app/refresh-apim-access-token.test.ts
@@ -0,0 +1,92 @@
+import { mockDeep } from 'jest-mock-extended';
+import { Dependencies, createApplication } from 'app/refresh-apim-access-token';
+
+function setup() {
+ const mocks = mockDeep({
+ nhsAuthClient: {
+ tokenEndpoint: 'fake_nhs_auth_token_endpoint',
+ getAccessToken: jest.fn(async () => ({
+ access_token: 'fake_access_token',
+ expires_at: 1_674_778_100,
+ token_type: 'fake_token_type',
+ })),
+ },
+ keystore: {
+ getPrivateKey: jest.fn(async () => ({
+ kid: 'fake_kid',
+ key: 'fake_private_key',
+ })),
+ getAPIKey: jest.fn().mockResolvedValue('fake_pds_api_key'),
+ },
+ tokenGenerator: {
+ generate: jest.fn(() => 'fake_jwt'),
+ },
+ });
+
+ const refreshApimAccessToken = createApplication(mocks);
+
+ return {
+ refreshApimAccessToken,
+ mocks,
+ };
+}
+
+describe('refreshApimAccessToken', () => {
+ it('gets the private key and api key from the keystore', async () => {
+ const { mocks, refreshApimAccessToken } = setup();
+
+ await refreshApimAccessToken();
+
+ expect(mocks.keystore.getPrivateKey).toHaveBeenCalled();
+ expect(mocks.keystore.getAPIKey).toHaveBeenCalled();
+ });
+
+ it('generates a JWT with the correct claims using the private key', async () => {
+ const { mocks, refreshApimAccessToken } = setup();
+
+ await refreshApimAccessToken();
+
+ expect(mocks.tokenGenerator.generate.mock.lastCall).toMatchInlineSnapshot(`
+ [
+ {
+ "kid": "fake_kid",
+ },
+ {
+ "aud": "fake_nhs_auth_token_endpoint",
+ "iss": "fake_pds_api_key",
+ "sub": "fake_pds_api_key",
+ },
+ "fake_private_key",
+ ]
+ `);
+ });
+
+ it('exchanges the JWT for an access token from the nhs auth server', async () => {
+ const { mocks, refreshApimAccessToken } = setup();
+
+ await refreshApimAccessToken();
+
+ expect(mocks.nhsAuthClient.getAccessToken.mock.lastCall)
+ .toMatchInlineSnapshot(`
+ [
+ "fake_jwt",
+ ]
+ `);
+ });
+
+ it('persists the access token', async () => {
+ const { mocks, refreshApimAccessToken } = setup();
+
+ await refreshApimAccessToken();
+
+ expect(mocks.keystore.putAccessToken.mock.lastCall).toMatchInlineSnapshot(`
+ [
+ {
+ "access_token": "fake_access_token",
+ "expires_at": 1674778100,
+ "token_type": "fake_token_type",
+ },
+ ]
+ `);
+ });
+});
diff --git a/src/lambdas/apim-access-token-refresher/src/__tests__/index.test.ts b/src/lambdas/apim-access-token-refresher/src/__tests__/index.test.ts
new file mode 100644
index 00000000..da532673
--- /dev/null
+++ b/src/lambdas/apim-access-token-refresher/src/__tests__/index.test.ts
@@ -0,0 +1,16 @@
+import * as indexModule from 'index';
+
+jest.mock('infra/container', () => ({
+ createContainer: jest.fn(() => ({})),
+}));
+
+jest.mock('app/refresh-apim-access-token', () => ({
+ createApplication: jest.fn(() => jest.fn(() => ({}))),
+}));
+
+describe('index', () => {
+ it('should export handler', async () => {
+ expect(indexModule.handler).toBeDefined();
+ await expect(indexModule.handler()).resolves.not.toThrow();
+ });
+});
diff --git a/src/lambdas/apim-access-token-refresher/src/__tests__/infra/config.test.ts b/src/lambdas/apim-access-token-refresher/src/__tests__/infra/config.test.ts
new file mode 100644
index 00000000..2902c80f
--- /dev/null
+++ b/src/lambdas/apim-access-token-refresher/src/__tests__/infra/config.test.ts
@@ -0,0 +1,15 @@
+import { loadConfig } from 'infra/config';
+
+jest.mock('utils', () => ({
+ defaultConfigReader: {
+ getValue: jest.fn(),
+ getInt: jest.fn(),
+ },
+}));
+
+describe('config', () => {
+ it('should load config', () => {
+ const config = loadConfig();
+ expect(config).toBeDefined();
+ });
+});
diff --git a/src/lambdas/apim-access-token-refresher/src/__tests__/infra/container.test.ts b/src/lambdas/apim-access-token-refresher/src/__tests__/infra/container.test.ts
new file mode 100644
index 00000000..6b3d4097
--- /dev/null
+++ b/src/lambdas/apim-access-token-refresher/src/__tests__/infra/container.test.ts
@@ -0,0 +1,23 @@
+import { createContainer } from 'infra/container';
+
+jest.mock('infra/config', () => ({
+ loadConfig: jest.fn(() => ({
+ apimPrivateKeySsmParameterName: 'test-parameter-name',
+ })),
+}));
+
+jest.mock('jsonwebtoken', () => ({
+ sign: jest.fn(() => ({})),
+}));
+
+jest.mock('utils', () => ({
+ privateKeyFetcher: jest.fn(() => ({ getPrivateKey: jest.fn() })),
+ logger: {},
+}));
+
+describe('container', () => {
+ it('should create container', () => {
+ const container = createContainer();
+ expect(container).toBeDefined();
+ });
+});
diff --git a/src/lambdas/apim-access-token-refresher/src/__tests__/infra/jwt-generator.test.ts b/src/lambdas/apim-access-token-refresher/src/__tests__/infra/jwt-generator.test.ts
new file mode 100644
index 00000000..a8bfcc33
--- /dev/null
+++ b/src/lambdas/apim-access-token-refresher/src/__tests__/infra/jwt-generator.test.ts
@@ -0,0 +1,83 @@
+import { logger } from 'utils';
+import { JWTGenerator } from 'infra/jwt-generator';
+
+function setup() {
+ const uuid = jest.fn(() => 'totally-random-string');
+ const token = 'fake-jwt';
+ const signer = jest.fn(() => token);
+
+ const generator = new JWTGenerator(signer, uuid, logger);
+
+ const mocks = { uuid, signer };
+ const data = { token };
+
+ return { generator, mocks, data };
+}
+
+describe('JWTGenerator', () => {
+ beforeEach(() => {
+ jest.useFakeTimers();
+ jest.setSystemTime(new Date('2023-01-27'));
+ });
+
+ afterEach(() => {
+ jest.useRealTimers();
+ });
+
+ it('generates a signed jwt with the correct parameters', () => {
+ const { data, generator, mocks } = setup();
+
+ const result = generator.generate(
+ { kid: 'fake_kid' },
+ { aud: 'fake_aud', iss: 'fake_iss', sub: 'fake_sub' },
+ 'fake_key',
+ );
+
+ expect(mocks.signer).toHaveBeenCalledTimes(1);
+ expect(mocks.signer.mock.lastCall).toMatchInlineSnapshot(`
+ [
+ {
+ "aud": "fake_aud",
+ "exp": 1674777900,
+ "iss": "fake_iss",
+ "jti": "totally-random-string",
+ "sub": "fake_sub",
+ },
+ "fake_key",
+ {
+ "header": {
+ "alg": "RS512",
+ "kid": "fake_kid",
+ "typ": "JWT",
+ },
+ },
+ ]
+ `);
+
+ expect(result).toBe(data.token);
+ });
+
+ it('throws its own error if signing the token fails', () => {
+ expect.hasAssertions();
+
+ const { generator, mocks } = setup();
+
+ mocks.signer.mockImplementationOnce(() => {
+ throw new Error('signing error');
+ });
+
+ let caught: unknown;
+ try {
+ generator.generate(
+ { kid: 'fake_kid' },
+ { aud: 'fake_aud', iss: 'fake_iss', sub: 'fake_sub' },
+ 'fake_key',
+ );
+ } catch (error) {
+ caught = error;
+ }
+ expect(caught).toMatchInlineSnapshot(
+ `[Error: Unable to generate signed JWT.]`,
+ );
+ });
+});
diff --git a/src/lambdas/apim-access-token-refresher/src/__tests__/infra/nhs-auth-client.test.ts b/src/lambdas/apim-access-token-refresher/src/__tests__/infra/nhs-auth-client.test.ts
new file mode 100644
index 00000000..5048883d
--- /dev/null
+++ b/src/lambdas/apim-access-token-refresher/src/__tests__/infra/nhs-auth-client.test.ts
@@ -0,0 +1,82 @@
+import { logger } from 'utils';
+import type { AxiosInstance } from 'axios';
+import { mockDeep } from 'jest-mock-extended';
+import { NHSAuthClient } from 'infra/nhs-auth-client';
+
+function setup() {
+ const axios = mockDeep();
+
+ axios.post.mockResolvedValue({
+ data: {
+ access_token: 'fake_token',
+ expires_in: '600',
+ token_type: 'fake_token_type',
+ issued_at: '1674777500000',
+ },
+ });
+ const config = {
+ apimAuthTokenUrl: 'fake_token_url',
+ };
+
+ const mocks = { axios, config };
+
+ const client = new NHSAuthClient(config, axios, logger);
+
+ return { client, mocks };
+}
+
+describe('NHSAuthClient', () => {
+ describe('getAccessToken', () => {
+ it('makes a post request to the token endpoint', async () => {
+ const { client, mocks } = setup();
+
+ await client.getAccessToken('fake_jwt');
+
+ expect(mocks.axios.post.mock.calls).toMatchInlineSnapshot(`
+ [
+ [
+ "fake_token_url",
+ "grant_type=client_credentials&client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer&client_assertion=fake_jwt",
+ {
+ "headers": {
+ "content-type": "application/x-www-form-urlencoded",
+ },
+ },
+ ],
+ ]
+ `);
+ });
+
+ it('returns a formatted token with an expiry timestamp calculated from the issued_at and expires_in values', async () => {
+ const { client } = setup();
+
+ const result = await client.getAccessToken('fake_jwt');
+
+ expect(result).toMatchInlineSnapshot(`
+ {
+ "access_token": "fake_token",
+ "expires_at": 1674778100,
+ "token_type": "fake_token_type",
+ }
+ `);
+ });
+
+ it('throws an error if there is an issue requesting the token from the auth server', async () => {
+ expect.hasAssertions();
+
+ const { client, mocks } = setup();
+
+ mocks.axios.post.mockRejectedValueOnce(new Error('AxiosError'));
+
+ let caught: unknown;
+ try {
+ await client.getAccessToken('fake_jwt');
+ } catch (error) {
+ caught = error;
+ }
+ expect(caught).toMatchInlineSnapshot(
+ `[Error: Unable to obtain access token from NHS Auth Server.]`,
+ );
+ });
+ });
+});
diff --git a/src/lambdas/apim-access-token-refresher/src/__tests__/infra/ssm-keystore.test.ts b/src/lambdas/apim-access-token-refresher/src/__tests__/infra/ssm-keystore.test.ts
new file mode 100644
index 00000000..1fb623ac
--- /dev/null
+++ b/src/lambdas/apim-access-token-refresher/src/__tests__/infra/ssm-keystore.test.ts
@@ -0,0 +1,226 @@
+import { SSMClient } from '@aws-sdk/client-ssm';
+import { logger } from 'utils';
+import { mockDeep } from 'jest-mock-extended';
+import { Config, SSMKeyStore } from 'infra/ssm-keystore';
+
+function setup() {
+ const ssm = mockDeep();
+ const key = {
+ key: 'fake_key',
+ kid: 'fake_kid',
+ };
+ const getPrivateKey = jest.fn().mockResolvedValue(key);
+
+ const config: Config = {
+ apimAccessTokenSsmParameterName: 'fake_access_token_parameter_name',
+ apimApiKeySsmParameterName: 'fake_api_key_parameter_name',
+ };
+
+ // @ts-ignore
+ const keystore = new SSMKeyStore(ssm, config, logger, getPrivateKey);
+
+ const mocks = { ssm, getPrivateKey };
+
+ const data = { config, key };
+
+ return { keystore, mocks, data };
+}
+
+describe('SSMKeyStore', () => {
+ describe('getPrivateKey', () => {
+ it('invokes the given getPrivateKey callback and returns the resulting key', async () => {
+ const { data, keystore, mocks } = setup();
+
+ const result = await keystore.getPrivateKey();
+
+ expect(mocks.getPrivateKey).toHaveBeenCalled();
+
+ expect(result).toBe(data.key);
+ });
+
+ it('throws its own error if the given getPrivateKey callback errors', async () => {
+ expect.hasAssertions();
+
+ const { keystore, mocks } = setup();
+
+ mocks.getPrivateKey.mockRejectedValueOnce(
+ new Error('GetPrivateKeyError'),
+ );
+
+ let caught: unknown;
+ try {
+ await keystore.getPrivateKey();
+ } catch (error) {
+ caught = error;
+ }
+ expect(caught).toMatchInlineSnapshot(
+ `[Error: Error fetching private key.]`,
+ );
+ });
+ });
+
+ describe('putAccessToken', () => {
+ it('puts the access token into SSM Parameter Store', async () => {
+ const { keystore, mocks } = setup();
+
+ await keystore.putAccessToken({
+ access_token: 'fake_access_token',
+ expires_at: 1_674_778_100,
+ token_type: 'fake_token_type',
+ });
+
+ expect(mocks.ssm.send.mock.calls).toMatchInlineSnapshot(`
+ [
+ [
+ PutParameterCommand {
+ "deserialize": null,
+ "input": {
+ "Name": "fake_access_token_parameter_name",
+ "Overwrite": true,
+ "Value": "{"access_token":"fake_access_token","expires_at":1674778100,"token_type":"fake_token_type"}",
+ },
+ "middlewareStack": {
+ "add": [Function],
+ "addRelativeTo": [Function],
+ "applyToStack": [Function],
+ "clone": [Function],
+ "concat": [Function],
+ "identify": [Function],
+ "identifyOnResolve": [Function],
+ "remove": [Function],
+ "removeByTag": [Function],
+ "resolve": [Function],
+ "use": [Function],
+ },
+ "schema": [
+ 9,
+ "com.amazonaws.ssm",
+ "PutParameter",
+ 0,
+ [Function],
+ [Function],
+ ],
+ "serialize": null,
+ },
+ ],
+ ]
+ `);
+ });
+
+ it('throws its own error if there is an error from SSM Parameter Store', async () => {
+ expect.hasAssertions();
+
+ const { keystore, mocks } = setup();
+
+ mocks.ssm.send.mockImplementationOnce(() => {
+ throw new Error('MockAWSError');
+ });
+
+ let caught: any;
+ try {
+ await keystore.putAccessToken({
+ access_token: 'fake_access_token',
+ expires_at: 1_674_778_100,
+ token_type: 'fake_token_type',
+ });
+ } catch (error) {
+ caught = error;
+ }
+ expect(caught).toMatchInlineSnapshot(
+ `[Error: Unable to store Access Token in SSM Parameter Store.]`,
+ );
+ });
+ });
+
+ describe('getAPIKey', () => {
+ it('requests the API key from SSM Parameter Store using the configured name and returns the parameter value', async () => {
+ const { keystore, mocks } = setup();
+
+ const expected = 'mock_api_key';
+
+ // @ts-ignore
+ mocks.ssm.send.mockResolvedValueOnce({
+ Parameter: { Value: expected },
+ });
+
+ const result = await keystore.getAPIKey();
+
+ expect(mocks.ssm.send.mock.calls).toMatchInlineSnapshot(`
+ [
+ [
+ GetParameterCommand {
+ "deserialize": null,
+ "input": {
+ "Name": "fake_api_key_parameter_name",
+ "WithDecryption": true,
+ },
+ "middlewareStack": {
+ "add": [Function],
+ "addRelativeTo": [Function],
+ "applyToStack": [Function],
+ "clone": [Function],
+ "concat": [Function],
+ "identify": [Function],
+ "identifyOnResolve": [Function],
+ "remove": [Function],
+ "removeByTag": [Function],
+ "resolve": [Function],
+ "use": [Function],
+ },
+ "schema": [
+ 9,
+ "com.amazonaws.ssm",
+ "GetParameter",
+ 0,
+ [Function],
+ [Function],
+ ],
+ "serialize": null,
+ },
+ ],
+ ]
+ `);
+ expect(result).toEqual(expected);
+ });
+
+ it('errors if there is no parameter attribute returned from SSM Parameter Store', async () => {
+ expect.hasAssertions();
+
+ const { keystore, mocks } = setup();
+
+ // @ts-ignore
+ mocks.ssm.send.mockResolvedValueOnce({});
+
+ let caught: any;
+ try {
+ await keystore.getAPIKey();
+ } catch (error) {
+ caught = error;
+ }
+ expect(caught).toMatchInlineSnapshot(
+ `[Error: Unable to retrieve APIM API Key from SSM Parameter Store.]`,
+ );
+ });
+
+ it('errors if there is no parameter value returned from SSM Parameter Store', async () => {
+ expect.hasAssertions();
+
+ const { keystore, mocks } = setup();
+
+ // @ts-ignore
+ mocks.ssm.send.mockResolvedValueOnce({
+ Parameter: { Value: '' },
+ });
+
+ let caught: unknown;
+ try {
+ await keystore.getAPIKey();
+ } catch (error) {
+ caught = error;
+ }
+ expect(caught).toMatchInlineSnapshot(
+ `[Error: Unable to retrieve APIM API Key from SSM Parameter Store.]`,
+ );
+ });
+ });
+});
diff --git a/src/lambdas/apim-access-token-refresher/src/apis/cli.ts b/src/lambdas/apim-access-token-refresher/src/apis/cli.ts
new file mode 100644
index 00000000..57ffe661
--- /dev/null
+++ b/src/lambdas/apim-access-token-refresher/src/apis/cli.ts
@@ -0,0 +1,17 @@
+import { createContainer } from 'infra/container';
+import {
+ type Dependencies as ApplicationDependencies,
+ createApplication,
+} from 'app/refresh-apim-access-token';
+
+type CLIDependencies = Record;
+
+type Dependencies = ApplicationDependencies & CLIDependencies;
+
+(function main(d: Dependencies) {
+ const refreshApimAccessToken = createApplication(d);
+
+ refreshApimAccessToken().catch((error: unknown) => {
+ console.log(error); // eslint-disable-line no-console
+ });
+})(createContainer());
diff --git a/src/lambdas/apim-access-token-refresher/src/app/refresh-apim-access-token.ts b/src/lambdas/apim-access-token-refresher/src/app/refresh-apim-access-token.ts
new file mode 100644
index 00000000..1a20e024
--- /dev/null
+++ b/src/lambdas/apim-access-token-refresher/src/app/refresh-apim-access-token.ts
@@ -0,0 +1,74 @@
+import type { ApimAccessToken, Logger } from 'utils';
+
+export interface IKeyStore {
+ getPrivateKey(): Promise<{ key: string; kid: string }>;
+ getAPIKey(): Promise;
+ putAccessToken(token: ApimAccessToken): Promise;
+}
+
+interface ITokenGenerator {
+ generate(
+ header: { kid: string },
+ payload: { iss: string; sub: string; aud: string },
+ signingKey: string,
+ ): string;
+}
+
+interface INHSAuthClient {
+ tokenEndpoint: string;
+ getAccessToken(jwt: string): Promise;
+}
+
+export type Dependencies = {
+ keystore: IKeyStore;
+ logger: Logger;
+ nhsAuthClient: INHSAuthClient;
+ tokenGenerator: ITokenGenerator;
+};
+
+export function createApplication({
+ keystore,
+ logger,
+ nhsAuthClient,
+ tokenGenerator,
+}: Dependencies) {
+ return async function refreshApimAccessToken() {
+ logger.info({
+ description: 'Fetching Private Key and API key from Keystore.',
+ });
+
+ const apiKey = await keystore.getAPIKey();
+ const { key, kid } = await keystore.getPrivateKey();
+
+ logger.info({
+ description: 'Fetched Private Key and API key from Keystore.',
+ kid,
+ });
+
+ logger.info({ description: 'Generating signed JWT.' });
+
+ const jwt = tokenGenerator.generate(
+ { kid },
+ {
+ iss: apiKey,
+ sub: apiKey,
+ aud: nhsAuthClient.tokenEndpoint,
+ },
+ key,
+ );
+
+ logger.info({ description: 'Generated signed JWT.' });
+
+ logger.info({ description: 'Exchanging signed JWT for new Access Token.' });
+
+ const accessToken = await nhsAuthClient.getAccessToken(jwt);
+
+ logger.info({ description: 'Obtained new Access Token.' });
+
+ logger.info({ description: 'Storing Access Token.' });
+
+ await keystore.putAccessToken(accessToken);
+
+ logger.info({ description: 'Access Token successfully stored.' });
+ };
+}
diff --git a/src/lambdas/apim-access-token-refresher/src/index.ts b/src/lambdas/apim-access-token-refresher/src/index.ts
new file mode 100644
index 00000000..690363bb
--- /dev/null
+++ b/src/lambdas/apim-access-token-refresher/src/index.ts
@@ -0,0 +1,21 @@
+// This is a Lambda entrypoint file.
+
+import { createContainer } from 'infra/container';
+import {
+ type Dependencies as ApplicationDependencies,
+ createApplication,
+} from 'app/refresh-apim-access-token';
+
+type LambdaAPIDependencies = Record;
+
+type Dependencies = ApplicationDependencies & LambdaAPIDependencies;
+
+function createLambdaHandler(d: Dependencies) {
+ const refreshApimAccessToken = createApplication(d);
+
+ return async function lambdaHandler() {
+ await refreshApimAccessToken();
+ };
+}
+
+export const handler = createLambdaHandler(createContainer());
diff --git a/src/lambdas/apim-access-token-refresher/src/infra/config.ts b/src/lambdas/apim-access-token-refresher/src/infra/config.ts
new file mode 100644
index 00000000..e14a40aa
--- /dev/null
+++ b/src/lambdas/apim-access-token-refresher/src/infra/config.ts
@@ -0,0 +1,16 @@
+import { defaultConfigReader } from 'utils';
+
+export function loadConfig() {
+ return {
+ apimAuthTokenUrl: defaultConfigReader.getValue('APIM_AUTH_TOKEN_URL'),
+ apimAccessTokenSsmParameterName: defaultConfigReader.getValue(
+ 'APIM_ACCESS_TOKEN_SSM_PARAMETER_NAME',
+ ),
+ apimApiKeySsmParameterName: defaultConfigReader.getValue(
+ 'APIM_API_KEY_SSM_PARAMETER_NAME',
+ ),
+ apimPrivateKeySsmParameterName: defaultConfigReader.getValue(
+ 'APIM_PRIVATE_KEY_SSM_PARAMETER_NAME',
+ ),
+ };
+}
diff --git a/src/lambdas/apim-access-token-refresher/src/infra/container.ts b/src/lambdas/apim-access-token-refresher/src/infra/container.ts
new file mode 100644
index 00000000..cd64f955
--- /dev/null
+++ b/src/lambdas/apim-access-token-refresher/src/infra/container.ts
@@ -0,0 +1,24 @@
+import { SSMClient } from '@aws-sdk/client-ssm';
+import { logger, privateKeyFetcher } from 'utils';
+import axios from 'axios';
+import { randomUUID } from 'node:crypto';
+import { sign } from 'jsonwebtoken';
+import { SSMKeyStore } from 'infra/ssm-keystore';
+import { NHSAuthClient } from 'infra/nhs-auth-client';
+import { JWTGenerator } from 'infra/jwt-generator';
+import { loadConfig } from 'infra/config';
+
+export function createContainer() {
+ const config = loadConfig();
+ const { getPrivateKey } = privateKeyFetcher(
+ config.apimPrivateKeySsmParameterName,
+ );
+
+ return {
+ config,
+ keystore: new SSMKeyStore(new SSMClient({}), config, logger, getPrivateKey),
+ logger,
+ nhsAuthClient: new NHSAuthClient(config, axios, logger),
+ tokenGenerator: new JWTGenerator(sign, randomUUID, logger),
+ };
+}
diff --git a/src/lambdas/apim-access-token-refresher/src/infra/jwt-generator.ts b/src/lambdas/apim-access-token-refresher/src/infra/jwt-generator.ts
new file mode 100644
index 00000000..3cb5fe0c
--- /dev/null
+++ b/src/lambdas/apim-access-token-refresher/src/infra/jwt-generator.ts
@@ -0,0 +1,48 @@
+import type { Logger } from 'utils';
+import { JwtHeader, JwtPayload } from 'jsonwebtoken';
+
+type StringGenerator = () => string;
+type Signer = (
+ payload: JwtPayload,
+ key: string,
+ options: { header: JwtHeader },
+) => string;
+
+export class JWTGenerator {
+ constructor(
+ private readonly _signer: Signer,
+ private readonly _uuid: StringGenerator,
+ private readonly _logger: Logger,
+ ) {}
+
+ generate(
+ { kid }: Pick,
+ { aud, iss, sub }: Pick,
+ key: string,
+ ): string {
+ try {
+ const header: JwtHeader = {
+ alg: 'RS512',
+ typ: 'JWT',
+ kid,
+ };
+
+ const payload: JwtPayload = {
+ exp: Math.floor(Date.now() / 1000) + 5 * 60,
+ jti: this._uuid(),
+ iss,
+ sub,
+ aud,
+ };
+
+ return this._signer(payload, key, { header });
+ } catch (error: unknown) {
+ this._logger.error({
+ description: 'Error generating signed JWT.',
+ err: error,
+ });
+
+ throw new Error('Unable to generate signed JWT.');
+ }
+ }
+}
diff --git a/src/lambdas/apim-access-token-refresher/src/infra/nhs-auth-client.ts b/src/lambdas/apim-access-token-refresher/src/infra/nhs-auth-client.ts
new file mode 100644
index 00000000..8ffc420b
--- /dev/null
+++ b/src/lambdas/apim-access-token-refresher/src/infra/nhs-auth-client.ts
@@ -0,0 +1,64 @@
+import type { ApimAccessToken, Logger } from 'utils';
+import type { AxiosInstance } from 'axios';
+import * as qs from 'qs';
+
+// * DOCS: https://digital.nhs.uk/developer/guides-and-documentation/security-and-authorisation/application-restricted-restful-apis-signed-jwt-authentication#step-5-get-an-access-token
+
+type RawAccessToken = {
+ access_token: string;
+ expires_in: string; // seconds until token expiry
+ token_type: string;
+ issued_at: string; // unix timestamp (milliseconds) at which the token was issued (UNDOCUMENTED)
+};
+
+type Config = {
+ apimAuthTokenUrl: string;
+};
+
+export class NHSAuthClient {
+ constructor(
+ private readonly _config: Config,
+ private readonly _http: AxiosInstance,
+ private readonly _logger: Logger,
+ ) {}
+
+ get tokenEndpoint(): string {
+ return this._config.apimAuthTokenUrl;
+ }
+
+ async getAccessToken(jwt: string): Promise {
+ const body = {
+ grant_type: 'client_credentials',
+ client_assertion_type:
+ 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
+ client_assertion: jwt,
+ };
+
+ try {
+ const result = await this._http.post(
+ this.tokenEndpoint,
+
+ qs.stringify(body),
+ {
+ headers: {
+ 'content-type': 'application/x-www-form-urlencoded',
+ },
+ },
+ );
+
+ return {
+ access_token: result.data.access_token,
+ expires_at:
+ Math.floor(Number.parseInt(result.data.issued_at, 10) / 1000) +
+ Number.parseInt(result.data.expires_in, 10),
+ token_type: result.data.token_type,
+ };
+ } catch (error: unknown) {
+ this._logger.error({
+ description: 'Error obtaining access token from NHS Auth Server.',
+ err: error,
+ });
+ throw new Error('Unable to obtain access token from NHS Auth Server.');
+ }
+ }
+}
diff --git a/src/lambdas/apim-access-token-refresher/src/infra/ssm-keystore.ts b/src/lambdas/apim-access-token-refresher/src/infra/ssm-keystore.ts
new file mode 100644
index 00000000..d68394c0
--- /dev/null
+++ b/src/lambdas/apim-access-token-refresher/src/infra/ssm-keystore.ts
@@ -0,0 +1,113 @@
+import {
+ GetParameterCommand,
+ GetParameterCommandOutput,
+ PutParameterCommand,
+ SSMClient,
+} from '@aws-sdk/client-ssm';
+import type { ApimAccessToken, Logger } from 'utils';
+
+export type Config = {
+ apimAccessTokenSsmParameterName: string;
+ apimApiKeySsmParameterName: string;
+};
+
+type Key = {
+ key: string;
+ kid: string;
+};
+
+type GetPrivateKeyFunction = () => Promise;
+
+export class SSMKeyStore {
+ constructor(
+ private readonly _client: SSMClient,
+ private readonly _config: Config,
+ private readonly _logger: Logger,
+ private readonly _getPrivateKeyFn: GetPrivateKeyFunction,
+ ) {}
+
+ async getPrivateKey(): Promise {
+ this._logger.info({
+ description: 'Fetching APIM Private Key from SSM Parameter Store.',
+ });
+ try {
+ const result = await this._getPrivateKeyFn();
+ this._logger.info({
+ description: 'Fetched APIM Private Key from SSM Parameter Store.',
+ });
+ return result;
+ } catch (error: unknown) {
+ this._logger.error({
+ description: 'Error fetching private key.',
+ err: error,
+ });
+ throw new Error('Error fetching private key.');
+ }
+ }
+
+ async getAPIKey(): Promise {
+ this._logger.info({
+ description: `Fetching APIM API Key from SSM Parameter Store.`,
+ });
+
+ let result: GetParameterCommandOutput;
+ try {
+ result = await this._client.send(
+ new GetParameterCommand({
+ Name: this._config.apimApiKeySsmParameterName,
+ WithDecryption: true,
+ }),
+ );
+ } catch (error: unknown) {
+ this._logger.error({
+ description: 'Error making SSM GetParameter request to AWS.',
+ err: error,
+ });
+
+ throw new Error(
+ 'Unable to retrieve APIM API Key from SSM Parameter Store.',
+ );
+ }
+
+ if (!result.Parameter?.Value) {
+ this._logger.error(
+ 'Response from SSM does not include a Parameter value.',
+ );
+ throw new Error(
+ 'Unable to retrieve APIM API Key from SSM Parameter Store.',
+ );
+ }
+
+ this._logger.info({
+ description: 'Fetched APIM API Key from SSM Parameter Store.',
+ });
+
+ return result.Parameter.Value;
+ }
+
+ async putAccessToken(accessToken: ApimAccessToken): Promise {
+ this._logger.info({
+ description: `Storing Access Token in SSM Parameter Store.`,
+ });
+
+ try {
+ await this._client.send(
+ new PutParameterCommand({
+ Name: this._config.apimAccessTokenSsmParameterName,
+ Value: JSON.stringify(accessToken),
+ Overwrite: true,
+ }),
+ );
+
+ this._logger.info({
+ description: `Stored Access Token in SSM Parameter Store.`,
+ });
+ } catch (error: unknown) {
+ this._logger.error({
+ description: 'Error making SSM PutParameter request to AWS.',
+ err: error,
+ });
+ throw new Error('Unable to store Access Token in SSM Parameter Store.');
+ }
+ }
+}
diff --git a/src/lambdas/apim-access-token-refresher/tsconfig.json b/src/lambdas/apim-access-token-refresher/tsconfig.json
new file mode 100644
index 00000000..de8ca2a7
--- /dev/null
+++ b/src/lambdas/apim-access-token-refresher/tsconfig.json
@@ -0,0 +1,14 @@
+{
+ "compilerOptions": {
+ "baseUrl": "./src/",
+ "isolatedModules": true
+ },
+ "exclude": [
+ "node_modules"
+ ],
+ "extends": "@tsconfig/node22/tsconfig.json",
+ "include": [
+ "src/**/*",
+ "./jest.config.ts"
+ ]
+}
diff --git a/src/lambdas/apim-key-generator/jest.config.ts b/src/lambdas/apim-key-generator/jest.config.ts
new file mode 100644
index 00000000..4a99cb66
--- /dev/null
+++ b/src/lambdas/apim-key-generator/jest.config.ts
@@ -0,0 +1,7 @@
+import { baseJestConfig } from '../../../jest.config.base';
+
+const config = baseJestConfig;
+
+config.coveragePathIgnorePatterns = ['/__tests__/', 'lambda.ts', '/config.ts'];
+
+export default config;
diff --git a/src/lambdas/apim-key-generator/package.json b/src/lambdas/apim-key-generator/package.json
new file mode 100644
index 00000000..deacdef5
--- /dev/null
+++ b/src/lambdas/apim-key-generator/package.json
@@ -0,0 +1,30 @@
+{
+ "dependencies": {
+ "date-fns": "^4.1.0",
+ "esbuild": "^0.25.9",
+ "jose": "^5.10.0",
+ "utils": "workspace:*"
+ },
+ "devDependencies": {
+ "@tsconfig/node22": "^22.0.2",
+ "@types/aws-lambda": "^8.10.148",
+ "@types/jest": "^29.5.14",
+ "@types/node": "^24.0.10",
+ "jest": "^29.7.0",
+ "jest-mock-extended": "^3.0.7",
+ "typescript": "^5.8.2"
+ },
+ "exports": {
+ ".": "./src/index.ts"
+ },
+ "name": "apim-key-generator",
+ "private": true,
+ "scripts": {
+ "lambda-build": "rm -rf dist && npx esbuild --bundle --minify --sourcemap --target=es2020 --platform=node --loader:.node=file --entry-names=[name] --outdir=dist src/lambda.ts",
+ "lint": "eslint .",
+ "lint:fix": "eslint . --fix",
+ "test:unit": "jest",
+ "typecheck": "tsc --noEmit"
+ },
+ "version": "0.0.1"
+}
diff --git a/src/lambdas/apim-key-generator/src/__tests__/refresh-keystores.test.ts b/src/lambdas/apim-key-generator/src/__tests__/refresh-keystores.test.ts
new file mode 100644
index 00000000..5a75dce5
--- /dev/null
+++ b/src/lambdas/apim-key-generator/src/__tests__/refresh-keystores.test.ts
@@ -0,0 +1,428 @@
+import {
+ Key,
+ createKeyStore,
+ deleteKey,
+ generateNewKey,
+ logger,
+ parameterStore,
+ uploadPublicKeystoreToS3,
+ validatePrivateKey,
+} from 'utils';
+import { cleanAndRefreshKeystores } from 'refresh-keystores';
+import { loadConfig } from 'config';
+
+jest.mock('utils', () => {
+ const originalModule = jest.requireActual('utils');
+
+ return {
+ ...originalModule,
+ parameterStore: {
+ getAllParameters: jest.fn(),
+ },
+ createKeyStore: jest.fn(),
+ deleteKey: jest.fn(),
+ generateNewKey: jest.fn(),
+ uploadPublicKeystoreToS3: jest.fn(),
+ validatePrivateKey: jest.fn(),
+ };
+});
+jest.mock('config');
+
+const setupMocks = (preExistingKeys?: string[]) => {
+ const mockKeyStore = {
+ add: jest.fn(),
+ all: jest.fn().mockReturnValue([{ toJSON: () => ({ kid: 'mock-kid' }) }]),
+ };
+
+ (createKeyStore as jest.Mock).mockImplementation(() => mockKeyStore);
+
+ (loadConfig as jest.Mock).mockReturnValue({
+ environment: 'env',
+ pemSSMPath: 'ssm-path',
+ staticAssetBucket: 'static-assets',
+ jwksFileName: 'auth/jwks.json',
+ });
+
+ const allParameters = preExistingKeys?.map((key) => ({
+ Name: `key-name-${key}`,
+ Value: `key-value-${key}`,
+ })) ?? [{ Name: 'key-name', Value: 'key-value' }];
+
+ const mockGetAllParameters = jest.fn().mockReturnValue(allParameters);
+ (parameterStore.getAllParameters as jest.Mock).mockImplementation(
+ mockGetAllParameters,
+ );
+
+ const mockValidatePrivateKey = jest.fn();
+ (validatePrivateKey as jest.Mock).mockImplementation(mockValidatePrivateKey);
+
+ const mockDeleteKey = jest.fn();
+ (deleteKey as jest.Mock).mockImplementation(mockDeleteKey);
+
+ const mockGenerateNewKey = jest.fn();
+ (generateNewKey as jest.Mock).mockImplementation(mockGenerateNewKey);
+
+ const mockUploadPublicKeystoreToS3 = jest.fn();
+ (uploadPublicKeystoreToS3 as jest.Mock).mockImplementation(
+ mockUploadPublicKeystoreToS3,
+ );
+
+ return {
+ mockGetAllParameters,
+ mockValidatePrivateKey,
+ mockDeleteKey,
+ mockGenerateNewKey,
+ mockUploadPublicKeystoreToS3,
+ mockKeyStore,
+ };
+};
+
+describe('cleanAndRefreshKeystores', () => {
+ beforeEach(() => {
+ jest.clearAllMocks();
+ jest.spyOn(logger, 'info').mockImplementation(() => logger);
+ });
+
+ it('Runs successfully when no keys need updating', async () => {
+ const {
+ mockDeleteKey,
+ mockGenerateNewKey,
+ mockGetAllParameters,
+ mockKeyStore,
+ mockUploadPublicKeystoreToS3,
+ mockValidatePrivateKey,
+ } = setupMocks();
+
+ mockValidatePrivateKey.mockResolvedValue({
+ valid: true,
+ keyJwk: {} as unknown as Key,
+ keyDate: new Date('2021-02-24'),
+ });
+
+ await cleanAndRefreshKeystores({
+ now: new Date('2021-02-25'),
+ minDaysBeforeRotation: 1,
+ });
+
+ expect(mockGetAllParameters).toHaveBeenCalled();
+ expect(mockValidatePrivateKey).toHaveBeenCalledTimes(1);
+ expect(mockDeleteKey).not.toHaveBeenCalled();
+ expect(mockGenerateNewKey).not.toHaveBeenCalled();
+ expect(mockUploadPublicKeystoreToS3).toHaveBeenCalledWith({
+ jwksFileName: 'auth/jwks.json',
+ keystore: mockKeyStore,
+ staticAssetBucket: 'static-assets',
+ });
+ expect(mockUploadPublicKeystoreToS3).toHaveBeenCalledTimes(1);
+ });
+
+ it('Runs successfully when a key needs deleting', async () => {
+ const {
+ mockDeleteKey,
+ mockGenerateNewKey,
+ mockGetAllParameters,
+ mockKeyStore,
+ mockUploadPublicKeystoreToS3,
+ mockValidatePrivateKey,
+ } = setupMocks();
+
+ mockValidatePrivateKey.mockResolvedValue({
+ valid: false,
+ keyJwk: {} as unknown as Key,
+ keyDate: new Date('2021-02-24'),
+ });
+
+ await cleanAndRefreshKeystores({
+ now: new Date('2021-02-25'),
+ minDaysBeforeRotation: 1,
+ });
+
+ expect(mockGetAllParameters).toHaveBeenCalled();
+ expect(mockValidatePrivateKey).toHaveBeenCalledTimes(1);
+ expect(mockDeleteKey).toHaveBeenCalled();
+ expect(mockGenerateNewKey).toHaveBeenCalled();
+ expect(mockUploadPublicKeystoreToS3).toHaveBeenCalledWith({
+ jwksFileName: 'auth/jwks.json',
+ keystore: mockKeyStore,
+ staticAssetBucket: 'static-assets',
+ });
+ expect(mockUploadPublicKeystoreToS3).toHaveBeenCalledTimes(1);
+ });
+
+ it('Runs successfully when a key needs generating', async () => {
+ const {
+ mockDeleteKey,
+ mockGenerateNewKey,
+ mockGetAllParameters,
+ mockKeyStore,
+ mockUploadPublicKeystoreToS3,
+ mockValidatePrivateKey,
+ } = setupMocks();
+
+ mockValidatePrivateKey.mockResolvedValue({
+ valid: true,
+ keyJwk: {} as unknown as Key,
+ keyDate: new Date('2021-02-23'),
+ });
+
+ await cleanAndRefreshKeystores({
+ now: new Date('2021-02-25'),
+ minDaysBeforeRotation: 1,
+ });
+
+ expect(mockGetAllParameters).toHaveBeenCalled();
+ expect(mockValidatePrivateKey).toHaveBeenCalledTimes(1);
+ expect(mockDeleteKey).not.toHaveBeenCalled();
+ expect(mockGenerateNewKey).toHaveBeenCalled();
+ expect(mockUploadPublicKeystoreToS3).toHaveBeenCalledWith({
+ jwksFileName: 'auth/jwks.json',
+ keystore: mockKeyStore,
+ staticAssetBucket: 'static-assets',
+ });
+ expect(mockUploadPublicKeystoreToS3).toHaveBeenCalledTimes(1);
+ });
+
+ it('Runs successfully when a key needs generating and one retaining (only just invalid)', async () => {
+ const {
+ mockDeleteKey,
+ mockGenerateNewKey,
+ mockGetAllParameters,
+ mockKeyStore,
+ mockUploadPublicKeystoreToS3,
+ mockValidatePrivateKey,
+ } = setupMocks(['2024-07-27']);
+
+ const now = new Date('2024-08-25');
+
+ mockValidatePrivateKey.mockResolvedValue({
+ valid: true,
+ keyJwk: {} as unknown as Key,
+ keyDate: new Date('2024-07-27'),
+ });
+
+ await cleanAndRefreshKeystores({
+ now,
+ });
+
+ expect(mockGetAllParameters).toHaveBeenCalled();
+ expect(mockValidatePrivateKey).toHaveBeenCalledWith({
+ Name: 'key-name-2024-07-27',
+ Value: 'key-value-2024-07-27',
+ minIssueDate: new Date('2024-06-30'),
+ now,
+ });
+ expect(mockValidatePrivateKey).toHaveBeenCalledTimes(1);
+
+ expect(mockDeleteKey).not.toHaveBeenCalled();
+ expect(mockGenerateNewKey).toHaveBeenCalled();
+ expect(mockUploadPublicKeystoreToS3).toHaveBeenCalledWith({
+ jwksFileName: 'auth/jwks.json',
+ keystore: mockKeyStore,
+ staticAssetBucket: 'static-assets',
+ });
+ expect(mockUploadPublicKeystoreToS3).toHaveBeenCalledTimes(1);
+ });
+
+ it('Runs successfully when a key needs generating and one retaining (only just within retention period)', async () => {
+ const {
+ mockDeleteKey,
+ mockGenerateNewKey,
+ mockGetAllParameters,
+ mockKeyStore,
+ mockUploadPublicKeystoreToS3,
+ mockValidatePrivateKey,
+ } = setupMocks(['2024-06-30']);
+
+ const now = new Date('2024-08-25');
+
+ mockValidatePrivateKey.mockResolvedValue({
+ valid: true,
+ keyJwk: {} as unknown as Key,
+ keyDate: new Date('2024-06-30'),
+ });
+
+ await cleanAndRefreshKeystores({
+ now,
+ });
+
+ expect(mockGetAllParameters).toHaveBeenCalled();
+ expect(mockValidatePrivateKey).toHaveBeenCalledWith({
+ Name: 'key-name-2024-06-30',
+ Value: 'key-value-2024-06-30',
+ minIssueDate: new Date('2024-06-30'),
+ now,
+ });
+ expect(mockValidatePrivateKey).toHaveBeenCalledTimes(1);
+
+ expect(mockDeleteKey).not.toHaveBeenCalled();
+ expect(mockGenerateNewKey).toHaveBeenCalled();
+ expect(mockUploadPublicKeystoreToS3).toHaveBeenCalledWith({
+ jwksFileName: 'auth/jwks.json',
+ keystore: mockKeyStore,
+ staticAssetBucket: 'static-assets',
+ });
+ expect(mockUploadPublicKeystoreToS3).toHaveBeenCalledTimes(1);
+ });
+
+ it('Runs successfully when a key needs generating, one retaining and one removed', async () => {
+ const {
+ mockDeleteKey,
+ mockGenerateNewKey,
+ mockGetAllParameters,
+ mockKeyStore,
+ mockUploadPublicKeystoreToS3,
+ mockValidatePrivateKey,
+ } = setupMocks(['2024-07-30', '2024-08-27']);
+
+ const now = new Date('2024-09-25');
+ const minIssueDate = new Date('2024-07-31');
+
+ mockValidatePrivateKey
+ .mockResolvedValueOnce({
+ valid: false,
+ keyJwk: {} as unknown as Key,
+ keyDate: new Date('2024-07-30'),
+ })
+ .mockResolvedValueOnce({
+ valid: true,
+ keyJwk: {} as unknown as Key,
+ keyDate: new Date('2024-08-27'),
+ });
+
+ await cleanAndRefreshKeystores({
+ now,
+ });
+
+ expect(mockGetAllParameters).toHaveBeenCalled();
+
+ expect(mockValidatePrivateKey).toHaveBeenCalledWith({
+ Name: 'key-name-2024-07-30',
+ Value: 'key-value-2024-07-30',
+ minIssueDate,
+ now,
+ });
+ expect(mockValidatePrivateKey).toHaveBeenCalledWith({
+ Name: 'key-name-2024-08-27',
+ Value: 'key-value-2024-08-27',
+ minIssueDate,
+ now,
+ });
+ expect(mockValidatePrivateKey).toHaveBeenCalledTimes(2);
+
+ expect(mockDeleteKey).toHaveBeenCalledWith({
+ Name: 'key-name-2024-07-30',
+ deleteReason: undefined,
+ warn: false,
+ });
+ expect(mockDeleteKey).toHaveBeenCalledTimes(1);
+
+ expect(mockGenerateNewKey).toHaveBeenCalled();
+ expect(mockUploadPublicKeystoreToS3).toHaveBeenCalledWith({
+ jwksFileName: 'auth/jwks.json',
+ keystore: mockKeyStore,
+ staticAssetBucket: 'static-assets',
+ });
+ expect(mockUploadPublicKeystoreToS3).toHaveBeenCalledTimes(1);
+ });
+
+ it('Runs successfully when skipping key generation as youngest key is recent enough', async () => {
+ const { mockGenerateNewKey, mockValidatePrivateKey } = setupMocks([
+ '2024-09-01',
+ ]);
+ const now = new Date('2024-09-25');
+
+ mockValidatePrivateKey.mockResolvedValue({
+ valid: true,
+ keyJwk: { kid: 'key-1' } as unknown as Key,
+ keyDate: new Date('2024-09-01'), // 24 days old < 28 days threshold
+ });
+
+ await cleanAndRefreshKeystores({
+ now,
+ minDaysBeforeRotation: 28,
+ });
+
+ expect(mockGenerateNewKey).not.toHaveBeenCalled();
+ expect(logger.info).toHaveBeenCalledWith({
+ description:
+ 'Keystore already contains a key less than 28 days old, skipping new key gen',
+ });
+ });
+
+ it('Does not update youngestKeyDate if first key is already newer than second key', async () => {
+ const {
+ mockGenerateNewKey,
+ mockUploadPublicKeystoreToS3,
+ mockValidatePrivateKey,
+ } = setupMocks(['2024-07-15', '2024-06-01']);
+
+ const now = new Date('2024-09-01');
+
+ // simulate multiple keys where the first key is newer — youngestKeyDate should NOT be updated
+ mockValidatePrivateKey
+ .mockResolvedValueOnce({
+ valid: true,
+ keyJwk: { kid: 'key1' } as unknown as Key,
+ keyDate: new Date('2024-07-15'), // newer key first
+ })
+ .mockResolvedValueOnce({
+ valid: true,
+ keyJwk: { kid: 'key2' } as unknown as Key,
+ keyDate: new Date('2024-06-01'), // older key second — should not update youngestKeyDate
+ });
+
+ await cleanAndRefreshKeystores({ now });
+
+ expect(mockValidatePrivateKey).toHaveBeenCalledTimes(2);
+ expect(mockGenerateNewKey).toHaveBeenCalled();
+ expect(mockUploadPublicKeystoreToS3).toHaveBeenCalled();
+ });
+
+ it('Runs successfully when updating youngestKeyDate if second key is newer', async () => {
+ const {
+ mockGenerateNewKey,
+ mockUploadPublicKeystoreToS3,
+ mockValidatePrivateKey,
+ } = setupMocks(['2024-06-01', '2024-07-15']);
+
+ const now = new Date('2024-09-01');
+
+ // simulate multiple keys with different keyDates
+ mockValidatePrivateKey
+ .mockResolvedValueOnce({
+ valid: true,
+ keyJwk: { kid: 'key1' } as unknown as Key,
+ keyDate: new Date('2024-06-01'),
+ })
+ .mockResolvedValueOnce({
+ valid: true,
+ keyJwk: { kid: 'key2' } as unknown as Key,
+ keyDate: new Date('2024-07-15'), // later
+ });
+
+ await cleanAndRefreshKeystores({ now });
+
+ expect(mockValidatePrivateKey).toHaveBeenCalledTimes(2);
+ expect(mockGenerateNewKey).toHaveBeenCalled();
+ expect(mockUploadPublicKeystoreToS3).toHaveBeenCalled();
+ });
+
+ it('Runs successfully when not specifying the "now" value', async () => {
+ const {
+ mockGenerateNewKey,
+ mockUploadPublicKeystoreToS3,
+ mockValidatePrivateKey,
+ } = setupMocks();
+
+ mockValidatePrivateKey.mockResolvedValue({
+ valid: false,
+ keyJwk: { kid: 'ignored' } as unknown as Key,
+ keyDate: new Date('2000-01-01'),
+ });
+
+ await cleanAndRefreshKeystores({});
+
+ expect(mockGenerateNewKey).toHaveBeenCalled();
+ expect(mockUploadPublicKeystoreToS3).toHaveBeenCalled();
+ });
+});
diff --git a/src/lambdas/apim-key-generator/src/config.ts b/src/lambdas/apim-key-generator/src/config.ts
new file mode 100644
index 00000000..00f9ae3a
--- /dev/null
+++ b/src/lambdas/apim-key-generator/src/config.ts
@@ -0,0 +1,21 @@
+import { defaultConfigReader } from 'utils';
+
+export const loadConfig = (): Config => {
+ const environment = defaultConfigReader.getValue('ENVIRONMENT');
+
+ const s3Bucket = defaultConfigReader.tryGetValue('KEYSTORE_S3_BUCKET');
+
+ return {
+ environment,
+ pemSSMPath: defaultConfigReader.getValue('SSM_PRIVATE_KEY_PARAMETER_NAME'),
+ staticAssetBucket: s3Bucket === null ? 'unavailable' : s3Bucket,
+ jwksFileName: 'auth/jwks.json',
+ };
+};
+
+export type Config = {
+ environment: string;
+ pemSSMPath: string;
+ staticAssetBucket: string;
+ jwksFileName: string;
+};
diff --git a/src/lambdas/apim-key-generator/src/lambda.ts b/src/lambdas/apim-key-generator/src/lambda.ts
new file mode 100644
index 00000000..ce8616d1
--- /dev/null
+++ b/src/lambdas/apim-key-generator/src/lambda.ts
@@ -0,0 +1,5 @@
+// This is a Lambda entrypoint file.
+
+import { cleanAndRefreshKeystores } from 'refresh-keystores';
+
+export const handler = async () => cleanAndRefreshKeystores({});
diff --git a/src/lambdas/apim-key-generator/src/refresh-keystores.ts b/src/lambdas/apim-key-generator/src/refresh-keystores.ts
new file mode 100644
index 00000000..f04876bf
--- /dev/null
+++ b/src/lambdas/apim-key-generator/src/refresh-keystores.ts
@@ -0,0 +1,109 @@
+import { isBefore, subDays } from 'date-fns';
+import {
+ NonNullSSMParam,
+ createKeyStore,
+ deleteKey,
+ generateNewKey,
+ logger,
+ nonNullParameterFilter,
+ parameterStore,
+ uploadPublicKeystoreToS3,
+ validatePrivateKey,
+} from 'utils';
+import { loadConfig } from 'config';
+
+type DeleteInvalidKeysAndCreateKeystoreParams = {
+ ssmPath: string;
+ minIssueDate: Date;
+ now: Date;
+};
+
+const deleteInvalidKeysAndCreateKeystore = async ({
+ minIssueDate,
+ now,
+ ssmPath,
+}: DeleteInvalidKeysAndCreateKeystoreParams) => {
+ const keystore = createKeyStore();
+ let youngestKeyDate: Date | null = null;
+
+ const allParams = await parameterStore.getAllParameters(ssmPath);
+ const keyParams = allParams.filter((p: any): p is NonNullSSMParam =>
+ nonNullParameterFilter(p),
+ );
+
+ for (const { Name, Value } of keyParams) {
+ const validationResult = await validatePrivateKey({
+ Name,
+ Value,
+ minIssueDate,
+ now,
+ });
+ if (validationResult.valid) {
+ const { keyDate, keyJwk } = validationResult;
+ await keystore.add(keyJwk);
+ // track the date of the youngest private key to determine rotation
+ if (!youngestKeyDate || isBefore(youngestKeyDate, keyDate)) {
+ youngestKeyDate = keyDate;
+ }
+ } else {
+ const { deleteReason, warn = false } = validationResult;
+
+ await deleteKey({
+ Name,
+ deleteReason,
+ warn,
+ });
+ }
+ }
+
+ logger.info({
+ description: `Read ${keystore.all().length} keys from SSM Parameter store.`,
+ });
+
+ return { keystore, youngestKeyDate };
+};
+
+export const cleanAndRefreshKeystores = async ({
+ maxAgeDays = 56,
+ minDaysBeforeRotation = 28,
+ now = new Date(),
+}) => {
+ const config = loadConfig();
+
+ // date beyond which keys should be deleted
+ const minIssueDate = subDays(now, maxAgeDays);
+ // most recent date beyond which we should gen a new key
+ const keygenThresholdDate = subDays(now, minDaysBeforeRotation);
+
+ const ssmPath = config.pemSSMPath;
+ const { keystore, youngestKeyDate } =
+ await deleteInvalidKeysAndCreateKeystore({
+ ssmPath,
+ minIssueDate,
+ now,
+ });
+
+ if (!youngestKeyDate || isBefore(youngestKeyDate, keygenThresholdDate)) {
+ await generateNewKey({
+ keystore,
+ ssmPath,
+ now,
+ });
+ } else {
+ logger.info({
+ description: `Keystore already contains a key less than ${minDaysBeforeRotation} days old, skipping new key gen`,
+ });
+ }
+
+ await uploadPublicKeystoreToS3({
+ jwksFileName: config.jwksFileName,
+ keystore,
+ staticAssetBucket: config.staticAssetBucket,
+ });
+
+ logger.info({
+ description: `Email auth keygen refresh complete: current key IDs: ${keystore
+ .all()
+ .map((key) => key.toJSON().kid)}`,
+ });
+};
diff --git a/src/lambdas/apim-key-generator/tsconfig.json b/src/lambdas/apim-key-generator/tsconfig.json
new file mode 100644
index 00000000..de8ca2a7
--- /dev/null
+++ b/src/lambdas/apim-key-generator/tsconfig.json
@@ -0,0 +1,14 @@
+{
+ "compilerOptions": {
+ "baseUrl": "./src/",
+ "isolatedModules": true
+ },
+ "exclude": [
+ "node_modules"
+ ],
+ "extends": "@tsconfig/node22/tsconfig.json",
+ "include": [
+ "src/**/*",
+ "./jest.config.ts"
+ ]
+}
diff --git a/src/utils/jest.config.ts b/src/utils/jest.config.ts
new file mode 100644
index 00000000..a7b6eec8
--- /dev/null
+++ b/src/utils/jest.config.ts
@@ -0,0 +1,21 @@
+import { baseJestConfig } from '../../jest.config.base';
+
+const utilsJestConfig = {
+ ...baseJestConfig,
+
+ coverageThreshold: {
+ global: {
+ branches: 85,
+ functions: 85,
+ lines: 85,
+ statements: -10,
+ },
+ },
+
+ coveragePathIgnorePatterns: [
+ ...(baseJestConfig.coveragePathIgnorePatterns ?? []),
+ 'index.ts',
+ ],
+};
+
+export default utilsJestConfig;
diff --git a/src/utils/package.json b/src/utils/package.json
new file mode 100644
index 00000000..066527c3
--- /dev/null
+++ b/src/utils/package.json
@@ -0,0 +1,47 @@
+{
+ "dependencies": {
+ "@aws-sdk/client-athena": "^3.984.0",
+ "@aws-sdk/client-cloudwatch": "^3.984.0",
+ "@aws-sdk/client-dynamodb": "^3.984.0",
+ "@aws-sdk/client-eventbridge": "^3.984.0",
+ "@aws-sdk/client-lambda": "^3.984.0",
+ "@aws-sdk/client-s3": "^3.984.0",
+ "@aws-sdk/client-sqs": "^3.984.0",
+ "@aws-sdk/client-ssm": "^3.984.0",
+ "@aws-sdk/lib-dynamodb": "^3.984.0",
+ "@aws-sdk/lib-storage": "^3.984.0",
+ "async-mutex": "^0.4.0",
+ "axios": "^1.18.1",
+ "date-fns": "^4.1.0",
+ "jose": "^5.10.0",
+ "winston": "^3.17.0",
+ "zod": "^4.1.12"
+ },
+ "devDependencies": {
+ "@aws-sdk/types": "^3.914.0",
+ "@tsconfig/node22": "catalog:tools",
+ "@types/aws-lambda": "catalog:tools",
+ "@types/jest": "^29.5.14",
+ "@types/mock-fs": "catalog:test",
+ "@types/node": "^24.0.10",
+ "aws-sdk-client-mock": "^4.1.0",
+ "aws-sdk-client-mock-jest": "^4.1.0",
+ "jest": "^29.7.0",
+ "jest-mock-extended": "^3.0.7",
+ "mock-fs": "catalog:test",
+ "typescript": "catalog:tools"
+ },
+ "exports": {
+ ".": "./src/index.ts",
+ "./logger": "./src/logger.ts"
+ },
+ "main": "src/index.ts",
+ "name": "utils",
+ "scripts": {
+ "lint": "eslint .",
+ "lint:fix": "eslint . --fix",
+ "test:unit": "jest",
+ "typecheck": "tsc --noEmit"
+ },
+ "version": "0.0.1"
+}
diff --git a/src/utils/src/__tests__/cache/cache.test.ts b/src/utils/src/__tests__/cache/cache.test.ts
new file mode 100644
index 00000000..1c5ac425
--- /dev/null
+++ b/src/utils/src/__tests__/cache/cache.test.ts
@@ -0,0 +1,30 @@
+import { newCache } from '../../cache';
+
+describe('newCache', () => {
+ const date = new Date('2020-01-01');
+
+ test('caches calls to an async function', async () => {
+ const fetchFn = jest.fn(async () => ({ value: 'value' }));
+
+ const cache = newCache(() => date, fetchFn);
+
+ expect(await cache.getCachedAsync('key')).toBe('value');
+ expect(await cache.getCachedAsync('key')).toBe('value');
+
+ expect(fetchFn).toHaveBeenCalledTimes(1);
+ });
+
+ test('can be cleared', async () => {
+ const fetchFn = jest.fn(async () => ({ value: 'value' }));
+
+ const cache = newCache(() => date, fetchFn);
+
+ expect(await cache.getCachedAsync('key')).toBe('value');
+
+ cache.clear();
+
+ expect(await cache.getCachedAsync('key')).toBe('value');
+
+ expect(fetchFn).toHaveBeenCalledTimes(2);
+ });
+});
diff --git a/src/utils/src/__tests__/config-reader.test.ts b/src/utils/src/__tests__/config-reader.test.ts
new file mode 100644
index 00000000..4c303897
--- /dev/null
+++ b/src/utils/src/__tests__/config-reader.test.ts
@@ -0,0 +1,128 @@
+import { configReaderBuilder } from '../config-reader';
+
+process.env.AN_INT = '462';
+process.env.A_NEGATIVE_INT = '-94';
+process.env.A_FLOAT = '3.14159265359'; // 🥧
+process.env.HAS_EXTRA_WHITESPACE = ' hello ';
+
+describe('configReader', () => {
+ const config = configReaderBuilder().build();
+ describe.each([config.getValue, config.getInt, config.getBoolean])(
+ '%p',
+ (func) => {
+ it('will throw when it can not resolve the requested key', () => {
+ expect(() => func('SOME_NONEXISTENT_VALUE')).toThrow(
+ 'SOME_NONEXISTENT_VALUE must be defined',
+ );
+ });
+ },
+ );
+
+ describe.each([config.getValue, config.tryGetValue])('%p', (func) => {
+ it('will trim whitespace from value', () => {
+ expect(func('HAS_EXTRA_WHITESPACE')).toBe('hello');
+ });
+ });
+
+ describe.each([config.tryGetValue, config.tryGetInt, config.tryGetBoolean])(
+ '%p',
+ (func) => {
+ it('will return null when it can not resolve the requested key', () => {
+ expect(func('SOME_NONEXISTENT_VALUE')).toBe(null);
+ });
+ },
+ );
+
+ describe.each([config.getInt, config.tryGetInt])('%p', (func) => {
+ it('will convert a value to an integer', () => {
+ expect(func('AN_INT')).toBe(462);
+ });
+
+ it('will convert a negative value to an integer', () => {
+ expect(func('A_NEGATIVE_INT')).toBe(-94);
+ });
+
+ // It is safer to thrown for a malformed value as this is more likely to be a configuration
+ // error than an attempt to no pass a value. Even for tryGetInt.
+ it('will throw if value is not an int', () => {
+ expect(() => func('A_FLOAT')).toThrow(
+ 'A_FLOAT is not valid. Expected an integer but got 3.14159265359',
+ );
+ });
+ });
+
+ describe.each([config.getBoolean, config.tryGetBoolean])('%p', (func) => {
+ test.each(['true', 'True'])('will convert %p to true', (string) => {
+ process.env.IS_TRUE = string;
+ expect(func('IS_TRUE')).toBe(true);
+ });
+
+ test.each(['false', 'False'])('will convert %p to false', (string) => {
+ process.env.IS_FALSE = string;
+ expect(func('IS_FALSE')).toBe(false);
+ });
+ });
+
+ describe('fluent interface', () => {
+ it('can valdiate a minimun integer', () => {
+ expect(() =>
+ config.read('AN_INT').asInt().validateGreaterThan(500).value(),
+ ).toThrow('AN_INT should be greater than 500 but got 462');
+ });
+ });
+});
+
+describe('prefixedConfigReader', () => {
+ const prefixedConfig = configReaderBuilder().withPrefix('PREFIXED').build();
+
+ describe.each([
+ prefixedConfig.getValue,
+ prefixedConfig.getInt,
+ prefixedConfig.getBoolean,
+ ])('%p', (func) => {
+ it('will throw when it can not resolve the requested key', () => {
+ expect(() => func('SOME_NONEXISTENT_VALUE')).toThrow(
+ 'PREFIXED_SOME_NONEXISTENT_VALUE must be defined',
+ );
+ });
+ });
+
+ describe.each([prefixedConfig.getValue, prefixedConfig.tryGetValue])(
+ '%p',
+ (func) => {
+ it('will return the configured value', () => {
+ process.env.PREFIXED_TEXT = 'hello';
+ expect(func('TEXT')).toBe('hello');
+ });
+ },
+ );
+
+ describe.each([prefixedConfig.getInt, prefixedConfig.tryGetInt])(
+ '%p',
+ (func) => {
+ it('will return the configured value', () => {
+ process.env.PREFIXED_INT = '25633';
+ expect(func('INT')).toBe(25_633);
+ });
+ },
+ );
+
+ describe.each([prefixedConfig.getBoolean, prefixedConfig.tryGetBoolean])(
+ '%p',
+ (func) => {
+ test.each(['true', 'True'])('will convert %p to true', (string) => {
+ process.env.PREFIXED_IS_TRUE = string;
+ expect(func('IS_TRUE')).toBe(true);
+ });
+ },
+ );
+
+ describe('fluent interface', () => {
+ it('can valdiate a minimun integer', () => {
+ process.env.PREFIXED_INT = '45';
+ expect(() =>
+ prefixedConfig.read('INT').asInt().validateGreaterThan(50).value(),
+ ).toThrow('PREFIXED_INT should be greater than 50 but got 45');
+ });
+ });
+});
diff --git a/src/utils/src/__tests__/in-memory-cache/in-memory-cache.test.ts b/src/utils/src/__tests__/in-memory-cache/in-memory-cache.test.ts
new file mode 100644
index 00000000..2c3574f5
--- /dev/null
+++ b/src/utils/src/__tests__/in-memory-cache/in-memory-cache.test.ts
@@ -0,0 +1,312 @@
+import { InMemoryCache } from '../../in-memory-cache';
+
+function setup() {
+ const cache = new InMemoryCache();
+
+ return { cache };
+}
+
+describe('cache', () => {
+ it('get returns null if the key is not set', async () => {
+ const { cache } = setup();
+
+ await cache.acquireLock();
+
+ const result = await cache.get('foo');
+
+ expect(result).toBe(null);
+ });
+
+ it('get returns the cached value if the key is set', async () => {
+ const { cache } = setup();
+
+ const key = 'foo';
+ const value = { foo: 'bar' };
+
+ await cache.acquireLock();
+
+ await cache.set(key, value);
+
+ const result = await cache.get(key);
+
+ expect(result).toBe(value);
+ });
+
+ it('delete removes the cached value', async () => {
+ const { cache } = setup();
+
+ const key = 'foo';
+ const value = { foo: 'bar' };
+
+ await cache.acquireLock();
+
+ await cache.set(key, value);
+
+ await cache.delete(key);
+
+ const result = await cache.get(key);
+
+ expect(result).toBe(null);
+ });
+
+ describe('setAll', () => {
+ it('should set the cache to the value provided when the cache is initially empty', async () => {
+ const { cache } = setup();
+ await cache.acquireLock();
+
+ const updates = new Map();
+ updates.set('key1', 'value1');
+ updates.set('key2', 'value2');
+
+ await cache.setAll(updates);
+
+ const res1 = await cache.get('key1');
+ expect(res1).toEqual('value1');
+
+ const res2 = await cache.get('key2');
+ expect(res2).toEqual('value2');
+ });
+
+ it('should set merge the cache provided with the cache in mem', async () => {
+ const { cache } = setup();
+
+ await cache.acquireLock();
+ await cache.set('key1', 'value1');
+ await cache.set('key2', 'value2');
+
+ const updates = new Map();
+ updates.set('key2', 'updated value2');
+ updates.set('key3', 'value3');
+
+ await cache.setAll(updates);
+
+ const res1 = await cache.get('key1');
+ expect(res1).toEqual('value1');
+
+ const res2 = await cache.get('key2');
+ expect(res2).toEqual('updated value2');
+
+ const res3 = await cache.get('key3');
+ expect(res3).toEqual('value3');
+ });
+ });
+
+ describe('entries', () => {
+ it('returns an array of all key-value pairs in the cache', async () => {
+ const { cache } = setup();
+ await cache.acquireLock();
+
+ const updates = new Map();
+ updates.set('key1', 'value1');
+ updates.set('key2', 'value2');
+
+ await cache.setAll(updates);
+
+ const result = await cache.entries();
+
+ expect(result).toHaveLength(2);
+ expect(result).toContainEqual(['key1', 'value1']);
+ expect(result).toContainEqual(['key2', 'value2']);
+ });
+ });
+});
+
+describe('lock', () => {
+ beforeAll(() => {
+ jest.useFakeTimers({ advanceTimers: true });
+ jest.spyOn(globalThis, 'setTimeout');
+ });
+
+ afterEach(() => {
+ jest.clearAllMocks();
+ });
+
+ afterAll(() => {
+ jest.useRealTimers();
+ });
+
+ it('errors if the lock is not acquired when calling get', async () => {
+ expect.hasAssertions();
+
+ const { cache } = setup();
+
+ await expect(cache.get('foo')).rejects.toThrowErrorMatchingInlineSnapshot(
+ `"Cannot access in-memory cache without first obtaining mutex lock"`,
+ );
+
+ expect(setTimeout).toHaveBeenCalledTimes(3);
+ });
+
+ it('errors if the lock is not acquired when calling set', async () => {
+ expect.hasAssertions();
+
+ const { cache } = setup();
+
+ await expect(
+ cache.set('foo', 'bar'),
+ ).rejects.toThrowErrorMatchingInlineSnapshot(
+ `"Cannot access in-memory cache without first obtaining mutex lock"`,
+ );
+
+ expect(setTimeout).toHaveBeenCalledTimes(3);
+ });
+
+ it('errors if the lock is not acquired when calling delete', async () => {
+ expect.hasAssertions();
+
+ const { cache } = setup();
+
+ await expect(
+ cache.delete('foo'),
+ ).rejects.toThrowErrorMatchingInlineSnapshot(
+ `"Cannot access in-memory cache without first obtaining mutex lock"`,
+ );
+
+ expect(setTimeout).toHaveBeenCalledTimes(3);
+ });
+
+ it('prevents further access to the cache until released', async () => {
+ expect.hasAssertions();
+
+ const { cache } = setup();
+
+ // two processes trying to acquire the lock and access the cache at the same time
+ const p1 = (async () => {
+ const r = await cache.acquireLock();
+
+ await cache.set('foo', 'bar');
+
+ return r;
+ })();
+
+ const p2 = (async () => {
+ const r = await cache.acquireLock();
+ const result = await cache.get('foo');
+ r();
+ return result;
+ })();
+
+ const release = await p1;
+
+ setTimeout(() => {
+ release();
+ }, 1000);
+
+ jest.advanceTimersByTime(1000);
+
+ // assert that the second promise does not resolve until the first has released the lock
+ // and that the value is consistent with the value set by first process
+ await expect(p2).resolves.toBe('bar');
+ });
+});
+
+describe('ttl', () => {
+ beforeEach(() => {
+ jest.useFakeTimers();
+ jest.setSystemTime(new Date('2024-07-18T15:07:52.000Z'));
+ });
+ afterEach(() => {
+ jest.useRealTimers();
+ });
+
+ it('returns the item if the global ttl has not expired', async () => {
+ const cache = new InMemoryCache({ ttl: 1000 });
+
+ await cache.acquireLock();
+
+ await cache.set('foo', 'bar');
+
+ await jest.advanceTimersByTimeAsync(999);
+
+ const result = await cache.get('foo');
+
+ expect(result).toBe('bar');
+ });
+
+ it('returns null if the global ttl has expired', async () => {
+ const cache = new InMemoryCache({ ttl: 1000 });
+
+ await cache.acquireLock();
+
+ await cache.set('foo', 'bar');
+
+ await jest.advanceTimersByTimeAsync(1000);
+
+ const result = await cache.get('foo');
+
+ expect(result).toBe(null);
+ });
+
+ it('returns the item if the local ttl has not expired', async () => {
+ const cache = new InMemoryCache({ ttl: 1000 });
+
+ await cache.acquireLock();
+
+ await cache.set('foo', 'bar', { ttl: 500 });
+
+ await jest.advanceTimersByTimeAsync(499);
+
+ const result = await cache.get('foo');
+
+ expect(result).toBe('bar');
+ });
+
+ it('returns null if the local ttl has expired', async () => {
+ const cache = new InMemoryCache({ ttl: 1000 });
+
+ await cache.acquireLock();
+
+ await cache.set('foo', 'bar', { ttl: 500 });
+
+ await jest.advanceTimersByTimeAsync(500);
+
+ const result = await cache.get('foo');
+
+ expect(result).toBe(null);
+ });
+
+ it('returns the value if the local ttl has not expired but global has', async () => {
+ const cache = new InMemoryCache({ ttl: 1000 });
+
+ await cache.acquireLock();
+
+ await cache.set('foo', 'bar', { ttl: 1500 });
+
+ await jest.advanceTimersByTimeAsync(1499);
+
+ const result = await cache.get('foo');
+
+ expect(result).toBe('bar');
+ });
+
+ it('can disable ttl on individual items when a global is set', async () => {
+ const cache = new InMemoryCache({ ttl: 1000 });
+
+ await cache.acquireLock();
+
+ await cache.set('foo', 'bar', { ttl: 0 });
+
+ await jest.advanceTimersByTimeAsync(1000);
+
+ const result = await cache.get('foo');
+
+ expect(result).toBe('bar');
+ });
+
+ it('entries does not return expired items', async () => {
+ const cache = new InMemoryCache({ ttl: 1000 });
+
+ await cache.acquireLock();
+
+ await cache.set('foo', 'bar');
+ await cache.set('foo2', 'bar2', { ttl: 500 });
+ await cache.set('foo3', 'bar3', { ttl: 1500 });
+
+ await jest.advanceTimersByTimeAsync(1000);
+
+ const result = await cache.entries();
+
+ expect(result).toHaveLength(1);
+ expect(result).toContainEqual(['foo3', 'bar3']);
+ });
+});
diff --git a/src/utils/src/__tests__/key-generation-utils/delete-key.test.ts b/src/utils/src/__tests__/key-generation-utils/delete-key.test.ts
new file mode 100644
index 00000000..8c9ccd68
--- /dev/null
+++ b/src/utils/src/__tests__/key-generation-utils/delete-key.test.ts
@@ -0,0 +1,54 @@
+import { logger } from '../../logger';
+import { parameterStore } from '../../ssm-utils';
+import { deleteKey } from '../../key-generation-utils';
+
+jest.mock('logger');
+jest.mock('ssm-utils');
+
+describe('deleteKey', () => {
+ beforeEach(jest.resetAllMocks);
+
+ const setupMocks = () => {
+ const mockLogInfo = jest.spyOn(logger, 'info');
+ const mockLogWarn = jest.spyOn(logger, 'warn');
+
+ const mockDeleteParameter = jest.fn();
+ (parameterStore.deleteParameter as jest.Mock).mockImplementation(
+ mockDeleteParameter,
+ );
+
+ return { mockLogInfo, mockLogWarn, mockDeleteParameter };
+ };
+
+ it('behaves as expected with warn = true', async () => {
+ const { mockDeleteParameter, mockLogInfo, mockLogWarn } = setupMocks();
+
+ await deleteKey({
+ Name: 'ssm-param',
+ deleteReason: 'Key expired',
+ warn: true,
+ });
+
+ expect(mockDeleteParameter).toHaveBeenCalledWith('ssm-param');
+ expect(mockLogWarn).toHaveBeenCalledWith({
+ description: 'Keygen deleted invalid private key ssm-param: Key expired',
+ });
+ expect(mockLogInfo).not.toHaveBeenCalled();
+ });
+
+ it('behaves as expected with warn = false', async () => {
+ const { mockDeleteParameter, mockLogInfo, mockLogWarn } = setupMocks();
+
+ await deleteKey({
+ Name: 'ssm-param',
+ deleteReason: 'Key expired',
+ warn: false,
+ });
+
+ expect(mockDeleteParameter).toHaveBeenCalledWith('ssm-param');
+ expect(mockLogInfo).toHaveBeenCalledWith({
+ description: 'Keygen deleted private key ssm-param: Key expired',
+ });
+ expect(mockLogWarn).not.toHaveBeenCalled();
+ });
+});
diff --git a/src/utils/src/__tests__/key-generation-utils/generate-new-key.test.ts b/src/utils/src/__tests__/key-generation-utils/generate-new-key.test.ts
new file mode 100644
index 00000000..74900147
--- /dev/null
+++ b/src/utils/src/__tests__/key-generation-utils/generate-new-key.test.ts
@@ -0,0 +1,50 @@
+import { logger } from '../../logger';
+import { parameterStore } from '../../ssm-utils';
+import { KeyStore, generateNewKey } from '../../key-generation-utils';
+
+jest.mock('ssm-utils', () => ({
+ parameterStore: {
+ addParameter: jest.fn(),
+ },
+}));
+
+const mockAddParameter = jest.fn();
+(parameterStore.addParameter as jest.Mock).mockImplementation(mockAddParameter);
+
+describe('generateNewKey', () => {
+ beforeEach(() => {
+ jest.clearAllMocks();
+ jest.spyOn(logger, 'info').mockImplementation(() => logger);
+ });
+
+ it('generate key on SSM', async () => {
+ const keystore = new KeyStore();
+
+ await generateNewKey({
+ keystore,
+ ssmPath: 'ssm-path',
+ now: new Date('2020-03-15'),
+ keyGenerationOptions: {
+ use: 'sig',
+ kid: 'test-key-id',
+ },
+ });
+
+ expect(mockAddParameter).toHaveBeenCalledWith(
+ 'ssm-path/privatekey_20200315_test-key-id.pem',
+ expect.anything(),
+ );
+ });
+
+ it('generate key without specifying kid', async () => {
+ const keystore = new KeyStore();
+
+ await generateNewKey({
+ keystore,
+ ssmPath: 'ssm-path',
+ now: new Date('2020-03-15'),
+ });
+
+ expect(mockAddParameter).toHaveBeenCalled();
+ });
+});
diff --git a/src/utils/src/__tests__/key-generation-utils/get-private-key.test.ts b/src/utils/src/__tests__/key-generation-utils/get-private-key.test.ts
new file mode 100644
index 00000000..c89dd9d1
--- /dev/null
+++ b/src/utils/src/__tests__/key-generation-utils/get-private-key.test.ts
@@ -0,0 +1,181 @@
+import { format } from 'date-fns';
+import { logger } from '../../logger';
+import { parameterStore } from '../../ssm-utils';
+import { privateKeyFetcher } from '../../key-generation-utils';
+
+jest.mock('ssm-utils', () => {
+ const originalModule = jest.requireActual('ssm-utils');
+
+ return {
+ ...originalModule,
+ parameterStore: {
+ getAllParameters: jest.fn(),
+ },
+ };
+});
+
+const { getPrivateKey } = privateKeyFetcher('ssm-path');
+
+const testKeyId1 = 'kCwsCGf_v7ffSQ8o5pK416vh024ZVnTiPOxAxzbi0lU';
+const testKeyId2 = 'wXQjso8bavigbplIxaB3rYbeGT_lgWAIGZ-25heprHo';
+const testKeyId3 = 'eU44V5UrGllANaLk3tmDz_Q7lybo0N8nM7D127Pr35k';
+
+const testPrivateKey1 =
+ '-----BEGIN EC PRIVATE KEY-----\n' + // gitleaks:allow This is a false positive, the string is not a secret and is used for testing purposes only.
+ 'MHcCAQEEIEpVnqrylY4xEsQdQgJhGFUFKGTGtl5cnKsIq2uNWa56oAoGCCqGSM49\n' +
+ 'AwEHoUQDQgAEqoc8zybajz/NEoUzP5G7lchuuD7dej7vKlWConh1mvI9gvmyRheT\n' +
+ '0vrkuPszvyLXTYusKKgiLZkqz3SHOjVhDw==\n' +
+ '-----END EC PRIVATE KEY-----\n';
+
+const testPrivateKey2 =
+ '-----BEGIN RSA PRIVATE KEY-----\n' + // gitleaks:allow This is a false positive, the string is not a secret and is used for testing purposes only.
+ 'MIICXAIBAAKBgQDOji5Hvtf2H3HjqF5OM78lqnbFPEzbaMYRCftKPTHzospfKI0C\n' +
+ 'IMd01cIUBgXwwbiHPj3yuLrnPp392ulDaWsFlpgDS67PcHG7c5oMfQ7BEIoheAk9\n' +
+ '7uMyY3jawVDOpeZEvV71lIWou0Dg3PeufH9u1kcuOwMR+kwidNm8RWYfHwIDAQAB\n' +
+ 'AoGABwdDf+F4i8FqKKrz+ok8OdXhELkKjHS2OKI0UMRgTL//TtmcYrQm1Uzou7Gw\n' +
+ 'xg5xbvipNvceNPwmeBrY0RhnMcvU522mDJiomuCbrTzTf0Y1IiGrDBuBYnRdVt1c\n' +
+ '4Q/aBQV7Z8lm4R0nsdUyae7I2k7qP0A54asmPT7qcmAhAeECQQDq5ririE2XrJFY\n' +
+ '1T2OrqcAufi4go0LAlsQU3cH14In9hssSgGcQizjG98azHPGUbtCKBvQjeYyyxPs\n' +
+ 'yUEqfi2XAkEA4Rut+jZSPTIL419YLA1vgWqjfX8j2sCzxCGTK5zWZT9F3UJEdydp\n' +
+ '7o1cHfqnoeJEtc7semZCZBl2orucj2HbuQJAI98H5Gn0L21S5NXriJZzOlEsAkEt\n' +
+ 'eLjrXxrf2nq2jZOvopvKkyon4Kao81a1d1uT1Q568OY6eRc5+7bgFLUgEQJAEqsT\n' +
+ '+4sjuNV8rOeMTWLz21y3oEG5/Hs8rUhHhzdjhFQB/D5xpRwMqe7pM8dEvaUhI568\n' +
+ 'd84hNWHzN72tVyq7aQJBALUHy2Dw0WHxmTcC1YYpGAoasu881G59ovaue5gfYToW\n' +
+ '+O+F9348DgRUamIqjLQxAPygpm77VGkywsKFoaUb4W0=\n' +
+ '-----END RSA PRIVATE KEY-----\n';
+
+const testPrivateKey3 =
+ '-----BEGIN RSA PRIVATE KEY-----\n' + // gitleaks:allow This is a false positive, the string is not a secret and is used for testing purposes only.
+ 'MIICWwIBAAKBgQCqIJHZnWgKNkWOdJjvxD1s5y8LHvBSa98Tzm2tYVXMlDV8He6/\n' +
+ '5t/kShyL6YMb+7JZwazRAoZa0OMCKFKBfxn4fiOq4q2dDfHtBMDWz/gz6SINMpKQ\n' +
+ '+H8mJOGqJ4sbHuRbmc6X9SgFhFT3cc0DrxiDNXEgFotwDAtvu2GWUOEuHQIDAQAB\n' +
+ 'AoGACi2yrDNnsxy2IqTFNasnBan7PY4XUMcVbKjwFOx65qeDX66mxyJ4CL+KX7CT\n' +
+ '4Iu5ivc0cLjW8v4GZu2kqgzBr+xXdNMSyCjvIqqamXyyfROfqkHweiYwxV1prdk5\n' +
+ '6zneaibWXKZoY78zrCoI0tLpd/qGUVv0F2Eq4VBkUyfUFWECQQDX/we7s8ijtM+k\n' +
+ 'Qnu66qhyfcKVlXh7QpQXoWNu2HCAPTeCrfSBKhkIltUsGQXizkmAQmPp6sFdLJFE\n' +
+ '3iJtr9ClAkEAyaLEBgfJVZaFqUYit9I2H26Q4/nClwf56lF+4Gi6sd8Xu/MqSncX\n' +
+ 'Rk78v/22Y3szHStY+1633zut3qx76Hc2GQJAOvzxNbfRsbOtiWSGufNf8XSa8ZMS\n' +
+ 'hkcWfqWarCj8AGm3gT7UqXm/wHLA4PwseVZxCFAZTUbJbBLB0ZcAvAfp6QJAcMgS\n' +
+ '3tCiI7ZSwtDRAIKa9U/RyUJdPj8e4Zp93iWWL4F6dA1aHVapdREfPIA78T7q4yjo\n' +
+ '14kuTbXC1eciU2/CmQJATfjLKRCl6xSOzDBiko5syG6EBXSealZAYluXlxIkpqlF\n' +
+ '6dEWd1fYFs35C6o5+bT8N5Eg7tn0ftZ3bfwZcK5Blg==\n' +
+ '-----END RSA PRIVATE KEY-----\n';
+
+describe('getPrivateKey', () => {
+ beforeEach(() => {
+ jest.resetAllMocks();
+ process.env.NO_CACHE = 'true';
+ jest.spyOn(logger, 'info').mockImplementation(() => logger);
+ jest.spyOn(logger, 'error').mockImplementation(() => logger);
+ });
+
+ it('gets private key from ssm', async () => {
+ (parameterStore.getAllParameters as jest.Mock).mockReturnValue([
+ {
+ Name: `privatekey_20201105_${testKeyId1}.pem`,
+ Value: testPrivateKey1,
+ },
+ ]);
+
+ const testOutput = await getPrivateKey();
+
+ const expectedOutput = {
+ kid: testKeyId1,
+ key: testPrivateKey1,
+ };
+
+ expect(testOutput).toMatchObject(expectedOutput);
+ });
+
+ it('selects second youngest key when youngest key has been generated today', async () => {
+ const todaysDateUnformatted = new Date();
+ const todaysDate = format(todaysDateUnformatted, 'yyyyMMdd');
+
+ (parameterStore.getAllParameters as jest.Mock).mockReturnValue([
+ {
+ Name: `privatekey_${todaysDate}_${testKeyId1}.pem`,
+ Value: testPrivateKey1,
+ },
+ {
+ Name: `privatekey_20201203_${testKeyId2}.pem`,
+ Value: testPrivateKey2,
+ },
+ {
+ Name: `privatekey_20211103_${testKeyId3}.pem`,
+ Value: testPrivateKey3,
+ },
+ ]);
+
+ const testOutput = await getPrivateKey();
+
+ const expectedOutput = {
+ kid: testKeyId3,
+ key: testPrivateKey3,
+ };
+
+ expect(testOutput).toMatchObject(expectedOutput);
+ });
+
+ it('selects second youngest key when youngest key has been generated on the previous day', async () => {
+ const yesterdaysDateUnformatted = new Date();
+ yesterdaysDateUnformatted.setDate(yesterdaysDateUnformatted.getDate() - 1);
+
+ const yesterdaysDate = format(yesterdaysDateUnformatted, 'yyyyMMdd');
+
+ (parameterStore.getAllParameters as jest.Mock).mockReturnValue([
+ {
+ Name: `privatekey_${yesterdaysDate}_${testKeyId1}.pem`,
+ Value: testPrivateKey1,
+ },
+ {
+ Name: `privatekey_20201103_${testKeyId2}.pem`,
+ Value: testPrivateKey2,
+ },
+ {
+ Name: `privatekey_20211103_${testKeyId3}.pem`,
+ Value: testPrivateKey3,
+ },
+ ]);
+
+ const testOutput = await getPrivateKey();
+
+ const expectedOutput = {
+ kid: testKeyId3,
+ key: testPrivateKey3,
+ };
+
+ expect(testOutput).toMatchObject(expectedOutput);
+ });
+
+ it('selects youngest key when more than one key exists and the youngest key wasnt generated today or yesterday', async () => {
+ (parameterStore.getAllParameters as jest.Mock).mockReturnValue([
+ {
+ Name: `privatekey_20221103_${testKeyId1}.pem`,
+ Value: testPrivateKey1,
+ },
+ {
+ Name: `privatekey_20201103_${testKeyId2}.pem`,
+ Value: testPrivateKey2,
+ },
+ {
+ Name: `privatekey_20211103_${testKeyId3}.pem`,
+ Value: testPrivateKey3,
+ },
+ ]);
+
+ const testOutput = await getPrivateKey();
+
+ const expectedOutput = {
+ kid: testKeyId1,
+ key: testPrivateKey1,
+ };
+
+ expect(testOutput).toMatchObject(expectedOutput);
+ });
+
+ it('throws error if no private keys found', async () => {
+ (parameterStore.getAllParameters as jest.Mock).mockReturnValue([]);
+
+ await expect(getPrivateKey()).rejects.toThrow('Failure in getPrivateKey()');
+ });
+});
diff --git a/src/utils/src/__tests__/key-generation-utils/jwk-key-store.test.ts b/src/utils/src/__tests__/key-generation-utils/jwk-key-store.test.ts
new file mode 100644
index 00000000..45abf04a
--- /dev/null
+++ b/src/utils/src/__tests__/key-generation-utils/jwk-key-store.test.ts
@@ -0,0 +1,30 @@
+import { Key, KeyStore } from '../../key-generation-utils';
+
+describe('KeyStore', () => {
+ it('can add multiple keys', () => {
+ const store = new KeyStore();
+ const key1 = Key.fromJWK({ kty: 'EC', x: 'a' });
+ const key2 = Key.fromJWK({ kty: 'EC', x: 'b' });
+
+ store.add(key1);
+ store.add(key2);
+
+ expect(store.all()).toHaveLength(2);
+ expect(store.all()[0]).toBe(key1);
+ expect(store.all()[1]).toBe(key2);
+ });
+
+ it('generates a key and adds it to the store', async () => {
+ const store = new KeyStore();
+
+ const key = await store.generate('RSA', 2048);
+ const jwk = key.toJSON();
+
+ expect(key).toBeInstanceOf(Key);
+ expect(store.all()).toHaveLength(1);
+ expect(store.all()[0]).toBe(key);
+
+ expect(jwk.kid).toBeDefined();
+ expect(key.toPEM()).toContain('-----BEGIN');
+ });
+});
diff --git a/src/utils/src/__tests__/key-generation-utils/jwk-key.test.ts b/src/utils/src/__tests__/key-generation-utils/jwk-key.test.ts
new file mode 100644
index 00000000..62a4438a
--- /dev/null
+++ b/src/utils/src/__tests__/key-generation-utils/jwk-key.test.ts
@@ -0,0 +1,75 @@
+import { Key } from '../../key-generation-utils';
+
+const testPrivateKeyPem =
+ '-----BEGIN EC PRIVATE KEY-----\n' + // gitleaks:allow This is a false positive, the string is not a secret and is used for testing purposes only.
+ 'MHcCAQEEIEpVnqrylY4xEsQdQgJhGFUFKGTGtl5cnKsIq2uNWa56oAoGCCqGSM49\n' +
+ 'AwEHoUQDQgAEqoc8zybajz/NEoUzP5G7lchuuD7dej7vKlWConh1mvI9gvmyRheT\n' +
+ '0vrkuPszvyLXTYusKKgiLZkqz3SHOjVhDw==\n' +
+ '-----END EC PRIVATE KEY-----\n';
+
+const testKid = 'test-key-id';
+
+describe('Key', () => {
+ describe('fromPEM', () => {
+ it('creates a Key from a valid PEM string', async () => {
+ const key = await Key.fromPemAndKid(testKid, testPrivateKeyPem);
+ expect(key).toBeInstanceOf(Key);
+ });
+
+ it('throws an error for an invalid PEM string', async () => {
+ await expect(
+ Key.fromPemAndKid(testKid, 'not-a-valid-pem'),
+ ).rejects.toThrow('Invalid PEM formatted message.');
+ });
+ });
+
+ describe('fromJWK', () => {
+ it('creates a Key from a public JWK object', () => {
+ const jwk = { kty: 'EC', crv: 'P-256', x: 'abc', y: 'def' };
+ const key = Key.fromJWK(jwk);
+ expect(key).toBeInstanceOf(Key);
+ });
+ });
+
+ describe('toJSON', () => {
+ it('returns only public JWK fields (strips private key material)', async () => {
+ const key = await Key.fromPemAndKid(testKid, testPrivateKeyPem);
+ const jwk = key.toJSON();
+
+ // Private fields must not be present
+ expect(jwk).not.toHaveProperty('d');
+ expect(jwk).not.toHaveProperty('p');
+ expect(jwk).not.toHaveProperty('q');
+ expect(jwk).not.toHaveProperty('dp');
+ expect(jwk).not.toHaveProperty('dq');
+ expect(jwk).not.toHaveProperty('qi');
+ expect(jwk).not.toHaveProperty('k');
+
+ // Public fields should be present
+ expect(jwk).toHaveProperty('kty');
+ expect(jwk).toHaveProperty('x');
+ expect(jwk).toHaveProperty('y');
+ });
+
+ it('returns all fields when constructed from a public-only JWK', () => {
+ const jwk = { kty: 'EC', crv: 'P-256', x: 'abc', y: 'def' };
+ const key = Key.fromJWK(jwk);
+ expect(key.toJSON()).toEqual(jwk);
+ });
+ });
+
+ describe('toPEM', () => {
+ it('returns the original PEM string when one was provided', async () => {
+ const key = await Key.fromPemAndKid(testKid, testPrivateKeyPem);
+ expect(key.toPEM()).toBe(testPrivateKeyPem);
+ });
+
+ it('throws when no private PEM is available (key created from public JWK)', () => {
+ const jwk = { kty: 'EC', crv: 'P-256', x: 'abc', y: 'def' };
+ const key = Key.fromJWK(jwk);
+ expect(() => key.toPEM()).toThrow(
+ 'No private key PEM available on this Key instance.',
+ );
+ });
+ });
+});
diff --git a/src/utils/src/__tests__/key-generation-utils/upload-public-keystore-to-s3.test.ts b/src/utils/src/__tests__/key-generation-utils/upload-public-keystore-to-s3.test.ts
new file mode 100644
index 00000000..4a203684
--- /dev/null
+++ b/src/utils/src/__tests__/key-generation-utils/upload-public-keystore-to-s3.test.ts
@@ -0,0 +1,60 @@
+import { logger } from '../../logger';
+import {
+ asKeyStore,
+ uploadPublicKeystoreToS3,
+} from '../../key-generation-utils';
+import { putDataS3 } from '../../s3-utils';
+
+jest.mock('s3-utils');
+
+const mockKeystore = {
+ keys: [
+ {
+ use: 'sig',
+ alg: 'RS512',
+ kty: 'RSA',
+ kid: 'Okfvj_Bm5PTZRQrMObDxR4_ytgt-1UgHmnaIs0ELcWM',
+ e: 'AQAB',
+ n: '2xDHfn-vcGl6s2MvGrcY74v9hgQnKgmJyDlV310lRQCEEhtQcDCyXhwj1pNf05y03fLyoQnzUi7JBoZHgUfoFX-5IFQBdLjtalB6eIhXLAtXqQ75VrikP3xlHZE3sn_l75wz5M12QYSBZBzAN570NCbs0541XExoMMgZBzCF7wE',
+ },
+ {
+ use: 'sig',
+ alg: 'RS512',
+ kty: 'RSA',
+ kid: 'sS784n6mE_DAjgFRfqZXrO-G5y-2zlmM-DBeMXvDTjM',
+ e: 'AQAB',
+ n: 'sYQa7uSeKUo_Sw8f3_CLPTJ9DbgqyhnJSktrkO3Qq0Jtd8P5Qen8c-Q_-zcj6ufGWcFOzsAR5P99dRAfGU9fZ5u6twsD18jJz8ddKdPL9Rym80i4fdfYt6_amr1VaukgEUmdrFlHLEaXnlF8ofqOKOOHt-a8VMw6St-Deot-rf0',
+ },
+ ],
+};
+
+const setup = async () => {
+ const mockPutDataS3 = jest.fn();
+ (putDataS3 as jest.Mock).mockImplementation(mockPutDataS3);
+
+ const keystore = await asKeyStore(mockKeystore);
+
+ return { mockPutDataS3, keystore };
+};
+
+describe('generateNewKey', () => {
+ beforeEach(() => {
+ jest.resetAllMocks();
+ jest.spyOn(logger, 'info').mockImplementation(() => logger);
+ });
+
+ it('upload keystore on S3', async () => {
+ const { keystore, mockPutDataS3 } = await setup();
+
+ await uploadPublicKeystoreToS3({
+ jwksFileName: 'jwks.json',
+ keystore,
+ staticAssetBucket: 'static-bucket-name',
+ });
+
+ expect(mockPutDataS3).toHaveBeenCalledWith(mockKeystore, {
+ Bucket: 'static-bucket-name',
+ Key: 'jwks.json',
+ });
+ });
+});
diff --git a/src/utils/src/__tests__/key-generation-utils/validate-private-key.test.ts b/src/utils/src/__tests__/key-generation-utils/validate-private-key.test.ts
new file mode 100644
index 00000000..e7de2faa
--- /dev/null
+++ b/src/utils/src/__tests__/key-generation-utils/validate-private-key.test.ts
@@ -0,0 +1,126 @@
+import {
+ ValidateKeyResult,
+ asKey,
+ validatePrivateKey,
+} from '../../key-generation-utils';
+
+const testPrivateKey =
+ '-----BEGIN EC PRIVATE KEY-----\n' + // gitleaks:allow This is a false positive, the string is not a secret and is used for testing purposes only.
+ 'MHcCAQEEIEpVnqrylY4xEsQdQgJhGFUFKGTGtl5cnKsIq2uNWa56oAoGCCqGSM49\n' +
+ 'AwEHoUQDQgAEqoc8zybajz/NEoUzP5G7lchuuD7dej7vKlWConh1mvI9gvmyRheT\n' +
+ '0vrkuPszvyLXTYusKKgiLZkqz3SHOjVhDw==\n' +
+ '-----END EC PRIVATE KEY-----\n';
+
+const testKid = 'test-key-id';
+
+describe('validatePrivateKey', () => {
+ beforeEach(() => {
+ jest.useFakeTimers();
+ });
+
+ it('rejects empty parameter name', async () => {
+ const testOutput = await validatePrivateKey({
+ Name: '',
+ Value: testPrivateKey,
+ minIssueDate: new Date('2020-11-15'),
+ now: new Date('2020-12-05'),
+ });
+
+ const expectedOutput: ValidateKeyResult = {
+ valid: false,
+ deleteReason:
+ 'Does not match the name format privatekey__.pem',
+ warn: true,
+ };
+
+ expect(testOutput).toEqual(expectedOutput);
+ });
+
+ it('rejects invalid parameter name', async () => {
+ const testOutput = await validatePrivateKey({
+ Name: 'bad-param-name',
+ Value: testPrivateKey,
+ minIssueDate: new Date('2020-11-15'),
+ now: new Date('2020-12-05'),
+ });
+
+ const expectedOutput: ValidateKeyResult = {
+ valid: false,
+ deleteReason:
+ 'Does not match the name format privatekey__.pem',
+ warn: true,
+ };
+
+ expect(testOutput).toEqual(expectedOutput);
+ });
+
+ it('rejects invalid parameter date', async () => {
+ const testOutput = await validatePrivateKey({
+ Name: 'privatekey_99999999_123.pem',
+ Value: testPrivateKey,
+ minIssueDate: new Date('2020-11-15'),
+ now: new Date('2020-12-05'),
+ });
+
+ const expectedOutput: ValidateKeyResult = {
+ valid: false,
+ deleteReason: "'99999999' is not a valid yyyyMMdd date",
+ warn: true,
+ };
+
+ expect(testOutput).toEqual(expectedOutput);
+ });
+
+ it('rejects expired key', async () => {
+ const testOutput = await validatePrivateKey({
+ Name: 'privatekey_20201104_123.pem',
+ Value: testPrivateKey,
+ minIssueDate: new Date('2020-11-15'),
+ now: new Date('2020-12-05'),
+ });
+
+ const expectedOutput: ValidateKeyResult = {
+ valid: false,
+ deleteReason: 'Key expired, keyDateString: 20201104',
+ };
+
+ expect(testOutput).toEqual(expectedOutput);
+ });
+
+ it('rejects invalid key', async () => {
+ const testOutput = await validatePrivateKey({
+ Name: 'privatekey_20201120_123.pem',
+ Value: 'invalid-pem',
+ minIssueDate: new Date('2020-11-15'),
+ now: new Date('2020-12-05'),
+ });
+
+ const expectedOutput: ValidateKeyResult = {
+ valid: false,
+ deleteReason:
+ 'Could not parse pem value, Error: Invalid PEM formatted message.',
+ warn: true,
+ };
+
+ expect(testOutput).toEqual(expectedOutput);
+ });
+
+ it('accepts valid key', async () => {
+ const testPrivateKeyJwk = await asKey(testKid, testPrivateKey);
+
+ const testOutput = await validatePrivateKey({
+ Name: `privatekey_20201120_${testKid}.pem`,
+ Value: testPrivateKey,
+ minIssueDate: new Date('2020-11-15'),
+ now: new Date('2020-12-05'),
+ });
+
+ const expectedOutput: ValidateKeyResult = {
+ valid: true,
+ keyDate: new Date('2020-11-20'),
+ keyJwk: testPrivateKeyJwk,
+ };
+
+ expect(testOutput).toEqual(expectedOutput);
+ });
+});
diff --git a/src/utils/src/__tests__/lambda-utils/get-apim-access-token.test.ts b/src/utils/src/__tests__/lambda-utils/get-apim-access-token.test.ts
new file mode 100644
index 00000000..5d39a056
--- /dev/null
+++ b/src/utils/src/__tests__/lambda-utils/get-apim-access-token.test.ts
@@ -0,0 +1,185 @@
+import { mockDeep } from 'jest-mock-extended';
+import { IParameterStore } from 'ssm-utils';
+import { logger } from 'logger';
+import { createGetApimAccessToken } from 'lambda-utils';
+import type { ApimAccessToken } from 'lambda-utils/types';
+
+const NOW = new Date('2022-01-01').valueOf();
+
+const tokenPath = '/ssm/path/token';
+
+const validAccessToken: ApimAccessToken = {
+ access_token: '123',
+ expires_at: NOW / 1000 + 15,
+ token_type: 'Bearer',
+};
+
+const expiringAccessToken: ApimAccessToken = {
+ access_token: '123',
+ expires_at: NOW / 1000 + 14,
+ token_type: 'Bearer',
+};
+
+const expiredAccessToken: ApimAccessToken = {
+ access_token: '123',
+ expires_at: NOW / 1000 - 1,
+ token_type: 'Bearer',
+};
+
+beforeAll(() => {
+ jest.useFakeTimers();
+ jest.setSystemTime(NOW);
+});
+
+afterAll(() => {
+ jest.useRealTimers();
+});
+
+function setup() {
+ const log = logger;
+ const parameterStore = mockDeep();
+
+ const mocks = {
+ log,
+ parameterStore,
+ };
+
+ const getApimAccessToken = createGetApimAccessToken(
+ tokenPath,
+ log,
+ parameterStore,
+ );
+
+ return { getApimAccessToken, mocks };
+}
+
+describe('createGetApimAccessToken', () => {
+ test('access token does not need refreshing', async () => {
+ const { getApimAccessToken, mocks } = setup();
+
+ mocks.parameterStore.getParameter.mockResolvedValueOnce({
+ Value: JSON.stringify(validAccessToken),
+ Version: 1,
+ });
+
+ const accessToken = await getApimAccessToken();
+
+ expect(mocks.parameterStore.getParameter).toHaveBeenCalledTimes(1);
+ expect(mocks.parameterStore.getParameter).toHaveBeenCalledWith(tokenPath);
+
+ expect(accessToken).toEqual(validAccessToken.access_token);
+ });
+
+ test('access token needs refreshing', async () => {
+ const { getApimAccessToken, mocks } = setup();
+
+ mocks.parameterStore.getParameter
+ .mockResolvedValueOnce({
+ Value: JSON.stringify(expiringAccessToken),
+ Version: 1,
+ })
+ .mockResolvedValueOnce({
+ Value: JSON.stringify(validAccessToken),
+ Version: 2,
+ });
+
+ const accessToken = await getApimAccessToken();
+
+ expect(mocks.parameterStore.getParameter).toHaveBeenCalledTimes(2);
+ expect(mocks.parameterStore.getParameter).toHaveBeenNthCalledWith(
+ 1,
+ tokenPath,
+ );
+ expect(mocks.parameterStore.getParameter).toHaveBeenNthCalledWith(
+ 2,
+ tokenPath,
+ );
+ expect(mocks.parameterStore.clearCachedParameter).toHaveBeenCalledTimes(1);
+ expect(mocks.parameterStore.clearCachedParameter).toHaveBeenCalledWith(
+ tokenPath,
+ 1,
+ );
+
+ expect(accessToken).toEqual(validAccessToken.access_token);
+ });
+
+ test('access token is not the correct format', async () => {
+ const { getApimAccessToken, mocks } = setup();
+
+ mocks.parameterStore.getParameter.mockResolvedValue({
+ Value: JSON.stringify({ ...validAccessToken, expires_at: undefined }),
+ Version: 1,
+ });
+
+ await expect(getApimAccessToken()).rejects.toThrow('Invalid token');
+ });
+
+ test('access token parameter is not found in SSM', async () => {
+ const { getApimAccessToken, mocks } = setup();
+
+ mocks.parameterStore.getParameter.mockResolvedValue({
+ Value: undefined,
+ Version: 1,
+ });
+
+ await expect(getApimAccessToken()).rejects.toThrow(
+ `APIM access token parameter "/ssm/path/token" not found in SSM`,
+ );
+ });
+
+ test('access token cannot be refreshed but is not expired', async () => {
+ const { getApimAccessToken, mocks } = setup();
+
+ mocks.parameterStore.getParameter.mockResolvedValue({
+ Value: JSON.stringify(expiringAccessToken),
+ Version: 1,
+ });
+
+ const accessToken = await getApimAccessToken();
+
+ expect(mocks.parameterStore.getParameter).toHaveBeenCalledTimes(2);
+ expect(mocks.parameterStore.getParameter).toHaveBeenNthCalledWith(
+ 1,
+ tokenPath,
+ );
+ expect(mocks.parameterStore.getParameter).toHaveBeenNthCalledWith(
+ 2,
+ tokenPath,
+ );
+ expect(mocks.parameterStore.clearCachedParameter).toHaveBeenCalledTimes(1);
+ expect(mocks.parameterStore.clearCachedParameter).toHaveBeenCalledWith(
+ tokenPath,
+ 1,
+ );
+
+ expect(accessToken).toEqual(expiringAccessToken.access_token);
+ });
+
+ test('access token cannot be refreshed and is expired', async () => {
+ const { getApimAccessToken, mocks } = setup();
+
+ mocks.parameterStore.getParameter.mockResolvedValue({
+ Value: JSON.stringify(expiredAccessToken),
+ Version: 1,
+ });
+
+ await expect(getApimAccessToken()).rejects.toThrow(
+ 'Failed to update token',
+ );
+
+ expect(mocks.parameterStore.getParameter).toHaveBeenCalledTimes(2);
+ expect(mocks.parameterStore.getParameter).toHaveBeenNthCalledWith(
+ 1,
+ tokenPath,
+ );
+ expect(mocks.parameterStore.getParameter).toHaveBeenNthCalledWith(
+ 2,
+ tokenPath,
+ );
+ expect(mocks.parameterStore.clearCachedParameter).toHaveBeenCalledTimes(1);
+ expect(mocks.parameterStore.clearCachedParameter).toHaveBeenCalledWith(
+ tokenPath,
+ 1,
+ );
+ });
+});
diff --git a/src/utils/src/__tests__/lambda-utils/lambda-client.test.ts b/src/utils/src/__tests__/lambda-utils/lambda-client.test.ts
new file mode 100644
index 00000000..cb4cc5c8
--- /dev/null
+++ b/src/utils/src/__tests__/lambda-utils/lambda-client.test.ts
@@ -0,0 +1,7 @@
+import { lambdaClient } from 'lambda-utils';
+
+describe('SQS Client Util', () => {
+ test('should produce a default SQS Client', () => {
+ expect(lambdaClient).toBeTruthy();
+ });
+});
diff --git a/src/utils/src/__tests__/logger.test.ts b/src/utils/src/__tests__/logger.test.ts
new file mode 100644
index 00000000..4b331091
--- /dev/null
+++ b/src/utils/src/__tests__/logger.test.ts
@@ -0,0 +1,10 @@
+import { logger } from 'logger';
+
+describe('Logger Util', () => {
+ test('should produce a logger', () => {
+ expect(logger.info).toBeTruthy();
+ expect(logger.error).toBeTruthy();
+ expect(logger.warn).toBeTruthy();
+ expect(logger.debug).toBeTruthy();
+ });
+});
diff --git a/src/utils/src/__tests__/s3-utils/copy-and-delete-object-s3.test.ts b/src/utils/src/__tests__/s3-utils/copy-and-delete-object-s3.test.ts
new file mode 100644
index 00000000..7a65841b
--- /dev/null
+++ b/src/utils/src/__tests__/s3-utils/copy-and-delete-object-s3.test.ts
@@ -0,0 +1,34 @@
+import { mockClient } from 'aws-sdk-client-mock';
+import 'aws-sdk-client-mock-jest';
+import {
+ CopyObjectCommand,
+ DeleteObjectCommand,
+ S3Client,
+} from '@aws-sdk/client-s3';
+import { copyAndDeleteObjectS3 } from '../../s3-utils/copy-and-delete-object-s3';
+
+const s3Client = mockClient(S3Client);
+
+it('puts data in S3', async () => {
+ await copyAndDeleteObjectS3(
+ {
+ Bucket: 'sourceBucket',
+ Key: 'sourceKey',
+ },
+ {
+ Bucket: 'destinationBucket',
+ Key: 'destinationKey',
+ },
+ );
+
+ expect(s3Client).toHaveReceivedCommandWith(CopyObjectCommand, {
+ Bucket: 'destinationBucket',
+ CopySource: '/sourceBucket/sourceKey',
+ Key: 'destinationKey',
+ });
+
+ expect(s3Client).toHaveReceivedCommandWith(DeleteObjectCommand, {
+ Bucket: 'sourceBucket',
+ Key: 'sourceKey',
+ });
+});
diff --git a/src/utils/src/__tests__/s3-utils/get-object-s3.test.ts b/src/utils/src/__tests__/s3-utils/get-object-s3.test.ts
new file mode 100644
index 00000000..aa6e7014
--- /dev/null
+++ b/src/utils/src/__tests__/s3-utils/get-object-s3.test.ts
@@ -0,0 +1,344 @@
+import { Readable } from 'node:stream';
+import {
+ getS3Object,
+ getS3ObjectBufferFromUri,
+ getS3ObjectFromUri,
+ getS3ObjectMetadata,
+ s3Client,
+} from '../../s3-utils';
+
+describe('getS3Object', () => {
+ afterEach(jest.resetAllMocks);
+
+ it('Should throw an error if invalid key', async () => {
+ s3Client.send = jest.fn().mockImplementationOnce(() => {
+ throw new Error('No file found');
+ });
+
+ await expect(
+ getS3Object({
+ Bucket: 'bucket-name',
+ Key: 'config.test.json',
+ }),
+ ).rejects.toThrow(
+ "Could not retrieve from bucket 's3://bucket-name/config.test.json' from S3: Could not retrieve from bucket 's3://bucket-name/config.test.json' from S3: No file found",
+ );
+ });
+
+ it('Should return config', async () => {
+ const result = JSON.stringify({
+ featureFlags: {
+ testFlag: true,
+ },
+ });
+
+ s3Client.send = jest
+ .fn()
+ .mockReturnValueOnce({ Body: Readable.from([result]) });
+
+ const s3Location = {
+ Bucket: 'bucket-name',
+ Key: 'config.test.json',
+ };
+
+ const data = await getS3Object(s3Location);
+
+ expect(s3Client.send).toHaveBeenCalledWith(
+ expect.objectContaining({ input: s3Location }),
+ );
+ expect(data).toEqual(result);
+ });
+
+ it('Should return config by S3 version', async () => {
+ const result = JSON.stringify({
+ featureFlags: {
+ testFlag: true,
+ },
+ });
+
+ s3Client.send = jest
+ .fn()
+ .mockReturnValueOnce({ Body: Readable.from([result]) });
+
+ const s3Location = {
+ Bucket: 'bucket-name',
+ Key: 'config.test.json',
+ VersionId: 'versionId',
+ };
+
+ const data = await getS3Object(s3Location);
+
+ expect(s3Client.send).toHaveBeenCalledWith(
+ expect.objectContaining({ input: s3Location }),
+ );
+ expect(data).toEqual(result);
+ });
+
+ it('Should return default when object does not exist', async () => {
+ const defaultValue = 'the default value';
+
+ s3Client.send = jest.fn().mockImplementationOnce(() => {
+ throw new Error('not found');
+ });
+
+ const data = await getS3Object(
+ {
+ Bucket: 'bucket-name',
+ Key: 'config.test.json',
+ },
+ defaultValue,
+ );
+
+ expect(data).toEqual(defaultValue);
+ });
+});
+
+describe('getS3ObjectFromUri', () => {
+ afterEach(jest.resetAllMocks);
+
+ it('Should throw an error for invalid S3 URI format', async () => {
+ await expect(getS3ObjectFromUri('invalid-uri')).rejects.toThrow(
+ 'Invalid S3 URI format: invalid-uri',
+ );
+ });
+
+ it('Should throw an error for S3 URI without bucket', async () => {
+ await expect(getS3ObjectFromUri('s3://')).rejects.toThrow(
+ 'Invalid S3 URI format: s3://',
+ );
+ });
+
+ it('Should throw an error for S3 URI without key', async () => {
+ await expect(getS3ObjectFromUri('s3://bucket-name/')).rejects.toThrow(
+ 'Invalid S3 URI format: s3://bucket-name/',
+ );
+ });
+
+ it('Should parse valid S3 URI and retrieve object', async () => {
+ const result = JSON.stringify({
+ featureFlags: {
+ testFlag: true,
+ },
+ });
+
+ s3Client.send = jest
+ .fn()
+ .mockReturnValueOnce({ Body: Readable.from([result]) });
+
+ const uri = 's3://bucket-name/config.test.json';
+ const data = await getS3ObjectFromUri(uri);
+
+ expect(s3Client.send).toHaveBeenCalledWith(
+ expect.objectContaining({
+ input: {
+ Bucket: 'bucket-name',
+ Key: 'config.test.json',
+ VersionId: undefined,
+ },
+ }),
+ );
+ expect(data).toEqual(result);
+ });
+
+ it('Should parse S3 URI with nested path', async () => {
+ const result = 'test content';
+
+ s3Client.send = jest
+ .fn()
+ .mockReturnValueOnce({ Body: Readable.from([result]) });
+
+ const uri = 's3://bucket-name/path/to/nested/file.json';
+ const data = await getS3ObjectFromUri(uri);
+
+ expect(s3Client.send).toHaveBeenCalledWith(
+ expect.objectContaining({
+ input: {
+ Bucket: 'bucket-name',
+ Key: 'path/to/nested/file.json',
+ VersionId: undefined,
+ },
+ }),
+ );
+ expect(data).toEqual(result);
+ });
+
+ it('Should throw an error if object not found', async () => {
+ s3Client.send = jest.fn().mockImplementationOnce(() => {
+ throw new Error('No file found');
+ });
+
+ await expect(
+ getS3ObjectFromUri('s3://bucket-name/config.test.json'),
+ ).rejects.toThrow(
+ "Could not retrieve from bucket 's3://bucket-name/config.test.json' from S3",
+ );
+ });
+});
+
+describe('getS3ObjectBufferFromUri', () => {
+ afterEach(jest.resetAllMocks);
+
+ it('Should throw an error for invalid S3 URI format', async () => {
+ await expect(getS3ObjectBufferFromUri('invalid-uri')).rejects.toThrow(
+ 'Invalid S3 URI format: invalid-uri',
+ );
+ });
+
+ it('Should throw an error for S3 URI without bucket', async () => {
+ await expect(getS3ObjectBufferFromUri('s3://')).rejects.toThrow(
+ 'Invalid S3 URI format: s3://',
+ );
+ });
+
+ it('Should throw an error for S3 URI without key', async () => {
+ await expect(getS3ObjectBufferFromUri('s3://bucket-name/')).rejects.toThrow(
+ 'Invalid S3 URI format: s3://bucket-name/',
+ );
+ });
+
+ it('Should parse valid S3 URI and retrieve object', async () => {
+ const result = JSON.stringify({
+ featureFlags: {
+ testFlag: true,
+ },
+ });
+
+ s3Client.send = jest
+ .fn()
+ .mockReturnValueOnce({ Body: Readable.from([result]) });
+
+ const uri = 's3://bucket-name/config.test.json';
+ const data = await getS3ObjectBufferFromUri(uri);
+
+ expect(s3Client.send).toHaveBeenCalledWith(
+ expect.objectContaining({
+ input: {
+ Bucket: 'bucket-name',
+ Key: 'config.test.json',
+ VersionId: undefined,
+ },
+ }),
+ );
+
+ const expectedBuffer = Buffer.from(result);
+ expect(data).toEqual(expectedBuffer);
+ });
+
+ it('Should parse S3 URI with nested path', async () => {
+ const result = 'test content';
+
+ s3Client.send = jest
+ .fn()
+ .mockReturnValueOnce({ Body: Readable.from([result]) });
+
+ const uri = 's3://bucket-name/path/to/nested/file.json';
+ const data = await getS3ObjectBufferFromUri(uri);
+
+ expect(s3Client.send).toHaveBeenCalledWith(
+ expect.objectContaining({
+ input: {
+ Bucket: 'bucket-name',
+ Key: 'path/to/nested/file.json',
+ VersionId: undefined,
+ },
+ }),
+ );
+
+ const expectedBuffer = Buffer.from(result);
+ expect(data).toEqual(expectedBuffer);
+ });
+
+ it('Should throw an error if object not found', async () => {
+ s3Client.send = jest.fn().mockImplementationOnce(() => {
+ throw new Error('No file found');
+ });
+
+ await expect(
+ getS3ObjectBufferFromUri('s3://bucket-name/config.test.json'),
+ ).rejects.toThrow(
+ "Could not retrieve from bucket 's3://bucket-name/config.test.json' from S3",
+ );
+ });
+});
+
+describe('getS3ObjectMetadata', () => {
+ afterEach(jest.resetAllMocks);
+
+ it('Should retrieve metadata for object', async () => {
+ const metadata = {
+ messagereference: 'test-ref-001',
+ senderid: 'SENDER_001',
+ createdat: '2026-01-19T12:00:00Z',
+ };
+
+ s3Client.send = jest.fn().mockReturnValueOnce({ Metadata: metadata });
+
+ const s3Location = {
+ Bucket: 'bucket-name',
+ Key: 'test-file.pdf',
+ };
+
+ const result = await getS3ObjectMetadata(s3Location);
+
+ expect(s3Client.send).toHaveBeenCalledWith(
+ expect.objectContaining({
+ input: s3Location,
+ }),
+ );
+ expect(result).toEqual(metadata);
+ });
+
+ it('Should retrieve metadata with version ID', async () => {
+ const metadata = {
+ customkey: 'customvalue',
+ };
+
+ s3Client.send = jest.fn().mockReturnValueOnce({ Metadata: metadata });
+
+ const s3Location = {
+ Bucket: 'bucket-name',
+ Key: 'versioned-file.json',
+ VersionId: 'version-123',
+ };
+
+ const result = await getS3ObjectMetadata(s3Location);
+
+ expect(s3Client.send).toHaveBeenCalledWith(
+ expect.objectContaining({
+ input: s3Location,
+ }),
+ );
+ expect(result).toEqual(metadata);
+ });
+
+ it('Should throw an error if object not found', async () => {
+ s3Client.send = jest.fn().mockImplementationOnce(() => {
+ throw new Error('NoSuchKey');
+ });
+
+ await expect(
+ getS3ObjectMetadata({
+ Bucket: 'bucket-name',
+ Key: 'nonexistent.pdf',
+ }),
+ ).rejects.toThrow(
+ "Could not retrieve metadata from bucket 's3://bucket-name/nonexistent.pdf' from S3: NoSuchKey",
+ );
+ });
+
+ it('Should handle error objects', async () => {
+ const error = new Error('Access Denied');
+ s3Client.send = jest.fn().mockImplementationOnce(() => {
+ throw error;
+ });
+
+ await expect(
+ getS3ObjectMetadata({
+ Bucket: 'bucket-name',
+ Key: 'forbidden.pdf',
+ }),
+ ).rejects.toThrow(
+ "Could not retrieve metadata from bucket 's3://bucket-name/forbidden.pdf' from S3: Access Denied",
+ );
+ });
+});
diff --git a/src/utils/src/__tests__/s3-utils/put-data-s3.test.ts b/src/utils/src/__tests__/s3-utils/put-data-s3.test.ts
new file mode 100644
index 00000000..1263479e
--- /dev/null
+++ b/src/utils/src/__tests__/s3-utils/put-data-s3.test.ts
@@ -0,0 +1,46 @@
+import { mockClient } from 'aws-sdk-client-mock';
+import 'aws-sdk-client-mock-jest';
+import { PutObjectCommand, S3Client } from '@aws-sdk/client-s3';
+import { putDataS3 } from '../../s3-utils';
+
+describe('putDataS3', () => {
+ it('puts data in S3', async () => {
+ const s3Client = mockClient(S3Client);
+ await putDataS3(
+ {
+ value1: '1a',
+ value2: '2a',
+ },
+ {
+ Bucket: 'bucket-name',
+ Key: 'bucket-key',
+ },
+ );
+
+ expect(s3Client).toHaveReceivedCommandWith(PutObjectCommand, {
+ Bucket: 'bucket-name',
+ Key: 'bucket-key',
+ Body: '{\n "value1": "1a",\n "value2": "2a"\n}',
+ });
+ });
+
+ it('throws an error when there is an issue puts data in S3', async () => {
+ const s3Client = mockClient(S3Client);
+ s3Client.rejectsOnce(new Error('It broke!'));
+
+ await expect(
+ putDataS3(
+ {
+ value1: '1a',
+ value2: '2a',
+ },
+ {
+ Bucket: 'bucket-name',
+ Key: 'bucket-key',
+ },
+ ),
+ ).rejects.toThrow(
+ 'Upload to bucket-name/bucket-key failed, error: Error: It broke!',
+ );
+ });
+});
diff --git a/src/utils/src/__tests__/s3-utils/put-file-s3.test.ts b/src/utils/src/__tests__/s3-utils/put-file-s3.test.ts
new file mode 100644
index 00000000..85457499
--- /dev/null
+++ b/src/utils/src/__tests__/s3-utils/put-file-s3.test.ts
@@ -0,0 +1,82 @@
+import { mockClient } from 'aws-sdk-client-mock';
+import 'aws-sdk-client-mock-jest';
+import { PutObjectCommand, S3Client } from '@aws-sdk/client-s3';
+import { putFileS3 } from '../../s3-utils';
+
+describe('putFileS3', () => {
+ it('puts buffer in S3', async () => {
+ const s3Client = mockClient(S3Client);
+ const testBuffer = Buffer.from('test pdf content');
+
+ await putFileS3(testBuffer, {
+ Bucket: 'bucket-name',
+ Key: 'file.pdf',
+ });
+
+ expect(s3Client).toHaveReceivedCommandWith(PutObjectCommand, {
+ Bucket: 'bucket-name',
+ Key: 'file.pdf',
+ Body: testBuffer,
+ Metadata: {},
+ });
+ });
+
+ it('puts buffer in S3 with ContentType', async () => {
+ const s3Client = mockClient(S3Client);
+ const testBuffer = Buffer.from('test pdf content');
+
+ await putFileS3(
+ testBuffer,
+ {
+ Bucket: 'bucket-name',
+ Key: 'file.pdf',
+ },
+ {},
+ 'application/pdf',
+ );
+
+ expect(s3Client).toHaveReceivedCommandWith(PutObjectCommand, {
+ Bucket: 'bucket-name',
+ Key: 'file.pdf',
+ Body: testBuffer,
+ Metadata: {},
+ ContentType: 'application/pdf',
+ });
+ });
+
+ it('puts buffer in S3 with metadata', async () => {
+ const s3Client = mockClient(S3Client);
+ const testBuffer = Buffer.from('test pdf content');
+
+ await putFileS3(
+ testBuffer,
+ {
+ Bucket: 'bucket-name',
+ Key: 'file.pdf',
+ },
+ { 'x-custom-metadata': 'value' },
+ );
+
+ expect(s3Client).toHaveReceivedCommandWith(PutObjectCommand, {
+ Bucket: 'bucket-name',
+ Key: 'file.pdf',
+ Body: testBuffer,
+ Metadata: { 'x-custom-metadata': 'value' },
+ });
+ });
+
+ it('throws an error when there is an issue putting buffer in S3', async () => {
+ const s3Client = mockClient(S3Client);
+ s3Client.rejectsOnce(new Error('It broke!'));
+ const testBuffer = Buffer.from('test pdf content');
+
+ await expect(
+ putFileS3(testBuffer, {
+ Bucket: 'bucket-name',
+ Key: 'file.pdf',
+ }),
+ ).rejects.toThrow(
+ 'Upload to bucket-name/file.pdf failed, error: Error: It broke!',
+ );
+ });
+});
diff --git a/src/utils/src/__tests__/ssm-utils/parameter-filters.test.ts b/src/utils/src/__tests__/ssm-utils/parameter-filters.test.ts
new file mode 100644
index 00000000..c567094f
--- /dev/null
+++ b/src/utils/src/__tests__/ssm-utils/parameter-filters.test.ts
@@ -0,0 +1,17 @@
+import { nonNullParameterFilter } from '../../ssm-utils';
+
+describe('parameterFilters', () => {
+ test('nonNullParameterFilter returns true if parameter is not null or undefined', () => {
+ expect(
+ nonNullParameterFilter({ Name: 'some-name', Value: 'some-value' }),
+ ).toBe(true);
+ });
+ test('nonNullParameterFilter returns false if parameter is undefined', () => {
+ expect(
+ nonNullParameterFilter({ Name: undefined, Value: 'some-value' }),
+ ).toBe(false);
+ expect(
+ nonNullParameterFilter({ Name: 'some-name', Value: undefined }),
+ ).toBe(false);
+ });
+});
diff --git a/src/utils/src/__tests__/ssm-utils/parameter-store-cache.test.ts b/src/utils/src/__tests__/ssm-utils/parameter-store-cache.test.ts
new file mode 100644
index 00000000..804de7cb
--- /dev/null
+++ b/src/utils/src/__tests__/ssm-utils/parameter-store-cache.test.ts
@@ -0,0 +1,426 @@
+import {
+ Parameter,
+ ParameterNotFound,
+ ParameterType,
+} from '@aws-sdk/client-ssm';
+import { ParameterStore, ParameterStoreCache } from '../../ssm-utils';
+
+const mockReleaser = jest.fn();
+const mockAcquireLock = () => mockReleaser;
+const mockGet = jest.fn();
+const mockSet = jest.fn();
+const mockSetAll = jest.fn();
+const mockDelete = jest.fn();
+
+jest.mock('../../in-memory-cache', () => ({
+ InMemoryCache: jest.fn().mockImplementation(() => ({
+ acquireLock: mockAcquireLock,
+ get: mockGet,
+ set: mockSet,
+ setAll: mockSetAll,
+ delete: mockDelete,
+ })),
+}));
+
+const parameterStore = new ParameterStoreCache();
+const param = (name: string, value: string, version = 1): Parameter => ({
+ Name: name,
+ Value: value,
+ Version: version,
+});
+
+const undefinedParam = undefined as unknown as Parameter;
+
+const firstMyParamName = '/comms/myParams/one';
+const firstMyParam = param(firstMyParamName, 'some value for me one');
+
+const secondMyParamName = '/comms/myParams/two';
+const secondMyParam = param(secondMyParamName, 'some value for me two', 3);
+
+const firstYourParamName = '/comms/yourParams/one';
+const firstYourParam = param(firstYourParamName, 'some value for you one');
+
+const mockGetParamFromSourceSpy = jest.spyOn(
+ ParameterStore.prototype,
+ 'getParameter',
+);
+const mockGetAllParamsFromSourceSpy = jest.spyOn(
+ ParameterStore.prototype,
+ 'getAllParameters',
+);
+const mockAddParamToSourceSpy = jest.spyOn(
+ ParameterStore.prototype,
+ 'addParameter',
+);
+const mockDeleteParamFromSourceSpy = jest.spyOn(
+ ParameterStore.prototype,
+ 'deleteParameter',
+);
+
+describe('ParameterStoreCache', () => {
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ describe('getParameter', () => {
+ describe('When the cache is empty', () => {
+ test('returns the param from source', async () => {
+ mockGetParamFromSourceSpy.mockResolvedValue(firstMyParam);
+
+ const parameter = await parameterStore.getParameter(
+ '/comms/myParams/one',
+ );
+
+ expect(mockGetParamFromSourceSpy).toHaveBeenCalledTimes(1);
+ expect(parameter).toEqual(firstMyParam);
+ });
+
+ test('sets the retrieved param from source to cache', async () => {
+ mockGetParamFromSourceSpy.mockResolvedValue(firstMyParam);
+
+ await parameterStore.getParameter('/comms/myParams/one');
+
+ expect(mockSet).toHaveBeenCalledWith(
+ `name:${firstMyParamName}`,
+ firstMyParam,
+ );
+ });
+
+ test('should set param to cache if retrieved param is undefined', async () => {
+ mockGetParamFromSourceSpy.mockResolvedValue(undefinedParam);
+
+ await parameterStore.getParameter('/comms/myParams/noValue');
+
+ expect(mockSet).toHaveBeenCalled();
+ });
+
+ test('should return undefined if retrieved param is undefined', async () => {
+ mockGetParamFromSourceSpy.mockResolvedValue(undefinedParam);
+
+ const parameter = await parameterStore.getParameter(
+ '/comms/myParams/noValue',
+ );
+
+ expect(parameter).toEqual(undefined);
+ });
+
+ test('should set an error in the cache when one is encountered', async () => {
+ const e = new ParameterNotFound({ $metadata: {}, message: 'oops' });
+ mockGetParamFromSourceSpy.mockRejectedValue(e);
+
+ await expect(
+ parameterStore.getParameter('/comms/myParams/one'),
+ ).rejects.toThrow(e);
+ expect(mockSet).toHaveBeenCalled();
+ });
+ });
+
+ describe('When the force option is true', () => {
+ test('always fetch and return param from source even if param exists in cache', async () => {
+ mockGet.mockResolvedValue(firstMyParam);
+ mockGetParamFromSourceSpy.mockResolvedValue(firstMyParam);
+
+ const parameter = await parameterStore.getParameter(
+ '/comms/myParams/one',
+ { force: true },
+ );
+
+ expect(mockGet).not.toHaveBeenCalled();
+ expect(parameter).toEqual(firstMyParam);
+ });
+ });
+
+ describe('When the cache exists and when the force option is false', () => {
+ test('returns the param from cache that match the parameterName', async () => {
+ mockGet.mockResolvedValue(firstMyParam);
+
+ const parameter = await parameterStore.getParameter(
+ '/comms/myParams/one',
+ );
+
+ expect(mockGetParamFromSourceSpy).not.toHaveBeenCalled();
+ expect(parameter).toEqual(firstMyParam);
+ });
+
+ test('returns the param from source if param is not found in cache', async () => {
+ mockGet.mockResolvedValue(null);
+ mockGetParamFromSourceSpy.mockResolvedValue(secondMyParam);
+
+ const parameter = await parameterStore.getParameter(
+ '/comms/myParams/two',
+ );
+
+ expect(mockGetParamFromSourceSpy).toHaveBeenCalledTimes(1);
+ expect(parameter).toEqual(secondMyParam);
+ });
+
+ test('returns a cached error', async () => {
+ const e = new ParameterNotFound({ $metadata: {}, message: 'oops' });
+ mockGet.mockResolvedValue(e);
+
+ await expect(
+ parameterStore.getParameter('/comms/myParams/one'),
+ ).rejects.toThrow(e);
+ expect(mockGetParamFromSourceSpy).not.toHaveBeenCalled();
+ });
+ });
+ });
+
+ describe('getAllParameters', () => {
+ describe('When the cache is empty', () => {
+ test('returns the params from source', async () => {
+ mockGetAllParamsFromSourceSpy.mockResolvedValue([firstMyParam]);
+
+ const getAllParameters =
+ await parameterStore.getAllParameters('/comms/myParams/');
+
+ expect(mockGetAllParamsFromSourceSpy).toHaveBeenCalledTimes(1);
+ expect(getAllParameters).toEqual([firstMyParam]);
+ });
+
+ test('sets the retrieved params from source to name cache and path cache', async () => {
+ mockGetAllParamsFromSourceSpy.mockResolvedValue([
+ firstMyParam,
+ secondMyParam,
+ ]);
+
+ await parameterStore.getAllParameters('/comms/myParams/');
+
+ const expectedCache = new Map();
+ expectedCache.set(`name:${firstMyParamName}`, firstMyParam);
+ expectedCache.set(`name:${secondMyParamName}`, secondMyParam);
+ expectedCache.set(`path:/comms/myParams/*`, [
+ firstMyParam,
+ secondMyParam,
+ ]);
+
+ expect(mockSetAll).toHaveBeenCalledWith(expectedCache);
+ });
+
+ test('should set param in cache if it has no value', async () => {
+ const paramWithNoValue = param(firstMyParamName, '');
+ mockGetAllParamsFromSourceSpy.mockResolvedValue([paramWithNoValue]);
+
+ await parameterStore.getAllParameters('/comms/myParams/');
+
+ const expectedCache = new Map();
+ expectedCache.set(`name:${firstMyParamName}`, paramWithNoValue);
+ expectedCache.set(`path:/comms/myParams/*`, [paramWithNoValue]);
+ expect(mockSetAll).toHaveBeenCalledWith(expectedCache);
+ });
+
+ test('sets all the retrieved params that match the pathPrefix recursivley from source to name cache and path cache', async () => {
+ mockGetAllParamsFromSourceSpy.mockResolvedValue([
+ firstMyParam,
+ secondMyParam,
+ firstYourParam,
+ ]);
+
+ await parameterStore.getAllParameters('/comms/', { recursive: true });
+
+ const expectedCache = new Map();
+ expectedCache.set(`name:${firstMyParamName}`, firstMyParam);
+ expectedCache.set(`name:${secondMyParamName}`, secondMyParam);
+ expectedCache.set(`name:${firstYourParamName}`, firstYourParam);
+ expectedCache.set(`path:/comms/**/*`, [
+ firstMyParam,
+ secondMyParam,
+ firstYourParam,
+ ]);
+
+ expect(mockSetAll).toHaveBeenCalledWith(expectedCache);
+ });
+ });
+
+ describe('When the force option is true', () => {
+ test('always fetch and return params from source even if param exists in cache', async () => {
+ const existingCachedParams = new Map();
+ existingCachedParams.set(`name:${firstMyParamName}`, firstMyParam);
+ existingCachedParams.set(`path:/comms/myParams/*`, [firstMyParam]);
+ mockGet.mockResolvedValue(existingCachedParams);
+
+ mockGetAllParamsFromSourceSpy.mockResolvedValue([secondMyParam]);
+
+ const getAllParameters = await parameterStore.getAllParameters(
+ '/comms/myParams/',
+ { force: true },
+ );
+
+ expect(mockGet).not.toHaveBeenCalled();
+ expect(getAllParameters).toEqual([secondMyParam]);
+ });
+ });
+
+ describe('When the cache exists and when the force option is false', () => {
+ test('returns only the params from cache that start with the pathPrefix', async () => {
+ mockGet.mockResolvedValue([firstMyParam, secondMyParam]);
+
+ const getAllMyParameters =
+ await parameterStore.getAllParameters('/comms/myParams/');
+
+ expect(mockGet).toHaveBeenCalledWith(`path:/comms/myParams/*`);
+ expect(mockGetAllParamsFromSourceSpy).not.toHaveBeenCalled();
+ expect(getAllMyParameters).toEqual([firstMyParam, secondMyParam]);
+ });
+
+ test('returns all cached params that match the path prefix when recursive is true', async () => {
+ mockGet.mockResolvedValue([
+ firstMyParam,
+ secondMyParam,
+ firstYourParam,
+ ]);
+
+ const getAllMyParameters = await parameterStore.getAllParameters(
+ '/comms/',
+ { recursive: true },
+ );
+
+ expect(mockGet).toHaveBeenCalledWith(`path:/comms/**/*`);
+ expect(mockGetAllParamsFromSourceSpy).not.toHaveBeenCalled();
+ expect(getAllMyParameters).toEqual([
+ firstMyParam,
+ secondMyParam,
+ firstYourParam,
+ ]);
+ });
+
+ test('adds a trailing slash if pathPrefix is provided without one', async () => {
+ mockGet.mockResolvedValue([firstMyParam, secondMyParam]);
+
+ const getAllMyParameters =
+ await parameterStore.getAllParameters('/comms/myParams');
+
+ expect(mockGet).toHaveBeenCalledWith(`path:/comms/myParams/*`);
+ expect(mockGetAllParamsFromSourceSpy).not.toHaveBeenCalled();
+ expect(getAllMyParameters).toEqual([firstMyParam, secondMyParam]);
+ });
+ });
+ });
+
+ describe('addParameter', () => {
+ test('should add the param to the source', async () => {
+ mockAddParamToSourceSpy.mockResolvedValue(firstMyParam);
+
+ await parameterStore.addParameter(
+ firstMyParamName,
+ firstMyParam.Value!,
+ ParameterType.STRING,
+ true,
+ );
+
+ expect(mockAddParamToSourceSpy).toHaveBeenCalledTimes(1);
+ expect(mockAddParamToSourceSpy).toHaveBeenCalledWith(
+ firstMyParamName,
+ firstMyParam.Value!,
+ ParameterType.STRING,
+ true,
+ );
+ });
+
+ test('should add the param to the cache', async () => {
+ mockAddParamToSourceSpy.mockResolvedValue(firstMyParam);
+
+ await parameterStore.addParameter(
+ firstMyParamName,
+ firstMyParam.Value!,
+ ParameterType.STRING,
+ true,
+ );
+
+ expect(mockSet).toHaveBeenCalledWith(
+ `name:${firstMyParamName}`,
+ firstMyParam,
+ );
+ });
+
+ test('should return the added param', async () => {
+ mockAddParamToSourceSpy.mockResolvedValue(firstMyParam);
+
+ const addedParam = await parameterStore.addParameter(
+ firstMyParamName,
+ firstMyParam.Value!,
+ ParameterType.STRING,
+ true,
+ );
+
+ expect(addedParam).toEqual(firstMyParam);
+ });
+
+ test('does not cache if there was an error adding param to source', async () => {
+ const e = new Error('PutParameter Error');
+ mockAddParamToSourceSpy.mockRejectedValue(e);
+
+ await expect(
+ parameterStore.addParameter(firstMyParamName, firstMyParam.Value!),
+ ).rejects.toThrow(e);
+
+ expect(mockSet).not.toHaveBeenCalled();
+ });
+ });
+
+ describe('clearCachedParameter', () => {
+ test('clears SSM parameter from cache if found and no version specified', async () => {
+ mockGet.mockResolvedValue({ value: '12', version: 1 });
+
+ await parameterStore.clearCachedParameter('ssm-param');
+
+ expect(mockDelete).toHaveBeenCalledTimes(1);
+ expect(mockDelete).toHaveBeenCalledWith('name:ssm-param');
+ });
+
+ test('clears SSM parameter from cache if version matches', async () => {
+ mockGet.mockResolvedValue({
+ Name: 'ssm-param',
+ Value: 'some-value',
+ Version: 1,
+ });
+
+ await parameterStore.clearCachedParameter('ssm-param', 1);
+
+ expect(mockDelete).toHaveBeenCalledWith('name:ssm-param');
+ });
+
+ test('does not clear from cache if cached version does not match specified version', async () => {
+ mockGet.mockResolvedValue({
+ Name: 'ssm-param',
+ Value: 'some-value',
+ Version: 2,
+ });
+
+ await parameterStore.clearCachedParameter('ssm-param', 1);
+
+ expect(mockDelete).not.toHaveBeenCalled();
+ });
+ });
+
+ describe('deleteParameter', () => {
+ test('deletes the parameter from the source', async () => {
+ mockDeleteParamFromSourceSpy.mockResolvedValue();
+
+ await parameterStore.deleteParameter('ssm-param');
+
+ expect(mockDeleteParamFromSourceSpy).toHaveBeenCalledTimes(1);
+ expect(mockDeleteParamFromSourceSpy).toHaveBeenCalledWith('ssm-param');
+ });
+
+ test('deletes the parameter from the cache', async () => {
+ mockDeleteParamFromSourceSpy.mockResolvedValue();
+
+ await parameterStore.deleteParameter('ssm-param');
+
+ expect(mockDelete).toHaveBeenCalledTimes(1);
+ expect(mockDelete).toHaveBeenCalledWith('name:ssm-param');
+ });
+
+ test('does not delete from cache if ssm delete fails', async () => {
+ const e = new Error('DeleteError');
+ mockDeleteParamFromSourceSpy.mockRejectedValue(e);
+
+ await expect(
+ parameterStore.deleteParameter(firstMyParamName),
+ ).rejects.toThrow(e);
+
+ expect(mockDelete).not.toHaveBeenCalled();
+ });
+ });
+});
diff --git a/src/utils/src/__tests__/ssm-utils/parameter-store.test.ts b/src/utils/src/__tests__/ssm-utils/parameter-store.test.ts
new file mode 100644
index 00000000..9c93b4c2
--- /dev/null
+++ b/src/utils/src/__tests__/ssm-utils/parameter-store.test.ts
@@ -0,0 +1,206 @@
+import 'aws-sdk-client-mock-jest';
+import { mockClient } from 'aws-sdk-client-mock';
+import {
+ DeleteParameterCommand,
+ GetParameterCommand,
+ GetParametersByPathCommand,
+ ParameterNotFound,
+ ParameterType,
+ PutParameterCommand,
+ SSMClient,
+} from '@aws-sdk/client-ssm';
+
+import { ParameterStore } from '../../ssm-utils';
+
+const ssmClientMock = mockClient(SSMClient);
+
+const parameterStore = new ParameterStore();
+
+beforeEach(() => {
+ ssmClientMock.reset();
+});
+
+describe('deleteParameter', () => {
+ test('deletes SSM parameter', async () => {
+ await parameterStore.deleteParameter('ssm-param');
+
+ expect(ssmClientMock).toHaveReceivedCommandWith(DeleteParameterCommand, {
+ Name: 'ssm-param',
+ });
+ });
+
+ test('does not throw ParameterNotFound exception', async () => {
+ const e = new ParameterNotFound({ $metadata: {}, message: 'oops' });
+ ssmClientMock.on(DeleteParameterCommand).rejects(e);
+
+ await expect(
+ parameterStore.deleteParameter('ssm-param'),
+ ).resolves.toBeUndefined();
+ });
+
+ test('raises other exceptions', async () => {
+ const e = new Error('oops');
+ ssmClientMock.on(DeleteParameterCommand).rejects(e);
+
+ await expect(() =>
+ parameterStore.deleteParameter('ssm-param'),
+ ).rejects.toBe(e);
+ });
+});
+
+describe('addParameter', () => {
+ beforeEach(() => {
+ ssmClientMock.on(PutParameterCommand).resolves({ Version: 1 });
+ });
+
+ test('adds SSM parameter with defaults', async () => {
+ await parameterStore.addParameter('ssm-param', '12');
+
+ expect(ssmClientMock).toHaveReceivedCommandWith(PutParameterCommand, {
+ Name: 'ssm-param',
+ Value: '12',
+ Type: 'SecureString',
+ Overwrite: true,
+ });
+ });
+
+ test('adds SSM parameter with given override and type', async () => {
+ await parameterStore.addParameter(
+ 'ssm-param',
+ '12',
+ ParameterType.STRING,
+ false,
+ );
+
+ expect(ssmClientMock).toHaveReceivedCommandWith(PutParameterCommand, {
+ Name: 'ssm-param',
+ Value: '12',
+ Type: 'String',
+ Overwrite: false,
+ });
+ });
+
+ test('returns the created parameter', async () => {
+ const parameter = await parameterStore.addParameter('ssm-param', '12');
+
+ expect(parameter).toEqual({
+ Name: 'ssm-param',
+ Value: '12',
+ Version: 1,
+ });
+ });
+});
+
+describe('getAllParameters', () => {
+ test('gets SSM parameters by path', async () => {
+ const mockParameters = [
+ {
+ Name: 'ssm-param-1',
+ Value: '13',
+ Version: 1,
+ },
+ {
+ Name: 'ssm-param-2',
+ Value: '14',
+ Version: 22,
+ },
+ ];
+
+ ssmClientMock.on(GetParametersByPathCommand).resolvesOnce({
+ Parameters: mockParameters,
+ });
+
+ const params = await parameterStore.getAllParameters('ssm-path');
+
+ expect(ssmClientMock).toHaveReceivedCommandWith(
+ GetParametersByPathCommand,
+ {
+ Path: 'ssm-path',
+ },
+ );
+
+ expect(params).toEqual(mockParameters);
+ });
+
+ test('throws an error if failure to read from SSM path)', async () => {
+ ssmClientMock
+ .on(GetParametersByPathCommand)
+ .rejectsOnce(new Error('It broke!'));
+
+ await expect(parameterStore.getAllParameters('/some/path')).rejects.toThrow(
+ 'Failed to read SSM from path /some/path. ERR: Error: It broke!',
+ );
+ });
+});
+
+describe('getParameter', () => {
+ test('gets an SSM parameter', async () => {
+ const parameter = {
+ Name: 'ssm-param',
+ Value: '12',
+ Version: 1,
+ };
+
+ ssmClientMock.on(GetParameterCommand).resolvesOnce({
+ Parameter: parameter,
+ });
+
+ const result = await parameterStore.getParameter('ssm-param');
+
+ expect(ssmClientMock).toHaveReceivedCommandWith(GetParameterCommand, {
+ Name: 'ssm-param',
+ WithDecryption: true,
+ });
+
+ expect(result).toEqual(parameter);
+ });
+
+ test('retries getting an SSM parameter on throttling exception', async () => {
+ jest.useFakeTimers();
+
+ const parameter = {
+ Name: 'ssm-param',
+ Value: '12',
+ Version: 1,
+ };
+
+ const error = new Error('some error');
+ error.name = 'ThrottlingException';
+
+ ssmClientMock.on(GetParameterCommand).rejectsOnce(error).resolvesOnce({
+ Parameter: parameter,
+ });
+
+ const promise = parameterStore.getParameter('ssm-param', {
+ maxAttempts: 2,
+ });
+
+ expect(ssmClientMock).toHaveReceivedCommandTimes(GetParameterCommand, 1);
+ expect(ssmClientMock).toHaveReceivedCommandWith(GetParameterCommand, {
+ Name: 'ssm-param',
+ WithDecryption: true,
+ });
+
+ await jest.advanceTimersByTimeAsync(3000);
+
+ expect(ssmClientMock).toHaveReceivedCommandTimes(GetParameterCommand, 2);
+
+ expect(await promise).toEqual(parameter);
+
+ jest.useRealTimers();
+ });
+
+ test('fails getting an SSM parameter on other exception', async () => {
+ const e = new Error('some error');
+ ssmClientMock.on(GetParameterCommand).rejectsOnce(e);
+
+ await expect(
+ parameterStore.getParameter('ssm-param', { maxAttempts: 2 }),
+ ).rejects.toThrow(e);
+
+ expect(ssmClientMock).toHaveReceivedCommandWith(GetParameterCommand, {
+ Name: 'ssm-param',
+ WithDecryption: true,
+ });
+ });
+});
diff --git a/src/utils/src/cache/cache.ts b/src/utils/src/cache/cache.ts
new file mode 100644
index 00000000..d3ba66e8
--- /dev/null
+++ b/src/utils/src/cache/cache.ts
@@ -0,0 +1,66 @@
+import { defaultConfigReader as config } from '../config-reader';
+
+const DEFAULT_CACHE_TIME_MILLISECONDS = 60_000 * 10; // 10 minutes
+
+type Result = {
+ value: T;
+ cacheTime?: number;
+};
+
+const newCache = (
+ getCurrentTime: () => Date,
+ fetch: (k: KeyType) => Promise>,
+) => {
+ const cacheTimeMilliSec =
+ config.tryGetInt('DEFAULT_CACHE_TIME_MILLISECONDS') ??
+ DEFAULT_CACHE_TIME_MILLISECONDS;
+
+ type Holder = {
+ value: ValueType;
+ timestamp: Date | undefined;
+ };
+
+ type State = Record;
+
+ let state: State = {};
+
+ let stateCacheTime = 0;
+
+ async function getCachedAsync(key: KeyType): Promise {
+ const flatKey: string = typeof key === 'string' ? key : JSON.stringify(key);
+
+ const currentTime = getCurrentTime();
+ const holder: Holder = state[flatKey];
+ if (
+ holder?.timestamp &&
+ // eslint-disable-next-line sonarjs/different-types-comparison
+ stateCacheTime !== undefined &&
+ currentTime.getTime() - holder.timestamp.getTime() < stateCacheTime
+ ) {
+ return holder.value;
+ }
+
+ // value not cached or expired, so fetch a new value from upstream
+ const result: Result = await fetch(key);
+
+ const cacheTime = result.cacheTime ?? cacheTimeMilliSec;
+
+ if (cacheTime) {
+ stateCacheTime = cacheTime;
+ }
+
+ state[flatKey] = {
+ value: result.value,
+ timestamp: currentTime,
+ };
+ return result.value;
+ }
+
+ function clear() {
+ state = {};
+ }
+
+ return { getCachedAsync, clear };
+};
+
+export { newCache };
diff --git a/src/utils/src/cache/index.ts b/src/utils/src/cache/index.ts
new file mode 100644
index 00000000..77e55d4e
--- /dev/null
+++ b/src/utils/src/cache/index.ts
@@ -0,0 +1 @@
+export * from './cache';
diff --git a/src/utils/src/config-reader.ts b/src/utils/src/config-reader.ts
new file mode 100644
index 00000000..25a396e8
--- /dev/null
+++ b/src/utils/src/config-reader.ts
@@ -0,0 +1,173 @@
+/* eslint-disable @typescript-eslint/no-use-before-define */
+type ConfigResult = {
+ readonly key: string;
+ readonly status: 'success' | 'missing' | 'errored';
+ value: () => T;
+ valueOrNull: () => T | null;
+};
+
+function success(key: string, value: T): ConfigResult {
+ return {
+ key,
+ status: 'success',
+ value: () => value,
+ valueOrNull: () => value,
+ };
+}
+
+function missing(key: string): ConfigResult {
+ return {
+ key,
+ status: 'missing',
+ value: () => {
+ throw new Error(`${key} must be defined`);
+ },
+ valueOrNull: () => null,
+ };
+}
+
+function errored(key: string, error: Error): ConfigResult {
+ return {
+ key,
+ status: 'errored',
+ value: () => {
+ throw error;
+ },
+ valueOrNull: () => {
+ throw error;
+ },
+ };
+}
+
+function extendNumberResult(
+ result: ConfigResult,
+): ConfigResult & {
+ validateGreaterThan: (
+ bound: number,
+ ) => ReturnType;
+} {
+ return {
+ ...result,
+ validateGreaterThan: (bound: number) => validateGreaterThan(result, bound),
+ };
+}
+
+function extendStringResult(
+ result: ConfigResult,
+): ConfigResult & {
+ asInt: () => ReturnType;
+ asBoolean: () => ReturnType;
+} {
+ return {
+ ...result,
+ asInt: () => extendNumberResult(asInt(result)),
+ asBoolean: () => asBoolean(result),
+ };
+}
+
+function read(key: string) {
+ const value = process.env[key];
+
+ const result: ConfigResult =
+ typeof value === 'string' ? success(key, value.trim()) : missing(key);
+
+ return extendStringResult(result);
+}
+
+function asInt(result: ConfigResult): ConfigResult {
+ if (result.status === 'success') {
+ const { key } = result;
+ const value = result.value();
+
+ if (/^[+-]?(\d+)$/.test(value)) {
+ return success(key, Number(value));
+ }
+
+ return errored(
+ key,
+ new Error(`${key} is not valid. Expected an integer but got ${value}`),
+ );
+ }
+
+ return result as unknown as ConfigResult;
+}
+
+function asBoolean(result: ConfigResult): ConfigResult {
+ if (result.status === 'success') {
+ const { key } = result;
+ const value = result.value();
+
+ switch (value.toLowerCase()) {
+ case 'true': {
+ return success(key, true);
+ }
+ case 'false': {
+ return success(key, false);
+ }
+ default: {
+ return errored(
+ key,
+ new Error(
+ `${key} is not valid. Expected true or false but got ${value}`,
+ ),
+ );
+ }
+ }
+ }
+
+ return result as unknown as ConfigResult;
+}
+
+function validateGreaterThan(
+ result: ConfigResult,
+ bound: number,
+): ConfigResult {
+ if (result.status === 'success') {
+ const { key } = result;
+ const value = result.value();
+
+ if (value < bound) {
+ return errored(
+ key,
+ new Error(`${key} should be greater than ${bound} but got ${value}`),
+ );
+ }
+ }
+
+ return result;
+}
+
+function createConfigReader(preprocessKey: (key: string) => string) {
+ const preprocessedRead = (key: string) => read(preprocessKey(key));
+
+ return {
+ read: preprocessedRead,
+ tryGetValue: (key: string) => preprocessedRead(key).valueOrNull(),
+ getValue: (key: string) => preprocessedRead(key).value(),
+ tryGetInt: (key: string) => preprocessedRead(key).asInt().valueOrNull(),
+ getInt: (key: string) => preprocessedRead(key).asInt().value(),
+ tryGetBoolean: (key: string) =>
+ preprocessedRead(key).asBoolean().valueOrNull(),
+ getBoolean: (key: string) => preprocessedRead(key).asBoolean().value(),
+ };
+}
+
+export function configReaderBuilder() {
+ let builderPrefix: string;
+ return {
+ withPrefix(prefix: string) {
+ builderPrefix = prefix;
+ return this;
+ },
+ build(): ConfigReader {
+ return createConfigReader((key: string) =>
+ builderPrefix ? `${builderPrefix}_${key}` : key,
+ );
+ },
+ };
+}
+
+export const defaultConfigReader = configReaderBuilder().build();
+
+export type ConfigReader = ReturnType;
+export type ConfigReaderBuilder = ReturnType;
diff --git a/src/utils/src/in-memory-cache/cache-item.ts b/src/utils/src/in-memory-cache/cache-item.ts
new file mode 100644
index 00000000..21095d6f
--- /dev/null
+++ b/src/utils/src/in-memory-cache/cache-item.ts
@@ -0,0 +1,18 @@
+export class CacheItem {
+ private readonly createdAt = Date.now();
+
+ constructor(
+ public readonly data: T,
+ private readonly ttl: number | null,
+ ) {}
+
+ get isExpired(): boolean {
+ return (
+ CacheItem.isValidTtl(this.ttl) && Date.now() >= this.createdAt + this.ttl
+ );
+ }
+
+ private static isValidTtl(ttl: number | null): ttl is number {
+ return ttl !== null && ttl > 0;
+ }
+}
diff --git a/src/utils/src/in-memory-cache/in-memory-cache.ts b/src/utils/src/in-memory-cache/in-memory-cache.ts
new file mode 100644
index 00000000..9963a6d2
--- /dev/null
+++ b/src/utils/src/in-memory-cache/in-memory-cache.ts
@@ -0,0 +1,148 @@
+import { Mutex } from 'async-mutex';
+import { logger } from '../logger';
+import { CacheItem } from './cache-item';
+
+export type LockReleaser = () => void;
+
+type CacheOptions = {
+ /**
+ * ms to persist the item in the cache.
+ * If omitted or given as a non-positive value, the item will not expire
+ */
+ ttl?: number;
+};
+
+type TTL = number | null;
+
+export interface ICache {
+ acquireLock(): Promise;
+ entries(): Promise<[string, T][]>;
+ get(key: string): Promise;
+ set(key: string, value: T, opts?: CacheOptions): Promise;
+ setAll(cache: Map, opts?: CacheOptions): Promise;
+ delete(key: string): Promise;
+}
+
+export class InMemoryCache implements ICache {
+ private cache = new Map();
+
+ private readonly lock = new Mutex();
+
+ private readonly logger = logger;
+
+ private readonly ttl: TTL;
+
+ constructor(opts: CacheOptions = {}) {
+ this.ttl = InMemoryCache.parseTtl(opts.ttl);
+ }
+
+ /**
+ * Acquire the mutex lock protecting this cache.
+ * This MUST be called before accessing the cache.
+ * Returns a releaser function which MUST be called once access to the cache is complete.
+ * Failure to release the lock can cause deadlock issues for other processes trying to access the cache.
+ */
+ async acquireLock(): Promise {
+ return this.lock.acquire();
+ }
+
+ async directGet(key: string): Promise {
+ const found = this.cache.get(key);
+
+ if (found) {
+ this.logger.debug(`In-memory cache hit for key "${key}"`);
+
+ if (found.isExpired) {
+ await this.delete(key);
+ this.logger.debug(
+ `Cached item expired. Deleted cached item for key "${key}"`,
+ );
+ } else {
+ return found.data as T;
+ }
+ }
+
+ this.logger.debug(`In-memory cache missed for key "${key}"`);
+
+ return null;
+ }
+
+ async get(key: string): Promise {
+ await this.checkLock();
+
+ return this.directGet(key);
+ }
+
+ async entries(): Promise<[string, T][]> {
+ await this.checkLock();
+
+ return [...this.cache.entries()]
+ .filter(([, v]) => !v.isExpired)
+ .map(([k, v]) => [k, v.data]) as [string, T][];
+ }
+
+ async set(key: string, value: T, opts: CacheOptions = {}): Promise {
+ const ttl = InMemoryCache.parseTtl(opts.ttl);
+
+ await this.checkLock();
+ this.cache.set(key, new CacheItem(value, this.getItemTtl(ttl)));
+ this.logger.debug(`Key "${key}" set in in-memory cache`);
+ }
+
+ async delete(key: string): Promise {
+ await this.checkLock();
+
+ this.cache.delete(key);
+ this.logger.debug(`Key "${key}" deleted from in-memory cache`);
+ }
+
+ async setAll(
+ cache: Map,
+ opts: CacheOptions = {},
+ ): Promise {
+ const ttl = InMemoryCache.parseTtl(opts.ttl);
+
+ await this.checkLock();
+
+ const items = [...cache.entries()].map(
+ ([key, value]) =>
+ [key, new CacheItem(value, this.getItemTtl(ttl))] as [
+ string,
+ CacheItem,
+ ],
+ );
+
+ this.cache = new Map([...this.cache.entries(), ...items]);
+ }
+
+ private async checkLock() {
+ let attempts = 1;
+
+ while (attempts <= 3) {
+ if (this.lock.isLocked()) {
+ return;
+ }
+ await new Promise((resolve) => {
+ setTimeout(resolve, 1000);
+ });
+ this.logger.warn(`Check ${attempts} of in-memory cache lock failed.`);
+ attempts += 1;
+ }
+
+ throw new Error(
+ 'Cannot access in-memory cache without first obtaining mutex lock',
+ );
+ }
+
+ private static parseTtl(ttl?: number): TTL {
+ return typeof ttl === 'number' ? ttl : null;
+ }
+
+ private getItemTtl(ttl: TTL) {
+ if (ttl !== null) return ttl;
+
+ if (this.ttl !== null) return this.ttl;
+
+ return null;
+ }
+}
diff --git a/src/utils/src/in-memory-cache/index.ts b/src/utils/src/in-memory-cache/index.ts
new file mode 100644
index 00000000..60b8dcc5
--- /dev/null
+++ b/src/utils/src/in-memory-cache/index.ts
@@ -0,0 +1 @@
+export * from './in-memory-cache';
diff --git a/src/utils/src/index.ts b/src/utils/src/index.ts
new file mode 100644
index 00000000..eb3186d8
--- /dev/null
+++ b/src/utils/src/index.ts
@@ -0,0 +1,8 @@
+export * from './lambda-utils';
+export * from './locations';
+export * from './logger';
+export * from './ssm-utils';
+export * from './s3-utils';
+export * from './config-reader';
+export * from './cache';
+export * from './key-generation-utils';
diff --git a/src/utils/src/key-generation-utils/delete-key.ts b/src/utils/src/key-generation-utils/delete-key.ts
new file mode 100644
index 00000000..5c030ad6
--- /dev/null
+++ b/src/utils/src/key-generation-utils/delete-key.ts
@@ -0,0 +1,26 @@
+import { logger } from '../logger';
+import { parameterStore } from '../ssm-utils';
+
+type DeleteKeyParams = {
+ Name: string;
+ deleteReason: string;
+ warn: boolean;
+};
+
+export const deleteKey = async ({
+ Name,
+ deleteReason,
+ warn,
+}: DeleteKeyParams) => {
+ await parameterStore.deleteParameter(Name);
+ // eslint-disable-next-line unicorn/no-negated-condition
+ if (!warn) {
+ logger.info({
+ description: `Keygen deleted private key ${Name}: ${deleteReason}`,
+ });
+ } else {
+ logger.warn({
+ description: `Keygen deleted invalid private key ${Name}: ${deleteReason}`,
+ });
+ }
+};
diff --git a/src/utils/src/key-generation-utils/generate-new-key.ts b/src/utils/src/key-generation-utils/generate-new-key.ts
new file mode 100644
index 00000000..4ac0ce30
--- /dev/null
+++ b/src/utils/src/key-generation-utils/generate-new-key.ts
@@ -0,0 +1,29 @@
+import { format } from 'date-fns';
+import { logger } from '../logger';
+import { parameterStore } from '../ssm-utils';
+import { KeyStore } from './jwk-key-store';
+import { KeyJson } from './types';
+
+type GenerateNewKeyParams = {
+ keystore: KeyStore;
+ ssmPath: string;
+ now: Date;
+ keyGenerationOptions?: Record;
+};
+
+export const generateNewKey = async ({
+ keyGenerationOptions = {},
+ keystore,
+ now,
+ ssmPath,
+}: GenerateNewKeyParams) => {
+ // generate new RSA Key
+ logger.info({ description: 'Generating new key' });
+ const key = await keystore.generate('RSA', 4096, keyGenerationOptions);
+ const { kid } = key.toJSON() as KeyJson;
+ const keyPem = key.toPEM();
+ const Name = `${ssmPath}/privatekey_${format(now, 'yyyyMMdd')}_${kid}.pem`;
+
+ await parameterStore.addParameter(Name, keyPem);
+ logger.info({ description: `generated new private key ${Name}` });
+};
diff --git a/src/utils/src/key-generation-utils/get-private-key.ts b/src/utils/src/key-generation-utils/get-private-key.ts
new file mode 100644
index 00000000..a15d14f6
--- /dev/null
+++ b/src/utils/src/key-generation-utils/get-private-key.ts
@@ -0,0 +1,104 @@
+import { format, isValid, parse } from 'date-fns';
+import { newCache } from '../cache';
+import { logger } from '../logger';
+import {
+ NonNullSSMParam,
+ nonNullParameterFilter,
+ parameterStore,
+} from '../ssm-utils';
+
+const PRIVATE_KEY_REGEX = /privatekey_(\d{8})_(.+)\.pem/;
+
+const validateParamName = (name: string) => {
+ // eslint-disable-next-line sonarjs/prefer-regexp-exec
+ const nameComponents = name?.match(PRIVATE_KEY_REGEX);
+ logger.info({ description: 'validating parameter name', parameter: name });
+ // return true if regex matches and component parses as a yyyyMMdd format
+ return (
+ nameComponents?.length === 3 &&
+ isValid(parse(nameComponents[1], 'yyyyMMdd', new Date()))
+ );
+};
+
+const getValidPrivateKey = async (ssmPath: string) => {
+ const allParams = await parameterStore.getAllParameters(ssmPath);
+ const paramList = allParams.filter((p): p is NonNullSSMParam =>
+ nonNullParameterFilter(p),
+ );
+
+ const keyList = paramList.filter((param) => validateParamName(param.Name));
+ if (keyList.length === 0) {
+ throw new Error(`No valid private keys found in SSM path ${ssmPath}`);
+ }
+
+ // CCM-1162: Return second youngest key if youngest key was
+ // created yesterday or today, this is to mitigate the fact
+ // that APIM caches our public key every hour and so a newly
+ // generated private key may not be valid if APIM's cache has not
+ // been refreshed
+ const [youngestKey, secondYoungestKey] = keyList.toSorted((a, b) => {
+ const aCreatedDate = Number(a.Name.split('_')[1]);
+ const bCreatedDate = Number(b.Name.split('_')[1]);
+ return bCreatedDate - aCreatedDate;
+ });
+
+ if (!secondYoungestKey) {
+ logger.info({
+ description: `Selecting youngest private key: ${youngestKey.Name}`,
+ });
+ return youngestKey;
+ }
+
+ const youngestKeyCreatedDate = youngestKey.Name.split('_')[1];
+
+ const todaysDateUnformatted = new Date();
+ const todaysDate = format(todaysDateUnformatted, 'yyyyMMdd');
+
+ todaysDateUnformatted.setDate(todaysDateUnformatted.getDate() - 1);
+ const yesterdaysDate = format(todaysDateUnformatted, 'yyyyMMdd');
+
+ if (
+ youngestKeyCreatedDate === todaysDate ||
+ youngestKeyCreatedDate === yesterdaysDate
+ ) {
+ logger.info({
+ description: `Selecting second youngest private key: ${secondYoungestKey.Name}`,
+ });
+ return secondYoungestKey;
+ }
+
+ logger.info({
+ description: `Selecting youngest private key: ${youngestKey.Name}`,
+ });
+ return youngestKey;
+};
+
+export const privateKeyFetcher = (pemSSMPath: string) => {
+ const fetchKey = async () => {
+ try {
+ const param = await getValidPrivateKey(pemSSMPath);
+ const keyPem = param.Value;
+
+ // eslint-disable-next-line sonarjs/prefer-regexp-exec
+ const kid = (param.Name.match(PRIVATE_KEY_REGEX) ?? [])[2];
+ return {
+ key: keyPem,
+ kid,
+ };
+ } catch (error) {
+ logger.error({ err: error });
+ throw new Error('Failure in getPrivateKey()');
+ }
+ };
+
+ const fetchKeyFromCache = async (_: string) => ({
+ value: await fetchKey(),
+ cacheTime: process.env.NO_CACHE ? 0 : 30_000,
+ });
+
+ const privateKeyCache = newCache(() => new Date(), fetchKeyFromCache);
+
+ return {
+ getPrivateKey: () => privateKeyCache.getCachedAsync(''),
+ };
+};
diff --git a/src/utils/src/key-generation-utils/index.ts b/src/utils/src/key-generation-utils/index.ts
new file mode 100644
index 00000000..b2dc323f
--- /dev/null
+++ b/src/utils/src/key-generation-utils/index.ts
@@ -0,0 +1,9 @@
+export * from './get-private-key';
+export * from './delete-key';
+export * from './generate-new-key';
+export * from './jwk';
+export * from './jwk-key';
+export * from './jwk-key-store';
+export * from './types';
+export * from './upload-public-keystore-to-s3';
+export * from './validate-private-key';
diff --git a/src/utils/src/key-generation-utils/jwk-key-store.ts b/src/utils/src/key-generation-utils/jwk-key-store.ts
new file mode 100644
index 00000000..d6867f6b
--- /dev/null
+++ b/src/utils/src/key-generation-utils/jwk-key-store.ts
@@ -0,0 +1,60 @@
+import { calculateJwkThumbprint, exportJWK } from 'jose';
+import { createPrivateKey, generateKeyPairSync } from 'node:crypto';
+import { type JWKJson, Key } from './jwk-key';
+
+/**
+ * Lightweight replacement for node-jose's `JWK.KeyStore`.
+ *
+ * Provides `add`, `all`, and `generate` with the same signatures used in this
+ * codebase. Keys are stored in an in-memory array.
+ */
+export class KeyStore {
+ private readonly _keys: Key[] = [];
+
+ /** Add an existing Key to the store. */
+ add(key: Key): void {
+ this._keys.push(key);
+ }
+
+ /** Return a shallow copy of all keys in the store. */
+ all(): Key[] {
+ return [...this._keys];
+ }
+
+ /**
+ * Generate a new RSA key, add it to the store, and return it.
+ *
+ * @param _type Key type – only `'RSA'` is used in this codebase.
+ * @param bits Key size in bits (e.g. 4096).
+ * @param options Optional JWK metadata (`kid`, `use`, `alg`, …). When `kid` is
+ * omitted a SHA-256 JWK thumbprint is calculated automatically.
+ */
+ async generate(
+ _type: string,
+ bits: number,
+ options: Record = {},
+ ): Promise {
+ const { privateKey: nodePrivateKey } = generateKeyPairSync('rsa', {
+ modulusLength: bits,
+ });
+
+ const pem = nodePrivateKey.export({
+ type: 'pkcs8',
+ format: 'pem',
+ }) as string;
+
+ // The re-import step is necessary to get a JWK with the correct fields populated for thumbprint calculation.
+ // Without this, the exported JWK is missing the `kid` field
+ const reImportedKey = createPrivateKey(pem);
+ const jwk = await exportJWK(reImportedKey);
+
+ const { kid: optionsKid, ...restOptions } = options;
+ const kid = optionsKid ?? (await calculateJwkThumbprint(jwk));
+
+ const finalJwk: JWKJson = { ...jwk, ...restOptions, kid };
+
+ const key = new Key(finalJwk, pem);
+ this._keys.push(key);
+ return key;
+ }
+}
diff --git a/src/utils/src/key-generation-utils/jwk-key.ts b/src/utils/src/key-generation-utils/jwk-key.ts
new file mode 100644
index 00000000..c084ac7f
--- /dev/null
+++ b/src/utils/src/key-generation-utils/jwk-key.ts
@@ -0,0 +1,70 @@
+import { exportJWK } from 'jose';
+import { createPrivateKey } from 'node:crypto';
+
+export type JWKJson = Record;
+
+/** Private fields that must be stripped when producing a public JWK. */
+const PRIVATE_JWK_FIELDS = new Set(['d', 'dp', 'dq', 'k', 'p', 'q', 'qi']);
+
+/**
+ * Lightweight replacement for node-jose's `JWK.Key`.
+ *
+ * Stores the raw private PEM (when available) alongside the exported JWK so that
+ * `toJSON()` (public JWK) and `toPEM()` (private PEM) can be served cheaply without
+ * keeping a live crypto object in memory on the long path.
+ */
+export class Key {
+ /** The full JWK representation of this key (may contain private key material). */
+ private readonly _jwk: JWKJson;
+
+ /** Original PEM string used to import this key, if available. */
+ private readonly _privatePem: string | null;
+
+ constructor(jwk: JWKJson, privatePem: string | null) {
+ this._jwk = jwk;
+ this._privatePem = privatePem;
+ }
+
+ /**
+ * Create a Key from a PEM-encoded private key string and a specified key ID (kid).
+ * Throws an error with a stable message when the PEM cannot be parsed,
+ * matching the behaviour callers expect.
+ */
+ static async fromPemAndKid(kid: string, pem: string): Promise {
+ try {
+ const nodeKey = createPrivateKey(pem);
+ const jwk = await exportJWK(nodeKey);
+ return new Key({ ...jwk, kid }, pem);
+ } catch {
+ throw new Error('Invalid PEM formatted message.');
+ }
+ }
+
+ /** Create a Key from an already-parsed public JWK object (no private material). */
+ static fromJWK(jwk: JWKJson): Key {
+ return new Key(jwk, null);
+ }
+
+ /**
+ * Returns the *public* JWK representation of this key (private key fields are
+ * stripped), matching the default behaviour of node-jose's `key.toJSON()`.
+ */
+ toJSON(): JWKJson {
+ return Object.fromEntries(
+ Object.entries(this._jwk).filter(
+ ([field]) => !PRIVATE_JWK_FIELDS.has(field),
+ ),
+ );
+ }
+
+ /**
+ * Returns the PEM encoding of the key.
+ * `key.toPEM()` usage.
+ */
+ toPEM(): string {
+ if (!this._privatePem) {
+ throw new Error('No private key PEM available on this Key instance.');
+ }
+ return this._privatePem;
+ }
+}
diff --git a/src/utils/src/key-generation-utils/jwk.ts b/src/utils/src/key-generation-utils/jwk.ts
new file mode 100644
index 00000000..b91f2388
--- /dev/null
+++ b/src/utils/src/key-generation-utils/jwk.ts
@@ -0,0 +1,30 @@
+import { type JWKJson, Key } from './jwk-key';
+import { KeyStore } from './jwk-key-store';
+
+// ---------------------------------------------------------------------------
+// Factory helpers mirroring node-jose's JWK namespace
+// ---------------------------------------------------------------------------
+
+/** Create a new empty KeyStore. */
+export const createKeyStore = (): KeyStore => new KeyStore();
+
+/**
+ * Import a single PEM-encoded private key.
+ * the input is always treated as a PEM string.
+ */
+export const asKey = async (kid: string, pem: string): Promise =>
+ Key.fromPemAndKid(kid, pem);
+
+/**
+ * Import a JWKS JSON object into a KeyStore.
+ * Useful in tests where a static set of public JWK objects is provided.
+ */
+export const asKeyStore = async (json: {
+ keys: JWKJson[];
+}): Promise => {
+ const store = new KeyStore();
+ for (const keyJson of json.keys) {
+ store.add(Key.fromJWK(keyJson));
+ }
+ return store;
+};
diff --git a/src/utils/src/key-generation-utils/types.ts b/src/utils/src/key-generation-utils/types.ts
new file mode 100644
index 00000000..ca32252e
--- /dev/null
+++ b/src/utils/src/key-generation-utils/types.ts
@@ -0,0 +1,25 @@
+export type KeyJson = {
+ kid: string;
+ kty: string;
+ use: string;
+ alg: string;
+ e: string;
+ n: string;
+};
+
+export type KeyStoreJson = {
+ keys: KeyJson[];
+};
+
+export type RSAPublicKey = {
+ kty: string;
+ kid: string;
+ n: string;
+ e: string;
+ alg?: string;
+ use?: string;
+};
+
+export type RSAPublicKeystore = {
+ keys: RSAPublicKey[];
+};
diff --git a/src/utils/src/key-generation-utils/upload-public-keystore-to-s3.ts b/src/utils/src/key-generation-utils/upload-public-keystore-to-s3.ts
new file mode 100644
index 00000000..225f03c2
--- /dev/null
+++ b/src/utils/src/key-generation-utils/upload-public-keystore-to-s3.ts
@@ -0,0 +1,40 @@
+import { logger } from '../logger';
+import { putDataS3 } from '../s3-utils';
+import { KeyStore } from './jwk-key-store';
+import { KeyStoreJson } from './types';
+
+type UploadPublicKeystoreToS3Params = {
+ keystore: KeyStore;
+ staticAssetBucket: string;
+ jwksFileName: string;
+};
+
+export const uploadPublicKeystoreToS3 = async ({
+ jwksFileName,
+ keystore,
+ staticAssetBucket,
+}: UploadPublicKeystoreToS3Params) => {
+ // upload public keys as JWKS to S3
+ const keys = [];
+
+ // we do this because it's hard to convince node-jose to return these values directly
+ //
+ // as far as I can tell node-jose doesn't take alg or sig into account in terms of
+ // generating any different output, so this is only a question or relabelling
+ //
+ // node-jose may not be the ideal library for what we want to do here
+ for (const inputJwk of keystore.all()) {
+ keys.push({ use: 'sig', alg: 'RS512', ...inputJwk.toJSON() });
+ }
+ const keystoreJson = { keys } as KeyStoreJson;
+ const Bucket = staticAssetBucket;
+ const Key = jwksFileName;
+
+ logger.info({ description: `Uploading JWKS to ${Bucket}/${Key}` });
+ await putDataS3(keystoreJson, {
+ Bucket,
+ Key,
+ });
+
+ logger.info({ description: 'Keygen: public keystore updated' });
+};
diff --git a/src/utils/src/key-generation-utils/validate-private-key.ts b/src/utils/src/key-generation-utils/validate-private-key.ts
new file mode 100644
index 00000000..f6540475
--- /dev/null
+++ b/src/utils/src/key-generation-utils/validate-private-key.ts
@@ -0,0 +1,68 @@
+import { isBefore, parse } from 'date-fns';
+import { Key } from './jwk-key';
+import { asKey } from './jwk';
+
+type ValidatePrivateKeyParams = {
+ Name: string;
+ Value: string;
+ minIssueDate: Date;
+ now: Date;
+};
+
+export type ValidateKeyResult =
+ | {
+ valid: false;
+ deleteReason: string;
+ warn?: boolean;
+ }
+ | { valid: true; keyJwk: Key; keyDate: Date };
+
+// private key param names are /riskstrat//emailauth/privatekey__.pem
+const privateKeyRegex = /privatekey_(\d{8})_(.+)\.pem/;
+
+export const validatePrivateKey = async ({
+ Name,
+ Value,
+ minIssueDate,
+ now,
+}: ValidatePrivateKeyParams): Promise => {
+ // split date and kid out of param name
+ // eslint-disable-next-line sonarjs/prefer-regexp-exec
+ const [, keyDateString, keyKid] = Name?.match(privateKeyRegex) ?? [];
+
+ if (!keyDateString || !keyKid) {
+ return {
+ valid: false,
+ deleteReason:
+ 'Does not match the name format privatekey__.pem',
+ warn: true,
+ };
+ }
+
+ const keyDate = parse(keyDateString, 'yyyyMMdd', now);
+ if (Number.isNaN(keyDate.getTime())) {
+ return {
+ valid: false,
+ deleteReason: `'${keyDateString}' is not a valid yyyyMMdd date`,
+ warn: true,
+ };
+ }
+
+ if (isBefore(keyDate, minIssueDate)) {
+ return {
+ valid: false,
+ deleteReason: `Key expired, keyDateString: ${keyDateString}`,
+ };
+ }
+
+ try {
+ const keyJwk = await asKey(keyKid, Value);
+ return { valid: true, keyJwk, keyDate };
+ } catch (error) {
+ return {
+ valid: false,
+ deleteReason: `Could not parse pem value, ${error}`,
+ warn: true,
+ };
+ }
+};
diff --git a/src/utils/src/lambda-utils/get-apim-access-token.ts b/src/utils/src/lambda-utils/get-apim-access-token.ts
new file mode 100644
index 00000000..0edba85e
--- /dev/null
+++ b/src/utils/src/lambda-utils/get-apim-access-token.ts
@@ -0,0 +1,65 @@
+import type { Logger } from '../logger';
+import type { ApimAccessToken } from './types';
+import { IParameterStore } from '../ssm-utils/types';
+
+export const doesAccessTokenNeedRefresh = (
+ token: ApimAccessToken,
+ tokenRefreshThresholdSeconds: number,
+): boolean =>
+ Date.now() / 1000 + tokenRefreshThresholdSeconds > token.expires_at;
+
+export function createGetApimAccessToken(
+ accessTokenSSMPath: string,
+ logger: Logger,
+ parameterStore: IParameterStore,
+) {
+ async function getParsedToken(): Promise<[ApimAccessToken, number?]> {
+ const parameter = await parameterStore.getParameter(accessTokenSSMPath);
+
+ if (!parameter?.Value) {
+ throw new Error(
+ `APIM access token parameter "${accessTokenSSMPath}" not found in SSM`,
+ );
+ }
+
+ return [JSON.parse(parameter.Value) as ApimAccessToken, parameter.Version];
+ }
+
+ return async function getApimAccessToken() {
+ if (accessTokenSSMPath === '') {
+ return '';
+ }
+
+ let [accessToken, version] = await getParsedToken();
+
+ logger.debug(`Access token expires at: ${accessToken.expires_at}`);
+
+ if (!accessToken || doesAccessTokenNeedRefresh(accessToken, 15)) {
+ logger.debug('Access token requires refresh');
+
+ await parameterStore.clearCachedParameter(accessTokenSSMPath, version);
+
+ // eslint-disable-next-line sonarjs/no-dead-store
+ [accessToken, version] = await getParsedToken();
+
+ logger.debug(
+ `Access token fetched. New access token expires at: ${accessToken.expires_at}`,
+ );
+ } else {
+ logger.debug('Access token does not require fetch');
+ }
+
+ if (!accessToken?.access_token || !accessToken?.expires_at) {
+ logger.error('Access token parameter has invalid format');
+ throw new Error('Invalid token');
+ }
+
+ // if we have just tried to refresh the token and it is out of date, then we have failed to update the token - throw an error
+ if (doesAccessTokenNeedRefresh(accessToken, 0)) {
+ logger.error('Access token is out of date.');
+ throw new Error('Failed to update token');
+ }
+
+ return accessToken.access_token;
+ };
+}
diff --git a/src/utils/src/lambda-utils/index.ts b/src/utils/src/lambda-utils/index.ts
new file mode 100644
index 00000000..73d05675
--- /dev/null
+++ b/src/utils/src/lambda-utils/index.ts
@@ -0,0 +1,3 @@
+export * from './lambda-client';
+export * from './get-apim-access-token';
+export * from './types';
diff --git a/src/utils/src/lambda-utils/lambda-client.ts b/src/utils/src/lambda-utils/lambda-client.ts
new file mode 100644
index 00000000..4a114e26
--- /dev/null
+++ b/src/utils/src/lambda-utils/lambda-client.ts
@@ -0,0 +1,16 @@
+import { LambdaClient, LambdaClientConfig } from '@aws-sdk/client-lambda';
+
+const region = process.env.AWS_REGION || 'eu-west-2';
+
+export function getLambdaClient(
+ additionalOptions: Partial = {},
+) {
+ return new LambdaClient({
+ region,
+ retryMode: 'standard',
+ maxAttempts: 5,
+ ...additionalOptions,
+ });
+}
+
+export const lambdaClient = getLambdaClient();
diff --git a/src/utils/src/lambda-utils/types.ts b/src/utils/src/lambda-utils/types.ts
new file mode 100644
index 00000000..5a873c50
--- /dev/null
+++ b/src/utils/src/lambda-utils/types.ts
@@ -0,0 +1,5 @@
+export type ApimAccessToken = {
+ access_token: string;
+ token_type: string;
+ expires_at: number; // unix timestamp (seconds) at which the token expires
+};
diff --git a/src/utils/src/locations.ts b/src/utils/src/locations.ts
new file mode 100644
index 00000000..50d5e007
--- /dev/null
+++ b/src/utils/src/locations.ts
@@ -0,0 +1 @@
+export const region: () => string = () => process.env.AWS_REGION || 'eu-west-2';
diff --git a/src/utils/src/logger.ts b/src/utils/src/logger.ts
new file mode 100644
index 00000000..894fcba4
--- /dev/null
+++ b/src/utils/src/logger.ts
@@ -0,0 +1,15 @@
+import winston from 'winston';
+
+const { combine, errors, json, timestamp } = winston.format;
+
+export const logger = winston.createLogger({
+ level: process.env.LOG_LEVEL || 'info',
+ format: combine(errors({ stack: true, cause: true }), timestamp(), json()),
+ transports: [
+ new winston.transports.Stream({
+ stream: process.stdout,
+ }),
+ ],
+});
+
+export type Logger = winston.Logger;
diff --git a/src/utils/src/s3-utils/copy-and-delete-object-s3.ts b/src/utils/src/s3-utils/copy-and-delete-object-s3.ts
new file mode 100644
index 00000000..ceeee3ee
--- /dev/null
+++ b/src/utils/src/s3-utils/copy-and-delete-object-s3.ts
@@ -0,0 +1,24 @@
+import { CopyObjectCommand, DeleteObjectCommand } from '@aws-sdk/client-s3';
+import type { S3Location } from './get-object-s3';
+import { s3Client } from './s3-client';
+
+export async function copyAndDeleteObjectS3(
+ source: S3Location,
+ destination: S3Location,
+): Promise {
+ try {
+ const copyParams = {
+ Bucket: destination.Bucket,
+ CopySource: `/${source.Bucket}/${source.Key}`,
+ Key: destination.Key,
+ };
+
+ await s3Client.send(new CopyObjectCommand(copyParams));
+
+ await s3Client.send(new DeleteObjectCommand(source));
+ } catch (error) {
+ throw new Error(
+ `Move of ${source.Bucket}/${source.Key} to ${destination.Bucket}/${destination.Key} failed, error: ${error}`,
+ );
+ }
+}
diff --git a/src/utils/src/s3-utils/get-object-s3.ts b/src/utils/src/s3-utils/get-object-s3.ts
new file mode 100644
index 00000000..c66d130a
--- /dev/null
+++ b/src/utils/src/s3-utils/get-object-s3.ts
@@ -0,0 +1,151 @@
+import { type Readable } from 'node:stream';
+import { StringDecoder } from 'node:string_decoder';
+import {
+ GetObjectCommand,
+ GetObjectCommandOutput,
+ HeadObjectCommand,
+} from '@aws-sdk/client-s3';
+import { s3Client } from './s3-client';
+
+export function isReadable(
+ body: Readable | ReadableStream | Blob | undefined,
+): body is Readable {
+ // eslint-disable-next-line sonarjs/different-types-comparison
+ return body !== undefined && body && (body as Readable).read !== undefined;
+}
+
+export type GetObjectOutputReadableBody = GetObjectCommandOutput & {
+ Body: Readable;
+};
+
+export function isReadableBody(
+ response: GetObjectCommandOutput,
+): response is GetObjectOutputReadableBody {
+ return (
+ response.Body !== undefined &&
+ response.Body &&
+ // eslint-disable-next-line sonarjs/different-types-comparison
+ (response.Body as Readable).read !== undefined
+ );
+}
+
+export interface S3Location {
+ Bucket: string;
+ Key: string;
+ VersionId?: string;
+}
+
+async function streamToString(Body: Readable) {
+ return new Promise((resolve, reject) => {
+ const decoder = new StringDecoder('utf8');
+ let result = '';
+ Body.on('data', (chunk: Buffer) => {
+ result += decoder.write(chunk);
+ });
+ Body.on('error', (err) => reject(err));
+ Body.on('end', () => resolve(result + decoder.end()));
+ });
+}
+
+async function streamToBuffer(Body: Readable) {
+ return new Promise((resolve, reject) => {
+ const chunks: Buffer[] = [];
+ Body.on('data', (chunk: ArrayBuffer | SharedArrayBuffer) =>
+ chunks.push(Buffer.from(chunk)),
+ );
+ Body.on('error', (err) => reject(err));
+ Body.on('end', () => resolve(Buffer.concat(chunks)));
+ });
+}
+
+export async function getS3ObjectStream(
+ location: S3Location,
+): Promise {
+ const { Bucket, Key, VersionId } = location;
+ const params = {
+ Bucket,
+ Key,
+ VersionId,
+ };
+ try {
+ const { Body } = await s3Client.send(new GetObjectCommand(params));
+
+ // https://www.typescriptlang.org/docs/handbook/advanced-types.html#user-defined-type-guards
+ if (isReadable(Body)) {
+ return Body;
+ }
+ } catch (error_) {
+ const error = error_ as Error;
+ throw new Error(
+ `Could not retrieve from bucket 's3://${Bucket}/${Key}' from S3: ${error.message}`,
+ );
+ }
+ throw new Error(`Could not read file from bucket. 's3://${Bucket}/${Key}'`);
+}
+
+export async function getS3Object(
+ location: S3Location,
+ defaultValue?: string,
+): Promise {
+ try {
+ return await streamToString(await getS3ObjectStream(location));
+ } catch (error) {
+ if (defaultValue) {
+ return defaultValue;
+ }
+
+ const msg = error instanceof Error ? error.message : String(error);
+ throw new Error(
+ `Could not retrieve from bucket 's3://${location.Bucket}/${location.Key}' from S3: ${msg}`,
+ );
+ }
+}
+
+export async function getS3ObjectFromUri(uri: string): Promise {
+ const regex = /^s3:\/\/([^/]+)\/(.+)$/;
+ const match = regex.exec(uri);
+ if (!match) {
+ throw new Error(`Invalid S3 URI format: ${uri}`);
+ }
+ const [, Bucket, Key] = match;
+ return getS3Object({ Bucket, Key });
+}
+
+export async function getS3ObjectBufferFromUri(uri: string): Promise {
+ const regex = /^s3:\/\/([^/]+)\/(.+)$/;
+ const match = regex.exec(uri);
+ if (!match) {
+ throw new Error(`Invalid S3 URI format: ${uri}`);
+ }
+ const [, Bucket, Key] = match;
+
+ try {
+ return await streamToBuffer(await getS3ObjectStream({ Bucket, Key }));
+ } catch (error) {
+ const msg = error instanceof Error ? error.message : String(error);
+ throw new Error(
+ `Could not retrieve from bucket 's3://${Bucket}/${Key}' from S3: ${msg}`,
+ );
+ }
+}
+
+export async function getS3ObjectMetadata(
+ location: S3Location,
+): Promise | undefined> {
+ const { Bucket, Key, VersionId } = location;
+ try {
+ const response = await s3Client.send(
+ new HeadObjectCommand({
+ Bucket,
+ Key,
+ VersionId,
+ }),
+ );
+ return response.Metadata;
+ } catch (error) {
+ const msg = error instanceof Error ? error.message : String(error);
+ throw new Error(
+ `Could not retrieve metadata from bucket 's3://${Bucket}/${Key}' from S3: ${msg}`,
+ );
+ }
+}
diff --git a/src/utils/src/s3-utils/index.ts b/src/utils/src/s3-utils/index.ts
new file mode 100644
index 00000000..894af46f
--- /dev/null
+++ b/src/utils/src/s3-utils/index.ts
@@ -0,0 +1,5 @@
+export * from './get-object-s3';
+export * from './s3-client';
+export * from './put-data-s3';
+export * from './put-file-s3';
+export * from './copy-and-delete-object-s3';
diff --git a/src/utils/src/s3-utils/put-data-s3.ts b/src/utils/src/s3-utils/put-data-s3.ts
new file mode 100644
index 00000000..5aacbfe0
--- /dev/null
+++ b/src/utils/src/s3-utils/put-data-s3.ts
@@ -0,0 +1,24 @@
+import { PutObjectCommand, PutObjectCommandOutput } from '@aws-sdk/client-s3';
+import type { S3Location } from './get-object-s3';
+import { s3Client } from './s3-client';
+
+export async function putDataS3(
+ fileData: Record,
+ { Bucket, Key }: S3Location,
+ Metadata: Record = {},
+): Promise {
+ try {
+ const params = {
+ Bucket,
+ Key,
+ Body: JSON.stringify(fileData, null, 2),
+ Metadata,
+ };
+
+ const data = await s3Client.send(new PutObjectCommand(params));
+
+ return data;
+ } catch (error) {
+ throw new Error(`Upload to ${Bucket}/${Key} failed, error: ${error}`);
+ }
+}
diff --git a/src/utils/src/s3-utils/put-file-s3.ts b/src/utils/src/s3-utils/put-file-s3.ts
new file mode 100644
index 00000000..d60a2f1b
--- /dev/null
+++ b/src/utils/src/s3-utils/put-file-s3.ts
@@ -0,0 +1,26 @@
+import { PutObjectCommand, PutObjectCommandOutput } from '@aws-sdk/client-s3';
+import type { S3Location } from './get-object-s3';
+import { s3Client } from './s3-client';
+
+export async function putFileS3(
+ buffer: Buffer,
+ { Bucket, Key }: S3Location,
+ Metadata: Record = {},
+ ContentType?: string,
+): Promise {
+ try {
+ const params = {
+ Bucket,
+ Key,
+ Body: buffer,
+ Metadata,
+ ...(ContentType && { ContentType }),
+ };
+
+ const data = await s3Client.send(new PutObjectCommand(params));
+
+ return data;
+ } catch (error) {
+ throw new Error(`Upload to ${Bucket}/${Key} failed, error: ${error}`);
+ }
+}
diff --git a/src/utils/src/s3-utils/s3-client.ts b/src/utils/src/s3-utils/s3-client.ts
new file mode 100644
index 00000000..120ccbf7
--- /dev/null
+++ b/src/utils/src/s3-utils/s3-client.ts
@@ -0,0 +1,10 @@
+import { S3Client, type S3ClientConfig } from '@aws-sdk/client-s3';
+import { region } from '../locations';
+
+export const s3Client = new S3Client({ region: region() });
+
+export const createS3Client = (config: S3ClientConfig = {}) =>
+ new S3Client({
+ region: region(),
+ ...config,
+ });
diff --git a/src/utils/src/ssm-utils/index.ts b/src/utils/src/ssm-utils/index.ts
new file mode 100644
index 00000000..f31bfaba
--- /dev/null
+++ b/src/utils/src/ssm-utils/index.ts
@@ -0,0 +1,5 @@
+export * from './ssm-client';
+export * from './types';
+export * from './parameter-store-cache';
+export * from './parameter-store';
+export * from './parameter-filters';
diff --git a/src/utils/src/ssm-utils/parameter-filters.ts b/src/utils/src/ssm-utils/parameter-filters.ts
new file mode 100644
index 00000000..5c5bfd9c
--- /dev/null
+++ b/src/utils/src/ssm-utils/parameter-filters.ts
@@ -0,0 +1,11 @@
+/* eslint-disable sonarjs/different-types-comparison */
+import { Parameter } from '@aws-sdk/client-ssm';
+import { NonNullSSMParam } from './types';
+
+export const nonNullParameterFilter = (
+ param: Parameter,
+): param is NonNullSSMParam =>
+ param.Name !== undefined &&
+ param.Value !== undefined &&
+ param.Name !== null &&
+ param.Value !== null;
diff --git a/src/utils/src/ssm-utils/parameter-store-cache.ts b/src/utils/src/ssm-utils/parameter-store-cache.ts
new file mode 100644
index 00000000..25121d4b
--- /dev/null
+++ b/src/utils/src/ssm-utils/parameter-store-cache.ts
@@ -0,0 +1,191 @@
+import {
+ Parameter,
+ ParameterNotFound,
+ ParameterType,
+ SSMClient,
+} from '@aws-sdk/client-ssm';
+import { type ICache, InMemoryCache } from '../in-memory-cache';
+import { ParameterStore } from './parameter-store';
+import type {
+ GetAllParametersOptions,
+ GetParameterOptions,
+ IParameterStore,
+} from './types';
+
+type ParameterOrError = Parameter | ParameterNotFound;
+
+export class ParameterStoreCache
+ extends ParameterStore
+ implements IParameterStore
+{
+ private readonly cache: ICache;
+
+ constructor(dependencies?: { client: SSMClient }, timeToLive?: number) {
+ super(dependencies);
+ this.cache = new InMemoryCache({ ttl: timeToLive });
+ }
+
+ async getParameter(
+ parameterName: string,
+ options: GetParameterOptions = {},
+ ): Promise {
+ const release = await this.cache.acquireLock();
+ const key = this.getParameterNameKey(parameterName);
+
+ if (!options.force) {
+ const cached = await this.cache.get(key);
+ if (cached instanceof ParameterNotFound) {
+ release();
+ throw cached;
+ } else if (cached) {
+ release();
+ return cached;
+ }
+ }
+
+ let parameter;
+ try {
+ parameter = await super.getParameter(parameterName, options);
+
+ await this.cache.set(key, parameter);
+ } catch (error) {
+ if (error instanceof ParameterNotFound) {
+ await this.cache.set(key, error);
+ throw error;
+ }
+ } finally {
+ release();
+ }
+ return parameter;
+ }
+
+ /**
+ * Returns and caches a list of parameters from SSM.
+ *
+ * The first call to this method will retrieve data from SSM.
+ * Subsequent matching calls (i.e. with the same path prefix and recursion) will return the cached list.
+ *
+ * N.B Parameters retrieved when calling this method will also be cached individually for usage by getParameter.
+ * But beware that updates to individual parameters (e.g. using addParameter, deleteParameter, clearCachedParameter) will not be reflected in cached lists.
+ *
+ * To refetch a list from source, pass the option `{ force: true }`
+ *
+ * @param pathPrefix
+ * @param options
+ * @returns
+ */
+ async getAllParameters(
+ pathPrefix: string,
+ options: GetAllParametersOptions = {},
+ ): Promise {
+ const release = await this.cache.acquireLock();
+ const key = this.getParameterPathPrefixKey(pathPrefix, !!options.recursive);
+
+ try {
+ if (!options.force) {
+ const cached = await this.cache.get(key);
+
+ if (cached && cached.length > 0) {
+ return cached;
+ }
+ }
+
+ const parameters = await super.getAllParameters(pathPrefix, options);
+ const map = new Map();
+
+ if (parameters.length > 0) {
+ map.set(key, parameters);
+ }
+
+ for (const parameter of parameters) {
+ map.set(this.getParameterNameKey(parameter.Name as string), parameter);
+ }
+
+ await this.cache.setAll(map);
+
+ return parameters;
+ } finally {
+ release();
+ }
+ }
+
+ async addParameter(
+ parameterName: string,
+ parameterValue: string,
+ type: ParameterType = ParameterType.SECURE_STRING,
+ overwrite = true,
+ ) {
+ const release = await this.cache.acquireLock();
+
+ try {
+ const parameter = await super.addParameter(
+ parameterName,
+ parameterValue,
+ type,
+ overwrite,
+ );
+
+ await this.cache.set(this.getParameterNameKey(parameterName), parameter);
+
+ return parameter;
+ } finally {
+ release();
+ }
+ }
+
+ async clearCachedParameter(
+ parameterName: string,
+ version?: number,
+ ): Promise {
+ const release = await this.cache.acquireLock();
+ const key = this.getParameterNameKey(parameterName);
+
+ if (version) {
+ const cached = await this.cache.get(key);
+
+ if (
+ !(cached instanceof ParameterNotFound) &&
+ (!cached || cached.Version !== version)
+ ) {
+ release();
+ return;
+ }
+ }
+
+ try {
+ await this.cache.delete(key);
+ } finally {
+ release();
+ }
+ }
+
+ async deleteParameter(parameterName: string): Promise {
+ const release = await this.cache.acquireLock();
+
+ try {
+ await super.deleteParameter(parameterName);
+ await this.cache.delete(this.getParameterNameKey(parameterName));
+ } finally {
+ release();
+ }
+ }
+
+ // eslint-disable-next-line class-methods-use-this
+ private getParameterNameKey(name: string): string {
+ return `name:${name}`;
+ }
+
+ // eslint-disable-next-line class-methods-use-this
+ private getParameterPathPrefixKey(pathPrefix: string, recursive: boolean) {
+ const santisedPathPrefix = pathPrefix.endsWith('/')
+ ? pathPrefix
+ : `${pathPrefix}/`;
+ const key = `path:${santisedPathPrefix}`;
+
+ if (recursive) {
+ return `${key}**/*`;
+ }
+
+ return `${key}*`;
+ }
+}
diff --git a/src/utils/src/ssm-utils/parameter-store.ts b/src/utils/src/ssm-utils/parameter-store.ts
new file mode 100644
index 00000000..c6639436
--- /dev/null
+++ b/src/utils/src/ssm-utils/parameter-store.ts
@@ -0,0 +1,130 @@
+import {
+ DeleteParameterCommand,
+ GetParameterCommand,
+ Parameter,
+ ParameterNotFound,
+ ParameterType,
+ PutParameterCommand,
+ SSMClient,
+ paginateGetParametersByPath,
+} from '@aws-sdk/client-ssm';
+import { ssmClient } from './ssm-client';
+import {
+ GetAllParametersOptions,
+ GetParameterOptions,
+ IParameterStore,
+} from './types';
+
+export class ParameterStore implements IParameterStore {
+ private readonly ssmClient: SSMClient;
+
+ constructor(dependencies?: { client: SSMClient }) {
+ this.ssmClient = dependencies?.client ?? ssmClient;
+ }
+
+ async getParameter(
+ parameterName: string,
+ options: GetParameterOptions = {},
+ ): Promise {
+ const maxAttempts = options.maxAttempts || 3;
+
+ let attempt = 0;
+ let parameter: Parameter | undefined;
+
+ do {
+ try {
+ attempt += 1;
+
+ const result = await this.ssmClient.send(
+ new GetParameterCommand({
+ Name: parameterName,
+ WithDecryption: true,
+ }),
+ );
+
+ parameter = result.Parameter;
+ } catch (error_) {
+ const error = error_ as Error;
+ if (error.name === 'ThrottlingException' && attempt < maxAttempts) {
+ // sleep for 3 seconds
+ await new Promise((resolve) => {
+ setTimeout(resolve, 3 * 1000);
+ });
+ } else {
+ throw error_;
+ }
+ }
+ } while (attempt < maxAttempts && !parameter);
+
+ return parameter;
+ }
+
+ async getAllParameters(
+ pathPrefix: string,
+ { recursive = false }: GetAllParametersOptions = {},
+ ): Promise {
+ try {
+ const paginator = paginateGetParametersByPath(
+ { client: this.ssmClient },
+ {
+ Path: pathPrefix,
+ WithDecryption: true,
+ Recursive: recursive,
+ },
+ );
+
+ const paramsList: Parameter[] = [];
+
+ for await (const page of paginator) {
+ paramsList.push(...(page.Parameters ?? []));
+ }
+
+ return paramsList;
+ } catch (error) {
+ throw new Error(
+ `Failed to read SSM from path ${pathPrefix}. ERR: ${error}`,
+ );
+ }
+ }
+
+ async addParameter(
+ parameterName: string,
+ parameterValue: string,
+ type: ParameterType = ParameterType.SECURE_STRING,
+ overwrite = true,
+ ): Promise {
+ const result = await this.ssmClient.send(
+ new PutParameterCommand({
+ Name: parameterName,
+ Value: parameterValue,
+ Type: type,
+ Overwrite: overwrite,
+ }),
+ );
+
+ return {
+ Name: parameterName,
+ Value: parameterValue,
+ Version: result.Version,
+ };
+ }
+
+ async deleteParameter(parameterName: string) {
+ try {
+ await this.ssmClient.send(
+ new DeleteParameterCommand({ Name: parameterName }),
+ );
+ } catch (error) {
+ if (!(error instanceof ParameterNotFound)) {
+ throw error;
+ }
+ }
+ }
+
+ // eslint-disable-next-line class-methods-use-this
+ async clearCachedParameter(_: string): Promise {
+ /* no-op */
+ }
+}
+
+export const parameterStore = new ParameterStore();
diff --git a/src/utils/src/ssm-utils/ssm-client.ts b/src/utils/src/ssm-utils/ssm-client.ts
new file mode 100644
index 00000000..379a2a5c
--- /dev/null
+++ b/src/utils/src/ssm-utils/ssm-client.ts
@@ -0,0 +1,7 @@
+import { SSMClient } from '@aws-sdk/client-ssm';
+
+export const ssmClient = new SSMClient({
+ region: process.env.AWS_REGION || 'eu-west-2',
+ retryMode: 'standard',
+ maxAttempts: 10,
+});
diff --git a/src/utils/src/ssm-utils/types.ts b/src/utils/src/ssm-utils/types.ts
new file mode 100644
index 00000000..ea1a750c
--- /dev/null
+++ b/src/utils/src/ssm-utils/types.ts
@@ -0,0 +1,32 @@
+import { ParameterType, Parameter as SSMParameter } from '@aws-sdk/client-ssm';
+
+export type Parameter = SSMParameter;
+
+export type GetParameterOptions = { maxAttempts?: number; force?: boolean };
+
+export type GetAllParametersOptions = { recursive?: boolean; force?: boolean };
+
+export interface IParameterStore {
+ getParameter(
+ parameterName: string,
+ options?: GetParameterOptions,
+ ): Promise;
+ /**
+ * Refer to implementation documentation for usage details.
+ */
+ getAllParameters(
+ pathPrefix: string,
+ options?: GetAllParametersOptions,
+ ): Promise;
+ addParameter: (
+ parameterName: string,
+ parameterValue: string,
+ type?: ParameterType,
+ overwrite?: boolean,
+ ) => Promise;
+ deleteParameter: (parameterName: string) => Promise;
+ clearCachedParameter(parameterName: string, version?: number): Promise;
+}
+
+export type NonNullSSMParam = Omit &
+ Required>;
diff --git a/src/utils/tsconfig.json b/src/utils/tsconfig.json
new file mode 100644
index 00000000..de8ca2a7
--- /dev/null
+++ b/src/utils/tsconfig.json
@@ -0,0 +1,14 @@
+{
+ "compilerOptions": {
+ "baseUrl": "./src/",
+ "isolatedModules": true
+ },
+ "exclude": [
+ "node_modules"
+ ],
+ "extends": "@tsconfig/node22/tsconfig.json",
+ "include": [
+ "src/**/*",
+ "./jest.config.ts"
+ ]
+}
diff --git a/turbo.json b/turbo.json
new file mode 100644
index 00000000..a24d7688
--- /dev/null
+++ b/turbo.json
@@ -0,0 +1,39 @@
+{
+ "$schema": "https://turborepo.dev/schema.json",
+ "tasks": {
+ "generate-dependencies": {
+ "dependsOn": [
+ "^generate-dependencies"
+ ],
+ "inputs": [
+ "src/utils/",
+ "src/utils/schemas/**"
+ ]
+ },
+ "lint": {
+ "dependsOn": [
+ "^lint"
+ ]
+ },
+ "lint:fix": {
+ "dependsOn": [
+ "^lint:fix"
+ ]
+ },
+ "test:contract": {
+ "dependsOn": [
+ "^test:contract"
+ ]
+ },
+ "test:unit": {
+ "dependsOn": [
+ "^test:unit"
+ ]
+ },
+ "typecheck": {
+ "dependsOn": [
+ "^typecheck"
+ ]
+ }
+ }
+}