From 5761f5e6061b156bf8f26a15faf1deb672c5b94b Mon Sep 17 00:00:00 2001 From: Justin Frahm Date: Wed, 17 Sep 2025 12:43:14 -0600 Subject: [PATCH 01/32] WIP: refactor backend to be multi-service --- .github/workflows/check-common-cdk.yml | 56 + .../check-compact-connect-ui-app.yml | 108 + .github/workflows/check-compact-connect.yml | 108 + .github/workflows/check-lambda-js.yml | 66 - ...eck-python.yml => check-multi-account.yml} | 30 +- backend/bin/compile_requirements.sh | 31 - backend/bin/pre-commit | 2 + backend/bin/sync_deps.sh | 34 - .../common-cdk/common_constructs/README.md | 9 + .../common_constructs/access_logs_bucket.py | 0 .../common_constructs/alarm_topic.py | 0 .../common_constructs/bucket.py | 0 .../frontend_app_config_utility.py | 0 .../common_constructs/security_profile.py | 0 .../service_principal_name.py | 0 .../slack_channel_configuration.py | 0 .../common_constructs/stack.py | 0 .../common_constructs/webacl.py | 0 backend/common-cdk/requirements-dev.in | 6 + backend/common-cdk/requirements.in | 4 + backend/common-cdk/ruff.toml | 112 + .../tests}/__init__.py | 0 .../tests}/test_alarm_topic.py | 1 + .../{ => compact-connect-ui-app}/.coveragerc | 1 + backend/compact-connect-ui-app/README.md | 318 + backend/compact-connect-ui-app/app.py | 195 + .../app_clients/README.md | 272 + .../app_clients/bin/create_app_client.py | 341 + .../app_clients/bin/manage_signature_keys.py | 390 + .../README.md | 1 + .../bin/compile_requirements.sh | 5 + .../bin/run_python_tests.py | 16 +- .../bin/run_tests.sh | 2 +- .../compact-connect-ui-app/bin/sync_deps.sh | 8 + .../cdk.context.beta-example.json | 64 + .../cdk.context.deploy-example.json | 24 + .../cdk.context.prod-example.json | 64 + .../cdk.context.sandbox-example.json | 32 + .../cdk.context.test-example.json | 65 + backend/compact-connect-ui-app/cdk.json | 103 + .../lambdas/nodejs/.gitignore | 6 + .../lambdas/nodejs/.mocharc.js | 0 .../lambdas/nodejs/Gruntfile.js | 64 + .../lambdas/nodejs/README.md | 39 + .../nodejs/cloudfront-csp/.editorconfig | 0 .../lambdas/nodejs/cloudfront-csp/.gitignore | 0 .../lambdas/nodejs/cloudfront-csp/README.md | 0 .../lambdas/nodejs/cloudfront-csp/index.js | 0 .../cloudfront-csp/test/config/index.js | 0 .../nodejs/cloudfront-csp/test/index.test.js | 0 .../lambdas/nodejs/eslint.config.mjs | 82 + .../lambdas/nodejs/jest.config.mjs | 20 + .../lambdas/nodejs/package.json | 62 + .../lambdas/nodejs/tsconfig.json | 32 + .../lambdas/nodejs/yarn.lock | 6245 +++++++++++++++++ .../pipeline/__init__.py | 399 ++ .../design/.!35012!pipeline-architecture.pdf | Bin 0 -> 353 bytes .../pipeline/design/README.md | 96 + .../pipeline/design/pipeline-architecture.pdf | Bin 0 -> 89240 bytes .../pipeline/frontend_pipeline.py | 0 .../pipeline/frontend_stage.py | 1 + .../pipeline/synth_substitute_stack.py | 30 + .../pipeline/synth_substitute_stage.py | 38 + .../requirements-dev.in | 6 + .../requirements-dev.txt | 108 + .../compact-connect-ui-app/requirements.in | 6 + .../compact-connect-ui-app/requirements.txt | 74 + backend/compact-connect-ui-app/ruff.toml | 112 + .../compact-connect-ui-app/stacks/__init__.py | 0 .../frontend_deployment_stack/__init__.py | 0 .../frontend_deployment_stack/deployment.py | 0 .../frontend_deployment_stack/distribution.py | 0 .../compact-connect-ui-app/tests/__init__.py | 5 + .../tests/app/__init__.py | 0 .../compact-connect-ui-app/tests/app/base.py | 149 + .../tests/app/test_pipeline.py | 41 + ...end-FrontendDeploymentStack-UI_BUCKET.json | 0 ...ontendDeploymentStack-UI_DISTRIBUTION.json | 0 ...Stack-UI_DISTRIBUTION_LAMBDA_FUNCTION.json | 0 ...end-FrontendDeploymentStack-UI_BUCKET.json | 0 ...ontendDeploymentStack-UI_DISTRIBUTION.json | 0 ...Stack-UI_DISTRIBUTION_LAMBDA_FUNCTION.json | 0 ...end-FrontendDeploymentStack-UI_BUCKET.json | 0 ...ontendDeploymentStack-UI_DISTRIBUTION.json | 0 ...Stack-UI_DISTRIBUTION_LAMBDA_FUNCTION.json | 0 .../.!35287!military_affiliation.pdf | Bin 0 -> 9 bytes .../test_files/military_affiliation.pdf | Bin 0 -> 24457 bytes .../resources/test_ui_directory/index.html | 0 backend/compact-connect/.coveragerc | 1 + backend/compact-connect/app.py | 76 +- .../bin/compile_requirements.sh | 25 + .../compact-connect/bin/run_python_tests.py | 180 + backend/compact-connect/bin/run_tests.sh | 129 + backend/compact-connect/bin/sync_deps.sh | 28 + .../common_constructs/README.md | 8 + .../common_constructs/cc_api.py | 2 +- .../common_constructs/cognito_user_backup.py | 2 +- .../common_constructs/resource_scope_mixin.py | 1 + .../lambdas/nodejs/cloudfront-csp/yarn.lock | 2971 -------- .../lambdas/nodejs/eslint.config.mjs | 8 - .../lambdas/nodejs/package.json | 2 +- .../python/compact-configuration/.coveragerc | 10 - .../python/custom-resources/.coveragerc | 10 - .../lambdas/python/data-events/.coveragerc | 10 - .../python/provider-data-v1/.coveragerc | 10 - .../lambdas/python/purchases/.coveragerc | 10 - .../python/staff-user-pre-token/.coveragerc | 10 - .../lambdas/python/staff-users/.coveragerc | 10 - backend/compact-connect/pipeline/__init__.py | 222 +- .../pipeline/backend_pipeline.py | 3 +- .../compact-connect/pipeline/backend_stage.py | 3 +- backend/compact-connect/ruff.toml | 112 + .../stacks/api_lambda_stack/__init__.py | 2 +- .../stacks/api_lambda_stack/provider_users.py | 2 +- .../stacks/api_stack/__init__.py | 4 +- .../compact-connect/stacks/api_stack/api.py | 4 +- .../stacks/api_stack/v1_api/api.py | 2 +- .../stacks/api_stack/v1_api/attestations.py | 2 +- .../api_stack/v1_api/bulk_upload_url.py | 1 + .../v1_api/compact_configuration_api.py | 2 +- .../stacks/api_stack/v1_api/credentials.py | 2 +- .../stacks/api_stack/v1_api/post_licenses.py | 2 +- .../api_stack/v1_api/provider_management.py | 2 +- .../stacks/api_stack/v1_api/provider_users.py | 2 +- .../api_stack/v1_api/public_lookup_api.py | 2 +- .../stacks/api_stack/v1_api/purchases.py | 2 +- .../stacks/api_stack/v1_api/staff_users.py | 2 +- .../disaster_recovery_stack/__init__.py | 2 +- .../restore_dynamo_db_table_step_function.py | 3 +- .../sync_table_step_function.py | 3 +- .../stacks/event_listener_stack/__init__.py | 4 +- .../compact-connect/stacks/ingest_stack.py | 4 +- .../stacks/managed_login_stack.py | 2 +- .../stacks/notification_stack.py | 4 +- .../stacks/persistent_stack/__init__.py | 4 +- .../persistent_stack/bulk_uploads_bucket.py | 6 +- .../compact_configuration_table.py | 2 +- .../compact_configuration_upload.py | 3 +- .../persistent_stack/data_event_table.py | 4 +- .../stacks/persistent_stack/provider_table.py | 2 +- .../persistent_stack/provider_users_bucket.py | 6 +- .../stacks/persistent_stack/ssn_table.py | 4 +- .../stacks/persistent_stack/staff_users.py | 4 +- .../transaction_history_table.py | 2 +- .../transaction_reports_bucket.py | 3 +- .../user_email_notifications.py | 3 +- .../stacks/persistent_stack/users_table.py | 2 +- .../stacks/provider_users/__init__.py | 4 +- .../stacks/provider_users/provider_users.py | 4 +- .../compact-connect/stacks/reporting_stack.py | 4 +- .../stacks/state_api_stack/__init__.py | 4 +- .../stacks/state_api_stack/api.py | 2 +- .../state_api_stack/v1_api/bulk_upload_url.py | 2 +- .../state_api_stack/v1_api/post_licenses.py | 2 +- .../v1_api/provider_management.py | 2 +- .../stacks/state_auth/__init__.py | 2 +- .../stacks/state_auth/state_auth_users.py | 2 +- .../transaction_monitoring_stack/__init__.py | 2 +- ...transaction_history_processing_workflow.py | 4 +- backend/compact-connect/tests/__init__.py | 5 + backend/compact-connect/tests/app/base.py | 36 +- .../tests/app/test_api/__init__.py | 2 +- .../tests/app/test_cognito_backup.py | 2 +- .../tests/app/test_pipeline.py | 39 +- .../compact-connect/tests/app/test_sandbox.py | 1 - .../test_cognito_user_backup.py | 1 + .../test_queue_event_listener.py | 1 + .../test_queued_lambda_processor.py | 1 + .../common => multi-account}/.coveragerc | 9 +- .../backups/requirements-dev.txt | 24 +- .../multi-account/backups/requirements.txt | 14 +- .../multi-account/bin/compile_requirements.sh | 9 + backend/multi-account/bin/run_python_tests.py | 171 + backend/multi-account/bin/run_tests.sh | 115 + backend/multi-account/bin/sync_deps.sh | 7 + .../control-tower/requirements-dev.in | 5 + .../control-tower/requirements-dev.txt | 98 +- .../control-tower/requirements.txt | 14 +- .../log-aggregation/requirements-dev.txt | 6 +- .../log-aggregation/requirements.txt | 14 +- backend/multi-account/ruff.toml | 112 + backend/social-work-app/README.md | 3 + 182 files changed, 11062 insertions(+), 3687 deletions(-) create mode 100644 .github/workflows/check-common-cdk.yml create mode 100644 .github/workflows/check-compact-connect-ui-app.yml create mode 100644 .github/workflows/check-compact-connect.yml delete mode 100644 .github/workflows/check-lambda-js.yml rename .github/workflows/{check-python.yml => check-multi-account.yml} (62%) delete mode 100755 backend/bin/compile_requirements.sh delete mode 100755 backend/bin/sync_deps.sh create mode 100644 backend/common-cdk/common_constructs/README.md rename backend/{compact-connect => common-cdk}/common_constructs/access_logs_bucket.py (100%) rename backend/{compact-connect => common-cdk}/common_constructs/alarm_topic.py (100%) rename backend/{compact-connect => common-cdk}/common_constructs/bucket.py (100%) rename backend/{compact-connect => common-cdk}/common_constructs/frontend_app_config_utility.py (100%) rename backend/{compact-connect => common-cdk}/common_constructs/security_profile.py (100%) rename backend/{compact-connect => common-cdk}/common_constructs/service_principal_name.py (100%) rename backend/{compact-connect => common-cdk}/common_constructs/slack_channel_configuration.py (100%) rename backend/{compact-connect => common-cdk}/common_constructs/stack.py (100%) rename backend/{compact-connect => common-cdk}/common_constructs/webacl.py (100%) create mode 100644 backend/common-cdk/requirements-dev.in create mode 100644 backend/common-cdk/requirements.in create mode 100644 backend/common-cdk/ruff.toml rename backend/{compact-connect/common_constructs => common-cdk/tests}/__init__.py (100%) rename backend/{compact-connect/tests/common_constructs => common-cdk/tests}/test_alarm_topic.py (99%) rename backend/{ => compact-connect-ui-app}/.coveragerc (93%) create mode 100644 backend/compact-connect-ui-app/README.md create mode 100644 backend/compact-connect-ui-app/app.py create mode 100644 backend/compact-connect-ui-app/app_clients/README.md create mode 100755 backend/compact-connect-ui-app/app_clients/bin/create_app_client.py create mode 100755 backend/compact-connect-ui-app/app_clients/bin/manage_signature_keys.py create mode 100644 backend/compact-connect-ui-app/app_clients/it_staff_onboarding_instructions/README.md create mode 100755 backend/compact-connect-ui-app/bin/compile_requirements.sh rename backend/{ => compact-connect-ui-app}/bin/run_python_tests.py (88%) rename backend/{ => compact-connect-ui-app}/bin/run_tests.sh (98%) create mode 100755 backend/compact-connect-ui-app/bin/sync_deps.sh create mode 100644 backend/compact-connect-ui-app/cdk.context.beta-example.json create mode 100644 backend/compact-connect-ui-app/cdk.context.deploy-example.json create mode 100644 backend/compact-connect-ui-app/cdk.context.prod-example.json create mode 100644 backend/compact-connect-ui-app/cdk.context.sandbox-example.json create mode 100644 backend/compact-connect-ui-app/cdk.context.test-example.json create mode 100644 backend/compact-connect-ui-app/cdk.json create mode 100644 backend/compact-connect-ui-app/lambdas/nodejs/.gitignore rename backend/{compact-connect => compact-connect-ui-app}/lambdas/nodejs/.mocharc.js (100%) create mode 100644 backend/compact-connect-ui-app/lambdas/nodejs/Gruntfile.js create mode 100644 backend/compact-connect-ui-app/lambdas/nodejs/README.md rename backend/{compact-connect => compact-connect-ui-app}/lambdas/nodejs/cloudfront-csp/.editorconfig (100%) rename backend/{compact-connect => compact-connect-ui-app}/lambdas/nodejs/cloudfront-csp/.gitignore (100%) rename backend/{compact-connect => compact-connect-ui-app}/lambdas/nodejs/cloudfront-csp/README.md (100%) rename backend/{compact-connect => compact-connect-ui-app}/lambdas/nodejs/cloudfront-csp/index.js (100%) rename backend/{compact-connect => compact-connect-ui-app}/lambdas/nodejs/cloudfront-csp/test/config/index.js (100%) rename backend/{compact-connect => compact-connect-ui-app}/lambdas/nodejs/cloudfront-csp/test/index.test.js (100%) create mode 100644 backend/compact-connect-ui-app/lambdas/nodejs/eslint.config.mjs create mode 100644 backend/compact-connect-ui-app/lambdas/nodejs/jest.config.mjs create mode 100644 backend/compact-connect-ui-app/lambdas/nodejs/package.json create mode 100644 backend/compact-connect-ui-app/lambdas/nodejs/tsconfig.json create mode 100644 backend/compact-connect-ui-app/lambdas/nodejs/yarn.lock create mode 100644 backend/compact-connect-ui-app/pipeline/__init__.py create mode 100644 backend/compact-connect-ui-app/pipeline/design/.!35012!pipeline-architecture.pdf create mode 100644 backend/compact-connect-ui-app/pipeline/design/README.md create mode 100644 backend/compact-connect-ui-app/pipeline/design/pipeline-architecture.pdf rename backend/{compact-connect => compact-connect-ui-app}/pipeline/frontend_pipeline.py (100%) rename backend/{compact-connect => compact-connect-ui-app}/pipeline/frontend_stage.py (99%) create mode 100644 backend/compact-connect-ui-app/pipeline/synth_substitute_stack.py create mode 100644 backend/compact-connect-ui-app/pipeline/synth_substitute_stage.py create mode 100644 backend/compact-connect-ui-app/requirements-dev.in create mode 100644 backend/compact-connect-ui-app/requirements-dev.txt create mode 100644 backend/compact-connect-ui-app/requirements.in create mode 100644 backend/compact-connect-ui-app/requirements.txt create mode 100644 backend/compact-connect-ui-app/ruff.toml create mode 100644 backend/compact-connect-ui-app/stacks/__init__.py rename backend/{compact-connect => compact-connect-ui-app}/stacks/frontend_deployment_stack/__init__.py (100%) rename backend/{compact-connect => compact-connect-ui-app}/stacks/frontend_deployment_stack/deployment.py (100%) rename backend/{compact-connect => compact-connect-ui-app}/stacks/frontend_deployment_stack/distribution.py (100%) create mode 100644 backend/compact-connect-ui-app/tests/__init__.py create mode 100644 backend/compact-connect-ui-app/tests/app/__init__.py create mode 100644 backend/compact-connect-ui-app/tests/app/base.py create mode 100644 backend/compact-connect-ui-app/tests/app/test_pipeline.py rename backend/{compact-connect => compact-connect-ui-app}/tests/resources/snapshots/BetaFrontend-FrontendDeploymentStack-UI_BUCKET.json (100%) rename backend/{compact-connect => compact-connect-ui-app}/tests/resources/snapshots/BetaFrontend-FrontendDeploymentStack-UI_DISTRIBUTION.json (100%) rename backend/{compact-connect => compact-connect-ui-app}/tests/resources/snapshots/BetaFrontend-FrontendDeploymentStack-UI_DISTRIBUTION_LAMBDA_FUNCTION.json (100%) rename backend/{compact-connect => compact-connect-ui-app}/tests/resources/snapshots/ProdFrontend-FrontendDeploymentStack-UI_BUCKET.json (100%) rename backend/{compact-connect => compact-connect-ui-app}/tests/resources/snapshots/ProdFrontend-FrontendDeploymentStack-UI_DISTRIBUTION.json (100%) rename backend/{compact-connect => compact-connect-ui-app}/tests/resources/snapshots/ProdFrontend-FrontendDeploymentStack-UI_DISTRIBUTION_LAMBDA_FUNCTION.json (100%) rename backend/{compact-connect => compact-connect-ui-app}/tests/resources/snapshots/TestFrontend-FrontendDeploymentStack-UI_BUCKET.json (100%) rename backend/{compact-connect => compact-connect-ui-app}/tests/resources/snapshots/TestFrontend-FrontendDeploymentStack-UI_DISTRIBUTION.json (100%) rename backend/{compact-connect => compact-connect-ui-app}/tests/resources/snapshots/TestFrontend-FrontendDeploymentStack-UI_DISTRIBUTION_LAMBDA_FUNCTION.json (100%) create mode 100644 backend/compact-connect-ui-app/tests/resources/test_files/.!35287!military_affiliation.pdf create mode 100644 backend/compact-connect-ui-app/tests/resources/test_files/military_affiliation.pdf rename backend/{compact-connect => compact-connect-ui-app}/tests/resources/test_ui_directory/index.html (100%) create mode 100755 backend/compact-connect/bin/compile_requirements.sh create mode 100755 backend/compact-connect/bin/run_python_tests.py create mode 100755 backend/compact-connect/bin/run_tests.sh create mode 100755 backend/compact-connect/bin/sync_deps.sh create mode 100644 backend/compact-connect/common_constructs/README.md delete mode 100644 backend/compact-connect/lambdas/nodejs/cloudfront-csp/yarn.lock delete mode 100644 backend/compact-connect/lambdas/python/compact-configuration/.coveragerc delete mode 100644 backend/compact-connect/lambdas/python/custom-resources/.coveragerc delete mode 100644 backend/compact-connect/lambdas/python/data-events/.coveragerc delete mode 100644 backend/compact-connect/lambdas/python/provider-data-v1/.coveragerc delete mode 100644 backend/compact-connect/lambdas/python/purchases/.coveragerc delete mode 100644 backend/compact-connect/lambdas/python/staff-user-pre-token/.coveragerc delete mode 100644 backend/compact-connect/lambdas/python/staff-users/.coveragerc create mode 100644 backend/compact-connect/ruff.toml rename backend/{compact-connect/lambdas/python/common => multi-account}/.coveragerc (51%) create mode 100755 backend/multi-account/bin/compile_requirements.sh create mode 100755 backend/multi-account/bin/run_python_tests.py create mode 100755 backend/multi-account/bin/run_tests.sh create mode 100755 backend/multi-account/bin/sync_deps.sh create mode 100644 backend/multi-account/ruff.toml create mode 100644 backend/social-work-app/README.md diff --git a/.github/workflows/check-common-cdk.yml b/.github/workflows/check-common-cdk.yml new file mode 100644 index 000000000..80d381f9b --- /dev/null +++ b/.github/workflows/check-common-cdk.yml @@ -0,0 +1,56 @@ +name: Check-Common-CDK + +on: + pull_request: + paths: + - backend/common-cdk/** + +env: + AWS_REGION : "us-east-1" + +# Permission can be added at job level or workflow level +permissions: + id-token: write # This is required for requesting the JWT + contents: read # This is required for actions/checkout + +jobs: + LintPython: + runs-on: ubuntu-latest + steps: + - uses: actions/setup-python@v5 + with: + python-version: '3.12' + + # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it + - uses: actions/checkout@v2 + + - name: Install dev dependencies + run: "pip install -r backend/common-cdk/requirements-dev.in" + + - name: Lint Code + run: "cd backend/common-cdk; ruff check $(git ls-files '*.py')" + + - name: Check Dependencies + run: "pip-audit" + + TestApp: + runs-on: ubuntu-latest + steps: + # Checks-out the repository under $GITHUB_WORKSPACE + - uses: actions/checkout@v2 + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Install dev dependencies + run: "pip install -r backend/common-cdk/requirements-dev.in" + + - name: Install all Python dependencies + run: "pip install -r backend/common-cdk/requirements.in" + + - name: Test backend + # Start at 14%, because that's what our 'unit' coverage is, while we convert this to more stand-alone + # We will raise this up to 90 over time, to support these constructs more like a library + run: "cd backend/common-cdk; pytest tests --cov=common_constructs --cov-fail-under 14" diff --git a/.github/workflows/check-compact-connect-ui-app.yml b/.github/workflows/check-compact-connect-ui-app.yml new file mode 100644 index 000000000..915c2c2ad --- /dev/null +++ b/.github/workflows/check-compact-connect-ui-app.yml @@ -0,0 +1,108 @@ +name: Check-CompactConnect-UI-Infra + +on: + pull_request: + paths: + - backend/compact-connect-ui-app/** + +env: + AWS_REGION : "us-east-1" + +# Permission can be added at job level or workflow level +permissions: + id-token: write # This is required for requesting the JWT + contents: read # This is required for actions/checkout + +jobs: + LintPython: + runs-on: ubuntu-latest + steps: + - uses: actions/setup-python@v5 + with: + python-version: '3.12' + + # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it + - uses: actions/checkout@v2 + + - name: Install dev dependencies + run: "pip install -r backend/compact-connect-ui-app/requirements-dev.txt" + + - name: Lint Code + run: "cd backend/compact-connect-ui-app; ruff check $(git ls-files '*.py')" + + - name: Check Dependencies + run: "pip-audit" + + LintNode: + runs-on: ubuntu-latest + + steps: + # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it + - uses: actions/checkout@v2 + + - run: echo "💡 The ${{ github.repository }} repository has been cloned to the runner." + - run: echo "🖥️ The workflow is now ready to test your code on the runner." + + # Setup Node + - name: Setup Node + uses: actions/setup-node@v1 + with: + node-version: '22.1.0' + + # Use any cached yarn dependencies (saves build time) + - uses: actions/cache@v4 + with: + path: '**/node_modules' + key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }} + + # Install Yarn Dependencies + - name: Install Node.js dependencies + run: yarn install + working-directory: ./backend/compact-connect-ui-app/lambdas/nodejs + + # Run Linter Checks + - name: Run linter + run: yarn run lint + working-directory: ./backend/compact-connect-ui-app/lambdas/nodejs + + # Audit dependencies for vulnerabilities + - name: Audit dependencies (Cloudfront CSP Lambda) + run: yarn run audit:dependencies + working-directory: ./backend/compact-connect-ui-app/lambdas/nodejs + + TestApp: + runs-on: ubuntu-latest + steps: + # Checks-out the repository under $GITHUB_WORKSPACE + - uses: actions/checkout@v2 + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + + # Setup Node + - name: Setup Node + uses: actions/setup-node@v1 + with: + node-version: '22.1.0' + + # Use any cached yarn dependencies (saves build time) + - uses: actions/cache@v4 + with: + path: '**/node_modules' + key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }} + + # Install Yarn Dependencies + - name: Install Node.js dependencies + run: yarn install + working-directory: ./backend/compact-connect-ui-app/lambdas/nodejs + + - name: Install dev dependencies + run: "pip install -r backend/compact-connect-ui-app/requirements-dev.txt" + + - name: Install all Python dependencies + run: "cd backend/compact-connect-ui-app; bin/sync_deps.sh" + + - name: Test backend + run: "cd backend/compact-connect-ui-app; bin/run_tests.sh -l all -no" diff --git a/.github/workflows/check-compact-connect.yml b/.github/workflows/check-compact-connect.yml new file mode 100644 index 000000000..267fbf577 --- /dev/null +++ b/.github/workflows/check-compact-connect.yml @@ -0,0 +1,108 @@ +name: Check-CompactConnect + +on: + pull_request: + paths: + - backend/compact-connect/** + +env: + AWS_REGION : "us-east-1" + +# Permission can be added at job level or workflow level +permissions: + id-token: write # This is required for requesting the JWT + contents: read # This is required for actions/checkout + +jobs: + LintPython: + runs-on: ubuntu-latest + steps: + - uses: actions/setup-python@v5 + with: + python-version: '3.12' + + # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it + - uses: actions/checkout@v2 + + - name: Install dev dependencies + run: "pip install -r backend/compact-connect/requirements-dev.txt" + + - name: Lint Code + run: "cd backend/compact-connect; ruff check $(git ls-files '*.py')" + + - name: Check Dependencies + run: "pip-audit" + + LintNode: + runs-on: ubuntu-latest + + steps: + # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it + - uses: actions/checkout@v2 + + - run: echo "💡 The ${{ github.repository }} repository has been cloned to the runner." + - run: echo "🖥️ The workflow is now ready to test your code on the runner." + + # Setup Node + - name: Setup Node + uses: actions/setup-node@v1 + with: + node-version: '22.1.0' + + # Use any cached yarn dependencies (saves build time) + - uses: actions/cache@v4 + with: + path: '**/node_modules' + key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }} + + # Install Yarn Dependencies + - name: Install Node.js dependencies + run: yarn install + working-directory: ./backend/compact-connect/lambdas/nodejs + + # Run Linter Checks + - name: Run linter + run: yarn run lint + working-directory: ./backend/compact-connect/lambdas/nodejs + + # Audit dependencies for vulnerabilities + - name: Audit dependencies + run: yarn run audit:dependencies + working-directory: ./backend/compact-connect/lambdas/nodejs + + TestApp: + runs-on: ubuntu-latest + steps: + # Checks-out the repository under $GITHUB_WORKSPACE + - uses: actions/checkout@v2 + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + + # Setup Node + - name: Setup Node + uses: actions/setup-node@v1 + with: + node-version: '22.1.0' + + # Use any cached yarn dependencies (saves build time) + - uses: actions/cache@v4 + with: + path: '**/node_modules' + key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }} + + # Install Yarn Dependencies + - name: Install Node.js dependencies + run: yarn install + working-directory: ./backend/compact-connect/lambdas/nodejs + + - name: Install dev dependencies + run: "pip install -r backend/compact-connect/requirements-dev.txt" + + - name: Install all Python dependencies + run: "cd backend/compact-connect; bin/sync_deps.sh" + + - name: Test backend + run: "cd backend/compact-connect; bin/run_tests.sh -l all -no" diff --git a/.github/workflows/check-lambda-js.yml b/.github/workflows/check-lambda-js.yml deleted file mode 100644 index d32e93710..000000000 --- a/.github/workflows/check-lambda-js.yml +++ /dev/null @@ -1,66 +0,0 @@ -# Compact Connect - Lambda JavaScript Checks - -name: Check-Lambda-JS - -# Controls when the action will run. -on: - # Triggers the workflow on pull requests to trunk branches involving changes to lambda javascript - pull_request: - branches: - - main - - development - - ia-web-development - - ia-development - paths: - - backend/compact-connect/lambdas/nodejs/** - - # Allows you to run this workflow manually from the Actions tab - workflow_dispatch: - -# A workflow run is made up of one or more jobs that can run sequentially or in parallel -jobs: - CheckLambdas: - runs-on: ubuntu-latest - - steps: - - run: echo "🎉 The job was automatically triggered by a ${{ github.event_name }} event." - - run: echo "🐧 This job is now running on a ${{ runner.os }} server hosted by GitHub!" - - run: echo "🔎 The name of your branch is ${{ github.ref }} and your repository is ${{ github.repository }}." - - # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - - uses: actions/checkout@v2 - - - run: echo "💡 The ${{ github.repository }} repository has been cloned to the runner." - - run: echo "🖥️ The workflow is now ready to test your code on the runner." - - # Setup Node - - name: Setup Node - uses: actions/setup-node@v1 - with: - node-version: '22.1.0' - - # Use any cached yarn dependencies (saves build time) - - uses: actions/cache@v4 - with: - path: '**/node_modules' - key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }} - - # Install Yarn Dependencies - - name: Install JS dependencies (Cloudfront CSP Lambda) - run: yarn install - working-directory: ./backend/compact-connect/lambdas/nodejs - - # Run Linter Checks - - name: Run linter (Cloudfront CSP Lambda) - run: yarn run lint - working-directory: ./backend/compact-connect/lambdas/nodejs - - # Run Unit Tests - - name: Run unit tests (Cloudfront CSP Lambda) - run: yarn test - working-directory: ./backend/compact-connect/lambdas/nodejs - - # Audit dependencies for vulnerabilities - - name: Audit dependencies (Cloudfront CSP Lambda) - run: yarn run audit:dependencies - working-directory: ./backend/compact-connect/lambdas/nodejs diff --git a/.github/workflows/check-python.yml b/.github/workflows/check-multi-account.yml similarity index 62% rename from .github/workflows/check-python.yml rename to .github/workflows/check-multi-account.yml index c3606048a..ca7cf7f9b 100644 --- a/.github/workflows/check-python.yml +++ b/.github/workflows/check-multi-account.yml @@ -1,9 +1,9 @@ -name: Check-Python +name: Check-Multi-Account on: pull_request: paths: - - backend/** + - backend/multi-account/** env: AWS_REGION : "us-east-1" @@ -25,30 +25,30 @@ jobs: - uses: actions/checkout@v2 - name: Install dev dependencies - run: "pip install -r backend/compact-connect/requirements-dev.txt" + run: "pip install -r backend/multi-account/control-tower/requirements-dev.txt" - name: Lint Code - run: "cd backend; ruff check $(git ls-files '*.py')" + run: "cd backend/multi-account; ruff check $(git ls-files '*.py')" - TestPython: + - name: Check Dependencies + run: "pip-audit" + + TestApps: runs-on: ubuntu-latest steps: + # Checks-out the repository under $GITHUB_WORKSPACE + - uses: actions/checkout@v2 + - name: Setup Python uses: actions/setup-python@v5 with: python-version: '3.12' - # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - - uses: actions/checkout@v2 - - name: Install dev dependencies - run: "pip install -r backend/compact-connect/requirements-dev.txt" + run: "pip install -r backend/multi-account/control-tower/requirements-dev.txt" - - name: Install all dependencies - run: "cd backend; bin/sync_deps.sh" + - name: Install all Python dependencies + run: "cd backend/multi-account; bin/sync_deps.sh" - name: Test backend - run: "cd backend; bin/run_tests.sh -l python -no" - - - name: Check Dependencies - run: "pip-audit" + run: "cd backend/multi-account; bin/run_tests.sh -l all -no" diff --git a/backend/bin/compile_requirements.sh b/backend/bin/compile_requirements.sh deleted file mode 100755 index c64ad58c6..000000000 --- a/backend/bin/compile_requirements.sh +++ /dev/null @@ -1,31 +0,0 @@ -set -e - -pip-compile --no-emit-index-url --upgrade --no-strip-extras multi-account/backups/requirements-dev.in -pip-compile --no-emit-index-url --upgrade --no-strip-extras multi-account/backups/requirements.in -pip-compile --no-emit-index-url --upgrade --no-strip-extras multi-account/control-tower/requirements-dev.in -pip-compile --no-emit-index-url --upgrade --no-strip-extras multi-account/control-tower/requirements.in -pip-compile --no-emit-index-url --upgrade --no-strip-extras multi-account/log-aggregation/requirements-dev.in -pip-compile --no-emit-index-url --upgrade --no-strip-extras multi-account/log-aggregation/requirements.in -pip-compile --no-emit-index-url --upgrade --no-strip-extras compact-connect/requirements-dev.in -pip-compile --no-emit-index-url --upgrade --no-strip-extras compact-connect/requirements.in -pip-compile --no-emit-index-url --upgrade --no-strip-extras compact-connect/lambdas/python/cognito-backup/requirements-dev.in -pip-compile --no-emit-index-url --upgrade --no-strip-extras compact-connect/lambdas/python/cognito-backup/requirements.in -pip-compile --no-emit-index-url --upgrade --no-strip-extras compact-connect/lambdas/python/compact-configuration/requirements-dev.in -pip-compile --no-emit-index-url --upgrade --no-strip-extras compact-connect/lambdas/python/compact-configuration/requirements.in -pip-compile --no-emit-index-url --upgrade --no-strip-extras compact-connect/lambdas/python/common/requirements-dev.in -pip-compile --no-emit-index-url --upgrade --no-strip-extras compact-connect/lambdas/python/common/requirements.in -pip-compile --no-emit-index-url --upgrade --no-strip-extras compact-connect/lambdas/python/custom-resources/requirements-dev.in -pip-compile --no-emit-index-url --upgrade --no-strip-extras compact-connect/lambdas/python/custom-resources/requirements.in -pip-compile --no-emit-index-url --upgrade --no-strip-extras compact-connect/lambdas/python/data-events/requirements-dev.in -pip-compile --no-emit-index-url --upgrade --no-strip-extras compact-connect/lambdas/python/data-events/requirements.in -pip-compile --no-emit-index-url --upgrade --no-strip-extras compact-connect/lambdas/python/disaster-recovery/requirements-dev.in -pip-compile --no-emit-index-url --upgrade --no-strip-extras compact-connect/lambdas/python/disaster-recovery/requirements.in -pip-compile --no-emit-index-url --upgrade --no-strip-extras compact-connect/lambdas/python/provider-data-v1/requirements-dev.in -pip-compile --no-emit-index-url --upgrade --no-strip-extras compact-connect/lambdas/python/provider-data-v1/requirements.in -pip-compile --no-emit-index-url --upgrade --no-strip-extras compact-connect/lambdas/python/purchases/requirements-dev.in -pip-compile --no-emit-index-url --upgrade --no-strip-extras compact-connect/lambdas/python/purchases/requirements.in -pip-compile --no-emit-index-url --upgrade --no-strip-extras compact-connect/lambdas/python/staff-user-pre-token/requirements-dev.in -pip-compile --no-emit-index-url --upgrade --no-strip-extras compact-connect/lambdas/python/staff-user-pre-token/requirements.in -pip-compile --no-emit-index-url --upgrade --no-strip-extras compact-connect/lambdas/python/staff-users/requirements-dev.in -pip-compile --no-emit-index-url --upgrade --no-strip-extras compact-connect/lambdas/python/staff-users/requirements.in -bin/sync_deps.sh diff --git a/backend/bin/pre-commit b/backend/bin/pre-commit index 038b6fbb1..e0a6859f5 100755 --- a/backend/bin/pre-commit +++ b/backend/bin/pre-commit @@ -7,6 +7,8 @@ # # To enable this hook, move this file to ".git/hooks/pre-commit". +# TODO: Investigate a reasonable pre-commit testing approach with multi-service + if git rev-parse --verify HEAD >/dev/null 2>&1 then against=HEAD diff --git a/backend/bin/sync_deps.sh b/backend/bin/sync_deps.sh deleted file mode 100755 index c987ad9af..000000000 --- a/backend/bin/sync_deps.sh +++ /dev/null @@ -1,34 +0,0 @@ -( - cd compact-connect/lambdas/nodejs - yarn install -) - -pip-sync \ - multi-account/backups/requirements-dev.txt \ - multi-account/backups/requirements.txt \ - multi-account/control-tower/requirements-dev.txt \ - multi-account/control-tower/requirements.txt \ - multi-account/log-aggregation/requirements-dev.txt \ - multi-account/log-aggregation/requirements.txt \ - compact-connect/requirements-dev.txt \ - compact-connect/requirements.txt \ - compact-connect/lambdas/python/cognito-backup/requirements-dev.txt \ - compact-connect/lambdas/python/cognito-backup/requirements.txt \ - compact-connect/lambdas/python/compact-configuration/requirements-dev.txt \ - compact-connect/lambdas/python/compact-configuration/requirements.txt \ - compact-connect/lambdas/python/common/requirements-dev.txt \ - compact-connect/lambdas/python/common/requirements.txt \ - compact-connect/lambdas/python/custom-resources/requirements-dev.txt \ - compact-connect/lambdas/python/custom-resources/requirements.txt \ - compact-connect/lambdas/python/data-events/requirements-dev.txt \ - compact-connect/lambdas/python/data-events/requirements.txt \ - compact-connect/lambdas/python/disaster-recovery/requirements-dev.txt \ - compact-connect/lambdas/python/disaster-recovery/requirements.txt \ - compact-connect/lambdas/python/provider-data-v1/requirements-dev.txt \ - compact-connect/lambdas/python/provider-data-v1/requirements.txt \ - compact-connect/lambdas/python/purchases/requirements-dev.txt \ - compact-connect/lambdas/python/purchases/requirements.txt \ - compact-connect/lambdas/python/staff-user-pre-token/requirements-dev.txt \ - compact-connect/lambdas/python/staff-user-pre-token/requirements.txt \ - compact-connect/lambdas/python/staff-users/requirements-dev.txt \ - compact-connect/lambdas/python/staff-users/requirements.txt diff --git a/backend/common-cdk/common_constructs/README.md b/backend/common-cdk/common_constructs/README.md new file mode 100644 index 000000000..ca7bb143d --- /dev/null +++ b/backend/common-cdk/common_constructs/README.md @@ -0,0 +1,9 @@ +# Common Constructs + +This is the root node of a +[namespace package](https://docs.python.org/3/reference/import.html#reference-namespace-package), which houses common +CDK constructs that are used across different apps in the CompactConnect project. Modules in this package will be +merged in Python with similarly-named namespace packages in each app's specific folder. + +> **Note: Do not add an `__init__.py` file to any of the `common_constructs` packages, or they will break the +> import behavior. diff --git a/backend/compact-connect/common_constructs/access_logs_bucket.py b/backend/common-cdk/common_constructs/access_logs_bucket.py similarity index 100% rename from backend/compact-connect/common_constructs/access_logs_bucket.py rename to backend/common-cdk/common_constructs/access_logs_bucket.py diff --git a/backend/compact-connect/common_constructs/alarm_topic.py b/backend/common-cdk/common_constructs/alarm_topic.py similarity index 100% rename from backend/compact-connect/common_constructs/alarm_topic.py rename to backend/common-cdk/common_constructs/alarm_topic.py diff --git a/backend/compact-connect/common_constructs/bucket.py b/backend/common-cdk/common_constructs/bucket.py similarity index 100% rename from backend/compact-connect/common_constructs/bucket.py rename to backend/common-cdk/common_constructs/bucket.py diff --git a/backend/compact-connect/common_constructs/frontend_app_config_utility.py b/backend/common-cdk/common_constructs/frontend_app_config_utility.py similarity index 100% rename from backend/compact-connect/common_constructs/frontend_app_config_utility.py rename to backend/common-cdk/common_constructs/frontend_app_config_utility.py diff --git a/backend/compact-connect/common_constructs/security_profile.py b/backend/common-cdk/common_constructs/security_profile.py similarity index 100% rename from backend/compact-connect/common_constructs/security_profile.py rename to backend/common-cdk/common_constructs/security_profile.py diff --git a/backend/compact-connect/common_constructs/service_principal_name.py b/backend/common-cdk/common_constructs/service_principal_name.py similarity index 100% rename from backend/compact-connect/common_constructs/service_principal_name.py rename to backend/common-cdk/common_constructs/service_principal_name.py diff --git a/backend/compact-connect/common_constructs/slack_channel_configuration.py b/backend/common-cdk/common_constructs/slack_channel_configuration.py similarity index 100% rename from backend/compact-connect/common_constructs/slack_channel_configuration.py rename to backend/common-cdk/common_constructs/slack_channel_configuration.py diff --git a/backend/compact-connect/common_constructs/stack.py b/backend/common-cdk/common_constructs/stack.py similarity index 100% rename from backend/compact-connect/common_constructs/stack.py rename to backend/common-cdk/common_constructs/stack.py diff --git a/backend/compact-connect/common_constructs/webacl.py b/backend/common-cdk/common_constructs/webacl.py similarity index 100% rename from backend/compact-connect/common_constructs/webacl.py rename to backend/common-cdk/common_constructs/webacl.py diff --git a/backend/common-cdk/requirements-dev.in b/backend/common-cdk/requirements-dev.in new file mode 100644 index 000000000..34e2caeca --- /dev/null +++ b/backend/common-cdk/requirements-dev.in @@ -0,0 +1,6 @@ +pytest>=6.2.5 +pytest-cov +coverage +ruff +pip-tools +pip-audit diff --git a/backend/common-cdk/requirements.in b/backend/common-cdk/requirements.in new file mode 100644 index 000000000..fc655459b --- /dev/null +++ b/backend/common-cdk/requirements.in @@ -0,0 +1,4 @@ +aws-cdk-lib>=2.142.0 +aws-cdk-aws-lambda-python-alpha>=2.142.0a0 +constructs>=10.0.0,<11.0.0 +cdk-nag>=2.28.10, <3 diff --git a/backend/common-cdk/ruff.toml b/backend/common-cdk/ruff.toml new file mode 100644 index 000000000..66682be80 --- /dev/null +++ b/backend/common-cdk/ruff.toml @@ -0,0 +1,112 @@ +# Exclude a variety of commonly ignored directories. +exclude = [ + ".bzr", + ".direnv", + ".eggs", + ".git", + ".git-rewrite", + ".hg", + ".ipynb_checkpoints", + ".mypy_cache", + ".nox", + ".pants.d", + ".pyenv", + ".pytest_cache", + ".pytype", + ".ruff_cache", + ".svn", + ".tox", + ".venv", + ".vscode", + "__pypackages__", + "_build", + "buck-out", + "build", + "dist", + "node_modules", + "site-packages", + "venv" +] + +line-length = 120 +indent-width = 4 + +# Assume Python 3.12 +target-version = "py312" + +[lint] +select = [ + "A", # flake8-builtins + "ARG", # flake8-unused-arguments + "B", # flake8-bugbear + "BLE", # flake8-blind-except + "DTZ", # flake8-datetimez + "E", # pycodestyle + "F", # Pyflakes + "FIX", # flake8-fixme + "FA", # flake8-future-annotations + "G", # flake8-logging-format + "I", # isort + "N", # pep8-naming + "PIE", # flake8-pie + "SLF", # flake8-self + "RET", # flake8-return + "RSE", # flake8-raise + "S", # flake8-bandit + "T", # flake8-print + "UP", # pyupgrade + "W", # warning +] +ignore = [ + "S311", # suspicious-non-cryptographic-random-usage + "N818", # error-suffix-on-exception-name + # the following are ignored by recommendation of ruff documentation + # due to conflicts with the ruff formatter see https://docs.astral.sh/ruff/formatter/#conflicting-lint-rules + "W191", # indentation contains tabs + "E111", # indentation-with-invalid-multiple + "E114", # indentation-with-invalid-multiple-comment + "E117", # over-indented + "D206", # indent-with-spaces + "D300", # triple-single-quotes + "Q000", # bad-quotes-inline-string + "Q001", # bad-quotes-multiline-string + "Q002", # bad-quotes-docstring + "Q003", # avoidable-escaped-quote + "COM812", # missing-trailing-comma + "COM819", # prohibited-trailing-comma + "ISC001", # single-line-implicit-string-concatenation + "ISC002", # multi-line-implicit-string-concatenation +] + +# Allow fix for all enabled rules (when `--fix`) is provided. +fixable = ["ALL"] +unfixable = [] + +# Allow unused variables when underscore-prefixed. +dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" + +[format] +quote-style = "single" + +# Like Black, indent with spaces, rather than tabs. +indent-style = "space" + +# Like Black, respect magic trailing commas. +skip-magic-trailing-comma = false + +# Like Black, automatically detect the appropriate line ending. +line-ending = "auto" + +# Enable auto-formatting of code examples in docstrings. Markdown, +# reStructuredText code/literal blocks and doctests are all supported. +# +# This is currently disabled by default, but it is planned for this +# to be opt-out in the future. +docstring-code-format = false + +# Set the line length limit used when formatting code snippets in +# docstrings. +# +# This only has an effect when the `docstring-code-format` setting is +# enabled. +docstring-code-line-length = "dynamic" diff --git a/backend/compact-connect/common_constructs/__init__.py b/backend/common-cdk/tests/__init__.py similarity index 100% rename from backend/compact-connect/common_constructs/__init__.py rename to backend/common-cdk/tests/__init__.py diff --git a/backend/compact-connect/tests/common_constructs/test_alarm_topic.py b/backend/common-cdk/tests/test_alarm_topic.py similarity index 99% rename from backend/compact-connect/tests/common_constructs/test_alarm_topic.py rename to backend/common-cdk/tests/test_alarm_topic.py index 08ab26ce0..c7375210f 100644 --- a/backend/compact-connect/tests/common_constructs/test_alarm_topic.py +++ b/backend/common-cdk/tests/test_alarm_topic.py @@ -5,6 +5,7 @@ from aws_cdk.aws_chatbot import CfnSlackChannelConfiguration from aws_cdk.aws_kms import Key from aws_cdk.aws_sns import CfnSubscription, CfnTopic + from common_constructs.alarm_topic import AlarmTopic diff --git a/backend/.coveragerc b/backend/compact-connect-ui-app/.coveragerc similarity index 93% rename from backend/.coveragerc rename to backend/compact-connect-ui-app/.coveragerc index 9b9fe13dc..6e6498b9e 100644 --- a/backend/.coveragerc +++ b/backend/compact-connect-ui-app/.coveragerc @@ -5,6 +5,7 @@ include = data_file = .coverage omit = + */bin/* */cdk.out/* */smoke-test/* */tests/* diff --git a/backend/compact-connect-ui-app/README.md b/backend/compact-connect-ui-app/README.md new file mode 100644 index 000000000..c577ba4f0 --- /dev/null +++ b/backend/compact-connect-ui-app/README.md @@ -0,0 +1,318 @@ +# Compact Connect - Backend developer documentation + +## Looking for technical user documentation? +[Find it here](./docs/README.md) + +## Introduction + +This is an [AWS-CDK](https://aws.amazon.com/cdk/) based project for the backend components of the licensure compact system. + +## Table of Contents +- **[Prerequisites](#prerequisites)** +- **[Environment Config](#environment-config)** +- **[Installing Dependencies](#installing-dependencies)** +- **[Local Development](#local-development)** +- **[Tests](#tests)** +- **[Deployment](#deployment)** +- **[Google reCAPTCHA Setup](#google-recaptcha-setup)** +- **[Decommissioning](#decommissioning)** +- **[More Info](#more-info)** + +## Prerequisites +[Back to top](#compact-connect---backend-developer-documentation) + +To deploy this app, you will need: +1) Access to an AWS account +2) Python>=3.12 installed on your machine, preferably through a virtual environment management tool like [pyenv](https://github.com/pyenv/pyenv), + for clean management of virtual environments across multiple Python versions. +3) Otherwise, follow the [Prerequisites section](https://cdkworkshop.com/15-prerequisites.html) of the CDK workshop to + prepare your system to work with AWS-CDK, including a NodeJS install. +4) Follow the steps in the [Installing Dependencies](#installing-dependencies) section. + +## Environment Config +[Back to top](#compact-connect---backend-developer-documentation) + +The `cdk.json` file tells the CDK Toolkit how to execute your app, including configuration specific to any given target +deployment. You can add local configuration that will be merged into the `cdk.json['context']` values with a +`cdk.context.json` file that you will not check in. + +This project is set up like a standard Python project. To use it, create and activate a python virtual environment +using the tools of your choice (`pyenv` and `venv` are common). + +Once the virtualenv is activated, you can install the required dependencies. + +## Installing Dependencies +[Back to top](#compact-connect---backend-developer-documentation) + +Python requirements are pinned in [`requirements.txt`](requirements.txt). Install them using `pip`: + +``` +$ pip install -r requirements.txt +``` + +Node.js requirements (for some selected Lambda runtimes) are defined in [`package.json`](./lambdas/nodejs). Install them using `yarn`. + +```shell +$ cd lambdas/nodejs +$ yarn install +``` + +At this point you can now synthesize the CloudFormation template(s) for this code. + +``` +$ cdk synth +``` + +For development work there are additional requirements in `requirements-dev.txt` to install with +`pip install -r requirements-dev.txt`. + +To add additional dependencies, for example other CDK libraries, just add them to the `requirements.in` file and rerun +`pip-compile requirements.in`, then `pip install -r requirements.txt` command. + +## Local Development +[Back to top](#compact-connect---backend-developer-documentation) + +Local development can be done by editing the python code and `cdk.json`. For development purposes, this is simply a +Python project that can be exercised with local tests. Be sure to install the development requirements: + +``` +pip install -r requirements-dev.txt +``` + +Note that this project is a cloud-native app that has many small modular runtimes and integrations. Simulating that +distributed environment locally is not feasible, so the project relies heavily on test-driven development and solid +unit/functional tests to be incorporated in the development workflow. If you want to deploy this app to see how it runs +in the cloud, you can do so by configuring context for your own sandbox AWS account with context variables in +`cdk.context.json` and running the appropriate `cdk deploy` command. + +Once the deployment completes, you may want to run a local frontend. To do so, you must [populate a `.env` +file](../../webroot/README.md#environment-variables) with data on certain AWS resources (for example, AWS Cognito auth +domains and client IDs). A quick way to do that is to run `bin/fetch_aws_resources.py --as-env` from the +`backend/compact-connect` directory and copy/paste the output into `webroot/.env`. To see more data on your deployment +in human-readable format (for example, DynamoDB table names), run `bin/fetch_aws_resources.py` without any additional +flags. + +## Tests +[Back to top](#compact-connect---backend-developer-documentation) + +Being a cloud project whose infrastructure is written in Python, establishing tests, using the python `unittest` +library is critical to maintaining reliability and velocity. Be sure that any updates you add are covered +by tests, so we don't introduce bugs or cost time identifying testable bugs after deployment. Note that all +unit/functional tests bundled with this app should be designed to execute with zero requirements for environmental +setup (including environment variables) beyond simply installing the dependencies in `requirements*.txt` files. CDK +tests are defined under the [tests](./tests) directory. Runtime code tests should be similarly bundled within the +lambda folders. Code that is common across all lambdas should be tested in the `common-python` directory, to reduce +duplication and ensure consistency across the app. + +To execute the tests, simply run `bin/sync_deps.sh` then `bin/run_tests.sh` from the `backend` directory. + +## Documentation +[Back to top](#compact-connect---backend-developer-documentation) + +Keeping documentation current is an important part of feature development in this project. If the feature involves a non-trivial amount of architecture or other technical design, be sure that the design and considerations are captured in the [design documentation](./docs/design). If any updates are made to the API, be sure to follow these steps to keep the documentation current: +1) Export a fresh api specification (OAS 3.0) is exported from API Gateway and used to update [the Open API Specification JSON file](./docs/api-specification/latest-oas30.json). +2) Run `bin/trim_oas30.py` to organize and trim the API to include only supported API endpoints (and update the script itself, if needed). +3) If you exported the api specification from somewhere other than the CSG Test environment, be sure to set the `servers[0].url` entry back to the correct base URL for the CSG Test environment. +4) Use `bin/update_postman_collection.py` to update the [Postman Collection and Environment](./docs/postman), based on your new api spec, as appropriate. + +## Deployment +[Back to top](#compact-connect---backend-developer-documentation) + +### AWS Service Quota Increases +Before deploying to any environment (sandbox, test, beta, or production), you'll need to request service quota increases for the following AWS services: + +#### 1. Resource Servers Per User Pool (Amazon Cognito) +The Staff Users pool in CompactConnect uses resource servers for every jurisdiction (50+ states/territories). It also has resource servers for each compact to implement granular permission scopes. As detailed in [User Architecture documentation](./docs/design/README.md#user-architecture), resource server scopes are defined at both the jurisdiction level (ie for state administrators) and the compact level (ie for compact administrators), allowing for fine-grained access control tailored to each entity's specific needs. + +**Required Steps:** +1. Visit the [AWS Service Quotas console](https://console.aws.amazon.com/servicequotas/home) in each AWS account you'll be deploying to +2. Search for "Amazon Cognito User Pools" +3. Find "Resource servers per user pool" (default value is 25) +4. Request an increase to at least 100 resource servers per user pool +5. Wait for AWS to approve the increase before attempting deployment + +This increase gives sufficient capacity for all jurisdictions (50+ states/territories) plus all compacts, with room for future expansion. + +#### 2. Concurrent Executions (AWS Lambda) +CompactConnect uses numerous Lambda functions to power its backend services. By default, new AWS accounts have a very low concurrent execution limit. + +**Required Steps:** +1. Visit the [AWS Service Quotas console](https://console.aws.amazon.com/servicequotas/home) in each AWS account you'll be deploying to +2. Search for "AWS Lambda" +3. Find "Concurrent executions" (default value is 10 for new accounts) +4. Request an increase to at least 1,000 concurrent executions +5. Wait for AWS to approve the increase before attempting deployment + +This increase ensures that your Lambda functions can scale appropriately during periods of high traffic without throttling. + +### First deploy to a Sandbox environment +The very first deploy to a new environment (like your personal sandbox account) requires a few steps to fully set up +its environment: +1) *Optional:* Create a new Route53 HostedZone in your AWS sandbox account for the DNS domain name you want to use for + your app. See [About Route53 Hosted Zones](#about-route53-hosted-zones) for more. Note: Without this step, you will + not be able to log in to the UI hosted in CloudFront. The Oauth2 authentication process requires a predictable + callback url to be pre-configured, which the domain name provides. You can still run a local UI against this app, + so long as you leave the `allow_local_ui` context value set to `true` and remove the `domain_name` param in your environment's context. +2) *Optional if testing SES email notifications with custom domain:* By default, AWS does not allow sending emails to unverified email + addresses. If you need to test SES email notifications and do not want to request AWS to remove your account from + the SES sandbox, you will need to set up a verified SES email identity for each address you want to send emails to. + See [Creating an email address identity](https://docs.aws.amazon.com/ses/latest/dg/creating-identities.html#verify-email-addresses-procedure). Alternatively, you can request AWS to remove your account + from the SES sandbox, which will allow you to send emails to addresses that are not verified. See [SES Sandbox](https://docs.aws.amazon.com/ses/latest/dg/request-production-access.html). + If you do not specify the `domain_name` field in your environment context, cognito will use its default email configuration. + See [Default User Pool Email Settings](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-email.html#user-pool-email-default) +3) Copy [cdk.context.sandbox-example.json](./cdk.context.sandbox-example.json) to `cdk.context.json`. +4) At the top level of the JSON structure update the `"environment_name"` field to your own name. +5) Update the environment entry under `ssm_context.environments` to your own name and your own AWS sandbox account id (which you can find by following these [these instructions](https://docs.aws.amazon.com/accounts/latest/reference/manage-acct-identifiers.html#FindAccountId)), + and domain name, if you set one up. **If you opted not to create a HostedZone, remove the `domain_name` field.** + The key under `environments` must match the value you put under `environment_name`. +6) Configure your aws cli to authenticate against your own account. There are several ways to do this based on the + type of authentication you use to login to your account. See the [AWS CLI Configuration Guide](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-quickstart.html). +7) Complete the [Google reCAPTCHA Setup](#google-recaptcha-setup) steps for your sandbox environment. +8) Run `cdk bootstrap` to add some base CDK support infrastructure to your AWS account. +9) Run `cdk deploy 'Sandbox/*'` to get the initial backend stack resources deployed. +10) *Optional:* If you have a domain name configured for your sandbox environment, once the backend stacks have successfully deployed, you can deploy the frontend UI by setting the 'deploy_sandbox_ui' context field to `true` and run `cdk deploy 'SandboxUI/*'`. If you do not have a domain name configured, you can still run the UI from local host (see the README.md under the webroot folder for more information about running the app on localhost). If you do have a domain name configured, the application will be accessible at the 'app' subdomain of the configured domain name (e.g., app.[configured_domain.com]). + +### Subsequent sandbox deploys: +For any future deploys, everything is set up so a simple `cdk deploy 'Sandbox/*'` should update all your infrastructure +to reflect the changes in your code. Full deployment steps are: +1) Make sure your python environment is active. +2) Run `bin/sync_deps.sh` from `backend/` to ensure you have the latest requirements installed. +3) Configure your aws cli to authenticate against your own account. +4) Run `cdk deploy 'Sandbox/*'` to deploy the app to your AWS account. + +### Verifying SES configuration for Cognito User Notifications +If your account is in the SES sandbox, the simplest way to verify that SES is integrated with your cognito user pool is +to first go the AWS SES console and create an SES verified email identity for the email address you want to send a test +message to, See [Creating an email address identity](https://docs.aws.amazon.com/ses/latest/dg/creating-identities.html#verify-email-addresses-procedure). + +Once you have verified your email address, go to the AWS Cognito console and find your user pool. From there, you have +the option to create a new user using your verified email address, and select the option to send an email invite. Once +you create the user, you should receive an email notification from Cognito, and you can verify that +the FROM address is using your custom domain. The DMARC authentication will reject any emails from your domain that are +not properly configured using SPF and DKIM, so if you get the email notification from Cognito, you've verified that the +authentication is working as expected. + +### First deploy to the production environment +The production environment requires a few steps to fully set up before deploys can be automated. Refer to the +[README.md](../multi-account/README.md) for details on setting up a full multi-account architecture environment. Once +that is done, perform the following steps to deploy the CI/CD pipelines into the appropriate AWS account: +- Complete the [Google reCAPTCHA Setup](#google-recaptcha-setup) steps for each environment you will be deploying to (test, beta, prod). Use the appropriate domain name for the environment (ie `app.test.compactconnect.org` for test environment, `app.beta.compactconnect.org` for beta environment, `app.compactconnect.org` for production). For the production environment, make sure to complete the billing setup steps as well. +- Have someone who has suitable permissions in the GitHub organization that hosts this code navigate to the AWS Console + for the Deploy account, go to the + [AWS CodeStar Connections](https://us-east-1.console.aws.amazon.com/codesuite/settings/connections) page and create a + connection that grants AWS permission to receive GitHub events. Note the ARN of the resulting connection for + the next step. +- Create a new Route53 hosted zone for the domain name you plan to use for the app in each of the production, beta, and test AWS + accounts. See [About Route53 hosted zones](#about-route53-hosted-zones) below for more detail. +- Request AWS to remove your account from the SES sandbox and wait for them to complete this request. + See [SES Sandbox](https://docs.aws.amazon.com/ses/latest/dg/request-production-access.html). +- With the [aws-cli](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html), set up your local machine to authenticate against the Deploy account as an administrator. +- For every environment, copy the appropriate example context file (`cdk.context.deploy-example.json` for the `Deploy` account, `cdk.context.test-example.json` for the `Test` account, `cdk.context.beta-example.json` for the `Beta` account, or `cdk.context.prod-example.json` for the `Prod` account) to `cdk.context.json` and update the values to match your respective accounts and other identifiers, including the code star connection you just created to match the identifiers for your actual accounts and resources. You will then need to run the `bin/put_ssm_context.sh ` script to push relevant content from your `cdk.context.json` script into an SSM Parameter in your Deploy account. Replace `` with the target environment. For example, to set up for the test environment: `bin/put_ssm_context.sh test`. + For example, to set up for the test environment: `bin/put_ssm_context.sh test`. +- Optional: If a Slack integration is desired for operational support, have someone with permission to install Slack + apps in your workspace and Admin access to each of the Test, Beta, Prod, and Deploy accounts log into each AWS account + and go to the Chatbot service. Select 'Slack' under the **Configure a chat client** box and click **Configure + client**, then follow the Slack authorization prompts. This will authorize AWS to integrate with the channels you + identify in your `cdk.context.json` file. For each Slack channel you want to integrate, be sure to invite your new AWS app to those channels. Update the + `notifications.slack` sections of the `cdk.context.json` file with the details for your Slack workspace and channels. + If you opt not to integrate with Slack, remove the `slack` fields from the file. +- Set cli-environment variables `CDK_DEFAULT_ACCOUNT` and `CDK_DEFAULT_REGION` to your deployment account id and `us-east-1`, respectively. +- For each environment (test, beta, prod), you need to deploy both the backend and frontend pipeline stacks: + +1. **Deploy the Backend Pipeline Stacks first (note: you will need to approve the permission change requests for each stack deployment in the terminal)**: + `cdk deploy --context action=bootstrapDeploy TestBackendPipelineStack BetaBackendPipelineStack ProdBackendPipelineStack` + +2. **Then deploy the Frontend Pipeline Stacks (approve the permission change requests for each stack deployment)**: + `cdk deploy --context action=bootstrapDeploy TestFrontendPipelineStack BetaFrontendPipelineStack ProdFrontendPipelineStack` + +**Important**: When a pipeline stack is deployed, it will automatically trigger a deployment to its environment from the configured branch in your GitHub repo. The first time you deploy the backend pipeline, it should pass all the steps except the final trigger of the frontend pipeline, since the frontend pipeline will not exist until you deploy it. From there on, the pipelines should integrate as designed. + +### Subsequent production deploys + +Once the pipelines are established with the above steps, deployments will be automatically handled: + +- Pushes to the `development` branch will trigger the test backend pipeline, which will then trigger the test frontend pipeline +- Pushes to the `main` branch will trigger both the beta and production backend pipelines, which will then trigger their respective frontend pipelines. + +## Google reCAPTCHA Setup +[Back to top](#compact-connect---backend-developer-documentation) + +The practitioner registration endpoint uses Google reCAPTCHA to prevent abuse. Follow these steps to set up reCAPTCHA for your environment: + +1. Visit https://www.google.com/recaptcha/ +2. Go to "v3 Admin Console" + - If needed, enter your Google account credentials +3. Create a site + - Recaptcha type is v3 (score based) + - Domain will be the frontend browser domain for the environment ('localhost' for local development) + - Google Cloud Platform may require a project name + - Submit +4. Open the Settings for the new site + - The Site Key (Public) will need to be set in the cdk.context.json for the appropriate environment under the field named `recaptcha_public_key` for deployments, or in your local `.env` file of the webroot folder (if running the app locally) + - The Secret Key (Private) will need to be manually stored in the AWS account in secrets manager, using the following secret name: + `compact-connect/env/{value of 'environment_name' in cdk.context.json}/recaptcha/token` + The value of the secret key should be in the following format: + ``` + { + "token": "" + } + ``` + You can run the following aws cli command to create the secret (make sure you are logged in to the same AWS account you want to store the secret in, under the us-east-1 region): + ``` + aws secretsmanager create-secret --name compact-connect/env/{value of 'environment_name' in cdk.context.json}/recaptcha/token --secret-string '{"token": ""}' + ``` + +For Production environments, additional billing setup is required: +1. In the Settings for a reCAPTCHA site, click "View in Cloud Console" +2. From the main nav, go to Billing +3. If you have an existing billing account, you may link it. Otherwise, you can create a New Billing account, where you will add payment information +4. More info on Google Recaptcha billing: https://cloud.google.com/recaptcha/docs/billing-information + +### Useful commands + +* `cdk ls` list all stacks in the app +* `cdk synth` emits the synthesized CloudFormation template +* `cdk deploy` deploy this stack to your default AWS account/region +* `cdk diff` compare deployed stack with current state +* `cdk docs` open CDK documentation + +## Decommissioning +[Back to top](#compact-connect---backend-developer-documentation) + +You can tear down resources associated with any of the CloudFormation stacks for this application with +`cdk destroy `. Most persistent resources with data remain in the Persistent stack, so you can freely +destroy the others without losing users or data. If you wish to destroy the Persistent stack as well, be aware that +some resources may be left behind as CloudFormation is designed to err on the side of orphaning resources over data +loss. You can identify any resources that weren't destroyed by watching the stack deletion from the AWS CloudFormation +Console, then looking at the resources after its delete is complete, to look for any with a `Delete Skipped` status. + +## About Route53 hosted zones + +A Hosted Zone in Route53 represents a collection of DNS records for a particular domain and its subdomains, managed +together. See the [Route53 FAQs for more](https://aws.amazon.com/route53/faqs/). When creating a hosted zone, you have +to also configure the domain name registrar (be it AWS or some other vendor) to point to the name servers associated +with your hosted zone, before the records in the zone will have any effect. When deploying this app, creating a hosted +zone in the AWS account for the UI and API domains is part of the environment setup. If you use the common approach +of having your test environments be a subdomain of your production environments (i.e. `compcatconnect.org` for prod +and `test.compcatconnect.org` for test), you need to delegate nameserver authority from your production hosted zone +(`compactconnect.org` in this example) to your test account's hosted zone (`test.compactconnect.org`). To do this, you +need to create your production hosted zone (`compactconnect.org`) in your production account first, then create your +test hosted zone (`test.compactconnect.org`) in your test account second, then delegate name server authority to your +test subdomain. To do this, find the NS record associated with your test hosted zone and copy its value, which should +look something like: +```text +ns-1.awsdns-19.co.uk. +ns-2.awsdns-18.com. +ns-5.awsdns-15.net. +ns-6.awsdns-16.org. +``` + +Copy those name server values and, back in your production hosted zone, create a new NS record that matches the test +one, with the same value (i.e. Record Name: `test.compcatconnect.org`, Type: `NS`, Value: ``). Once that +is done, your test hosted zone is ready for use by the app. You will need to perform this action for your beta environment as well, should you choose to deploy one. + +## More Info +[Back to top](#compact-connect---backend-developer-documentation) + +- [cdk-workshop](https://cdkworkshop.com/): If you are new to CDK, I highly recommend you go through the CDK Workshop for a quick + introduction to the technology and its concepts before getting too deep into any particular project. diff --git a/backend/compact-connect-ui-app/app.py b/backend/compact-connect-ui-app/app.py new file mode 100644 index 000000000..be006f967 --- /dev/null +++ b/backend/compact-connect-ui-app/app.py @@ -0,0 +1,195 @@ +#!/usr/bin/env python3 +import os +import sys + +from aws_cdk import App, Environment + +# Make the `common_constructs` namespace package under `common-cdk` available to Python +sys.path.insert(0, os.path.abspath(os.path.join('..', 'common-cdk'))) + +from common_constructs.stack import StandardTags +from pipeline import ( + ACTION_CONTEXT_KEY, + PIPELINE_STACK_CONTEXT_KEY, + PIPELINE_SYNTH_ACTION, + BetaFrontendPipelineStack, + DeploymentResourcesStack, + ProdFrontendPipelineStack, + TestFrontendPipelineStack, +) +from pipeline.frontend_stage import FrontendStage + +# Pipeline stack name constants for DRY code +TEST_FRONTEND_PIPELINE_STACK = 'TestFrontendPipelineStack' +BETA_FRONTEND_PIPELINE_STACK = 'BetaFrontendPipelineStack' +PROD_FRONTEND_PIPELINE_STACK = 'ProdFrontendPipelineStack' +DEPLOYMENT_RESOURCES_STACK = 'DeploymentResourcesStack' + +# CDK path +CDK_PATH = 'backend/compact-connect-ui-app' + + +class CompactConnectApp(App): + """ + CompactConnect CDK Application + + This application implements a CDK Pipeline deployment architecture with + performance optimizations for faster synthesis and deployment workflows. + + Pipeline Execution Flow: + ---------------------- + - GitHub push → Frontend Pipeline + - GitHub push → Backend Pipeline → Frontend Pipeline + + Stack Structure: + --------------- + - Frontend Pipeline Stacks: TestFrontendPipelineStack, BetaFrontendPipelineStack, ProdFrontendPipelineStack + - DeploymentResourcesStack: Shared resources needed by all pipeline stacks + + Each pipeline type is in its own dedicated stack to avoid self-mutation conflicts. + + Environment Deployments: + ------------------------------- + see README.md for instructions on how to deploy to a sandbox or pipeline environment. + """ + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.sandbox_environment = self.node.try_get_context('sandbox') + + # Toggle for developers to deploy to a sandbox account without the pipeline + if self.sandbox_environment: + self._setup_sandbox_environment() + else: + self._setup_pipeline_environment() + + def _setup_sandbox_environment(self): + """Set up sandbox environment stacks""" + # ssm_context must be provided locally for a sandbox deploy + # TODO: review what context is still needed in this reduced app + ssm_context = self.node.get_context('ssm_context') + environment_name = self.node.get_context('environment_name') + environment_context = ssm_context['environments'][environment_name] + + # TODO: review this toggle flow for what can be simplified + # TODO: update pipeline docs + # NOTE: for first-time sandbox deployments, ensure you deploy the backend stage successfully first + # by running `cdk deploy 'Sandbox/*'`, then if you have a domain name configured and want to deploy the UI for + # your sandbox environment, set the 'deploy_sandbox_ui' field to true and deploy this stack by running + # `cdk deploy 'SandboxUI/*'. This ensures the user pool values are configured to be bundled with the UI build + # artifact. + if environment_context.get('deploy_sandbox_ui', False): + if not environment_context.get('domain_name'): + raise ValueError( + 'Cannot deploy sandbox UI if domain name is not configured for your environment. ' + 'You may still run the app from localhost. See README.md in the webroot folder for ' + 'more information about running the app from localhost.' + ) + + self.sandbox_frontend_stage = FrontendStage( + self, + 'SandboxUI', + environment_name=environment_name, + environment_context=environment_context, + ) + + def _setup_pipeline_environment(self): + """ + Set up pipeline environment stacks based on action and pipeline stack context + + This method implements the conditional stack creation pattern that is key to optimizing + synthesis performance in the CI/CD pipelines. It follows these rules: + + 1. For bootstrapDeploy: Creates all stacks to ensure permissions are correctly set when deploying resources + 2. For pipelineSynth: Creates only the specific stack requested to minimize synthesis time + + This approach dramatically reduces synthesis time in the pipeline while maintaining + all necessary permissions and relationships between stacks during bootstrap deployments. + """ + self.tags = self.node.get_context('tags') + self.action = self.node.try_get_context(ACTION_CONTEXT_KEY) + self.pipeline_stack_name = self.node.try_get_context(PIPELINE_STACK_CONTEXT_KEY) + + # Validate when in pipeline synth mode + if self.action == PIPELINE_SYNTH_ACTION and not self.pipeline_stack_name: + raise ValueError( + f"When action is '{PIPELINE_SYNTH_ACTION}', '{PIPELINE_STACK_CONTEXT_KEY}' context must be specified." + ) + + self.environment = Environment( + account=os.environ['CDK_DEFAULT_ACCOUNT'], + region=os.environ['CDK_DEFAULT_REGION'], + ) + + self.add_all_pipeline_stacks() + + def add_all_pipeline_stacks(self): + """ + add all pipeline stacks for deployment + + This is needed so that permissions set by the DeploymentResourcesStack are properly added for the pipeline + stack resources in every environment. + """ + # This stack must be declared first, as all other pipeline stacks depend on it. + # TODO: decide how we will handle these common deployment resources after breaking up into multiple apps + self.add_deployment_resources_stack() + + self.add_test_frontend_pipeline_stack() + self.add_beta_frontend_pipeline_stack() + self.add_prod_frontend_pipeline_stack() + + def add_deployment_resources_stack(self): + """add the deployment resources stack""" + self.deployment_resources_stack = DeploymentResourcesStack( + self, + DEPLOYMENT_RESOURCES_STACK, + env=self.environment, + standard_tags=StandardTags(**self.tags, environment='deploy'), + ) + + def add_test_frontend_pipeline_stack(self): + """add and return the Test Frontend Pipeline Stack""" + self.test_frontend_pipeline_stack = TestFrontendPipelineStack( + self, + TEST_FRONTEND_PIPELINE_STACK, + pipeline_shared_encryption_key=self.deployment_resources_stack.pipeline_shared_encryption_key, + pipeline_alarm_topic=self.deployment_resources_stack.pipeline_alarm_topic, + pipeline_access_logs_bucket=self.deployment_resources_stack.pipeline_access_logs_bucket, + env=self.environment, + standard_tags=StandardTags(**self.tags, environment='pipeline'), + cdk_path=CDK_PATH, + ) + return self.test_frontend_pipeline_stack + + def add_beta_frontend_pipeline_stack(self): + """add and return the Beta Frontend Pipeline Stack""" + self.beta_frontend_pipeline_stack = BetaFrontendPipelineStack( + self, + BETA_FRONTEND_PIPELINE_STACK, + pipeline_shared_encryption_key=self.deployment_resources_stack.pipeline_shared_encryption_key, + pipeline_alarm_topic=self.deployment_resources_stack.pipeline_alarm_topic, + pipeline_access_logs_bucket=self.deployment_resources_stack.pipeline_access_logs_bucket, + env=self.environment, + standard_tags=StandardTags(**self.tags, environment='pipeline'), + cdk_path=CDK_PATH, + ) + return self.beta_frontend_pipeline_stack + + def add_prod_frontend_pipeline_stack(self): + """add and return the Production Frontend Pipeline Stack""" + self.prod_frontend_pipeline_stack = ProdFrontendPipelineStack( + self, + PROD_FRONTEND_PIPELINE_STACK, + pipeline_shared_encryption_key=self.deployment_resources_stack.pipeline_shared_encryption_key, + pipeline_alarm_topic=self.deployment_resources_stack.pipeline_alarm_topic, + pipeline_access_logs_bucket=self.deployment_resources_stack.pipeline_access_logs_bucket, + env=self.environment, + standard_tags=StandardTags(**self.tags, environment='pipeline'), + cdk_path=CDK_PATH, + ) + return self.prod_frontend_pipeline_stack + + +if __name__ == '__main__': + app = CompactConnectApp() + app.synth() diff --git a/backend/compact-connect-ui-app/app_clients/README.md b/backend/compact-connect-ui-app/app_clients/README.md new file mode 100644 index 000000000..a80c8e51a --- /dev/null +++ b/backend/compact-connect-ui-app/app_clients/README.md @@ -0,0 +1,272 @@ +# App Client Management for Staff Users + +## Overview + +This document is a guide for technical staff for managing Cognito app clients for machine-to-machine authentication in +the State API. All app clients must be documented in the external 'Compact Connect App Client Registry' Google Sheet +(If you do not have access to said registry, contact a maintainer of the project and request access). + +## Creating a New App Client + +### 1. Prerequisites + +Before creating a new app client, ensure you have: +- Jurisdiction requirements documented (compact and state) +- Contact information for the consuming team +- Approval to grant the app client with the requested scopes +- AWS credentials configured with permissions to create app clients for the State Auth user pool in the needed AWS + accounts +- Python 3.10+ installed with boto3 dependency (`pip install boto3`) + +### 2. Update Registry + +Add the new app client information to the external Google Sheet registry for tracking and disaster recovery purposes +(ie a deployment error or AWS region outage causes app client data to be lost so it must be recreated). + +#### **Scope Configuration** + +Scopes are the permissions that the app client will have. There are two tiers of scopes: + +##### **Compact-Level Scopes:** + +These are the scopes that are scoped to a specific compact. Granting these scopes will allow the app client to perform +actions across all jurisdictions within that compact. Generally, the only scope that should be granted at the compact +level is the `{compact}/readGeneral` scope if needed. + +The following scopes are available at the compact level: +``` +{compact}/admin +{compact}/readGeneral +{compact}/readSSN +{compact}/write +``` + +##### **Jurisdiction-Level Scopes:** + +These are the scopes that are scoped to a specific jurisdiction/compact combination. Granting these scopes will allow +the app client to perform actions within a specific jurisdiction/compact combination. You should only grant these +scopes if the consuming team has a specific need for a jurisdiction/compact combination. + +The following scopes are available at the jurisdiction level: +```text +{jurisdiction}/{compact}.admin +{jurisdiction}/{compact}.write +{jurisdiction}/{compact}.readPrivate +{jurisdiction}/{compact}.readSSN +``` + +Currently, the most common scope needed by app clients is `{jurisdiction}/{compact}.write`, which allows uploading +license data for a jurisdiction/compact combination. Scopes that expose PII (e.g., `.readSSN`, `.readPrivate`) should +be granted sparingly and will require valid request signatures once a signing public key is configured for the +jurisdiction. + +### 3. Create App Client Using Interactive Python Script + +**Use the provided Python script in the bin directory for streamlined app client creation:** + + +```bash +python3 bin/create_app_client.py -e -u +``` + +**Interactive Process:** +The script will prompt you for: +- App client name (e.g., "example-ky-app-client-v1") +- Compact (aslp, octp, coun) +- State postal abbreviation (e.g., "ky", "la") +- Additional scopes (optional) + +**Automatic Scope Generation:** +The script automatically creates these standard scopes: +- `{compact}/readGeneral` - General read access for the compact +- `{state}/{compact}.write` - Write access for the specific state/compact combination + +### 4. **Send Credentials to Consuming Team** + +**When using the Python script (recommended):** +The script will output two separate sections: + +**A. Credentials JSON (for one-time link service):** +```json +{ + "clientId": "6g34example89j", + "clientSecret": "1234example567890" +} +``` +**Important:** These credentials should be securely transmitted to the consuming team via an encrypted channel +(i.e., a one-time use link). Copy this JSON and use it with your one-time secret link generator. Once you have sent +the credentials over to the IT staff, ensure you remove all remnants of the credentials from your device. + +**B. Email Template:** +The script will also generate an email template with contextual information (compact name, state, auth URL, license +upload URL) that you can copy/paste into your email client. This template includes a placeholder for the one-time +link that you'll generate separately. + + +#### Email Instructions for consuming team + +As part of the email message sent to the consuming team, be sure to include the onboarding instructions document from +the `it_staff_onboarding_instructions/` directory. + +## Managing API Signing Public Keys + +### Overview + +Signature-based authentication provides an additional layer of security for API access to sensitive licensure data. Each +compact/state combination can have multiple SIGNATURE public keys configured to support key rotation and zero-downtime +deployments. + +### Authorization Requirements + +**⚠️ CRITICAL SECURITY NOTICE:** Due to the sensitivity of the data protected by SIGNATURE authentication (including +partial Social Security Numbers, personal addresses, and professional license details), configuration of new SIGNATURE +public keys in production environments **MUST** include explicit authorization from the state board executive director. + + +### Creating SIGNATURE Public Keys + +Once a state configures a public key, they will be able to access the SIGNATURE-required API endpoints. API endpoints with +_optional_ SIGNATURE support will also begin to enforce SIGNATURE signatures for that combination of compact and state. **This +means that, once a compact/state has a public key configured, they will be denied access to SIGNATURE-Optional endpoints, +such as the `POST license` endpoint, unless they have also implemented SIGNATURE signatures there as well.** Be sure that +the representative is advised that they should begin signing those requests _before_ CompactConnect has a configured +public key. + +#### 1. Prerequisites + +Before creating a new SIGNATURE public key, ensure you have: +- **Production Authorization**: Explicit approval from the state board executive director for production environments +- Validated the identity of the individual providing the public key to you +- Jurisdiction and compact information confirmed +- Contact information for the state IT representative +- The public key file (`.pub` format) from the state IT representative +- AWS credentials configured with permissions to write to the compact configuration table +- Python 3.10+ installed with boto3 dependency (`pip install boto3`) + +#### 2. Key ID Naming Convention + +The state IT department should provide an identifier; however, you can recommend a descriptive key ID that includes: +- Environment indicator (if applicable) +- Version or date suffix + +Examples: +- `prod-key-001` +- `beta-key-2024-01` + +#### 3. Create SIGNATURE Public Key Using Interactive Python Script + +**Use the provided Python script in the bin directory for streamlined SIGNATURE key management:** + +```bash +python3 bin/manage_signature_keys.py create -t +``` + +**Interactive Process:** +The script will prompt you for: +- Compact (aslp, octp, coun) +- State postal abbreviation (e.g., "ky", "la") +- Key ID (e.g., "client-org-prod-key-001") + +**File Reading:** +The script will: +- Notify you that it will read the public key from `.pub` +- Validate the PEM format of the public key +- Check for existing keys with the same ID +- Write the key to the compact configuration database + +#### 4. Database Schema + +SIGNATURE keys are stored in the compact configuration table with the following schema: +- **Primary Key (pk)**: `{compact}#SIGNATURE_KEYS#{state}` +- **Sort Key (sk)**: `{compact}#JURISDICTION#{jurisdiction}#{key_id}` +- **Additional Fields**: + - `publicKey`: PEM-encoded public key content + - `compact`: Compact abbreviation + - `jurisdiction`: Jurisdiction abbreviation + - `keyId`: Key identifier + - `createdAt`: Creation timestamp + +### Deleting SIGNATURE Public Keys + +#### 1. Prerequisites + +Before deleting a SIGNATURE public key, ensure you have: +- Confirmation that the key is no longer in use by the state IT department +- Confirmation of the key id to be deleted +- Understanding of the impact on API access for the compact/state combination + +#### 2. Delete SIGNATURE Public Key Using Interactive Python Script + +```bash +python3 bin/manage_signature_keys.py delete -t +``` + +**Interactive Process:** +The script will: +- Prompt for compact and state +- List all existing keys for the compact/state combination +- Allow you to select the specific key ID to delete +- Require typing "DELETE" to confirm the deletion +- Remove the key from the compact configuration database + +### Key Rotation Best Practices + +#### 1. Planning + +- Coordinate with the State IT representative well in advance +- Plan for zero-downtime deployment + +#### 2. Implementation + +- Create new keys before removing old ones +- Allow both keys to be active during the transition period +- Monitor API access and authentication success rates +- Remove old keys only after confirming new keys are working correctly + +#### 3. Documentation + +- Document key rotation dates and reasons +- Maintain audit trail of all key management activities + +### Security Considerations + +#### 1. Key Storage + +- Public keys are stored in DynamoDB with appropriate access controls +- Private keys should never be stored in CompactConnect systems +- State IT departments are responsible for secure private key management + +#### 2. Access Control + +- Only authorized technical staff should have access to key management resources +- All key management activities should be logged and audited +- Production key creation requires executive director approval + +## Rotating App Client Credentials + +Unfortunately, AWS Cognito does not support rotating app client credentials for an existing app client. The only way +to rotate credentials is to create a new app client with a new clientId and clientSecret and then delete the old one. +The following process should be performed if credentials are accidentally exposed or in the event of a security breach +where the old credentials are compromised. + +### 1. Pre-rotation Tasks + +- Contact consuming team to schedule rotation +- Follow "Creating a New App Client" steps above using either the Python script (recommended) or AWS CLI, you will +increment clientName version suffix by 1 (e.g. "example-ky-app-client-v1" -> "example-ky-app-client-v2") +- Follow “Creating a New App Client” using the Python script (recommended) or AWS CLI. Increment the client name’s + version suffix by 1 (e.g., “example-ky-app-client-v1” -> “example-ky-app-client-v2”). +- Update the external Google Sheet registry with new client information + +### 2. Migration + +- Provide new client id and client secret to consuming team +- Consuming team will need to confirm that the new credentials are deployed in their systems, the old app client is +not in use, and their systems are working as expected. + +### 3. Cleanup + +- Delete old app client from Cognito using the following cli command: +``` +aws cognito-idp delete-user-pool-client --user-pool-id '' --client-id '' +``` diff --git a/backend/compact-connect-ui-app/app_clients/bin/create_app_client.py b/backend/compact-connect-ui-app/app_clients/bin/create_app_client.py new file mode 100755 index 000000000..bb99339b2 --- /dev/null +++ b/backend/compact-connect-ui-app/app_clients/bin/create_app_client.py @@ -0,0 +1,341 @@ +#!/usr/bin/env python3 +# ruff: noqa: T201 we use print statements for scripts run locally + +""" +Script to create AWS Cognito app clients interactively. + +This script prompts users for the necessary information to create app clients +in different environments (test, beta, prod) and automatically generates +the standard scopes based on compact and state inputs. +""" + +import argparse +import json +import re +import sys +from pathlib import Path + +import boto3 +from botocore.exceptions import ClientError, NoCredentialsError + + +def load_cdk_config(): + """Load configuration from cdk.json file.""" + # Find cdk.json file - look in parent directories + current_dir = Path(__file__).parent + cdk_json_path = None + + # Look up the directory tree for cdk.json + for parent in [current_dir] + list(current_dir.parents): + potential_path = parent / 'cdk.json' + if potential_path.exists(): + cdk_json_path = potential_path + break + + if not cdk_json_path: + raise FileNotFoundError('Could not find cdk.json file in current directory or parent directories') + + with open(cdk_json_path) as f: + cdk_config = json.load(f) + + context = cdk_config.get('context', {}) + + return { + 'compacts': context.get('compacts', []), + 'active_compact_member_jurisdictions': context.get('active_compact_member_jurisdictions', {}), + } + + +# Load configuration from cdk.json +CDK_CONFIG = load_cdk_config() +VALID_COMPACTS = CDK_CONFIG['compacts'] +ACTIVE_COMPACT_JURISDICTIONS = CDK_CONFIG['active_compact_member_jurisdictions'] + + +# Valid scope patterns for validation +VALID_SCOPE_PATTERNS = [ + r'^[a-z]+/readGeneral$', + r'^[a-z]+/readSSN$', + r'^[a-z]+/write$', + r'^[a-z]+/admin$', + r'^[a-z]{2}/[a-z]+\.write$', + r'^[a-z]{2}/[a-z]+\.readPrivate$', + r'^[a-z]{2}/[a-z]+\.readSSN$', + r'^[a-z]{2}/[a-z]+\.admin$', +] + + +def validate_compact(compact): + """Validate compact input.""" + compact = compact.lower().strip() + if compact not in VALID_COMPACTS: + raise ValueError(f'Invalid compact: {compact}. Valid compacts are: {", ".join(VALID_COMPACTS)}') + return compact + + +def validate_state(state, compact): + """Validate state postal abbreviation for the given compact.""" + state = state.lower().strip() + + # Get valid states for this compact + valid_states = ACTIVE_COMPACT_JURISDICTIONS.get(compact, []) + + if state not in valid_states: + raise ValueError( + f'Invalid state: {state}. Valid states for {compact.upper()} compact are: {", ".join(sorted(valid_states))}' + ) + return state + + +def validate_scope(scope): + """Validate a single scope against known patterns.""" + scope = scope.strip() + for pattern in VALID_SCOPE_PATTERNS: + if re.match(pattern, scope): + return True + return False + + +def validate_additional_scopes(scopes_input): + """Validate additional scopes input.""" + if not scopes_input.strip(): + return [] + + scopes = [scope.strip() for scope in scopes_input.split(',')] + invalid_scopes = [] + + for scope in scopes: + if not validate_scope(scope): + invalid_scopes.append(scope) + + if invalid_scopes: + print(f'\nInvalid scopes detected: {", ".join(invalid_scopes)}') + print('Valid scope patterns:') + print(' Compact-level: {compact}/readGeneral, {compact}/readSSN, {compact}/write, {compact}/admin') + print( + 'Jurisdiction-level: {state}/{compact}.write, {state}/{compact}.readPrivate, {state}/{compact}.readSSN, ' + '{state}/{compact}.admin' + ) + raise ValueError('Invalid scopes provided') + + return scopes + + +def get_user_input(): + """Get user input for app client configuration.""" + print('=== App Client Configuration ===\n') + + # Get client name + client_name = input("Enter the app client name (e.g., 'example-ky-app-client-v1'): ").strip() + if not client_name: + raise ValueError('Client name is required') + + # Get compact + while True: + try: + print(f'\nValid compacts: {", ".join(VALID_COMPACTS)}') + compact = input('Enter the compact: ').strip() + compact = validate_compact(compact) + break + except ValueError as e: + print(f'Error: {e}') + + # Get state + while True: + try: + valid_states = ACTIVE_COMPACT_JURISDICTIONS.get(compact, []) + print(f'\nValid states for {compact.upper()} compact: {", ".join(sorted(valid_states))}') + state = input("Enter the state postal abbreviation (e.g., 'ky', 'la'): ").strip() + state = validate_state(state, compact) + break + except ValueError as e: + print(f'Error: {e}') + + # Get additional scopes (optional) + print('\nThe following scope will be automatically included:') + print(f' - {state}/{compact}.write') + + additional_scopes = [] + while True: + try: + scopes_input = input('\nEnter any additional scopes (comma-separated, or press Enter for none): ').strip() + additional_scopes = validate_additional_scopes(scopes_input) + break + except ValueError as e: + print(f'Error: {e}') + continue + + # Generate final scope list + scopes = [f'{state}/{compact}.write'] + scopes.extend(additional_scopes) + + # Remove duplicates + deduped_scopes = list(set(scopes)) + + print('\nFinal configuration:') + print(f' Client Name: {client_name}') + print(f' Compact: {compact}') + print(f' State: {state}') + print(f' Scopes: {", ".join(deduped_scopes)}') + + confirm = input('\nProceed with this configuration? (y/N): ').strip().lower() + if confirm != 'y': + print('Configuration cancelled.') + sys.exit(0) + + return {'clientName': client_name, 'compact': compact, 'state': state, 'scopes': deduped_scopes} + + +def create_app_client(user_pool_id, config): + """Create the app client using boto3 Cognito client.""" + client_name = config['clientName'] + scopes = config['scopes'] + + print(f'\nCreating app client: {client_name}') + print(f'With scopes: {", ".join(scopes)}') + + try: + # Create boto3 Cognito IDP client + cognito_client = boto3.client('cognito-idp', region_name='us-east-1') + + # Create the user pool client + return cognito_client.create_user_pool_client( + UserPoolId=user_pool_id, + ClientName=client_name, + PreventUserExistenceErrors='ENABLED', + GenerateSecret=True, + TokenValidityUnits={'AccessToken': 'minutes'}, + AccessTokenValidity=15, + AllowedOAuthFlowsUserPoolClient=True, + AllowedOAuthFlows=['client_credentials'], + AllowedOAuthScopes=scopes, + ) + + except NoCredentialsError: + print('Error: AWS credentials not found. Please configure your AWS credentials.') + print("You can use 'aws configure' or set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment variables.") + sys.exit(1) + except ClientError as e: + error_code = e.response['Error']['Code'] + error_message = e.response['Error']['Message'] + print(f'Error creating app client: {error_code} - {error_message}') + sys.exit(1) + + +def print_credentials(client_id, client_secret): + """Print only the sensitive credentials in JSON format for secure copy/paste.""" + credentials = { + 'clientId': client_id, + 'clientSecret': client_secret, + } + + print('\n' + '=' * 60) + print('APP CLIENT CREDENTIALS (FOR ONE-TIME LINK SERVICE)') + print('=' * 60) + print(json.dumps(credentials, indent=2)) + print('=' * 60) + print('Please copy the JSON above and use it with your one-time secret link generator.') + print('Do not leave these credentials in terminal history or logs.') + print('=' * 60) + + +def print_email_template(environment, compact, state): + """Print an email template with contextual information for the consuming team.""" + # Get environment-specific URLs + auth_urls = { + 'beta': 'https://compact-connect-state-auth-beta.auth.us-east-1.amazoncognito.com/oauth2/token', + 'prod': 'https://compact-connect-state-auth.auth.us-east-1.amazoncognito.com/oauth2/token', + 'test': 'https://compact-connect-state-auth-test.auth.us-east-1.amazoncognito.com/oauth2/token', + } + + api_base_urls = { + 'beta': 'https://state-api.beta.compactconnect.org', + 'prod': 'https://state-api.compactconnect.org', + 'test': 'https://state-api.test.compactconnect.org', + } + + # Compact name mapping + compact_names = { + 'aslp': 'Audiology and Speech Language Pathology', + 'octp': 'Occupational Therapy', + 'coun': 'Counseling', + } + + compact_name = compact_names.get(compact, compact.upper()) + auth_url = auth_urls.get(environment) + license_upload_url = f'{api_base_urls.get(environment)}/v1/compacts/{compact}/jurisdictions/{state}/licenses' + + email_template = f""" +Thank you for integrating with Compact Connect! You have been designated as the IT professional who is able to handle +credentials for secure machine-to-machine authentication between your state and CompactConnect. + +Details for these credentials are: +Compact: {compact_name} +State: {state.upper()} +Auth URL: {auth_url} +License Upload URL: {license_upload_url} + +Follow this link to your API credentials as soon as you are ready to securely store them. They will only be viewable +once: + + +For more information on CompactConnect and how to integrate your state IT system with ours, see the documentation +here: +https://github.com/csg-org/CompactConnect/blob/development/backend/compact-connect/docs/it_staff_onboarding_instructions.md +""" + + print('\n' + '=' * 60) + print('EMAIL TEMPLATE (COPY/PASTE INTO EMAIL CLIENT)') + print('=' * 60) + print(email_template.strip()) + print('=' * 60) + + +def main(): + parser = argparse.ArgumentParser(description='Create AWS Cognito app client interactively') + parser.add_argument( + '-e', '--environment', required=True, choices=['test', 'beta', 'prod'], help='Environment (test, beta, or prod)' + ) + parser.add_argument('-u', '--user-pool-id', required=True, help='AWS Cognito User Pool ID') + + args = parser.parse_args() + + try: + print(f'Creating app client for {args.environment} environment...') + print(f'User Pool ID: {args.user_pool_id}\n') + + # Get configuration from user input + config = get_user_input() + + # Create the app client + response = create_app_client(args.user_pool_id, config) + + # Extract credentials from response + user_pool_client = response.get('UserPoolClient', {}) + client_id = user_pool_client.get('ClientId') + client_secret = user_pool_client.get('ClientSecret') + client_name = user_pool_client.get('ClientName') + + if not client_id or not client_secret: + print('Error: Could not extract client ID or secret from AWS response') + sys.exit(1) + + print('\n✅ App client created successfully!') + print(f'Client Name: {client_name}') + print(f'Client ID: {client_id}') + + # Print credentials for secure copy/paste + print_credentials(client_id, client_secret) + + # Print email template + print_email_template(args.environment, config['compact'], config['state']) + + print('\n📝 Remember to add this app client to your external registry!') + + except Exception as e: # noqa: BLE001 + print(f'Error: {e}') + sys.exit(1) + + +if __name__ == '__main__': + main() diff --git a/backend/compact-connect-ui-app/app_clients/bin/manage_signature_keys.py b/backend/compact-connect-ui-app/app_clients/bin/manage_signature_keys.py new file mode 100755 index 000000000..50b4696cf --- /dev/null +++ b/backend/compact-connect-ui-app/app_clients/bin/manage_signature_keys.py @@ -0,0 +1,390 @@ +#!/usr/bin/env python3 +# ruff: noqa: T201 we use print statements for scripts run locally + +""" +Script to manage SIGNATURE public keys in the compact configuration database. + +This script allows users to create and delete SIGNATURE public keys for different +compact/jurisdiction combinations. It follows the same interactive style as +the create_app_client.py script. +""" + +import argparse +import json +import sys +from datetime import UTC, datetime +from pathlib import Path + +import boto3 +from botocore.exceptions import ClientError, NoCredentialsError + + +def load_cdk_config(): + """Load configuration from cdk.json file.""" + # Find cdk.json file - look in parent directories + current_dir = Path(__file__).parent + cdk_json_path = None + + # Look up the directory tree for cdk.json + for parent in [current_dir] + list(current_dir.parents): + potential_path = parent / 'cdk.json' + if potential_path.exists(): + cdk_json_path = potential_path + break + + if not cdk_json_path: + raise FileNotFoundError('Could not find cdk.json file in current directory or parent directories') + + with open(cdk_json_path) as f: + cdk_config = json.load(f) + + context = cdk_config.get('context', {}) + + return { + 'compacts': context.get('compacts', []), + 'active_compact_member_jurisdictions': context.get('active_compact_member_jurisdictions', {}), + } + + +# Load configuration from cdk.json +CDK_CONFIG = load_cdk_config() +VALID_COMPACTS = CDK_CONFIG['compacts'] +ACTIVE_COMPACT_JURISDICTIONS = CDK_CONFIG['active_compact_member_jurisdictions'] + + +def validate_compact(compact): + """Validate compact input.""" + compact = compact.lower().strip() + if compact not in VALID_COMPACTS: + raise ValueError(f'Invalid compact: {compact}. Valid compacts are: {", ".join(VALID_COMPACTS)}') + return compact + + +def validate_state(state, compact): + """Validate state postal abbreviation for the given compact.""" + state = state.lower().strip() + + # Get valid states for this compact + valid_states = ACTIVE_COMPACT_JURISDICTIONS.get(compact, []) + + if state not in valid_states: + raise ValueError( + f'Invalid state: {state}. Valid states for {compact.upper()} compact are: {", ".join(sorted(valid_states))}' + ) + return state + + +def validate_key_id(key_id): + """Validate key ID input.""" + key_id = key_id.strip() + if not key_id: + raise ValueError('Key ID cannot be empty') + if len(key_id) > 100: # Reasonable limit for key ID + raise ValueError('Key ID is too long (max 100 characters)') + if not key_id.replace('-', '').replace('_', '').isalnum(): + raise ValueError('Key ID can only contain alphanumeric characters, hyphens, and underscores') + return key_id + + +def get_user_input_for_create(): + """Get user input for creating a SIGNATURE public key.""" + print('=== SIGNATURE Public Key Creation ===\n') + + # Get compact + while True: + try: + print(f'Valid compacts: {", ".join(VALID_COMPACTS)}') + compact = input('Enter the compact: ').strip() + compact = validate_compact(compact) + break + except ValueError as e: + print(f'Error: {e}') + + # Get state + while True: + try: + valid_states = ACTIVE_COMPACT_JURISDICTIONS.get(compact, []) + print(f'\nValid states for {compact.upper()} compact: {", ".join(sorted(valid_states))}') + state = input("Enter the state postal abbreviation (e.g., 'ky', 'la'): ").strip() + state = validate_state(state, compact) + break + except ValueError as e: + print(f'Error: {e}') + + # Get key ID + while True: + try: + key_id = input('\nEnter the key ID (e.g., "client-key-001"): ').strip() + key_id = validate_key_id(key_id) + break + except ValueError as e: + print(f'Error: {e}') + + print('\nConfiguration:') + print(f' Compact: {compact}') + print(f' State: {state}') + print(f' Key ID: {key_id}') + print(f' Public key file: {key_id}.pub') + + confirm = input('\nProceed with this configuration? (y/N): ').strip().lower() + if confirm != 'y': + print('Configuration cancelled.') + sys.exit(0) + + return {'compact': compact, 'state': state, 'key_id': key_id} + + +def read_public_key_file(key_id): + """Read the public key from the specified file.""" + file_path = Path(f'{key_id}.pub') + + if not file_path.exists(): + raise FileNotFoundError( + f'Public key file "{file_path}" not found. Please ensure the file exists in the current directory.' + ) + + try: + with open(file_path) as f: + public_key_content = f.read().strip() + + if not public_key_content: + raise ValueError('Public key file is empty') + + # Basic validation that this looks like a PEM public key + if not public_key_content.startswith('-----BEGIN PUBLIC KEY-----'): + raise ValueError( + 'Public key file does not appear to be in PEM format (should start with "-----BEGIN PUBLIC KEY-----")' + ) + + if not public_key_content.endswith('-----END PUBLIC KEY-----'): + raise ValueError( + 'Public key file does not appear to be in PEM format (should end with "-----END PUBLIC KEY-----")' + ) + + return public_key_content + + except (FileNotFoundError, ValueError) as e: + raise ValueError(f'Error reading public key file: {e}') from e + + +def create_signature_key(table_name, config): + """Create the SIGNATURE public key in DynamoDB.""" + compact = config['compact'] + state = config['state'] + key_id = config['key_id'] + + print(f'\nCreating SIGNATURE public key: {key_id}') + print(f'For compact: {compact}, state: {state}') + + try: + # Create boto3 DynamoDB client + dynamodb = boto3.resource('dynamodb') + table = dynamodb.Table(table_name) + + # Check if key already exists + pk = f'{compact}#SIGNATURE_KEYS#{state}' + sk = f'{compact}#JURISDICTION#{state}#{key_id}' + + response = table.get_item(Key={'pk': pk, 'sk': sk}) + + if 'Item' in response: + print(f'\n⚠️ Warning: A key with ID "{key_id}" already exists for {compact}/{state}') + overwrite = input('Do you want to overwrite it? (y/N): ').strip().lower() + if overwrite != 'y': + print('Operation cancelled.') + sys.exit(0) + + # Read the public key file + print(f'\nReading public key from {key_id}.pub...') + public_key_pem = read_public_key_file(key_id) + + # Create the item + item = { + 'pk': pk, + 'sk': sk, + 'publicKey': public_key_pem, + 'compact': compact, + 'jurisdiction': state, + 'keyId': key_id, + 'createdAt': datetime.now(tz=UTC).isoformat(), + } + + # Write to DynamoDB + table.put_item(Item=item) + + print('\n✅ SIGNATURE public key created successfully!') + print(f'Key ID: {key_id}') + print(f'Compact: {compact}') + print(f'State: {state}') + + except NoCredentialsError: + print('Error: AWS credentials not found. Please configure your AWS credentials.') + print("You can use 'aws configure' or set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment variables.") + raise + except ClientError as e: + error_code = e.response['Error']['Code'] + error_message = e.response['Error']['Message'] + print(f'Error creating SIGNATURE key: {error_code} - {error_message}') + raise + + +def get_user_input_for_delete(): + """Get user input for deleting a SIGNATURE public key.""" + print('=== SIGNATURE Public Key Deletion ===\n') + + # Get compact + while True: + try: + print(f'Valid compacts: {", ".join(VALID_COMPACTS)}') + compact = input('Enter the compact: ').strip() + compact = validate_compact(compact) + break + except ValueError as e: + print(f'Error: {e}') + + # Get state + while True: + try: + valid_states = ACTIVE_COMPACT_JURISDICTIONS.get(compact, []) + print(f'\nValid states for {compact.upper()} compact: {", ".join(sorted(valid_states))}') + state = input("Enter the state postal abbreviation (e.g., 'ky', 'la'): ").strip() + state = validate_state(state, compact) + break + except ValueError as e: + print(f'Error: {e}') + + return {'compact': compact, 'state': state} + + +def list_existing_keys(table_name, config): + """List existing SIGNATURE keys for the given compact/state combination.""" + compact = config['compact'] + state = config['state'] + + try: + # Create boto3 DynamoDB client + dynamodb = boto3.resource('dynamodb') + table = dynamodb.Table(table_name) + + # Query for existing keys + pk = f'{compact}#SIGNATURE_KEYS#{state}' + sk_prefix = f'{compact}#JURISDICTION#{state}#' + + response = table.query( + KeyConditionExpression='pk = :pk AND begins_with(sk, :sk_prefix)', + ExpressionAttributeValues={':pk': pk, ':sk_prefix': sk_prefix}, + ) + + items = response.get('Items', []) + + if not items: + print(f'\nNo SIGNATURE keys found for {compact}/{state}') + return [] + + print(f'\nExisting SIGNATURE keys for {compact}/{state}:') + for i, item in enumerate(items, 1): + key_id = item['sk'].split('#')[-1] + created_at = item.get('createdAt', 'Unknown') + print(f' {i}. Key ID: {key_id} (Created: {created_at})') + + return items + + except NoCredentialsError: + print('Error: AWS credentials not found. Please configure your AWS credentials.') + print("You can use 'aws configure' or set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment variables.") + raise + except ClientError as e: + error_code = e.response['Error']['Code'] + error_message = e.response['Error']['Message'] + print(f'Error listing SIGNATURE keys: {error_code} - {error_message}') + raise + + +def delete_signature_key(table_name, config, key_id): + """Delete the specified SIGNATURE public key from DynamoDB.""" + compact = config['compact'] + state = config['state'] + + print(f'\nDeleting SIGNATURE public key: {key_id}') + print(f'For compact: {compact}, state: {state}') + + try: + # Create boto3 DynamoDB client + dynamodb = boto3.resource('dynamodb') + table = dynamodb.Table(table_name) + + # Delete the item + pk = f'{compact}#SIGNATURE_KEYS#{state}' + sk = f'{compact}#JURISDICTION#{state}#{key_id}' + + table.delete_item(Key={'pk': pk, 'sk': sk}) + + print('\n✅ SIGNATURE public key deleted successfully!') + print(f'Key ID: {key_id}') + print(f'Compact: {compact}') + print(f'State: {state}') + + except NoCredentialsError: + print('Error: AWS credentials not found. Please configure your AWS credentials.') + print("You can use 'aws configure' or set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment variables.") + raise + except ClientError as e: + error_code = e.response['Error']['Code'] + error_message = e.response['Error']['Message'] + print(f'Error deleting SIGNATURE key: {error_code} - {error_message}') + raise + + +def main(): + parser = argparse.ArgumentParser(description='Manage SIGNATURE public keys in the compact configuration database') + parser.add_argument('action', choices=['create', 'delete'], help='Action to perform (create or delete)') + parser.add_argument('-t', '--table-name', required=True, help='DynamoDB table name for compact configuration') + + args = parser.parse_args() + + print(f'Managing SIGNATURE keys for {args.table_name} table...\n') + + if args.action == 'create': + # Create flow + config = get_user_input_for_create() + + create_signature_key(args.table_name, config) + + elif args.action == 'delete': + # Delete flow + config = get_user_input_for_delete() + + # List existing keys + existing_keys = list_existing_keys(args.table_name, config) + + if not existing_keys: + print('\nNo keys to delete.') + sys.exit(0) + + # Get key ID to delete + while True: + key_id = input('\nEnter the exact key ID to delete: ').strip() + key_id = validate_key_id(key_id) + + # Check if key exists + key_exists = any(item['sk'].split('#')[-1] == key_id for item in existing_keys) + if not key_exists: + print(f'Error: Key ID "{key_id}" not found in the list above') + continue + + break + + # Final confirmation + print(f'\n⚠️ You are about to delete SIGNATURE key "{key_id}" for {config["compact"]}/{config["state"]}') + print('This action cannot be undone.') + + confirm = input('\nAre you sure you want to delete this key? Type "DELETE" to confirm: ').strip() + if confirm != 'DELETE': + print('Deletion cancelled.') + sys.exit(0) + + delete_signature_key(args.table_name, config, key_id) + + +if __name__ == '__main__': + main() diff --git a/backend/compact-connect-ui-app/app_clients/it_staff_onboarding_instructions/README.md b/backend/compact-connect-ui-app/app_clients/it_staff_onboarding_instructions/README.md new file mode 100644 index 000000000..f28dc34c4 --- /dev/null +++ b/backend/compact-connect-ui-app/app_clients/it_staff_onboarding_instructions/README.md @@ -0,0 +1 @@ +[Moved Here](../../docs/it_staff_onboarding_instructions.md) diff --git a/backend/compact-connect-ui-app/bin/compile_requirements.sh b/backend/compact-connect-ui-app/bin/compile_requirements.sh new file mode 100755 index 000000000..77379b9f5 --- /dev/null +++ b/backend/compact-connect-ui-app/bin/compile_requirements.sh @@ -0,0 +1,5 @@ +set -e + +pip-compile --no-emit-index-url --upgrade --no-strip-extras requirements-dev.in +pip-compile --no-emit-index-url --upgrade --no-strip-extras requirements.in +bin/sync_deps.sh diff --git a/backend/bin/run_python_tests.py b/backend/compact-connect-ui-app/bin/run_python_tests.py similarity index 88% rename from backend/bin/run_python_tests.py rename to backend/compact-connect-ui-app/bin/run_python_tests.py index 7fe37fbb5..1509c1497 100755 --- a/backend/bin/run_python_tests.py +++ b/backend/compact-connect-ui-app/bin/run_python_tests.py @@ -22,21 +22,7 @@ # Define the test directories to include TEST_DIRS = ( - 'compact-connect/lambdas/python/common', - 'compact-connect/lambdas/python/cognito-backup', - 'compact-connect/lambdas/python/compact-configuration', - 'compact-connect/lambdas/python/custom-resources', - 'compact-connect/lambdas/python/data-events', - 'compact-connect/lambdas/python/disaster-recovery', - 'compact-connect/lambdas/python/migration', - 'compact-connect/lambdas/python/provider-data-v1', - 'compact-connect/lambdas/python/purchases', - 'compact-connect/lambdas/python/staff-user-pre-token', - 'compact-connect/lambdas/python/staff-users', - 'compact-connect', # CDK tests - 'multi-account/control-tower', - 'multi-account/log-aggregation', - 'multi-account/backups', # Data retention backup infrastructure + '.', # CDK tests ) diff --git a/backend/bin/run_tests.sh b/backend/compact-connect-ui-app/bin/run_tests.sh similarity index 98% rename from backend/bin/run_tests.sh rename to backend/compact-connect-ui-app/bin/run_tests.sh index d0aad2fb2..ad8dcb5ee 100755 --- a/backend/bin/run_tests.sh +++ b/backend/compact-connect-ui-app/bin/run_tests.sh @@ -88,7 +88,7 @@ EXIT=1 if [[ "$LANGUAGE" == 'nodejs' || "$LANGUAGE" == 'all' ]]; then echo "Running NodeJS tests..." ( - cd compact-connect/lambdas/nodejs + cd lambdas/nodejs yarn test || exit "$?" if [[ "$REPORT" == true && "$OPEN_REPORT" == true ]]; then open 'coverage/lcov-report/index.html' diff --git a/backend/compact-connect-ui-app/bin/sync_deps.sh b/backend/compact-connect-ui-app/bin/sync_deps.sh new file mode 100755 index 000000000..f8048085e --- /dev/null +++ b/backend/compact-connect-ui-app/bin/sync_deps.sh @@ -0,0 +1,8 @@ +( + cd lambdas/nodejs + yarn install +) + +pip-sync \ + requirements-dev.txt \ + requirements.txt diff --git a/backend/compact-connect-ui-app/cdk.context.beta-example.json b/backend/compact-connect-ui-app/cdk.context.beta-example.json new file mode 100644 index 000000000..fdba61161 --- /dev/null +++ b/backend/compact-connect-ui-app/cdk.context.beta-example.json @@ -0,0 +1,64 @@ +{ + "ssm_context": { + "github_repo_string": "csg-org/CompactConnect", + "app_name": "compact-connect", + "environments": { + "pipeline": { + "account_id": "000000000000", + "region": "us-east-1", + "connection_arn": "arn:aws:codestar-connections:us-east-1:000000000000:connection/11111111-1111-1111-111111111111" + }, + "beta": { + "account_id": "222233334444", + "region": "us-east-1", + "domain_name": "beta.compactconnect.org", + "backup_enabled": false, + "recaptcha_public_key": "123-KFEUsjehfuejILDVUKkRnAF9SSzb8o9uv5lY7Ih", + "robots_meta": "noindex,nofollow", + "notifications": { + "ses_operations_support_email": "justin@example.com", + "email": [ + "justin@example.com" + ], + "slack": [ + { + "channel_name": "ops-monitoring", + "channel_id": "C0123456789", + "workspace_id": "T01234567" + } + ] + }, + "backup_policies": { + "general_data": { + "schedule": { + "week_day": "5", + "year": "*", + "month": "*", + "hour": "5", + "minute": "0" + }, + "delete_after_days": 180, + "cold_storage_after_days": 30 + }, + "frequent_updates": { + "schedule": { + "week_day": "5", + "year": "*", + "month": "*", + "hour": "5", + "minute": "0" + }, + "delete_after_days": 180, + "cold_storage_after_days": 30 + } + } + } + }, + "backup_config": { + "backup_account_id": "111122223333", + "backup_region": "us-west-2", + "general_vault_name": "CompactConnectBackupVault", + "ssn_vault_name": "CompactConnectBackupVault-SSN" + } + } +} diff --git a/backend/compact-connect-ui-app/cdk.context.deploy-example.json b/backend/compact-connect-ui-app/cdk.context.deploy-example.json new file mode 100644 index 000000000..b9f4efce5 --- /dev/null +++ b/backend/compact-connect-ui-app/cdk.context.deploy-example.json @@ -0,0 +1,24 @@ +{ + "ssm_context": { + "github_repo_string": "csg-org/CompactConnect", + "app_name": "compact-connect", + "environments": { + "deploy": { + "account_id": "000000000000", + "region": "us-east-1", + "notifications": { + "email": [ + "justin@example.com" + ], + "slack": [ + { + "channel_name": "ops-monitoring", + "channel_id": "C0123456789", + "workspace_id": "T01234567" + } + ] + } + } + } + } +} diff --git a/backend/compact-connect-ui-app/cdk.context.prod-example.json b/backend/compact-connect-ui-app/cdk.context.prod-example.json new file mode 100644 index 000000000..a6e2789b5 --- /dev/null +++ b/backend/compact-connect-ui-app/cdk.context.prod-example.json @@ -0,0 +1,64 @@ +{ + "ssm_context": { + "github_repo_string": "csg-org/CompactConnect", + "app_name": "compact-connect", + "environments": { + "pipeline": { + "account_id": "000000000000", + "region": "us-east-1", + "connection_arn": "arn:aws:codestar-connections:us-east-1:000000000000:connection/11111111-1111-1111-111111111111" + }, + "prod": { + "account_id": "000011112222", + "region": "us-east-1", + "domain_name": "compactconnect.org", + "backup_enabled": true, + "robots_meta": "index,follow", + "recaptcha_public_key": "123-KFEUsjehfuejILDVUKkRnAF9SSzb8o9uv5lY7Ih", + "notifications": { + "ses_operations_support_email": "justin@example.com", + "email": [ + "justin@example.com" + ], + "slack": [ + { + "channel_name": "ops-monitoring", + "channel_id": "C0123456789", + "workspace_id": "T01234567" + } + ] + }, + "backup_policies": { + "general_data": { + "schedule": { + "year": "*", + "month": "*", + "day": "*", + "hour": "5", + "minute": "0" + }, + "delete_after_days": 730, + "cold_storage_after_days": 30 + }, + "frequent_updates": { + "schedule": { + "year": "*", + "month": "*", + "day": "*", + "hour": "*", + "minute": "0" + }, + "delete_after_days": 730, + "cold_storage_after_days": 30 + } + } + } + }, + "backup_config": { + "backup_account_id": "111122223333", + "backup_region": "us-west-2", + "general_vault_name": "CompactConnectBackupVault", + "ssn_vault_name": "CompactConnectSSNBackupVault" + } + } +} diff --git a/backend/compact-connect-ui-app/cdk.context.sandbox-example.json b/backend/compact-connect-ui-app/cdk.context.sandbox-example.json new file mode 100644 index 000000000..bea66dcb8 --- /dev/null +++ b/backend/compact-connect-ui-app/cdk.context.sandbox-example.json @@ -0,0 +1,32 @@ +{ + "sandbox": true, + "environment_name": "justin", + "sandbox_active_compact_member_jurisdictions": { + "aslp": ["al", "ak", "ar", "co", "de", "ky", "la", "me", "md", "mn", "ms", "mo", "ne", "oh"], + "octp": ["al", "ar", "ky", "la", "ms", "ne", "oh"], + "coun": ["al", "ar", "fl", "ga", "ky", "ne", "oh", "ut"] + }, + "ssm_context": { + "github_repo_string": "csg-org/CompactConnect", + "app_name": "compact-connect", + "environments": { + "justin": { + "account_id": "111122223333", + "region": "us-east-1", + "domain_name": "justin.compactconnect.org", + "backup_enabled": false, + "allow_local_ui": true, + "deploy_sandbox_ui": false, + "security_profile": "VULNERABLE", + "recaptcha_public_key": "123-KFEUsjehfuejILDVUKkRnAF9SSzb8o9uv5lY7Ih", + "robots_meta": "noindex,nofollow", + "notifications": { + "ses_operations_support_email": "justin@example.com", + "email": [ + "justin@example.com" + ] + } + } + } + } +} diff --git a/backend/compact-connect-ui-app/cdk.context.test-example.json b/backend/compact-connect-ui-app/cdk.context.test-example.json new file mode 100644 index 000000000..56cf93bad --- /dev/null +++ b/backend/compact-connect-ui-app/cdk.context.test-example.json @@ -0,0 +1,65 @@ +{ + "ssm_context": { + "github_repo_string": "csg-org/CompactConnect", + "app_name": "compact-connect", + "environments": { + "pipeline": { + "account_id": "000000000000", + "region": "us-east-1", + "connection_arn": "arn:aws:codestar-connections:us-east-1:000000000000:connection/11111111-1111-1111-111111111111" + }, + "test": { + "account_id": "111122223333", + "region": "us-east-1", + "domain_name": "test.compactconnect.org", + "backup_enabled": true, + "allow_local_ui": true, + "recaptcha_public_key": "123-KFEUsjehfuejILDVUKkRnAF9SSzb8o9uv5lY7Ih", + "robots_meta": "noindex,nofollow", + "notifications": { + "ses_operations_support_email": "justin@example.com", + "email": [ + "justin@example.com" + ], + "slack": [ + { + "channel_name": "preprod-ops-monitoring", + "channel_id": "C1234567890", + "workspace_id": "T01234567" + } + ] + }, + "backup_policies": { + "general_data": { + "schedule": { + "week_day": "5", + "year": "*", + "month": "*", + "hour": "5", + "minute": "0" + }, + "delete_after_days": 180, + "cold_storage_after_days": 30 + }, + "frequent_updates": { + "schedule": { + "week_day": "5", + "year": "*", + "month": "*", + "hour": "5", + "minute": "0" + }, + "delete_after_days": 180, + "cold_storage_after_days": 30 + } + } + } + }, + "backup_config": { + "backup_account_id": "111122223333", + "backup_region": "us-west-2", + "general_vault_name": "CompactConnectBackupVault", + "ssn_vault_name": "CompactConnectBackupVault-SSN" + } + } +} diff --git a/backend/compact-connect-ui-app/cdk.json b/backend/compact-connect-ui-app/cdk.json new file mode 100644 index 000000000..cfedbddf2 --- /dev/null +++ b/backend/compact-connect-ui-app/cdk.json @@ -0,0 +1,103 @@ +{ + "app": "python3 app.py", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "requirements*.txt", + "source.bat", + "**/__init__.py", + "**/__pycache__", + "tests" + ] + }, + "context": { + "tags": { + "project": "compact-connect", + "service": "license-data" + }, + "jurisdictions": [ + "al", "ak", "az", "ar", "ca", "co", "ct", "de", "dc", "fl", + "ga", "hi", "id", "il", "in", "ia", "ks", "ky", "la", "me", + "md", "ma", "mi", "mn", "ms", "mo", "mt", "ne", "nv", "nh", + "nj", "nm", "ny", "nc", "nd", "oh", "ok", "or", "pa", "pr", + "ri", "sc", "sd", "tn", "tx", "ut", "vt", "va", "vi", "wa", + "wv", "wi", "wy" + ], + "license_types": { + "aslp": [ + {"name": "audiologist", "abbreviation": "aud"}, + {"name": "speech-language pathologist", "abbreviation": "slp"} + ], + "octp": [ + {"name": "occupational therapist", "abbreviation": "ot"}, + {"name": "occupational therapy assistant", "abbreviation": "ota"} + ], + "coun": [ + {"name": "licensed professional counselor", "abbreviation": "lpc"} + ] + }, + "compacts": [ + "aslp", + "octp", + "coun" + ], + "active_compact_member_jurisdictions": { + "aslp": ["al", "ak", "ar", "co", "de", "fl", "ga", "id", "in", "ia", "ks", "ky", "la", "me", "md", "mn", "ms", "mo", "mt", "ne", "nh", "nc", "oh", "ok", "ri", "sc", "tn", "ut", "vt", "va", "vi", "wa", "wv", "wi", "wy"], + "octp": ["al", "ar", "az", "co", "de", "ga", "ia", "in", "ky", "la", "me", "md", "mn", "ms", "mo", "mt", "ne", "nh", "nc", "nd", "oh", "ri", "sc", "sd", "tn", "ut", "vt", "va", "wa", "wv", "wi", "wy"], + "coun": ["al", "ar", "az", "co", "ct", "dc", "de", "fl", "ga", "ia", "in", "ks", "ky", "la", "me", "md", "mn", "ms", "mo", "mt", "ne", "nh", "nj", "nc", "nd", "oh", "ok", "ri", "sc", "sd", "tn", "ut", "vt", "va", "wa", "wv", "wi", "wy"] + }, + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-us-gov" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/core:validateSnapshotRemovalPolicy": true, + "@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": true, + "@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true, + "@aws-cdk/aws-route53-patters:useCertificate": true, + "@aws-cdk/customresources:installLatestAwsSdkDefault": false, + "@aws-cdk/aws-rds:databaseProxyUniqueResourceName": true, + "@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": true, + "@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": true, + "@aws-cdk/aws-ec2:launchTemplateDefaultUserData": true, + "@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": true, + "@aws-cdk/aws-redshift:columnId": true, + "@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": true, + "@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": true, + "@aws-cdk/aws-apigateway:requestValidatorUniqueId": true, + "@aws-cdk/aws-kms:aliasNameRef": true, + "@aws-cdk/aws-autoscaling:generateLaunchTemplateInsteadOfLaunchConfig": true, + "@aws-cdk/core:includePrefixInUniqueNameGeneration": true, + "@aws-cdk/aws-efs:denyAnonymousAccess": true, + "@aws-cdk/aws-opensearchservice:enableOpensearchMultiAzWithStandby": true, + "@aws-cdk/aws-lambda-nodejs:useLatestRuntimeVersion": true, + "@aws-cdk/aws-efs:mountTargetOrderInsensitiveLogicalId": true, + "@aws-cdk/aws-rds:auroraClusterChangeScopeOfInstanceParameterGroupWithEachParameters": true, + "@aws-cdk/aws-appsync:useArnForSourceApiAssociationIdentifier": true, + "@aws-cdk/aws-rds:preventRenderingDeprecatedCredentials": true, + "@aws-cdk/aws-codepipeline-actions:useNewDefaultBranchForCodeCommitSource": true, + "@aws-cdk/aws-cloudwatch-actions:changeLambdaPermissionLogicalIdForLambdaAction": true, + "@aws-cdk/aws-codepipeline:crossAccountKeysDefaultValueToFalse": true, + "@aws-cdk/aws-codepipeline:defaultPipelineTypeToV2": true, + "@aws-cdk/aws-kms:reduceCrossAccountRegionPolicyScope": true, + "@aws-cdk/aws-eks:nodegroupNameAttribute": true, + "@aws-cdk/aws-ec2:ebsDefaultGp3Volume": true + } +} diff --git a/backend/compact-connect-ui-app/lambdas/nodejs/.gitignore b/backend/compact-connect-ui-app/lambdas/nodejs/.gitignore new file mode 100644 index 000000000..22f3e812b --- /dev/null +++ b/backend/compact-connect-ui-app/lambdas/nodejs/.gitignore @@ -0,0 +1,6 @@ +lib/**/*.mjs +lib/**/*.js +lib/**/*.js.map +bin/**/*.mjs +bin/**/*.js +bin/**/*.js.map diff --git a/backend/compact-connect/lambdas/nodejs/.mocharc.js b/backend/compact-connect-ui-app/lambdas/nodejs/.mocharc.js similarity index 100% rename from backend/compact-connect/lambdas/nodejs/.mocharc.js rename to backend/compact-connect-ui-app/lambdas/nodejs/.mocharc.js diff --git a/backend/compact-connect-ui-app/lambdas/nodejs/Gruntfile.js b/backend/compact-connect-ui-app/lambdas/nodejs/Gruntfile.js new file mode 100644 index 000000000..92efc316e --- /dev/null +++ b/backend/compact-connect-ui-app/lambdas/nodejs/Gruntfile.js @@ -0,0 +1,64 @@ +// +// Gruntfile.js +// CompactConnect +// +// Created by InspiringApps on 7/22/2024. +// + +module.exports = function (grunt) { + require('jit-grunt')(grunt); + + grunt.initConfig({ + //==================== + //= Directory config = + //==================== + lambdaFiles: [ + 'cloudfront-csp/**/**.js', + '!**/node_modules/**/*.js', + '!**/dist/**/*.js', + '!Gruntfile.js', + ], + changedFiles: ['<%= lambdaFiles %>'], + + //=========== + //= ES Lint = + //=========== + eslint: { + initial: { + src: ['<%= lambdaFiles %>'], + }, + watch: { + src: '<%= changedFiles %>', + } + }, + + //================ + //= Watch config = + //================ + watch: { + lambdaFiles: { + files: ['<%= lambdaFiles %>'], + tasks: ['eslint:watch'], + options: { spawn: false } + }, + } + + }); + + // Update the `changedFiles` value with just the files that changed. + // Tasks that operate on <%= changedFiles %> will then operate faster. + var changedFiles = Object.create(null); + var onChange = grunt.util._.debounce(function() { + grunt.config('changedFiles', Object.keys(changedFiles)); + changedFiles = Object.create(null); + }, 200); + + grunt.event.on('watch', function(action, filepath) { + changedFiles[filepath] = action; + onChange(); + }); + + // Task definition + grunt.registerTask('default', ['eslint:initial', 'watch']); + grunt.registerTask('check', ['eslint:initial']); +}; diff --git a/backend/compact-connect-ui-app/lambdas/nodejs/README.md b/backend/compact-connect-ui-app/lambdas/nodejs/README.md new file mode 100644 index 000000000..2d27942eb --- /dev/null +++ b/backend/compact-connect-ui-app/lambdas/nodejs/README.md @@ -0,0 +1,39 @@ +# NodeJS Lambdas + +This folder contains all lambda runtimes that are written with NodeJS/TypeScript. Because these lambdas are each bundled through CDK with ESBuild, we can pull common code and tests together, leaving only the entrypoints in a lambda-specific folder, leaving ESBuild to pull in only needed lib code. + + +## Prerequisites +* **[Node](https://github.com/creationix/nvm#installation) `22.X`** +* **[Yarn](https://yarnpkg.com/en/) `1.22.22`** + * `npm install --global yarn@1.22.22` + +_[back to top](#ingest-event-reporter-lambda)_ + +--- +## Installing dependencies +- `yarn install` + +## Bundling the runtime +- `yarn build` + +_[back to top](#ingest-event-reporter-lambda)_ + +--- +## Local development +- **Linting** + - `yarn run lint` + - Lints all code in all the Lambda function +- **Running an individual Lambda** + - The easiest way to execute the Lambda is to run the tests ([see below](#tests)) + - Commenting out certain tests to limit the execution scope & repetition is trivial + +_[back to top](#ingest-event-reporter-lambda)_ + +--- +## Testing +This project uses `jest` and `aws-sdk-client-mock` for approachable unit testing. The code in this folder can be tested by running: +- `yarn install` +- `yarn test` + +or by using the utility scripts located at `backend/bin`. diff --git a/backend/compact-connect/lambdas/nodejs/cloudfront-csp/.editorconfig b/backend/compact-connect-ui-app/lambdas/nodejs/cloudfront-csp/.editorconfig similarity index 100% rename from backend/compact-connect/lambdas/nodejs/cloudfront-csp/.editorconfig rename to backend/compact-connect-ui-app/lambdas/nodejs/cloudfront-csp/.editorconfig diff --git a/backend/compact-connect/lambdas/nodejs/cloudfront-csp/.gitignore b/backend/compact-connect-ui-app/lambdas/nodejs/cloudfront-csp/.gitignore similarity index 100% rename from backend/compact-connect/lambdas/nodejs/cloudfront-csp/.gitignore rename to backend/compact-connect-ui-app/lambdas/nodejs/cloudfront-csp/.gitignore diff --git a/backend/compact-connect/lambdas/nodejs/cloudfront-csp/README.md b/backend/compact-connect-ui-app/lambdas/nodejs/cloudfront-csp/README.md similarity index 100% rename from backend/compact-connect/lambdas/nodejs/cloudfront-csp/README.md rename to backend/compact-connect-ui-app/lambdas/nodejs/cloudfront-csp/README.md diff --git a/backend/compact-connect/lambdas/nodejs/cloudfront-csp/index.js b/backend/compact-connect-ui-app/lambdas/nodejs/cloudfront-csp/index.js similarity index 100% rename from backend/compact-connect/lambdas/nodejs/cloudfront-csp/index.js rename to backend/compact-connect-ui-app/lambdas/nodejs/cloudfront-csp/index.js diff --git a/backend/compact-connect/lambdas/nodejs/cloudfront-csp/test/config/index.js b/backend/compact-connect-ui-app/lambdas/nodejs/cloudfront-csp/test/config/index.js similarity index 100% rename from backend/compact-connect/lambdas/nodejs/cloudfront-csp/test/config/index.js rename to backend/compact-connect-ui-app/lambdas/nodejs/cloudfront-csp/test/config/index.js diff --git a/backend/compact-connect/lambdas/nodejs/cloudfront-csp/test/index.test.js b/backend/compact-connect-ui-app/lambdas/nodejs/cloudfront-csp/test/index.test.js similarity index 100% rename from backend/compact-connect/lambdas/nodejs/cloudfront-csp/test/index.test.js rename to backend/compact-connect-ui-app/lambdas/nodejs/cloudfront-csp/test/index.test.js diff --git a/backend/compact-connect-ui-app/lambdas/nodejs/eslint.config.mjs b/backend/compact-connect-ui-app/lambdas/nodejs/eslint.config.mjs new file mode 100644 index 000000000..bea30f936 --- /dev/null +++ b/backend/compact-connect-ui-app/lambdas/nodejs/eslint.config.mjs @@ -0,0 +1,82 @@ +import typescriptParser from '@typescript-eslint/parser'; +import typescriptPlugin from '@typescript-eslint/eslint-plugin'; + +const OFF = 0; +const WARNING = 1; +const ERROR = 2; + +export default [ + { + ignores: ['cdk.out/**/*'], + }, + { + files: ['**/*.ts', '**/*.js'], + languageOptions: { + ecmaVersion: 2022, + sourceType: 'module', + parser: typescriptParser, + globals: { + es2022: true, + node: true, + }, + }, + plugins: { + '@typescript-eslint': typescriptPlugin, + }, + rules: { + indent: [ERROR, 4], + 'linebreak-style': [ERROR, 'unix'], + quotes: [ERROR, 'single', { allowTemplateLiterals: true }], + semi: [ERROR, 'always'], + 'max-len': [ERROR, { + code: 120, + ignoreComments: true, + ignoreUrls: true, + ignoreTemplateLiterals: true, + ignoreRegExpLiterals: true, + ignoreStrings: true, + }], + 'no-multi-spaces': [ERROR, { ignoreEOLComments: true }], + 'arrow-parens': [ERROR, 'always'], + 'comma-dangle': [ERROR, { + functions: 'never', + imports: 'never', + exports: 'ignore', + arrays: 'ignore', + objects: 'ignore', + }], + 'array-bracket-spacing': OFF, + 'object-curly-spacing': [ERROR, 'always', { + objectsInObjects: false, + arraysInObjects: false, + }], + 'no-param-reassign': [ERROR, { props: false }], + 'max-classes-per-file': [WARNING, 8], + 'lines-between-class-members': [ERROR, 'always', { exceptAfterSingleLine: true }], + 'implicit-arrow-linebreak': OFF, + 'class-methods-use-this': OFF, + '@typescript-eslint/no-explicit-any': OFF, + 'padding-line-between-statements': [ + ERROR, + { blankLine: 'always', prev: ['const', 'let', 'var'], next: '*' }, + { blankLine: 'any', prev: ['const', 'let', 'var'], next: ['const', 'let', 'var'] }, + ], + } + }, + { + files: ['**/*.js'], + rules: { + '@typescript-eslint/no-var-requires': OFF, + '@typescript-eslint/camelcase': OFF, + }, + }, + { + files: ['**/__tests__/*.{j,t}s?(x)'], + rules: { + 'no-unused-expressions': OFF, + 'quote-props': OFF, + 'import/no-extraneous-dependencies': OFF, + '@typescript-eslint/no-var-requires': OFF, + }, + } +]; diff --git a/backend/compact-connect-ui-app/lambdas/nodejs/jest.config.mjs b/backend/compact-connect-ui-app/lambdas/nodejs/jest.config.mjs new file mode 100644 index 000000000..023b7de50 --- /dev/null +++ b/backend/compact-connect-ui-app/lambdas/nodejs/jest.config.mjs @@ -0,0 +1,20 @@ +export default { + preset: 'ts-jest', + transform: {}, // Disables all transformations to commonJS + testEnvironment: 'node', + testMatch: ['**/tests/**/*.test.[jt]s?(x)'], + moduleFileExtensions: ['ts', 'js'], + verbose: true, + testPathIgnorePatterns: [ + '/node_modules/', + ], + passWithNoTests: true, // Allow Jest to pass when no tests are found + coverageThreshold: { + global: { + branches: 90, + functions: 90, + lines: 90, + statements: 90, + } + } +}; diff --git a/backend/compact-connect-ui-app/lambdas/nodejs/package.json b/backend/compact-connect-ui-app/lambdas/nodejs/package.json new file mode 100644 index 000000000..10867f024 --- /dev/null +++ b/backend/compact-connect-ui-app/lambdas/nodejs/package.json @@ -0,0 +1,62 @@ +{ + "name": "compact-connect", + "version": "1.0.0", + "type": "commonjs", + "description": "NodeJS lambdas for Compact Connect", + "scripts": { + "build": "tsc", + "watch": "tsc -w", + "test:ingest": "NODE_OPTIONS=\"$NODE_OPTIONS --experimental-vm-modules\" jest --coverage", + "lint:ingest": "eslint '**/*.ts' --no-error-on-unmatched-pattern", + "lint:ingest:fix": "eslint --fix '**/*.ts' --no-error-on-unmatched-pattern", + "test:csp": "mocha cloudfront-csp/test", + "lint:csp": "grunt check", + "lint": "eslint '**/*.ts' --no-error-on-unmatched-pattern && grunt check", + "test": "NODE_OPTIONS=\"$NODE_OPTIONS --experimental-vm-modules\" jest --coverage && mocha cloudfront-csp/test", + "audit:dependencies": "yarn audit --groups dependencies --level moderate", + "grunt": "grunt" + }, + "author": "Inspiring Apps", + "license": "UNLICENSED", + "devDependencies": { + "@types/aws-lambda": "8.10.145", + "@types/jest": "^29.5.12", + "@types/node": "22.5.4", + "@types/nodemailer": "^6.4.14", + "@types/react": "^18.3.12", + "@typescript-eslint/eslint-plugin": "^8.12.2", + "@typescript-eslint/parser": "^8.12.2", + "aws-sdk-client-mock": "^4.1.0", + "aws-sdk-client-mock-jest": "^4.1.0", + "esbuild": "0.24.0", + "eslint": "^9.13.0", + "jest": "^29.7.0", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "ts-jest": "^29.2.5", + "ts-node": "^10.9.2", + "tslib": "^2.8.0", + "tsx": "^4.19.2", + "typescript": "5.6.3", + "chai": "^4.1.2", + "chai-match-pattern": "^1.1.0", + "chalk": "^4.1.2", + "grunt": "^1.6.1", + "grunt-contrib-watch": "^1.1.0", + "grunt-eslint": "^25.0.0", + "jit-grunt": "^0.10.0", + "lambda-local": "^2.2.0", + "mocha": "^11.0.0" + }, + "dependencies": { + "@aws-lambda-powertools/logger": "^2.10.0", + "@aws-sdk/client-dynamodb": "^3.682.0", + "@aws-sdk/client-s3": "^3.682.0", + "@aws-sdk/client-ses": "^3.682.0", + "@aws-sdk/util-dynamodb": "^3.682.0", + "@usewaypoint/email-builder": "^0.0.6", + "aws-lambda": "1.0.7", + "nodemailer": "^6.9.12", + "zod": "^3.23.8" + } +} diff --git a/backend/compact-connect-ui-app/lambdas/nodejs/tsconfig.json b/backend/compact-connect-ui-app/lambdas/nodejs/tsconfig.json new file mode 100644 index 000000000..bf3fc50fc --- /dev/null +++ b/backend/compact-connect-ui-app/lambdas/nodejs/tsconfig.json @@ -0,0 +1,32 @@ +{ + "compilerOptions": { + "target": "esnext", + "module": "esnext", + "strict": true, + "jsx": "preserve", + "importHelpers": true, + "moduleResolution": "node", + "experimentalDecorators": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "sourceMap": true, + "baseUrl": ".", + "noImplicitAny": false, + "lib": [ + "esnext", + "scripthost" + ], + "typeRoots": [ + "./node_modules/@types" + ] + }, + "include": [ + "**/handler.ts", + "lib/**/*.ts", + "lib/**/*.tsx" + ], + "exclude": [ + "cdk.out", + "node_modules" + ] +} diff --git a/backend/compact-connect-ui-app/lambdas/nodejs/yarn.lock b/backend/compact-connect-ui-app/lambdas/nodejs/yarn.lock new file mode 100644 index 000000000..d902386c0 --- /dev/null +++ b/backend/compact-connect-ui-app/lambdas/nodejs/yarn.lock @@ -0,0 +1,6245 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@ampproject/remapping@^2.2.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.3.0.tgz#ed441b6fa600072520ce18b43d2c8cc8caecc7f4" + integrity sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw== + dependencies: + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.24" + +"@aws-crypto/crc32@5.2.0": + version "5.2.0" + resolved "https://registry.yarnpkg.com/@aws-crypto/crc32/-/crc32-5.2.0.tgz#cfcc22570949c98c6689cfcbd2d693d36cdae2e1" + integrity sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg== + dependencies: + "@aws-crypto/util" "^5.2.0" + "@aws-sdk/types" "^3.222.0" + tslib "^2.6.2" + +"@aws-crypto/crc32c@5.2.0": + version "5.2.0" + resolved "https://registry.yarnpkg.com/@aws-crypto/crc32c/-/crc32c-5.2.0.tgz#4e34aab7f419307821509a98b9b08e84e0c1917e" + integrity sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag== + dependencies: + "@aws-crypto/util" "^5.2.0" + "@aws-sdk/types" "^3.222.0" + tslib "^2.6.2" + +"@aws-crypto/sha1-browser@5.2.0": + version "5.2.0" + resolved "https://registry.yarnpkg.com/@aws-crypto/sha1-browser/-/sha1-browser-5.2.0.tgz#b0ee2d2821d3861f017e965ef3b4cb38e3b6a0f4" + integrity sha512-OH6lveCFfcDjX4dbAvCFSYUjJZjDr/3XJ3xHtjn3Oj5b9RjojQo8npoLeA/bNwkOkrSQ0wgrHzXk4tDRxGKJeg== + dependencies: + "@aws-crypto/supports-web-crypto" "^5.2.0" + "@aws-crypto/util" "^5.2.0" + "@aws-sdk/types" "^3.222.0" + "@aws-sdk/util-locate-window" "^3.0.0" + "@smithy/util-utf8" "^2.0.0" + tslib "^2.6.2" + +"@aws-crypto/sha256-browser@5.2.0": + version "5.2.0" + resolved "https://registry.yarnpkg.com/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz#153895ef1dba6f9fce38af550e0ef58988eb649e" + integrity sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw== + dependencies: + "@aws-crypto/sha256-js" "^5.2.0" + "@aws-crypto/supports-web-crypto" "^5.2.0" + "@aws-crypto/util" "^5.2.0" + "@aws-sdk/types" "^3.222.0" + "@aws-sdk/util-locate-window" "^3.0.0" + "@smithy/util-utf8" "^2.0.0" + tslib "^2.6.2" + +"@aws-crypto/sha256-js@5.2.0", "@aws-crypto/sha256-js@^5.2.0": + version "5.2.0" + resolved "https://registry.yarnpkg.com/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz#c4fdb773fdbed9a664fc1a95724e206cf3860042" + integrity sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA== + dependencies: + "@aws-crypto/util" "^5.2.0" + "@aws-sdk/types" "^3.222.0" + tslib "^2.6.2" + +"@aws-crypto/supports-web-crypto@^5.2.0": + version "5.2.0" + resolved "https://registry.yarnpkg.com/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz#a1e399af29269be08e695109aa15da0a07b5b5fb" + integrity sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg== + dependencies: + tslib "^2.6.2" + +"@aws-crypto/util@5.2.0", "@aws-crypto/util@^5.2.0": + version "5.2.0" + resolved "https://registry.yarnpkg.com/@aws-crypto/util/-/util-5.2.0.tgz#71284c9cffe7927ddadac793c14f14886d3876da" + integrity sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ== + dependencies: + "@aws-sdk/types" "^3.222.0" + "@smithy/util-utf8" "^2.0.0" + tslib "^2.6.2" + +"@aws-lambda-powertools/commons@^2.11.0": + version "2.11.0" + resolved "https://registry.yarnpkg.com/@aws-lambda-powertools/commons/-/commons-2.11.0.tgz#7fb54a8e775769b260455c9e7c6f414506212620" + integrity sha512-gtiBeUSyg4VcL4qra5G4AwuOl7CNF6g0oDasAS5KxkEi8IhTku6S7vJTpQ82+12oviwKBD+w27yEcGQmSw5xdg== + +"@aws-lambda-powertools/logger@^2.10.0": + version "2.11.0" + resolved "https://registry.yarnpkg.com/@aws-lambda-powertools/logger/-/logger-2.11.0.tgz#bd3426adfa761700d1420ba7b959eef929733769" + integrity sha512-40wST/S644ngwadp7Kk70Rgvh35yQ0R3HqFKxAtlBxQNAGsqxQtjJeBUg/KQDKUhE1IZH0ahXyEN3B7rSxPn0Q== + dependencies: + "@aws-lambda-powertools/commons" "^2.11.0" + lodash.merge "^4.6.2" + +"@aws-sdk/client-dynamodb@^3.682.0": + version "3.705.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/client-dynamodb/-/client-dynamodb-3.705.0.tgz#e25cd3a3bc6884549dfee5003e3b5622ea8cf047" + integrity sha512-uHmjzK4/r6KiXSMofSRmLdGb0N+X42yoTZN9YrQK2PxPMdLjh7JCGv4thlLcZP1NBHPfFxsEh61kqf8+1SfzgQ== + dependencies: + "@aws-crypto/sha256-browser" "5.2.0" + "@aws-crypto/sha256-js" "5.2.0" + "@aws-sdk/client-sso-oidc" "3.699.0" + "@aws-sdk/client-sts" "3.699.0" + "@aws-sdk/core" "3.696.0" + "@aws-sdk/credential-provider-node" "3.699.0" + "@aws-sdk/middleware-endpoint-discovery" "3.696.0" + "@aws-sdk/middleware-host-header" "3.696.0" + "@aws-sdk/middleware-logger" "3.696.0" + "@aws-sdk/middleware-recursion-detection" "3.696.0" + "@aws-sdk/middleware-user-agent" "3.696.0" + "@aws-sdk/region-config-resolver" "3.696.0" + "@aws-sdk/types" "3.696.0" + "@aws-sdk/util-endpoints" "3.696.0" + "@aws-sdk/util-user-agent-browser" "3.696.0" + "@aws-sdk/util-user-agent-node" "3.696.0" + "@smithy/config-resolver" "^3.0.12" + "@smithy/core" "^2.5.3" + "@smithy/fetch-http-handler" "^4.1.1" + "@smithy/hash-node" "^3.0.10" + "@smithy/invalid-dependency" "^3.0.10" + "@smithy/middleware-content-length" "^3.0.12" + "@smithy/middleware-endpoint" "^3.2.3" + "@smithy/middleware-retry" "^3.0.27" + "@smithy/middleware-serde" "^3.0.10" + "@smithy/middleware-stack" "^3.0.10" + "@smithy/node-config-provider" "^3.1.11" + "@smithy/node-http-handler" "^3.3.1" + "@smithy/protocol-http" "^4.1.7" + "@smithy/smithy-client" "^3.4.4" + "@smithy/types" "^3.7.1" + "@smithy/url-parser" "^3.0.10" + "@smithy/util-base64" "^3.0.0" + "@smithy/util-body-length-browser" "^3.0.0" + "@smithy/util-body-length-node" "^3.0.0" + "@smithy/util-defaults-mode-browser" "^3.0.27" + "@smithy/util-defaults-mode-node" "^3.0.27" + "@smithy/util-endpoints" "^2.1.6" + "@smithy/util-middleware" "^3.0.10" + "@smithy/util-retry" "^3.0.10" + "@smithy/util-utf8" "^3.0.0" + "@smithy/util-waiter" "^3.1.9" + "@types/uuid" "^9.0.1" + tslib "^2.6.2" + uuid "^9.0.1" + +"@aws-sdk/client-s3@^3.682.0": + version "3.705.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/client-s3/-/client-s3-3.705.0.tgz#7a4a4784bd5b3ca3187ff876b771eaf0cbde1c42" + integrity sha512-Fm0Cbc4zr0yG0DnNycz7ywlL5tQFdLSb7xCIPfzrxJb3YQiTXWxH5eu61SSsP/Z6RBNRolmRPvst/iNgX0fWvA== + dependencies: + "@aws-crypto/sha1-browser" "5.2.0" + "@aws-crypto/sha256-browser" "5.2.0" + "@aws-crypto/sha256-js" "5.2.0" + "@aws-sdk/client-sso-oidc" "3.699.0" + "@aws-sdk/client-sts" "3.699.0" + "@aws-sdk/core" "3.696.0" + "@aws-sdk/credential-provider-node" "3.699.0" + "@aws-sdk/middleware-bucket-endpoint" "3.696.0" + "@aws-sdk/middleware-expect-continue" "3.696.0" + "@aws-sdk/middleware-flexible-checksums" "3.701.0" + "@aws-sdk/middleware-host-header" "3.696.0" + "@aws-sdk/middleware-location-constraint" "3.696.0" + "@aws-sdk/middleware-logger" "3.696.0" + "@aws-sdk/middleware-recursion-detection" "3.696.0" + "@aws-sdk/middleware-sdk-s3" "3.696.0" + "@aws-sdk/middleware-ssec" "3.696.0" + "@aws-sdk/middleware-user-agent" "3.696.0" + "@aws-sdk/region-config-resolver" "3.696.0" + "@aws-sdk/signature-v4-multi-region" "3.696.0" + "@aws-sdk/types" "3.696.0" + "@aws-sdk/util-endpoints" "3.696.0" + "@aws-sdk/util-user-agent-browser" "3.696.0" + "@aws-sdk/util-user-agent-node" "3.696.0" + "@aws-sdk/xml-builder" "3.696.0" + "@smithy/config-resolver" "^3.0.12" + "@smithy/core" "^2.5.3" + "@smithy/eventstream-serde-browser" "^3.0.13" + "@smithy/eventstream-serde-config-resolver" "^3.0.10" + "@smithy/eventstream-serde-node" "^3.0.12" + "@smithy/fetch-http-handler" "^4.1.1" + "@smithy/hash-blob-browser" "^3.1.9" + "@smithy/hash-node" "^3.0.10" + "@smithy/hash-stream-node" "^3.1.9" + "@smithy/invalid-dependency" "^3.0.10" + "@smithy/md5-js" "^3.0.10" + "@smithy/middleware-content-length" "^3.0.12" + "@smithy/middleware-endpoint" "^3.2.3" + "@smithy/middleware-retry" "^3.0.27" + "@smithy/middleware-serde" "^3.0.10" + "@smithy/middleware-stack" "^3.0.10" + "@smithy/node-config-provider" "^3.1.11" + "@smithy/node-http-handler" "^3.3.1" + "@smithy/protocol-http" "^4.1.7" + "@smithy/smithy-client" "^3.4.4" + "@smithy/types" "^3.7.1" + "@smithy/url-parser" "^3.0.10" + "@smithy/util-base64" "^3.0.0" + "@smithy/util-body-length-browser" "^3.0.0" + "@smithy/util-body-length-node" "^3.0.0" + "@smithy/util-defaults-mode-browser" "^3.0.27" + "@smithy/util-defaults-mode-node" "^3.0.27" + "@smithy/util-endpoints" "^2.1.6" + "@smithy/util-middleware" "^3.0.10" + "@smithy/util-retry" "^3.0.10" + "@smithy/util-stream" "^3.3.1" + "@smithy/util-utf8" "^3.0.0" + "@smithy/util-waiter" "^3.1.9" + tslib "^2.6.2" + +"@aws-sdk/client-ses@^3.682.0": + version "3.699.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/client-ses/-/client-ses-3.699.0.tgz#b48d9c8865877b7baa4b1a90cb3e4c2d59c0c5e0" + integrity sha512-prpkr2jnhD2KsinQMBdX2wvSpNxFm9d02EUR4L78yxjg2oppXmu/cBjWdlVrSkqqE2EYfcHo0JV2WmRZZC1VyQ== + dependencies: + "@aws-crypto/sha256-browser" "5.2.0" + "@aws-crypto/sha256-js" "5.2.0" + "@aws-sdk/client-sso-oidc" "3.699.0" + "@aws-sdk/client-sts" "3.699.0" + "@aws-sdk/core" "3.696.0" + "@aws-sdk/credential-provider-node" "3.699.0" + "@aws-sdk/middleware-host-header" "3.696.0" + "@aws-sdk/middleware-logger" "3.696.0" + "@aws-sdk/middleware-recursion-detection" "3.696.0" + "@aws-sdk/middleware-user-agent" "3.696.0" + "@aws-sdk/region-config-resolver" "3.696.0" + "@aws-sdk/types" "3.696.0" + "@aws-sdk/util-endpoints" "3.696.0" + "@aws-sdk/util-user-agent-browser" "3.696.0" + "@aws-sdk/util-user-agent-node" "3.696.0" + "@smithy/config-resolver" "^3.0.12" + "@smithy/core" "^2.5.3" + "@smithy/fetch-http-handler" "^4.1.1" + "@smithy/hash-node" "^3.0.10" + "@smithy/invalid-dependency" "^3.0.10" + "@smithy/middleware-content-length" "^3.0.12" + "@smithy/middleware-endpoint" "^3.2.3" + "@smithy/middleware-retry" "^3.0.27" + "@smithy/middleware-serde" "^3.0.10" + "@smithy/middleware-stack" "^3.0.10" + "@smithy/node-config-provider" "^3.1.11" + "@smithy/node-http-handler" "^3.3.1" + "@smithy/protocol-http" "^4.1.7" + "@smithy/smithy-client" "^3.4.4" + "@smithy/types" "^3.7.1" + "@smithy/url-parser" "^3.0.10" + "@smithy/util-base64" "^3.0.0" + "@smithy/util-body-length-browser" "^3.0.0" + "@smithy/util-body-length-node" "^3.0.0" + "@smithy/util-defaults-mode-browser" "^3.0.27" + "@smithy/util-defaults-mode-node" "^3.0.27" + "@smithy/util-endpoints" "^2.1.6" + "@smithy/util-middleware" "^3.0.10" + "@smithy/util-retry" "^3.0.10" + "@smithy/util-utf8" "^3.0.0" + "@smithy/util-waiter" "^3.1.9" + tslib "^2.6.2" + +"@aws-sdk/client-sso-oidc@3.699.0": + version "3.699.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.699.0.tgz#a35665e681abd518b56330bc7dab63041fbdaf83" + integrity sha512-u8a1GorY5D1l+4FQAf4XBUC1T10/t7neuwT21r0ymrtMFSK2a9QqVHKMoLkvavAwyhJnARSBM9/UQC797PFOFw== + dependencies: + "@aws-crypto/sha256-browser" "5.2.0" + "@aws-crypto/sha256-js" "5.2.0" + "@aws-sdk/core" "3.696.0" + "@aws-sdk/credential-provider-node" "3.699.0" + "@aws-sdk/middleware-host-header" "3.696.0" + "@aws-sdk/middleware-logger" "3.696.0" + "@aws-sdk/middleware-recursion-detection" "3.696.0" + "@aws-sdk/middleware-user-agent" "3.696.0" + "@aws-sdk/region-config-resolver" "3.696.0" + "@aws-sdk/types" "3.696.0" + "@aws-sdk/util-endpoints" "3.696.0" + "@aws-sdk/util-user-agent-browser" "3.696.0" + "@aws-sdk/util-user-agent-node" "3.696.0" + "@smithy/config-resolver" "^3.0.12" + "@smithy/core" "^2.5.3" + "@smithy/fetch-http-handler" "^4.1.1" + "@smithy/hash-node" "^3.0.10" + "@smithy/invalid-dependency" "^3.0.10" + "@smithy/middleware-content-length" "^3.0.12" + "@smithy/middleware-endpoint" "^3.2.3" + "@smithy/middleware-retry" "^3.0.27" + "@smithy/middleware-serde" "^3.0.10" + "@smithy/middleware-stack" "^3.0.10" + "@smithy/node-config-provider" "^3.1.11" + "@smithy/node-http-handler" "^3.3.1" + "@smithy/protocol-http" "^4.1.7" + "@smithy/smithy-client" "^3.4.4" + "@smithy/types" "^3.7.1" + "@smithy/url-parser" "^3.0.10" + "@smithy/util-base64" "^3.0.0" + "@smithy/util-body-length-browser" "^3.0.0" + "@smithy/util-body-length-node" "^3.0.0" + "@smithy/util-defaults-mode-browser" "^3.0.27" + "@smithy/util-defaults-mode-node" "^3.0.27" + "@smithy/util-endpoints" "^2.1.6" + "@smithy/util-middleware" "^3.0.10" + "@smithy/util-retry" "^3.0.10" + "@smithy/util-utf8" "^3.0.0" + tslib "^2.6.2" + +"@aws-sdk/client-sso@3.696.0": + version "3.696.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/client-sso/-/client-sso-3.696.0.tgz#a9251e88cdfc91fb14191f760f68baa835e88f1c" + integrity sha512-q5TTkd08JS0DOkHfUL853tuArf7NrPeqoS5UOvqJho8ibV9Ak/a/HO4kNvy9Nj3cib/toHYHsQIEtecUPSUUrQ== + dependencies: + "@aws-crypto/sha256-browser" "5.2.0" + "@aws-crypto/sha256-js" "5.2.0" + "@aws-sdk/core" "3.696.0" + "@aws-sdk/middleware-host-header" "3.696.0" + "@aws-sdk/middleware-logger" "3.696.0" + "@aws-sdk/middleware-recursion-detection" "3.696.0" + "@aws-sdk/middleware-user-agent" "3.696.0" + "@aws-sdk/region-config-resolver" "3.696.0" + "@aws-sdk/types" "3.696.0" + "@aws-sdk/util-endpoints" "3.696.0" + "@aws-sdk/util-user-agent-browser" "3.696.0" + "@aws-sdk/util-user-agent-node" "3.696.0" + "@smithy/config-resolver" "^3.0.12" + "@smithy/core" "^2.5.3" + "@smithy/fetch-http-handler" "^4.1.1" + "@smithy/hash-node" "^3.0.10" + "@smithy/invalid-dependency" "^3.0.10" + "@smithy/middleware-content-length" "^3.0.12" + "@smithy/middleware-endpoint" "^3.2.3" + "@smithy/middleware-retry" "^3.0.27" + "@smithy/middleware-serde" "^3.0.10" + "@smithy/middleware-stack" "^3.0.10" + "@smithy/node-config-provider" "^3.1.11" + "@smithy/node-http-handler" "^3.3.1" + "@smithy/protocol-http" "^4.1.7" + "@smithy/smithy-client" "^3.4.4" + "@smithy/types" "^3.7.1" + "@smithy/url-parser" "^3.0.10" + "@smithy/util-base64" "^3.0.0" + "@smithy/util-body-length-browser" "^3.0.0" + "@smithy/util-body-length-node" "^3.0.0" + "@smithy/util-defaults-mode-browser" "^3.0.27" + "@smithy/util-defaults-mode-node" "^3.0.27" + "@smithy/util-endpoints" "^2.1.6" + "@smithy/util-middleware" "^3.0.10" + "@smithy/util-retry" "^3.0.10" + "@smithy/util-utf8" "^3.0.0" + tslib "^2.6.2" + +"@aws-sdk/client-sts@3.699.0": + version "3.699.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/client-sts/-/client-sts-3.699.0.tgz#9419be6bbf3809008128117afea8b9129b5a959d" + integrity sha512-++lsn4x2YXsZPIzFVwv3fSUVM55ZT0WRFmPeNilYIhZClxHLmVAWKH4I55cY9ry60/aTKYjzOXkWwyBKGsGvQg== + dependencies: + "@aws-crypto/sha256-browser" "5.2.0" + "@aws-crypto/sha256-js" "5.2.0" + "@aws-sdk/client-sso-oidc" "3.699.0" + "@aws-sdk/core" "3.696.0" + "@aws-sdk/credential-provider-node" "3.699.0" + "@aws-sdk/middleware-host-header" "3.696.0" + "@aws-sdk/middleware-logger" "3.696.0" + "@aws-sdk/middleware-recursion-detection" "3.696.0" + "@aws-sdk/middleware-user-agent" "3.696.0" + "@aws-sdk/region-config-resolver" "3.696.0" + "@aws-sdk/types" "3.696.0" + "@aws-sdk/util-endpoints" "3.696.0" + "@aws-sdk/util-user-agent-browser" "3.696.0" + "@aws-sdk/util-user-agent-node" "3.696.0" + "@smithy/config-resolver" "^3.0.12" + "@smithy/core" "^2.5.3" + "@smithy/fetch-http-handler" "^4.1.1" + "@smithy/hash-node" "^3.0.10" + "@smithy/invalid-dependency" "^3.0.10" + "@smithy/middleware-content-length" "^3.0.12" + "@smithy/middleware-endpoint" "^3.2.3" + "@smithy/middleware-retry" "^3.0.27" + "@smithy/middleware-serde" "^3.0.10" + "@smithy/middleware-stack" "^3.0.10" + "@smithy/node-config-provider" "^3.1.11" + "@smithy/node-http-handler" "^3.3.1" + "@smithy/protocol-http" "^4.1.7" + "@smithy/smithy-client" "^3.4.4" + "@smithy/types" "^3.7.1" + "@smithy/url-parser" "^3.0.10" + "@smithy/util-base64" "^3.0.0" + "@smithy/util-body-length-browser" "^3.0.0" + "@smithy/util-body-length-node" "^3.0.0" + "@smithy/util-defaults-mode-browser" "^3.0.27" + "@smithy/util-defaults-mode-node" "^3.0.27" + "@smithy/util-endpoints" "^2.1.6" + "@smithy/util-middleware" "^3.0.10" + "@smithy/util-retry" "^3.0.10" + "@smithy/util-utf8" "^3.0.0" + tslib "^2.6.2" + +"@aws-sdk/core@3.696.0": + version "3.696.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/core/-/core-3.696.0.tgz#bdf306bdc019f485738d91d8838eec877861dd26" + integrity sha512-3c9III1k03DgvRZWg8vhVmfIXPG6hAciN9MzQTzqGngzWAELZF/WONRTRQuDFixVtarQatmLHYVw/atGeA2Byw== + dependencies: + "@aws-sdk/types" "3.696.0" + "@smithy/core" "^2.5.3" + "@smithy/node-config-provider" "^3.1.11" + "@smithy/property-provider" "^3.1.9" + "@smithy/protocol-http" "^4.1.7" + "@smithy/signature-v4" "^4.2.2" + "@smithy/smithy-client" "^3.4.4" + "@smithy/types" "^3.7.1" + "@smithy/util-middleware" "^3.0.10" + fast-xml-parser "4.4.1" + tslib "^2.6.2" + +"@aws-sdk/credential-provider-env@3.696.0": + version "3.696.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-env/-/credential-provider-env-3.696.0.tgz#afad9e61cd03da404bb03e5bce83c49736b85271" + integrity sha512-T9iMFnJL7YTlESLpVFT3fg1Lkb1lD+oiaIC8KMpepb01gDUBIpj9+Y+pA/cgRWW0yRxmkDXNazAE2qQTVFGJzA== + dependencies: + "@aws-sdk/core" "3.696.0" + "@aws-sdk/types" "3.696.0" + "@smithy/property-provider" "^3.1.9" + "@smithy/types" "^3.7.1" + tslib "^2.6.2" + +"@aws-sdk/credential-provider-http@3.696.0": + version "3.696.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-http/-/credential-provider-http-3.696.0.tgz#535756f9f427fbe851a8c1db7b0e3aaaf7790ba2" + integrity sha512-GV6EbvPi2eq1+WgY/o2RFA3P7HGmnkIzCNmhwtALFlqMroLYWKE7PSeHw66Uh1dFQeVESn0/+hiUNhu1mB0emA== + dependencies: + "@aws-sdk/core" "3.696.0" + "@aws-sdk/types" "3.696.0" + "@smithy/fetch-http-handler" "^4.1.1" + "@smithy/node-http-handler" "^3.3.1" + "@smithy/property-provider" "^3.1.9" + "@smithy/protocol-http" "^4.1.7" + "@smithy/smithy-client" "^3.4.4" + "@smithy/types" "^3.7.1" + "@smithy/util-stream" "^3.3.1" + tslib "^2.6.2" + +"@aws-sdk/credential-provider-ini@3.699.0": + version "3.699.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.699.0.tgz#7919a454b05c5446d04a0d3270807046a029ee30" + integrity sha512-dXmCqjJnKmG37Q+nLjPVu22mNkrGHY8hYoOt3Jo9R2zr5MYV7s/NHsCHr+7E+BZ+tfZYLRPeB1wkpTeHiEcdRw== + dependencies: + "@aws-sdk/core" "3.696.0" + "@aws-sdk/credential-provider-env" "3.696.0" + "@aws-sdk/credential-provider-http" "3.696.0" + "@aws-sdk/credential-provider-process" "3.696.0" + "@aws-sdk/credential-provider-sso" "3.699.0" + "@aws-sdk/credential-provider-web-identity" "3.696.0" + "@aws-sdk/types" "3.696.0" + "@smithy/credential-provider-imds" "^3.2.6" + "@smithy/property-provider" "^3.1.9" + "@smithy/shared-ini-file-loader" "^3.1.10" + "@smithy/types" "^3.7.1" + tslib "^2.6.2" + +"@aws-sdk/credential-provider-node@3.699.0": + version "3.699.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-node/-/credential-provider-node-3.699.0.tgz#6a1e32a49a7fa71d10c85a927267d1782444def1" + integrity sha512-MmEmNDo1bBtTgRmdNfdQksXu4uXe66s0p1hi1YPrn1h59Q605eq/xiWbGL6/3KdkViH6eGUuABeV2ODld86ylg== + dependencies: + "@aws-sdk/credential-provider-env" "3.696.0" + "@aws-sdk/credential-provider-http" "3.696.0" + "@aws-sdk/credential-provider-ini" "3.699.0" + "@aws-sdk/credential-provider-process" "3.696.0" + "@aws-sdk/credential-provider-sso" "3.699.0" + "@aws-sdk/credential-provider-web-identity" "3.696.0" + "@aws-sdk/types" "3.696.0" + "@smithy/credential-provider-imds" "^3.2.6" + "@smithy/property-provider" "^3.1.9" + "@smithy/shared-ini-file-loader" "^3.1.10" + "@smithy/types" "^3.7.1" + tslib "^2.6.2" + +"@aws-sdk/credential-provider-process@3.696.0": + version "3.696.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-process/-/credential-provider-process-3.696.0.tgz#45da7b948aa40987b413c7c0d4a8125bf1433651" + integrity sha512-mL1RcFDe9sfmyU5K1nuFkO8UiJXXxLX4JO1gVaDIOvPqwStpUAwi3A1BoeZhWZZNQsiKI810RnYGo0E0WB/hUA== + dependencies: + "@aws-sdk/core" "3.696.0" + "@aws-sdk/types" "3.696.0" + "@smithy/property-provider" "^3.1.9" + "@smithy/shared-ini-file-loader" "^3.1.10" + "@smithy/types" "^3.7.1" + tslib "^2.6.2" + +"@aws-sdk/credential-provider-sso@3.699.0": + version "3.699.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.699.0.tgz#515e2ecd407bace3141b8b192505631de415667e" + integrity sha512-Ekp2cZG4pl9D8+uKWm4qO1xcm8/MeiI8f+dnlZm8aQzizeC+aXYy9GyoclSf6daK8KfRPiRfM7ZHBBL5dAfdMA== + dependencies: + "@aws-sdk/client-sso" "3.696.0" + "@aws-sdk/core" "3.696.0" + "@aws-sdk/token-providers" "3.699.0" + "@aws-sdk/types" "3.696.0" + "@smithy/property-provider" "^3.1.9" + "@smithy/shared-ini-file-loader" "^3.1.10" + "@smithy/types" "^3.7.1" + tslib "^2.6.2" + +"@aws-sdk/credential-provider-web-identity@3.696.0": + version "3.696.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.696.0.tgz#3f97c00bd3bc7cfd988e098af67ff7c8392ce188" + integrity sha512-XJ/CVlWChM0VCoc259vWguFUjJDn/QwDqHwbx+K9cg3v6yrqXfK5ai+p/6lx0nQpnk4JzPVeYYxWRpaTsGC9rg== + dependencies: + "@aws-sdk/core" "3.696.0" + "@aws-sdk/types" "3.696.0" + "@smithy/property-provider" "^3.1.9" + "@smithy/types" "^3.7.1" + tslib "^2.6.2" + +"@aws-sdk/endpoint-cache@3.693.0": + version "3.693.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/endpoint-cache/-/endpoint-cache-3.693.0.tgz#4b3f0bbc16dc2907e1b977e3d8ddfc7ba008fd12" + integrity sha512-/zK0ZZncBf5FbTfo8rJMcQIXXk4Ibhe5zEMiwFNivVPR2uNC0+oqfwXz7vjxwY0t6BPE3Bs4h9uFEz4xuGCY6w== + dependencies: + mnemonist "0.38.3" + tslib "^2.6.2" + +"@aws-sdk/middleware-bucket-endpoint@3.696.0": + version "3.696.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.696.0.tgz#5c4e6c75855e94a8d7160812b63cb911373a4811" + integrity sha512-V07jishKHUS5heRNGFpCWCSTjRJyQLynS/ncUeE8ZYtG66StOOQWftTwDfFOSoXlIqrXgb4oT9atryzXq7Z4LQ== + dependencies: + "@aws-sdk/types" "3.696.0" + "@aws-sdk/util-arn-parser" "3.693.0" + "@smithy/node-config-provider" "^3.1.11" + "@smithy/protocol-http" "^4.1.7" + "@smithy/types" "^3.7.1" + "@smithy/util-config-provider" "^3.0.0" + tslib "^2.6.2" + +"@aws-sdk/middleware-endpoint-discovery@3.696.0": + version "3.696.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-endpoint-discovery/-/middleware-endpoint-discovery-3.696.0.tgz#c1a4cf63888fbd6903c3a767355829397e5ae9f3" + integrity sha512-KZvgR3lB9zdLuuO+SxeQQVDn8R46Brlolsbv7JGyR6id0BNy6pqitHdcrZCyp9jaMjrSFcPROceeLy70Cu3pZg== + dependencies: + "@aws-sdk/endpoint-cache" "3.693.0" + "@aws-sdk/types" "3.696.0" + "@smithy/node-config-provider" "^3.1.11" + "@smithy/protocol-http" "^4.1.7" + "@smithy/types" "^3.7.1" + tslib "^2.6.2" + +"@aws-sdk/middleware-expect-continue@3.696.0": + version "3.696.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.696.0.tgz#9c8e56d45bc99899f0ed054ea67d0f9703b76356" + integrity sha512-vpVukqY3U2pb+ULeX0shs6L0aadNep6kKzjme/MyulPjtUDJpD3AekHsXRrCCGLmOqSKqRgQn5zhV9pQhHsb6Q== + dependencies: + "@aws-sdk/types" "3.696.0" + "@smithy/protocol-http" "^4.1.7" + "@smithy/types" "^3.7.1" + tslib "^2.6.2" + +"@aws-sdk/middleware-flexible-checksums@3.701.0": + version "3.701.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.701.0.tgz#1793c77302f37aeab61205a0dbd89b45081bcc4a" + integrity sha512-adNaPCyTT+CiVM0ufDiO1Fe7nlRmJdI9Hcgj0M9S6zR7Dw70Ra5z8Lslkd7syAccYvZaqxLklGjPQH/7GNxwTA== + dependencies: + "@aws-crypto/crc32" "5.2.0" + "@aws-crypto/crc32c" "5.2.0" + "@aws-crypto/util" "5.2.0" + "@aws-sdk/core" "3.696.0" + "@aws-sdk/types" "3.696.0" + "@smithy/is-array-buffer" "^3.0.0" + "@smithy/node-config-provider" "^3.1.11" + "@smithy/protocol-http" "^4.1.7" + "@smithy/types" "^3.7.1" + "@smithy/util-middleware" "^3.0.10" + "@smithy/util-stream" "^3.3.1" + "@smithy/util-utf8" "^3.0.0" + tslib "^2.6.2" + +"@aws-sdk/middleware-host-header@3.696.0": + version "3.696.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-host-header/-/middleware-host-header-3.696.0.tgz#20aae0efeb973ca1a6db1b1014acbcdd06ad472e" + integrity sha512-zELJp9Ta2zkX7ELggMN9qMCgekqZhFC5V2rOr4hJDEb/Tte7gpfKSObAnw/3AYiVqt36sjHKfdkoTsuwGdEoDg== + dependencies: + "@aws-sdk/types" "3.696.0" + "@smithy/protocol-http" "^4.1.7" + "@smithy/types" "^3.7.1" + tslib "^2.6.2" + +"@aws-sdk/middleware-location-constraint@3.696.0": + version "3.696.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.696.0.tgz#21edf9fab1b29cb5f819c3be57e1286a86ae8be2" + integrity sha512-FgH12OB0q+DtTrP2aiDBddDKwL4BPOrm7w3VV9BJrSdkqQCNBPz8S1lb0y5eVH4tBG+2j7gKPlOv1wde4jF/iw== + dependencies: + "@aws-sdk/types" "3.696.0" + "@smithy/types" "^3.7.1" + tslib "^2.6.2" + +"@aws-sdk/middleware-logger@3.696.0": + version "3.696.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-logger/-/middleware-logger-3.696.0.tgz#79d68b7e5ba181511ade769b11165bfb7527181e" + integrity sha512-KhkHt+8AjCxcR/5Zp3++YPJPpFQzxpr+jmONiT/Jw2yqnSngZ0Yspm5wGoRx2hS1HJbyZNuaOWEGuJoxLeBKfA== + dependencies: + "@aws-sdk/types" "3.696.0" + "@smithy/types" "^3.7.1" + tslib "^2.6.2" + +"@aws-sdk/middleware-recursion-detection@3.696.0": + version "3.696.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.696.0.tgz#aa437d645d74cb785905162266741125c18f182a" + integrity sha512-si/maV3Z0hH7qa99f9ru2xpS5HlfSVcasRlNUXKSDm611i7jFMWwGNLUOXFAOLhXotPX5G3Z6BLwL34oDeBMug== + dependencies: + "@aws-sdk/types" "3.696.0" + "@smithy/protocol-http" "^4.1.7" + "@smithy/types" "^3.7.1" + tslib "^2.6.2" + +"@aws-sdk/middleware-sdk-s3@3.696.0": + version "3.696.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.696.0.tgz#63199a2df26e097122c07edf2e178f6d407b0ba7" + integrity sha512-M7fEiAiN7DBMHflzOFzh1I2MNSlLpbiH2ubs87bdRc2wZsDPSbs4l3v6h3WLhxoQK0bq6vcfroudrLBgvCuX3Q== + dependencies: + "@aws-sdk/core" "3.696.0" + "@aws-sdk/types" "3.696.0" + "@aws-sdk/util-arn-parser" "3.693.0" + "@smithy/core" "^2.5.3" + "@smithy/node-config-provider" "^3.1.11" + "@smithy/protocol-http" "^4.1.7" + "@smithy/signature-v4" "^4.2.2" + "@smithy/smithy-client" "^3.4.4" + "@smithy/types" "^3.7.1" + "@smithy/util-config-provider" "^3.0.0" + "@smithy/util-middleware" "^3.0.10" + "@smithy/util-stream" "^3.3.1" + "@smithy/util-utf8" "^3.0.0" + tslib "^2.6.2" + +"@aws-sdk/middleware-ssec@3.696.0": + version "3.696.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-ssec/-/middleware-ssec-3.696.0.tgz#b809692109f02722b90a74ca3593d4b6906ddc18" + integrity sha512-w/d6O7AOZ7Pg3w2d3BxnX5RmGNWb5X4RNxF19rJqcgu/xqxxE/QwZTNd5a7eTsqLXAUIfbbR8hh0czVfC1pJLA== + dependencies: + "@aws-sdk/types" "3.696.0" + "@smithy/types" "^3.7.1" + tslib "^2.6.2" + +"@aws-sdk/middleware-user-agent@3.696.0": + version "3.696.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.696.0.tgz#626c89300f6b3af5aefc1cb6d9ac19eebf8bc97d" + integrity sha512-Lvyj8CTyxrHI6GHd2YVZKIRI5Fmnugt3cpJo0VrKKEgK5zMySwEZ1n4dqPK6czYRWKd5+WnYHYAuU+Wdk6Jsjw== + dependencies: + "@aws-sdk/core" "3.696.0" + "@aws-sdk/types" "3.696.0" + "@aws-sdk/util-endpoints" "3.696.0" + "@smithy/core" "^2.5.3" + "@smithy/protocol-http" "^4.1.7" + "@smithy/types" "^3.7.1" + tslib "^2.6.2" + +"@aws-sdk/region-config-resolver@3.696.0": + version "3.696.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/region-config-resolver/-/region-config-resolver-3.696.0.tgz#146c428702c09db75df5234b5d40ce49d147d0cf" + integrity sha512-7EuH142lBXjI8yH6dVS/CZeiK/WZsmb/8zP6bQbVYpMrppSTgB3MzZZdxVZGzL5r8zPQOU10wLC4kIMy0qdBVQ== + dependencies: + "@aws-sdk/types" "3.696.0" + "@smithy/node-config-provider" "^3.1.11" + "@smithy/types" "^3.7.1" + "@smithy/util-config-provider" "^3.0.0" + "@smithy/util-middleware" "^3.0.10" + tslib "^2.6.2" + +"@aws-sdk/signature-v4-multi-region@3.696.0": + version "3.696.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.696.0.tgz#3a110c24a659818df665857e4e894e40eb59762b" + integrity sha512-ijPkoLjXuPtgxAYlDoYls8UaG/VKigROn9ebbvPL/orEY5umedd3iZTcS9T+uAf4Ur3GELLxMQiERZpfDKaz3g== + dependencies: + "@aws-sdk/middleware-sdk-s3" "3.696.0" + "@aws-sdk/types" "3.696.0" + "@smithy/protocol-http" "^4.1.7" + "@smithy/signature-v4" "^4.2.2" + "@smithy/types" "^3.7.1" + tslib "^2.6.2" + +"@aws-sdk/token-providers@3.699.0": + version "3.699.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/token-providers/-/token-providers-3.699.0.tgz#354990dd52d651c1f7a64c4c0894c868cdc81de2" + integrity sha512-kuiEW9DWs7fNos/SM+y58HCPhcIzm1nEZLhe2/7/6+TvAYLuEWURYsbK48gzsxXlaJ2k/jGY3nIsA7RptbMOwA== + dependencies: + "@aws-sdk/types" "3.696.0" + "@smithy/property-provider" "^3.1.9" + "@smithy/shared-ini-file-loader" "^3.1.10" + "@smithy/types" "^3.7.1" + tslib "^2.6.2" + +"@aws-sdk/types@3.696.0", "@aws-sdk/types@^3.222.0": + version "3.696.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/types/-/types-3.696.0.tgz#559c3df74dc389b6f40ba6ec6daffeab155330cd" + integrity sha512-9rTvUJIAj5d3//U5FDPWGJ1nFJLuWb30vugGOrWk7aNZ6y9tuA3PI7Cc9dP8WEXKVyK1vuuk8rSFP2iqXnlgrw== + dependencies: + "@smithy/types" "^3.7.1" + tslib "^2.6.2" + +"@aws-sdk/util-arn-parser@3.693.0": + version "3.693.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-arn-parser/-/util-arn-parser-3.693.0.tgz#8dae27eb822ab4f88be28bb3c0fc11f1f13d3948" + integrity sha512-WC8x6ca+NRrtpAH64rWu+ryDZI3HuLwlEr8EU6/dbC/pt+r/zC0PBoC15VEygUaBA+isppCikQpGyEDu0Yj7gQ== + dependencies: + tslib "^2.6.2" + +"@aws-sdk/util-dynamodb@^3.682.0": + version "3.705.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-dynamodb/-/util-dynamodb-3.705.0.tgz#4ee2c1f56e2c6c0ef4b4170a24408b48e4d395a9" + integrity sha512-cETrLaH8HGnHy2ohse02OR1/ux6IeT9dA1kngfYZyv0RhC1nt0fm6xJCTt8KPucPTTgXgGWzLaCB/AYu9uxH7A== + dependencies: + tslib "^2.6.2" + +"@aws-sdk/util-endpoints@3.696.0": + version "3.696.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-endpoints/-/util-endpoints-3.696.0.tgz#79e18714419a423a64094381b849214499f00577" + integrity sha512-T5s0IlBVX+gkb9g/I6CLt4yAZVzMSiGnbUqWihWsHvQR1WOoIcndQy/Oz/IJXT9T2ipoy7a80gzV6a5mglrioA== + dependencies: + "@aws-sdk/types" "3.696.0" + "@smithy/types" "^3.7.1" + "@smithy/util-endpoints" "^2.1.6" + tslib "^2.6.2" + +"@aws-sdk/util-locate-window@^3.0.0": + version "3.693.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-locate-window/-/util-locate-window-3.693.0.tgz#1160f6d055cf074ca198eb8ecf89b6311537ad6c" + integrity sha512-ttrag6haJLWABhLqtg1Uf+4LgHWIMOVSYL+VYZmAp2v4PUGOwWmWQH0Zk8RM7YuQcLfH/EoR72/Yxz6A4FKcuw== + dependencies: + tslib "^2.6.2" + +"@aws-sdk/util-user-agent-browser@3.696.0": + version "3.696.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.696.0.tgz#2034765c81313d5e50783662332d35ec041755a0" + integrity sha512-Z5rVNDdmPOe6ELoM5AhF/ja5tSjbe6ctSctDPb0JdDf4dT0v2MfwhJKzXju2RzX8Es/77Glh7MlaXLE0kCB9+Q== + dependencies: + "@aws-sdk/types" "3.696.0" + "@smithy/types" "^3.7.1" + bowser "^2.11.0" + tslib "^2.6.2" + +"@aws-sdk/util-user-agent-node@3.696.0": + version "3.696.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.696.0.tgz#3267119e2be02185f3b4e0beb0cc495d392260b4" + integrity sha512-KhKqcfyXIB0SCCt+qsu4eJjsfiOrNzK5dCV7RAW2YIpp+msxGUUX0NdRE9rkzjiv+3EMktgJm3eEIS+yxtlVdQ== + dependencies: + "@aws-sdk/middleware-user-agent" "3.696.0" + "@aws-sdk/types" "3.696.0" + "@smithy/node-config-provider" "^3.1.11" + "@smithy/types" "^3.7.1" + tslib "^2.6.2" + +"@aws-sdk/xml-builder@3.696.0": + version "3.696.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/xml-builder/-/xml-builder-3.696.0.tgz#ff3e37ee23208f9986f20d326cc278c0ee341164" + integrity sha512-dn1mX+EeqivoLYnY7p2qLrir0waPnCgS/0YdRCAVU2x14FgfUYCH6Im3w3oi2dMwhxfKY5lYVB5NKvZu7uI9lQ== + dependencies: + "@smithy/types" "^3.7.1" + tslib "^2.6.2" + +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.25.9", "@babel/code-frame@^7.26.0", "@babel/code-frame@^7.26.2": + version "7.26.2" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.26.2.tgz#4b5fab97d33338eff916235055f0ebc21e573a85" + integrity sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ== + dependencies: + "@babel/helper-validator-identifier" "^7.25.9" + js-tokens "^4.0.0" + picocolors "^1.0.0" + +"@babel/compat-data@^7.25.9": + version "7.26.3" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.26.3.tgz#99488264a56b2aded63983abd6a417f03b92ed02" + integrity sha512-nHIxvKPniQXpmQLb0vhY3VaFb3S0YrTAwpOWJZh1wn3oJPjJk9Asva204PsBdmAE8vpzfHudT8DB0scYvy9q0g== + +"@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.23.9": + version "7.26.0" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.26.0.tgz#d78b6023cc8f3114ccf049eb219613f74a747b40" + integrity sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg== + dependencies: + "@ampproject/remapping" "^2.2.0" + "@babel/code-frame" "^7.26.0" + "@babel/generator" "^7.26.0" + "@babel/helper-compilation-targets" "^7.25.9" + "@babel/helper-module-transforms" "^7.26.0" + "@babel/helpers" "^7.26.0" + "@babel/parser" "^7.26.0" + "@babel/template" "^7.25.9" + "@babel/traverse" "^7.25.9" + "@babel/types" "^7.26.0" + convert-source-map "^2.0.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.3" + semver "^6.3.1" + +"@babel/generator@^7.26.0", "@babel/generator@^7.26.3", "@babel/generator@^7.7.2": + version "7.26.3" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.26.3.tgz#ab8d4360544a425c90c248df7059881f4b2ce019" + integrity sha512-6FF/urZvD0sTeO7k6/B15pMLC4CHUv1426lzr3N01aHJTl046uCAh9LXW/fzeXXjPNCJ6iABW5XaWOsIZB93aQ== + dependencies: + "@babel/parser" "^7.26.3" + "@babel/types" "^7.26.3" + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.25" + jsesc "^3.0.2" + +"@babel/helper-compilation-targets@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.9.tgz#55af025ce365be3cdc0c1c1e56c6af617ce88875" + integrity sha512-j9Db8Suy6yV/VHa4qzrj9yZfZxhLWQdVnRlXxmKLYlhWUVB1sB2G5sxuWYXk/whHD9iW76PmNzxZ4UCnTQTVEQ== + dependencies: + "@babel/compat-data" "^7.25.9" + "@babel/helper-validator-option" "^7.25.9" + browserslist "^4.24.0" + lru-cache "^5.1.1" + semver "^6.3.1" + +"@babel/helper-module-imports@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz#e7f8d20602ebdbf9ebbea0a0751fb0f2a4141715" + integrity sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw== + dependencies: + "@babel/traverse" "^7.25.9" + "@babel/types" "^7.25.9" + +"@babel/helper-module-transforms@^7.26.0": + version "7.26.0" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz#8ce54ec9d592695e58d84cd884b7b5c6a2fdeeae" + integrity sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw== + dependencies: + "@babel/helper-module-imports" "^7.25.9" + "@babel/helper-validator-identifier" "^7.25.9" + "@babel/traverse" "^7.25.9" + +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.25.9", "@babel/helper-plugin-utils@^7.8.0": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.25.9.tgz#9cbdd63a9443a2c92a725cca7ebca12cc8dd9f46" + integrity sha512-kSMlyUVdWe25rEsRGviIgOWnoT/nfABVWlqt9N19/dIPWViAOW2s9wznP5tURbs/IDuNk4gPy3YdYRgH3uxhBw== + +"@babel/helper-string-parser@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz#1aabb72ee72ed35789b4bbcad3ca2862ce614e8c" + integrity sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA== + +"@babel/helper-validator-identifier@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz#24b64e2c3ec7cd3b3c547729b8d16871f22cbdc7" + integrity sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ== + +"@babel/helper-validator-option@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz#86e45bd8a49ab7e03f276577f96179653d41da72" + integrity sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw== + +"@babel/helpers@^7.26.0": + version "7.26.0" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.26.0.tgz#30e621f1eba5aa45fe6f4868d2e9154d884119a4" + integrity sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw== + dependencies: + "@babel/template" "^7.25.9" + "@babel/types" "^7.26.0" + +"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.23.9", "@babel/parser@^7.25.9", "@babel/parser@^7.26.0", "@babel/parser@^7.26.3": + version "7.26.3" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.26.3.tgz#8c51c5db6ddf08134af1ddbacf16aaab48bac234" + integrity sha512-WJ/CvmY8Mea8iDXo6a7RK2wbmJITT5fN3BEkRuFlxVyNx8jOKIIhmC4fSkTcPcf8JyavbBwIe6OpiCOBXt/IcA== + dependencies: + "@babel/types" "^7.26.3" + +"@babel/plugin-syntax-async-generators@^7.8.4": + version "7.8.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" + integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-bigint@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz#4c9a6f669f5d0cdf1b90a1671e9a146be5300cea" + integrity sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-class-properties@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz#b5c987274c4a3a82b89714796931a6b53544ae10" + integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-syntax-class-static-block@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz#195df89b146b4b78b3bf897fd7a257c84659d406" + integrity sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-import-attributes@^7.24.7": + version "7.26.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.26.0.tgz#3b1412847699eea739b4f2602c74ce36f6b0b0f7" + integrity sha512-e2dttdsJ1ZTpi3B9UYGLw41hifAubg19AtCu/2I/F1QNVclOBr1dYpTdmdyZ84Xiz43BS/tCUkMAZNLv12Pi+A== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/plugin-syntax-import-meta@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" + integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-json-strings@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" + integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-jsx@^7.7.2": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.25.9.tgz#a34313a178ea56f1951599b929c1ceacee719290" + integrity sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/plugin-syntax-logical-assignment-operators@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" + integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" + integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-numeric-separator@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97" + integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-object-rest-spread@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" + integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-catch-binding@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1" + integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-chaining@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a" + integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-private-property-in-object@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz#0dc6671ec0ea22b6e94a1114f857970cd39de1ad" + integrity sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-top-level-await@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz#c1cfdadc35a646240001f06138247b741c34d94c" + integrity sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-typescript@^7.7.2": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.9.tgz#67dda2b74da43727cf21d46cf9afef23f4365399" + integrity sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/template@^7.25.9", "@babel/template@^7.3.3": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.25.9.tgz#ecb62d81a8a6f5dc5fe8abfc3901fc52ddf15016" + integrity sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg== + dependencies: + "@babel/code-frame" "^7.25.9" + "@babel/parser" "^7.25.9" + "@babel/types" "^7.25.9" + +"@babel/traverse@^7.25.9": + version "7.26.4" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.26.4.tgz#ac3a2a84b908dde6d463c3bfa2c5fdc1653574bd" + integrity sha512-fH+b7Y4p3yqvApJALCPJcwb0/XaOSgtK4pzV6WVjPR5GLFQBRI7pfoX2V2iM48NXvX07NUxxm1Vw98YjqTcU5w== + dependencies: + "@babel/code-frame" "^7.26.2" + "@babel/generator" "^7.26.3" + "@babel/parser" "^7.26.3" + "@babel/template" "^7.25.9" + "@babel/types" "^7.26.3" + debug "^4.3.1" + globals "^11.1.0" + +"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.25.9", "@babel/types@^7.26.0", "@babel/types@^7.26.3", "@babel/types@^7.3.3": + version "7.26.3" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.26.3.tgz#37e79830f04c2b5687acc77db97fbc75fb81f3c0" + integrity sha512-vN5p+1kl59GVKMvTHt55NzzmYVxprfJD+ql7U9NFIfKCBkYE55LYtS+WtPlaYOyzydrKI8Nezd+aZextrd+FMA== + dependencies: + "@babel/helper-string-parser" "^7.25.9" + "@babel/helper-validator-identifier" "^7.25.9" + +"@bcoe/v8-coverage@^0.2.3": + version "0.2.3" + resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" + integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== + +"@colors/colors@1.6.0", "@colors/colors@^1.6.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.6.0.tgz#ec6cd237440700bc23ca23087f513c75508958b0" + integrity sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA== + +"@cspotcode/source-map-support@^0.8.0": + version "0.8.1" + resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" + integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw== + dependencies: + "@jridgewell/trace-mapping" "0.3.9" + +"@dabh/diagnostics@^2.0.2": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@dabh/diagnostics/-/diagnostics-2.0.3.tgz#7f7e97ee9a725dffc7808d93668cc984e1dc477a" + integrity sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA== + dependencies: + colorspace "1.1.x" + enabled "2.0.x" + kuler "^2.0.0" + +"@esbuild/aix-ppc64@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.23.1.tgz#51299374de171dbd80bb7d838e1cfce9af36f353" + integrity sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ== + +"@esbuild/aix-ppc64@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.24.0.tgz#b57697945b50e99007b4c2521507dc613d4a648c" + integrity sha512-WtKdFM7ls47zkKHFVzMz8opM7LkcsIp9amDUBIAWirg70RM71WRSjdILPsY5Uv1D42ZpUfaPILDlfactHgsRkw== + +"@esbuild/android-arm64@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.23.1.tgz#58565291a1fe548638adb9c584237449e5e14018" + integrity sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw== + +"@esbuild/android-arm64@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.24.0.tgz#1add7e0af67acefd556e407f8497e81fddad79c0" + integrity sha512-Vsm497xFM7tTIPYK9bNTYJyF/lsP590Qc1WxJdlB6ljCbdZKU9SY8i7+Iin4kyhV/KV5J2rOKsBQbB77Ab7L/w== + +"@esbuild/android-arm@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.23.1.tgz#5eb8c652d4c82a2421e3395b808e6d9c42c862ee" + integrity sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ== + +"@esbuild/android-arm@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.24.0.tgz#ab7263045fa8e090833a8e3c393b60d59a789810" + integrity sha512-arAtTPo76fJ/ICkXWetLCc9EwEHKaeya4vMrReVlEIUCAUncH7M4bhMQ+M9Vf+FFOZJdTNMXNBrWwW+OXWpSew== + +"@esbuild/android-x64@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.23.1.tgz#ae19d665d2f06f0f48a6ac9a224b3f672e65d517" + integrity sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg== + +"@esbuild/android-x64@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.24.0.tgz#e8f8b196cfdfdd5aeaebbdb0110983460440e705" + integrity sha512-t8GrvnFkiIY7pa7mMgJd7p8p8qqYIz1NYiAoKc75Zyv73L3DZW++oYMSHPRarcotTKuSs6m3hTOa5CKHaS02TQ== + +"@esbuild/darwin-arm64@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.23.1.tgz#05b17f91a87e557b468a9c75e9d85ab10c121b16" + integrity sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q== + +"@esbuild/darwin-arm64@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.24.0.tgz#2d0d9414f2acbffd2d86e98253914fca603a53dd" + integrity sha512-CKyDpRbK1hXwv79soeTJNHb5EiG6ct3efd/FTPdzOWdbZZfGhpbcqIpiD0+vwmpu0wTIL97ZRPZu8vUt46nBSw== + +"@esbuild/darwin-x64@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.23.1.tgz#c58353b982f4e04f0d022284b8ba2733f5ff0931" + integrity sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw== + +"@esbuild/darwin-x64@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.24.0.tgz#33087aab31a1eb64c89daf3d2cf8ce1775656107" + integrity sha512-rgtz6flkVkh58od4PwTRqxbKH9cOjaXCMZgWD905JOzjFKW+7EiUObfd/Kav+A6Gyud6WZk9w+xu6QLytdi2OA== + +"@esbuild/freebsd-arm64@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.23.1.tgz#f9220dc65f80f03635e1ef96cfad5da1f446f3bc" + integrity sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA== + +"@esbuild/freebsd-arm64@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.0.tgz#bb76e5ea9e97fa3c753472f19421075d3a33e8a7" + integrity sha512-6Mtdq5nHggwfDNLAHkPlyLBpE5L6hwsuXZX8XNmHno9JuL2+bg2BX5tRkwjyfn6sKbxZTq68suOjgWqCicvPXA== + +"@esbuild/freebsd-x64@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.23.1.tgz#69bd8511fa013b59f0226d1609ac43f7ce489730" + integrity sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g== + +"@esbuild/freebsd-x64@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.24.0.tgz#e0e2ce9249fdf6ee29e5dc3d420c7007fa579b93" + integrity sha512-D3H+xh3/zphoX8ck4S2RxKR6gHlHDXXzOf6f/9dbFt/NRBDIE33+cVa49Kil4WUjxMGW0ZIYBYtaGCa2+OsQwQ== + +"@esbuild/linux-arm64@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.23.1.tgz#8050af6d51ddb388c75653ef9871f5ccd8f12383" + integrity sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g== + +"@esbuild/linux-arm64@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.24.0.tgz#d1b2aa58085f73ecf45533c07c82d81235388e75" + integrity sha512-TDijPXTOeE3eaMkRYpcy3LarIg13dS9wWHRdwYRnzlwlA370rNdZqbcp0WTyyV/k2zSxfko52+C7jU5F9Tfj1g== + +"@esbuild/linux-arm@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.23.1.tgz#ecaabd1c23b701070484990db9a82f382f99e771" + integrity sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ== + +"@esbuild/linux-arm@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.24.0.tgz#8e4915df8ea3e12b690a057e77a47b1d5935ef6d" + integrity sha512-gJKIi2IjRo5G6Glxb8d3DzYXlxdEj2NlkixPsqePSZMhLudqPhtZ4BUrpIuTjJYXxvF9njql+vRjB2oaC9XpBw== + +"@esbuild/linux-ia32@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.23.1.tgz#3ed2273214178109741c09bd0687098a0243b333" + integrity sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ== + +"@esbuild/linux-ia32@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.24.0.tgz#8200b1110666c39ab316572324b7af63d82013fb" + integrity sha512-K40ip1LAcA0byL05TbCQ4yJ4swvnbzHscRmUilrmP9Am7//0UjPreh4lpYzvThT2Quw66MhjG//20mrufm40mA== + +"@esbuild/linux-loong64@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.23.1.tgz#a0fdf440b5485c81b0fbb316b08933d217f5d3ac" + integrity sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw== + +"@esbuild/linux-loong64@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.24.0.tgz#6ff0c99cf647504df321d0640f0d32e557da745c" + integrity sha512-0mswrYP/9ai+CU0BzBfPMZ8RVm3RGAN/lmOMgW4aFUSOQBjA31UP8Mr6DDhWSuMwj7jaWOT0p0WoZ6jeHhrD7g== + +"@esbuild/linux-mips64el@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.23.1.tgz#e11a2806346db8375b18f5e104c5a9d4e81807f6" + integrity sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q== + +"@esbuild/linux-mips64el@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.24.0.tgz#3f720ccd4d59bfeb4c2ce276a46b77ad380fa1f3" + integrity sha512-hIKvXm0/3w/5+RDtCJeXqMZGkI2s4oMUGj3/jM0QzhgIASWrGO5/RlzAzm5nNh/awHE0A19h/CvHQe6FaBNrRA== + +"@esbuild/linux-ppc64@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.23.1.tgz#06a2744c5eaf562b1a90937855b4d6cf7c75ec96" + integrity sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw== + +"@esbuild/linux-ppc64@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.24.0.tgz#9d6b188b15c25afd2e213474bf5f31e42e3aa09e" + integrity sha512-HcZh5BNq0aC52UoocJxaKORfFODWXZxtBaaZNuN3PUX3MoDsChsZqopzi5UupRhPHSEHotoiptqikjN/B77mYQ== + +"@esbuild/linux-riscv64@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.23.1.tgz#65b46a2892fc0d1af4ba342af3fe0fa4a8fe08e7" + integrity sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA== + +"@esbuild/linux-riscv64@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.24.0.tgz#f989fdc9752dfda286c9cd87c46248e4dfecbc25" + integrity sha512-bEh7dMn/h3QxeR2KTy1DUszQjUrIHPZKyO6aN1X4BCnhfYhuQqedHaa5MxSQA/06j3GpiIlFGSsy1c7Gf9padw== + +"@esbuild/linux-s390x@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.23.1.tgz#e71ea18c70c3f604e241d16e4e5ab193a9785d6f" + integrity sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw== + +"@esbuild/linux-s390x@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.24.0.tgz#29ebf87e4132ea659c1489fce63cd8509d1c7319" + integrity sha512-ZcQ6+qRkw1UcZGPyrCiHHkmBaj9SiCD8Oqd556HldP+QlpUIe2Wgn3ehQGVoPOvZvtHm8HPx+bH20c9pvbkX3g== + +"@esbuild/linux-x64@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.23.1.tgz#d47f97391e80690d4dfe811a2e7d6927ad9eed24" + integrity sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ== + +"@esbuild/linux-x64@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.24.0.tgz#4af48c5c0479569b1f359ffbce22d15f261c0cef" + integrity sha512-vbutsFqQ+foy3wSSbmjBXXIJ6PL3scghJoM8zCL142cGaZKAdCZHyf+Bpu/MmX9zT9Q0zFBVKb36Ma5Fzfa8xA== + +"@esbuild/netbsd-x64@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.23.1.tgz#44e743c9778d57a8ace4b72f3c6b839a3b74a653" + integrity sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA== + +"@esbuild/netbsd-x64@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.24.0.tgz#1ae73d23cc044a0ebd4f198334416fb26c31366c" + integrity sha512-hjQ0R/ulkO8fCYFsG0FZoH+pWgTTDreqpqY7UnQntnaKv95uP5iW3+dChxnx7C3trQQU40S+OgWhUVwCjVFLvg== + +"@esbuild/openbsd-arm64@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.23.1.tgz#05c5a1faf67b9881834758c69f3e51b7dee015d7" + integrity sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q== + +"@esbuild/openbsd-arm64@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.0.tgz#5d904a4f5158c89859fd902c427f96d6a9e632e2" + integrity sha512-MD9uzzkPQbYehwcN583yx3Tu5M8EIoTD+tUgKF982WYL9Pf5rKy9ltgD0eUgs8pvKnmizxjXZyLt0z6DC3rRXg== + +"@esbuild/openbsd-x64@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.23.1.tgz#2e58ae511bacf67d19f9f2dcd9e8c5a93f00c273" + integrity sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA== + +"@esbuild/openbsd-x64@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.24.0.tgz#4c8aa88c49187c601bae2971e71c6dc5e0ad1cdf" + integrity sha512-4ir0aY1NGUhIC1hdoCzr1+5b43mw99uNwVzhIq1OY3QcEwPDO3B7WNXBzaKY5Nsf1+N11i1eOfFcq+D/gOS15Q== + +"@esbuild/sunos-x64@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.23.1.tgz#adb022b959d18d3389ac70769cef5a03d3abd403" + integrity sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA== + +"@esbuild/sunos-x64@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.24.0.tgz#8ddc35a0ea38575fa44eda30a5ee01ae2fa54dd4" + integrity sha512-jVzdzsbM5xrotH+W5f1s+JtUy1UWgjU0Cf4wMvffTB8m6wP5/kx0KiaLHlbJO+dMgtxKV8RQ/JvtlFcdZ1zCPA== + +"@esbuild/win32-arm64@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.23.1.tgz#84906f50c212b72ec360f48461d43202f4c8b9a2" + integrity sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A== + +"@esbuild/win32-arm64@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.24.0.tgz#6e79c8543f282c4539db684a207ae0e174a9007b" + integrity sha512-iKc8GAslzRpBytO2/aN3d2yb2z8XTVfNV0PjGlCxKo5SgWmNXx82I/Q3aG1tFfS+A2igVCY97TJ8tnYwpUWLCA== + +"@esbuild/win32-ia32@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.23.1.tgz#5e3eacc515820ff729e90d0cb463183128e82fac" + integrity sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ== + +"@esbuild/win32-ia32@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.24.0.tgz#057af345da256b7192d18b676a02e95d0fa39103" + integrity sha512-vQW36KZolfIudCcTnaTpmLQ24Ha1RjygBo39/aLkM2kmjkWmZGEJ5Gn9l5/7tzXA42QGIoWbICfg6KLLkIw6yw== + +"@esbuild/win32-x64@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.23.1.tgz#81fd50d11e2c32b2d6241470e3185b70c7b30699" + integrity sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg== + +"@esbuild/win32-x64@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.24.0.tgz#168ab1c7e1c318b922637fad8f339d48b01e1244" + integrity sha512-7IAFPrjSQIJrGsK6flwg7NFmwBoSTyF3rl7If0hNUFQU4ilTsEPL6GuMuU9BfIWVVGuRnuIidkSMC+c0Otu8IA== + +"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": + version "4.4.1" + resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz#d1145bf2c20132d6400495d6df4bf59362fd9d56" + integrity sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA== + dependencies: + eslint-visitor-keys "^3.4.3" + +"@eslint-community/regexpp@^4.10.0", "@eslint-community/regexpp@^4.12.1": + version "4.12.1" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.1.tgz#cfc6cffe39df390a3841cde2abccf92eaa7ae0e0" + integrity sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ== + +"@eslint/config-array@^0.19.0": + version "0.19.1" + resolved "https://registry.yarnpkg.com/@eslint/config-array/-/config-array-0.19.1.tgz#734aaea2c40be22bbb1f2a9dac687c57a6a4c984" + integrity sha512-fo6Mtm5mWyKjA/Chy1BYTdn5mGJoDNjC7C64ug20ADsRDGrA85bN3uK3MaKbeRkRuuIEAR5N33Jr1pbm411/PA== + dependencies: + "@eslint/object-schema" "^2.1.5" + debug "^4.3.1" + minimatch "^3.1.2" + +"@eslint/core@^0.9.0": + version "0.9.1" + resolved "https://registry.yarnpkg.com/@eslint/core/-/core-0.9.1.tgz#31763847308ef6b7084a4505573ac9402c51f9d1" + integrity sha512-GuUdqkyyzQI5RMIWkHhvTWLCyLo1jNK3vzkSyaExH5kHPDHcuL2VOpHjmMY+y3+NC69qAKToBqldTBgYeLSr9Q== + dependencies: + "@types/json-schema" "^7.0.15" + +"@eslint/eslintrc@^3.2.0": + version "3.2.0" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-3.2.0.tgz#57470ac4e2e283a6bf76044d63281196e370542c" + integrity sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w== + dependencies: + ajv "^6.12.4" + debug "^4.3.2" + espree "^10.0.1" + globals "^14.0.0" + ignore "^5.2.0" + import-fresh "^3.2.1" + js-yaml "^4.1.0" + minimatch "^3.1.2" + strip-json-comments "^3.1.1" + +"@eslint/js@9.16.0": + version "9.16.0" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.16.0.tgz#3df2b2dd3b9163056616886c86e4082f45dbf3f4" + integrity sha512-tw2HxzQkrbeuvyj1tG2Yqq+0H9wGoI2IMk4EOsQeX+vmd75FtJAzf+gTA69WF+baUKRYQ3x2kbLE08js5OsTVg== + +"@eslint/object-schema@^2.1.5": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@eslint/object-schema/-/object-schema-2.1.5.tgz#8670a8f6258a2be5b2c620ff314a1d984c23eb2e" + integrity sha512-o0bhxnL89h5Bae5T318nFoFzGy+YE5i/gGkoPAgkmTVdRKTiv3p8JHevPiPaMwoloKfEiiaHlawCqaZMqRm+XQ== + +"@eslint/plugin-kit@^0.2.3": + version "0.2.4" + resolved "https://registry.yarnpkg.com/@eslint/plugin-kit/-/plugin-kit-0.2.4.tgz#2b78e7bb3755784bb13faa8932a1d994d6537792" + integrity sha512-zSkKow6H5Kdm0ZUQUB2kV5JIXqoG0+uH5YADhaEHswm664N9Db8dXSi0nMJpacpMf+MyyglF1vnZohpEg5yUtg== + dependencies: + levn "^0.4.1" + +"@humanfs/core@^0.19.1": + version "0.19.1" + resolved "https://registry.yarnpkg.com/@humanfs/core/-/core-0.19.1.tgz#17c55ca7d426733fe3c561906b8173c336b40a77" + integrity sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA== + +"@humanfs/node@^0.16.6": + version "0.16.6" + resolved "https://registry.yarnpkg.com/@humanfs/node/-/node-0.16.6.tgz#ee2a10eaabd1131987bf0488fd9b820174cd765e" + integrity sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw== + dependencies: + "@humanfs/core" "^0.19.1" + "@humanwhocodes/retry" "^0.3.0" + +"@humanwhocodes/module-importer@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" + integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== + +"@humanwhocodes/retry@^0.3.0": + version "0.3.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/retry/-/retry-0.3.1.tgz#c72a5c76a9fbaf3488e231b13dc52c0da7bab42a" + integrity sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA== + +"@humanwhocodes/retry@^0.4.1": + version "0.4.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/retry/-/retry-0.4.1.tgz#9a96ce501bc62df46c4031fbd970e3cc6b10f07b" + integrity sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA== + +"@isaacs/cliui@^8.0.2": + version "8.0.2" + resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550" + integrity sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA== + dependencies: + string-width "^5.1.2" + string-width-cjs "npm:string-width@^4.2.0" + strip-ansi "^7.0.1" + strip-ansi-cjs "npm:strip-ansi@^6.0.1" + wrap-ansi "^8.1.0" + wrap-ansi-cjs "npm:wrap-ansi@^7.0.0" + +"@istanbuljs/load-nyc-config@^1.0.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" + integrity sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ== + dependencies: + camelcase "^5.3.1" + find-up "^4.1.0" + get-package-type "^0.1.0" + js-yaml "^3.13.1" + resolve-from "^5.0.0" + +"@istanbuljs/schema@^0.1.2", "@istanbuljs/schema@^0.1.3": + version "0.1.3" + resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" + integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== + +"@jest/console@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/console/-/console-29.7.0.tgz#cd4822dbdb84529265c5a2bdb529a3c9cc950ffc" + integrity sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg== + dependencies: + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + slash "^3.0.0" + +"@jest/core@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/core/-/core-29.7.0.tgz#b6cccc239f30ff36609658c5a5e2291757ce448f" + integrity sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg== + dependencies: + "@jest/console" "^29.7.0" + "@jest/reporters" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + ci-info "^3.2.0" + exit "^0.1.2" + graceful-fs "^4.2.9" + jest-changed-files "^29.7.0" + jest-config "^29.7.0" + 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" + jest-runner "^29.7.0" + jest-runtime "^29.7.0" + jest-snapshot "^29.7.0" + jest-util "^29.7.0" + jest-validate "^29.7.0" + jest-watcher "^29.7.0" + micromatch "^4.0.4" + pretty-format "^29.7.0" + slash "^3.0.0" + strip-ansi "^6.0.0" + +"@jest/environment@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.7.0.tgz#24d61f54ff1f786f3cd4073b4b94416383baf2a7" + integrity sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw== + dependencies: + "@jest/fake-timers" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + jest-mock "^29.7.0" + +"@jest/expect-utils@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.7.0.tgz#023efe5d26a8a70f21677d0a1afc0f0a44e3a1c6" + integrity sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA== + dependencies: + jest-get-type "^29.6.3" + +"@jest/expect@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-29.7.0.tgz#76a3edb0cb753b70dfbfe23283510d3d45432bf2" + integrity sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ== + dependencies: + expect "^29.7.0" + jest-snapshot "^29.7.0" + +"@jest/fake-timers@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-29.7.0.tgz#fd91bf1fffb16d7d0d24a426ab1a47a49881a565" + integrity sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ== + dependencies: + "@jest/types" "^29.6.3" + "@sinonjs/fake-timers" "^10.0.2" + "@types/node" "*" + jest-message-util "^29.7.0" + jest-mock "^29.7.0" + jest-util "^29.7.0" + +"@jest/globals@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-29.7.0.tgz#8d9290f9ec47ff772607fa864ca1d5a2efae1d4d" + integrity sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/expect" "^29.7.0" + "@jest/types" "^29.6.3" + jest-mock "^29.7.0" + +"@jest/reporters@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-29.7.0.tgz#04b262ecb3b8faa83b0b3d321623972393e8f4c7" + integrity sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg== + dependencies: + "@bcoe/v8-coverage" "^0.2.3" + "@jest/console" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@jridgewell/trace-mapping" "^0.3.18" + "@types/node" "*" + chalk "^4.0.0" + collect-v8-coverage "^1.0.0" + exit "^0.1.2" + glob "^7.1.3" + graceful-fs "^4.2.9" + istanbul-lib-coverage "^3.0.0" + istanbul-lib-instrument "^6.0.0" + istanbul-lib-report "^3.0.0" + istanbul-lib-source-maps "^4.0.0" + istanbul-reports "^3.1.3" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + jest-worker "^29.7.0" + slash "^3.0.0" + string-length "^4.0.1" + strip-ansi "^6.0.0" + v8-to-istanbul "^9.0.1" + +"@jest/schemas@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.3.tgz#430b5ce8a4e0044a7e3819663305a7b3091c8e03" + integrity sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA== + dependencies: + "@sinclair/typebox" "^0.27.8" + +"@jest/source-map@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-29.6.3.tgz#d90ba772095cf37a34a5eb9413f1b562a08554c4" + integrity sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw== + dependencies: + "@jridgewell/trace-mapping" "^0.3.18" + callsites "^3.0.0" + graceful-fs "^4.2.9" + +"@jest/test-result@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-29.7.0.tgz#8db9a80aa1a097bb2262572686734baed9b1657c" + integrity sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA== + dependencies: + "@jest/console" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/istanbul-lib-coverage" "^2.0.0" + collect-v8-coverage "^1.0.0" + +"@jest/test-sequencer@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz#6cef977ce1d39834a3aea887a1726628a6f072ce" + integrity sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw== + dependencies: + "@jest/test-result" "^29.7.0" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + slash "^3.0.0" + +"@jest/transform@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-29.7.0.tgz#df2dd9c346c7d7768b8a06639994640c642e284c" + integrity sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw== + dependencies: + "@babel/core" "^7.11.6" + "@jest/types" "^29.6.3" + "@jridgewell/trace-mapping" "^0.3.18" + babel-plugin-istanbul "^6.1.1" + chalk "^4.0.0" + convert-source-map "^2.0.0" + fast-json-stable-stringify "^2.1.0" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + jest-regex-util "^29.6.3" + jest-util "^29.7.0" + micromatch "^4.0.4" + pirates "^4.0.4" + slash "^3.0.0" + write-file-atomic "^4.0.2" + +"@jest/types@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.6.3.tgz#1131f8cf634e7e84c5e77bab12f052af585fba59" + integrity sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw== + dependencies: + "@jest/schemas" "^29.6.3" + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^17.0.8" + chalk "^4.0.0" + +"@jridgewell/gen-mapping@^0.3.5": + version "0.3.5" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz#dcce6aff74bdf6dad1a95802b69b04a2fcb1fb36" + integrity sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg== + dependencies: + "@jridgewell/set-array" "^1.2.1" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping" "^0.3.24" + +"@jridgewell/resolve-uri@^3.0.3", "@jridgewell/resolve-uri@^3.1.0": + version "3.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" + integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== + +"@jridgewell/set-array@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.2.1.tgz#558fb6472ed16a4c850b889530e6b36438c49280" + integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A== + +"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz#3188bcb273a414b0d215fd22a58540b989b9409a" + integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ== + +"@jridgewell/trace-mapping@0.3.9": + version "0.3.9" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9" + integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ== + dependencies: + "@jridgewell/resolve-uri" "^3.0.3" + "@jridgewell/sourcemap-codec" "^1.4.10" + +"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.18", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": + version "0.3.25" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz#15f190e98895f3fc23276ee14bc76b675c2e50f0" + integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ== + dependencies: + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" + +"@nodelib/fs.scandir@2.1.5": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" + integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== + dependencies: + "@nodelib/fs.stat" "2.0.5" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" + integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== + +"@nodelib/fs.walk@^1.2.3": + version "1.2.8" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" + integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== + dependencies: + "@nodelib/fs.scandir" "2.1.5" + fastq "^1.6.0" + +"@pkgjs/parseargs@^0.11.0": + version "0.11.0" + resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" + integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== + +"@sinclair/typebox@^0.27.8": + version "0.27.8" + resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" + integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== + +"@sinonjs/commons@^3.0.0", "@sinonjs/commons@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-3.0.1.tgz#1029357e44ca901a615585f6d27738dbc89084cd" + integrity sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ== + dependencies: + type-detect "4.0.8" + +"@sinonjs/fake-timers@11.2.2": + version "11.2.2" + resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-11.2.2.tgz#50063cc3574f4a27bd8453180a04171c85cc9699" + integrity sha512-G2piCSxQ7oWOxwGSAyFHfPIsyeJGXYtc6mFbnFA+kRXkiEnTl8c/8jul2S329iFBnDI9HGoeWWAZvuvOkZccgw== + dependencies: + "@sinonjs/commons" "^3.0.0" + +"@sinonjs/fake-timers@^10.0.2": + version "10.3.0" + resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz#55fdff1ecab9f354019129daf4df0dd4d923ea66" + integrity sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA== + dependencies: + "@sinonjs/commons" "^3.0.0" + +"@sinonjs/fake-timers@^13.0.1": + version "13.0.5" + resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-13.0.5.tgz#36b9dbc21ad5546486ea9173d6bea063eb1717d5" + integrity sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw== + dependencies: + "@sinonjs/commons" "^3.0.1" + +"@sinonjs/samsam@^8.0.0": + version "8.0.2" + resolved "https://registry.yarnpkg.com/@sinonjs/samsam/-/samsam-8.0.2.tgz#e4386bf668ff36c95949e55a38dc5f5892fc2689" + integrity sha512-v46t/fwnhejRSFTGqbpn9u+LQ9xJDse10gNnPgAcxgdoCDMXj/G2asWAC/8Qs+BAZDicX+MNZouXT1A7c83kVw== + dependencies: + "@sinonjs/commons" "^3.0.1" + lodash.get "^4.4.2" + type-detect "^4.1.0" + +"@sinonjs/text-encoding@^0.7.3": + version "0.7.3" + resolved "https://registry.yarnpkg.com/@sinonjs/text-encoding/-/text-encoding-0.7.3.tgz#282046f03e886e352b2d5f5da5eb755e01457f3f" + integrity sha512-DE427ROAphMQzU4ENbliGYrBSYPXF+TtLg9S8vzeA+OF4ZKzoDdzfL8sxuMUGS/lgRhM6j1URSk9ghf7Xo1tyA== + +"@smithy/abort-controller@^3.1.9": + version "3.1.9" + resolved "https://registry.yarnpkg.com/@smithy/abort-controller/-/abort-controller-3.1.9.tgz#47d323f754136a489e972d7fd465d534d72fcbff" + integrity sha512-yiW0WI30zj8ZKoSYNx90no7ugVn3khlyH/z5W8qtKBtVE6awRALbhSG+2SAHA1r6bO/6M9utxYKVZ3PCJ1rWxw== + dependencies: + "@smithy/types" "^3.7.2" + tslib "^2.6.2" + +"@smithy/chunked-blob-reader-native@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@smithy/chunked-blob-reader-native/-/chunked-blob-reader-native-3.0.1.tgz#39045ed278ee1b6f4c12715c7565678557274c29" + integrity sha512-VEYtPvh5rs/xlyqpm5NRnfYLZn+q0SRPELbvBV+C/G7IQ+ouTuo+NKKa3ShG5OaFR8NYVMXls9hPYLTvIKKDrQ== + dependencies: + "@smithy/util-base64" "^3.0.0" + tslib "^2.6.2" + +"@smithy/chunked-blob-reader@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@smithy/chunked-blob-reader/-/chunked-blob-reader-4.0.0.tgz#754099909957fb1986c16eb88afad75919d7129d" + integrity sha512-jSqRnZvkT4egkq/7b6/QRCNXmmYVcHwnJldqJ3IhVpQE2atObVJ137xmGeuGFhjFUr8gCEVAOKwSY79OvpbDaQ== + dependencies: + tslib "^2.6.2" + +"@smithy/config-resolver@^3.0.12", "@smithy/config-resolver@^3.0.13": + version "3.0.13" + resolved "https://registry.yarnpkg.com/@smithy/config-resolver/-/config-resolver-3.0.13.tgz#653643a77a33d0f5907a5e7582353886b07ba752" + integrity sha512-Gr/qwzyPaTL1tZcq8WQyHhTZREER5R1Wytmz4WnVGL4onA3dNk6Btll55c8Vr58pLdvWZmtG8oZxJTw3t3q7Jg== + dependencies: + "@smithy/node-config-provider" "^3.1.12" + "@smithy/types" "^3.7.2" + "@smithy/util-config-provider" "^3.0.0" + "@smithy/util-middleware" "^3.0.11" + tslib "^2.6.2" + +"@smithy/core@^2.5.3", "@smithy/core@^2.5.5": + version "2.5.5" + resolved "https://registry.yarnpkg.com/@smithy/core/-/core-2.5.5.tgz#c75b15caee9e58c800db3e6b99e9e373532d394a" + integrity sha512-G8G/sDDhXA7o0bOvkc7bgai6POuSld/+XhNnWAbpQTpLv2OZPvyqQ58tLPPlz0bSNsXktldDDREIv1LczFeNEw== + dependencies: + "@smithy/middleware-serde" "^3.0.11" + "@smithy/protocol-http" "^4.1.8" + "@smithy/types" "^3.7.2" + "@smithy/util-body-length-browser" "^3.0.0" + "@smithy/util-middleware" "^3.0.11" + "@smithy/util-stream" "^3.3.2" + "@smithy/util-utf8" "^3.0.0" + tslib "^2.6.2" + +"@smithy/credential-provider-imds@^3.2.6", "@smithy/credential-provider-imds@^3.2.8": + version "3.2.8" + resolved "https://registry.yarnpkg.com/@smithy/credential-provider-imds/-/credential-provider-imds-3.2.8.tgz#27ed2747074c86a7d627a98e56f324a65cba88de" + integrity sha512-ZCY2yD0BY+K9iMXkkbnjo+08T2h8/34oHd0Jmh6BZUSZwaaGlGCyBT/3wnS7u7Xl33/EEfN4B6nQr3Gx5bYxgw== + dependencies: + "@smithy/node-config-provider" "^3.1.12" + "@smithy/property-provider" "^3.1.11" + "@smithy/types" "^3.7.2" + "@smithy/url-parser" "^3.0.11" + tslib "^2.6.2" + +"@smithy/eventstream-codec@^3.1.10": + version "3.1.10" + resolved "https://registry.yarnpkg.com/@smithy/eventstream-codec/-/eventstream-codec-3.1.10.tgz#0c1a3457e7a23b71cd71525ceb668f8569a84dad" + integrity sha512-323B8YckSbUH0nMIpXn7HZsAVKHYHFUODa8gG9cHo0ySvA1fr5iWaNT+iIL0UCqUzG6QPHA3BSsBtRQou4mMqQ== + dependencies: + "@aws-crypto/crc32" "5.2.0" + "@smithy/types" "^3.7.2" + "@smithy/util-hex-encoding" "^3.0.0" + tslib "^2.6.2" + +"@smithy/eventstream-serde-browser@^3.0.13": + version "3.0.14" + resolved "https://registry.yarnpkg.com/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-3.0.14.tgz#0c3584c7cde2e210aacdfbbd2b57c1d7e2ca3b95" + integrity sha512-kbrt0vjOIihW3V7Cqj1SXQvAI5BR8SnyQYsandva0AOR307cXAc+IhPngxIPslxTLfxwDpNu0HzCAq6g42kCPg== + dependencies: + "@smithy/eventstream-serde-universal" "^3.0.13" + "@smithy/types" "^3.7.2" + tslib "^2.6.2" + +"@smithy/eventstream-serde-config-resolver@^3.0.10": + version "3.0.11" + resolved "https://registry.yarnpkg.com/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-3.0.11.tgz#5edceba836debea165ea93145231036f6286d67c" + integrity sha512-P2pnEp4n75O+QHjyO7cbw/vsw5l93K/8EWyjNCAAybYwUmj3M+hjSQZ9P5TVdUgEG08ueMAP5R4FkuSkElZ5tQ== + dependencies: + "@smithy/types" "^3.7.2" + tslib "^2.6.2" + +"@smithy/eventstream-serde-node@^3.0.12": + version "3.0.13" + resolved "https://registry.yarnpkg.com/@smithy/eventstream-serde-node/-/eventstream-serde-node-3.0.13.tgz#5aebd7b553becee277e411a2b69f6af8c9d7b3a6" + integrity sha512-zqy/9iwbj8Wysmvi7Lq7XFLeDgjRpTbCfwBhJa8WbrylTAHiAu6oQTwdY7iu2lxigbc9YYr9vPv5SzYny5tCXQ== + dependencies: + "@smithy/eventstream-serde-universal" "^3.0.13" + "@smithy/types" "^3.7.2" + tslib "^2.6.2" + +"@smithy/eventstream-serde-universal@^3.0.13": + version "3.0.13" + resolved "https://registry.yarnpkg.com/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-3.0.13.tgz#609c922ea14a0a3eed23a28ac110344c935704eb" + integrity sha512-L1Ib66+gg9uTnqp/18Gz4MDpJPKRE44geOjOQ2SVc0eiaO5l255ADziATZgjQjqumC7yPtp1XnjHlF1srcwjKw== + dependencies: + "@smithy/eventstream-codec" "^3.1.10" + "@smithy/types" "^3.7.2" + tslib "^2.6.2" + +"@smithy/fetch-http-handler@^4.1.1", "@smithy/fetch-http-handler@^4.1.2": + version "4.1.2" + resolved "https://registry.yarnpkg.com/@smithy/fetch-http-handler/-/fetch-http-handler-4.1.2.tgz#f034ff16416b37d92908a1381ef5fddbf4ef1879" + integrity sha512-R7rU7Ae3ItU4rC0c5mB2sP5mJNbCfoDc8I5XlYjIZnquyUwec7fEo78F6DA3SmgJgkU1qTMcZJuGblxZsl10ZA== + dependencies: + "@smithy/protocol-http" "^4.1.8" + "@smithy/querystring-builder" "^3.0.11" + "@smithy/types" "^3.7.2" + "@smithy/util-base64" "^3.0.0" + tslib "^2.6.2" + +"@smithy/hash-blob-browser@^3.1.9": + version "3.1.10" + resolved "https://registry.yarnpkg.com/@smithy/hash-blob-browser/-/hash-blob-browser-3.1.10.tgz#985e308189c2687a15004152b97506882ffb2b13" + integrity sha512-elwslXOoNunmfS0fh55jHggyhccobFkexLYC1ZeZ1xP2BTSrcIBaHV2b4xUQOdctrSNOpMqOZH1r2XzWTEhyfA== + dependencies: + "@smithy/chunked-blob-reader" "^4.0.0" + "@smithy/chunked-blob-reader-native" "^3.0.1" + "@smithy/types" "^3.7.2" + tslib "^2.6.2" + +"@smithy/hash-node@^3.0.10": + version "3.0.11" + resolved "https://registry.yarnpkg.com/@smithy/hash-node/-/hash-node-3.0.11.tgz#99e09ead3fc99c8cd7ca0f254ea0e35714f2a0d3" + integrity sha512-emP23rwYyZhQBvklqTtwetkQlqbNYirDiEEwXl2v0GYWMnCzxst7ZaRAnWuy28njp5kAH54lvkdG37MblZzaHA== + dependencies: + "@smithy/types" "^3.7.2" + "@smithy/util-buffer-from" "^3.0.0" + "@smithy/util-utf8" "^3.0.0" + tslib "^2.6.2" + +"@smithy/hash-stream-node@^3.1.9": + version "3.1.10" + resolved "https://registry.yarnpkg.com/@smithy/hash-stream-node/-/hash-stream-node-3.1.10.tgz#94716b4556f4ccf2807e605f47bb5b018ed7dfb0" + integrity sha512-olomK/jZQ93OMayW1zfTHwcbwBdhcZOHsyWyiZ9h9IXvc1mCD/VuvzbLb3Gy/qNJwI4MANPLctTp2BucV2oU/Q== + dependencies: + "@smithy/types" "^3.7.2" + "@smithy/util-utf8" "^3.0.0" + tslib "^2.6.2" + +"@smithy/invalid-dependency@^3.0.10": + version "3.0.11" + resolved "https://registry.yarnpkg.com/@smithy/invalid-dependency/-/invalid-dependency-3.0.11.tgz#8144d7b0af9d34ab5f672e1f674f97f8740bb9ae" + integrity sha512-NuQmVPEJjUX6c+UELyVz8kUx8Q539EDeNwbRyu4IIF8MeV7hUtq1FB3SHVyki2u++5XLMFqngeMKk7ccspnNyQ== + dependencies: + "@smithy/types" "^3.7.2" + tslib "^2.6.2" + +"@smithy/is-array-buffer@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz#f84f0d9f9a36601a9ca9381688bd1b726fd39111" + integrity sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA== + dependencies: + tslib "^2.6.2" + +"@smithy/is-array-buffer@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@smithy/is-array-buffer/-/is-array-buffer-3.0.0.tgz#9a95c2d46b8768946a9eec7f935feaddcffa5e7a" + integrity sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ== + dependencies: + tslib "^2.6.2" + +"@smithy/md5-js@^3.0.10": + version "3.0.11" + resolved "https://registry.yarnpkg.com/@smithy/md5-js/-/md5-js-3.0.11.tgz#27e4dab616348ff94aed24dc75e4017c582df40f" + integrity sha512-3NM0L3i2Zm4bbgG6Ymi9NBcxXhryi3uE8fIfHJZIOfZVxOkGdjdgjR9A06SFIZCfnEIWKXZdm6Yq5/aPXFFhsQ== + dependencies: + "@smithy/types" "^3.7.2" + "@smithy/util-utf8" "^3.0.0" + tslib "^2.6.2" + +"@smithy/middleware-content-length@^3.0.12": + version "3.0.13" + resolved "https://registry.yarnpkg.com/@smithy/middleware-content-length/-/middleware-content-length-3.0.13.tgz#6e08fe52739ac8fb3996088e0f8837e4b2ea187f" + integrity sha512-zfMhzojhFpIX3P5ug7jxTjfUcIPcGjcQYzB9t+rv0g1TX7B0QdwONW+ATouaLoD7h7LOw/ZlXfkq4xJ/g2TrIw== + dependencies: + "@smithy/protocol-http" "^4.1.8" + "@smithy/types" "^3.7.2" + tslib "^2.6.2" + +"@smithy/middleware-endpoint@^3.2.3", "@smithy/middleware-endpoint@^3.2.5": + version "3.2.5" + resolved "https://registry.yarnpkg.com/@smithy/middleware-endpoint/-/middleware-endpoint-3.2.5.tgz#bdcfdf1f342cf933b0b8a709996f9a8fbb8148f4" + integrity sha512-VhJNs/s/lyx4weiZdXSloBgoLoS8osV0dKIain8nGmx7of3QFKu5BSdEuk1z/U8x9iwes1i+XCiNusEvuK1ijg== + dependencies: + "@smithy/core" "^2.5.5" + "@smithy/middleware-serde" "^3.0.11" + "@smithy/node-config-provider" "^3.1.12" + "@smithy/shared-ini-file-loader" "^3.1.12" + "@smithy/types" "^3.7.2" + "@smithy/url-parser" "^3.0.11" + "@smithy/util-middleware" "^3.0.11" + tslib "^2.6.2" + +"@smithy/middleware-retry@^3.0.27": + version "3.0.30" + resolved "https://registry.yarnpkg.com/@smithy/middleware-retry/-/middleware-retry-3.0.30.tgz#2580322d0d28ad782b5b8c07c150b14efdc3b2f9" + integrity sha512-6323RL2BvAR3VQpTjHpa52kH/iSHyxd/G9ohb2MkBk2Ucu+oMtRXT8yi7KTSIS9nb58aupG6nO0OlXnQOAcvmQ== + dependencies: + "@smithy/node-config-provider" "^3.1.12" + "@smithy/protocol-http" "^4.1.8" + "@smithy/service-error-classification" "^3.0.11" + "@smithy/smithy-client" "^3.5.0" + "@smithy/types" "^3.7.2" + "@smithy/util-middleware" "^3.0.11" + "@smithy/util-retry" "^3.0.11" + tslib "^2.6.2" + uuid "^9.0.1" + +"@smithy/middleware-serde@^3.0.10", "@smithy/middleware-serde@^3.0.11": + version "3.0.11" + resolved "https://registry.yarnpkg.com/@smithy/middleware-serde/-/middleware-serde-3.0.11.tgz#c7d54e0add4f83e05c6878a011fc664e21022f12" + integrity sha512-KzPAeySp/fOoQA82TpnwItvX8BBURecpx6ZMu75EZDkAcnPtO6vf7q4aH5QHs/F1s3/snQaSFbbUMcFFZ086Mw== + dependencies: + "@smithy/types" "^3.7.2" + tslib "^2.6.2" + +"@smithy/middleware-stack@^3.0.10", "@smithy/middleware-stack@^3.0.11": + version "3.0.11" + resolved "https://registry.yarnpkg.com/@smithy/middleware-stack/-/middleware-stack-3.0.11.tgz#453af2096924e4064d9da4e053cfdf65d9a36acc" + integrity sha512-1HGo9a6/ikgOMrTrWL/WiN9N8GSVYpuRQO5kjstAq4CvV59bjqnh7TbdXGQ4vxLD3xlSjfBjq5t1SOELePsLnA== + dependencies: + "@smithy/types" "^3.7.2" + tslib "^2.6.2" + +"@smithy/node-config-provider@^3.1.11", "@smithy/node-config-provider@^3.1.12": + version "3.1.12" + resolved "https://registry.yarnpkg.com/@smithy/node-config-provider/-/node-config-provider-3.1.12.tgz#1b1d674fc83f943dc7b3017e37f16f374e878a6c" + integrity sha512-O9LVEu5J/u/FuNlZs+L7Ikn3lz7VB9hb0GtPT9MQeiBmtK8RSY3ULmsZgXhe6VAlgTw0YO+paQx4p8xdbs43vQ== + dependencies: + "@smithy/property-provider" "^3.1.11" + "@smithy/shared-ini-file-loader" "^3.1.12" + "@smithy/types" "^3.7.2" + tslib "^2.6.2" + +"@smithy/node-http-handler@^3.3.1", "@smithy/node-http-handler@^3.3.2": + version "3.3.2" + resolved "https://registry.yarnpkg.com/@smithy/node-http-handler/-/node-http-handler-3.3.2.tgz#b34685863b74dabdaf7860aa81b42d0d5437c7e0" + integrity sha512-t4ng1DAd527vlxvOfKFYEe6/QFBcsj7WpNlWTyjorwXXcKw3XlltBGbyHfSJ24QT84nF+agDha9tNYpzmSRZPA== + dependencies: + "@smithy/abort-controller" "^3.1.9" + "@smithy/protocol-http" "^4.1.8" + "@smithy/querystring-builder" "^3.0.11" + "@smithy/types" "^3.7.2" + tslib "^2.6.2" + +"@smithy/property-provider@^3.1.11", "@smithy/property-provider@^3.1.9": + version "3.1.11" + resolved "https://registry.yarnpkg.com/@smithy/property-provider/-/property-provider-3.1.11.tgz#161cf1c2a2ada361e417382c57f5ba6fbca8acad" + integrity sha512-I/+TMc4XTQ3QAjXfOcUWbSS073oOEAxgx4aZy8jHaf8JQnRkq2SZWw8+PfDtBvLUjcGMdxl+YwtzWe6i5uhL/A== + dependencies: + "@smithy/types" "^3.7.2" + tslib "^2.6.2" + +"@smithy/protocol-http@^4.1.7", "@smithy/protocol-http@^4.1.8": + version "4.1.8" + resolved "https://registry.yarnpkg.com/@smithy/protocol-http/-/protocol-http-4.1.8.tgz#0461758671335f65e8ff3fc0885ab7ed253819c9" + integrity sha512-hmgIAVyxw1LySOwkgMIUN0kjN8TG9Nc85LJeEmEE/cNEe2rkHDUWhnJf2gxcSRFLWsyqWsrZGw40ROjUogg+Iw== + dependencies: + "@smithy/types" "^3.7.2" + tslib "^2.6.2" + +"@smithy/querystring-builder@^3.0.11": + version "3.0.11" + resolved "https://registry.yarnpkg.com/@smithy/querystring-builder/-/querystring-builder-3.0.11.tgz#2ed04adbe725671824c5613d0d6f9376d791a909" + integrity sha512-u+5HV/9uJaeLj5XTb6+IEF/dokWWkEqJ0XiaRRogyREmKGUgZnNecLucADLdauWFKUNbQfulHFEZEdjwEBjXRg== + dependencies: + "@smithy/types" "^3.7.2" + "@smithy/util-uri-escape" "^3.0.0" + tslib "^2.6.2" + +"@smithy/querystring-parser@^3.0.11": + version "3.0.11" + resolved "https://registry.yarnpkg.com/@smithy/querystring-parser/-/querystring-parser-3.0.11.tgz#9d3177ea19ce8462f18d9712b395239e1ca1f969" + integrity sha512-Je3kFvCsFMnso1ilPwA7GtlbPaTixa3WwC+K21kmMZHsBEOZYQaqxcMqeFFoU7/slFjKDIpiiPydvdJm8Q/MCw== + dependencies: + "@smithy/types" "^3.7.2" + tslib "^2.6.2" + +"@smithy/service-error-classification@^3.0.11": + version "3.0.11" + resolved "https://registry.yarnpkg.com/@smithy/service-error-classification/-/service-error-classification-3.0.11.tgz#d3d7fc0aacd2e60d022507367e55c7939e5bcb8a" + integrity sha512-QnYDPkyewrJzCyaeI2Rmp7pDwbUETe+hU8ADkXmgNusO1bgHBH7ovXJiYmba8t0fNfJx75fE8dlM6SEmZxheog== + dependencies: + "@smithy/types" "^3.7.2" + +"@smithy/shared-ini-file-loader@^3.1.10", "@smithy/shared-ini-file-loader@^3.1.12": + version "3.1.12" + resolved "https://registry.yarnpkg.com/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-3.1.12.tgz#d98b1b663eb18935ce2cbc79024631d34f54042a" + integrity sha512-1xKSGI+U9KKdbG2qDvIR9dGrw3CNx+baqJfyr0igKEpjbHL5stsqAesYBzHChYHlelWtb87VnLWlhvfCz13H8Q== + dependencies: + "@smithy/types" "^3.7.2" + tslib "^2.6.2" + +"@smithy/signature-v4@^4.2.2": + version "4.2.4" + resolved "https://registry.yarnpkg.com/@smithy/signature-v4/-/signature-v4-4.2.4.tgz#3501d3d09fd82768867bfc00a7be4bad62f62f4d" + integrity sha512-5JWeMQYg81TgU4cG+OexAWdvDTs5JDdbEZx+Qr1iPbvo91QFGzjy0IkXAKaXUHqmKUJgSHK0ZxnCkgZpzkeNTA== + dependencies: + "@smithy/is-array-buffer" "^3.0.0" + "@smithy/protocol-http" "^4.1.8" + "@smithy/types" "^3.7.2" + "@smithy/util-hex-encoding" "^3.0.0" + "@smithy/util-middleware" "^3.0.11" + "@smithy/util-uri-escape" "^3.0.0" + "@smithy/util-utf8" "^3.0.0" + tslib "^2.6.2" + +"@smithy/smithy-client@^3.4.4", "@smithy/smithy-client@^3.5.0": + version "3.5.0" + resolved "https://registry.yarnpkg.com/@smithy/smithy-client/-/smithy-client-3.5.0.tgz#65cff262801b009998c1196764ee69929ee06f8a" + integrity sha512-Y8FeOa7gbDfCWf7njrkoRATPa5eNLUEjlJS5z5rXatYuGkCb80LbHcu8AQR8qgAZZaNHCLyo2N+pxPsV7l+ivg== + dependencies: + "@smithy/core" "^2.5.5" + "@smithy/middleware-endpoint" "^3.2.5" + "@smithy/middleware-stack" "^3.0.11" + "@smithy/protocol-http" "^4.1.8" + "@smithy/types" "^3.7.2" + "@smithy/util-stream" "^3.3.2" + tslib "^2.6.2" + +"@smithy/types@^3.7.1", "@smithy/types@^3.7.2": + version "3.7.2" + resolved "https://registry.yarnpkg.com/@smithy/types/-/types-3.7.2.tgz#05cb14840ada6f966de1bf9a9c7dd86027343e10" + integrity sha512-bNwBYYmN8Eh9RyjS1p2gW6MIhSO2rl7X9QeLM8iTdcGRP+eDiIWDt66c9IysCc22gefKszZv+ubV9qZc7hdESg== + dependencies: + tslib "^2.6.2" + +"@smithy/url-parser@^3.0.10", "@smithy/url-parser@^3.0.11": + version "3.0.11" + resolved "https://registry.yarnpkg.com/@smithy/url-parser/-/url-parser-3.0.11.tgz#e5f5ffabfb6230159167cf4cc970705fca6b8b2d" + integrity sha512-TmlqXkSk8ZPhfc+SQutjmFr5FjC0av3GZP4B/10caK1SbRwe/v+Wzu/R6xEKxoNqL+8nY18s1byiy6HqPG37Aw== + dependencies: + "@smithy/querystring-parser" "^3.0.11" + "@smithy/types" "^3.7.2" + tslib "^2.6.2" + +"@smithy/util-base64@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@smithy/util-base64/-/util-base64-3.0.0.tgz#f7a9a82adf34e27a72d0719395713edf0e493017" + integrity sha512-Kxvoh5Qtt0CDsfajiZOCpJxgtPHXOKwmM+Zy4waD43UoEMA+qPxxa98aE/7ZhdnBFZFXMOiBR5xbcaMhLtznQQ== + dependencies: + "@smithy/util-buffer-from" "^3.0.0" + "@smithy/util-utf8" "^3.0.0" + tslib "^2.6.2" + +"@smithy/util-body-length-browser@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@smithy/util-body-length-browser/-/util-body-length-browser-3.0.0.tgz#86ec2f6256310b4845a2f064e2f571c1ca164ded" + integrity sha512-cbjJs2A1mLYmqmyVl80uoLTJhAcfzMOyPgjwAYusWKMdLeNtzmMz9YxNl3/jRLoxSS3wkqkf0jwNdtXWtyEBaQ== + dependencies: + tslib "^2.6.2" + +"@smithy/util-body-length-node@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@smithy/util-body-length-node/-/util-body-length-node-3.0.0.tgz#99a291bae40d8932166907fe981d6a1f54298a6d" + integrity sha512-Tj7pZ4bUloNUP6PzwhN7K386tmSmEET9QtQg0TgdNOnxhZvCssHji+oZTUIuzxECRfG8rdm2PMw2WCFs6eIYkA== + dependencies: + tslib "^2.6.2" + +"@smithy/util-buffer-from@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz#6fc88585165ec73f8681d426d96de5d402021e4b" + integrity sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA== + dependencies: + "@smithy/is-array-buffer" "^2.2.0" + tslib "^2.6.2" + +"@smithy/util-buffer-from@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@smithy/util-buffer-from/-/util-buffer-from-3.0.0.tgz#559fc1c86138a89b2edaefc1e6677780c24594e3" + integrity sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA== + dependencies: + "@smithy/is-array-buffer" "^3.0.0" + tslib "^2.6.2" + +"@smithy/util-config-provider@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@smithy/util-config-provider/-/util-config-provider-3.0.0.tgz#62c6b73b22a430e84888a8f8da4b6029dd5b8efe" + integrity sha512-pbjk4s0fwq3Di/ANL+rCvJMKM5bzAQdE5S/6RL5NXgMExFAi6UgQMPOm5yPaIWPpr+EOXKXRonJ3FoxKf4mCJQ== + dependencies: + tslib "^2.6.2" + +"@smithy/util-defaults-mode-browser@^3.0.27": + version "3.0.30" + resolved "https://registry.yarnpkg.com/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-3.0.30.tgz#6c0d95af3f15bef8f1fe3f6217cc4f5ba8df5554" + integrity sha512-nLuGmgfcr0gzm64pqF2UT4SGWVG8UGviAdayDlVzJPNa6Z4lqvpDzdRXmLxtOdEjVlTOEdpZ9dd3ZMMu488mzg== + dependencies: + "@smithy/property-provider" "^3.1.11" + "@smithy/smithy-client" "^3.5.0" + "@smithy/types" "^3.7.2" + bowser "^2.11.0" + tslib "^2.6.2" + +"@smithy/util-defaults-mode-node@^3.0.27": + version "3.0.30" + resolved "https://registry.yarnpkg.com/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-3.0.30.tgz#33cdb02f90944b9ff221e2f8e0904a63ac1e335f" + integrity sha512-OD63eWoH68vp75mYcfYyuVH+p7Li/mY4sYOROnauDrtObo1cS4uWfsy/zhOTW8F8ZPxQC1ZXZKVxoxvMGUv2Ow== + dependencies: + "@smithy/config-resolver" "^3.0.13" + "@smithy/credential-provider-imds" "^3.2.8" + "@smithy/node-config-provider" "^3.1.12" + "@smithy/property-provider" "^3.1.11" + "@smithy/smithy-client" "^3.5.0" + "@smithy/types" "^3.7.2" + tslib "^2.6.2" + +"@smithy/util-endpoints@^2.1.6": + version "2.1.7" + resolved "https://registry.yarnpkg.com/@smithy/util-endpoints/-/util-endpoints-2.1.7.tgz#a088ebfab946a7219dd4763bfced82709894b82d" + integrity sha512-tSfcqKcN/Oo2STEYCABVuKgJ76nyyr6skGl9t15hs+YaiU06sgMkN7QYjo0BbVw+KT26zok3IzbdSOksQ4YzVw== + dependencies: + "@smithy/node-config-provider" "^3.1.12" + "@smithy/types" "^3.7.2" + tslib "^2.6.2" + +"@smithy/util-hex-encoding@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@smithy/util-hex-encoding/-/util-hex-encoding-3.0.0.tgz#32938b33d5bf2a15796cd3f178a55b4155c535e6" + integrity sha512-eFndh1WEK5YMUYvy3lPlVmYY/fZcQE1D8oSf41Id2vCeIkKJXPcYDCZD+4+xViI6b1XSd7tE+s5AmXzz5ilabQ== + dependencies: + tslib "^2.6.2" + +"@smithy/util-middleware@^3.0.10", "@smithy/util-middleware@^3.0.11": + version "3.0.11" + resolved "https://registry.yarnpkg.com/@smithy/util-middleware/-/util-middleware-3.0.11.tgz#2ab5c17266b42c225e62befcffb048afa682b5bf" + integrity sha512-dWpyc1e1R6VoXrwLoLDd57U1z6CwNSdkM69Ie4+6uYh2GC7Vg51Qtan7ITzczuVpqezdDTKJGJB95fFvvjU/ow== + dependencies: + "@smithy/types" "^3.7.2" + tslib "^2.6.2" + +"@smithy/util-retry@^3.0.10", "@smithy/util-retry@^3.0.11": + version "3.0.11" + resolved "https://registry.yarnpkg.com/@smithy/util-retry/-/util-retry-3.0.11.tgz#d267e5ccb290165cee69732547fea17b695a7425" + integrity sha512-hJUC6W7A3DQgaee3Hp9ZFcOxVDZzmBIRBPlUAk8/fSOEl7pE/aX7Dci0JycNOnm9Mfr0KV2XjIlUOcGWXQUdVQ== + dependencies: + "@smithy/service-error-classification" "^3.0.11" + "@smithy/types" "^3.7.2" + tslib "^2.6.2" + +"@smithy/util-stream@^3.3.1", "@smithy/util-stream@^3.3.2": + version "3.3.2" + resolved "https://registry.yarnpkg.com/@smithy/util-stream/-/util-stream-3.3.2.tgz#daeea26397e8541cf2499ce65bf0b8d528cba421" + integrity sha512-sInAqdiVeisUGYAv/FrXpmJ0b4WTFmciTRqzhb7wVuem9BHvhIG7tpiYHLDWrl2stOokNZpTTGqz3mzB2qFwXg== + dependencies: + "@smithy/fetch-http-handler" "^4.1.2" + "@smithy/node-http-handler" "^3.3.2" + "@smithy/types" "^3.7.2" + "@smithy/util-base64" "^3.0.0" + "@smithy/util-buffer-from" "^3.0.0" + "@smithy/util-hex-encoding" "^3.0.0" + "@smithy/util-utf8" "^3.0.0" + tslib "^2.6.2" + +"@smithy/util-uri-escape@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@smithy/util-uri-escape/-/util-uri-escape-3.0.0.tgz#e43358a78bf45d50bb736770077f0f09195b6f54" + integrity sha512-LqR7qYLgZTD7nWLBecUi4aqolw8Mhza9ArpNEQ881MJJIU2sE5iHCK6TdyqqzcDLy0OPe10IY4T8ctVdtynubg== + dependencies: + tslib "^2.6.2" + +"@smithy/util-utf8@^2.0.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@smithy/util-utf8/-/util-utf8-2.3.0.tgz#dd96d7640363259924a214313c3cf16e7dd329c5" + integrity sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A== + dependencies: + "@smithy/util-buffer-from" "^2.2.0" + tslib "^2.6.2" + +"@smithy/util-utf8@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@smithy/util-utf8/-/util-utf8-3.0.0.tgz#1a6a823d47cbec1fd6933e5fc87df975286d9d6a" + integrity sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA== + dependencies: + "@smithy/util-buffer-from" "^3.0.0" + tslib "^2.6.2" + +"@smithy/util-waiter@^3.1.9": + version "3.2.0" + resolved "https://registry.yarnpkg.com/@smithy/util-waiter/-/util-waiter-3.2.0.tgz#1e52f870e77d2e5572025f7606053e6ff00df93d" + integrity sha512-PpjSboaDUE6yl+1qlg3Si57++e84oXdWGbuFUSAciXsVfEZJJJupR2Nb0QuXHiunt2vGR+1PTizOMvnUPaG2Qg== + dependencies: + "@smithy/abort-controller" "^3.1.9" + "@smithy/types" "^3.7.2" + tslib "^2.6.2" + +"@tsconfig/node10@^1.0.7": + version "1.0.11" + resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.11.tgz#6ee46400685f130e278128c7b38b7e031ff5b2f2" + integrity sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw== + +"@tsconfig/node12@^1.0.7": + version "1.0.11" + resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.11.tgz#ee3def1f27d9ed66dac6e46a295cffb0152e058d" + integrity sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag== + +"@tsconfig/node14@^1.0.0": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.3.tgz#e4386316284f00b98435bf40f72f75a09dabf6c1" + integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow== + +"@tsconfig/node16@^1.0.2": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.4.tgz#0b92dcc0cc1c81f6f306a381f28e31b1a56536e9" + integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA== + +"@types/aws-lambda@8.10.145": + version "8.10.145" + resolved "https://registry.yarnpkg.com/@types/aws-lambda/-/aws-lambda-8.10.145.tgz#b2d31a987f4888e5553ff1819f57cafa475594d9" + integrity sha512-dtByW6WiFk5W5Jfgz1VM+YPA21xMXTuSFoLYIDY0L44jDLLflVPtZkYuu3/YxpGcvjzKFBZLU+GyKjR0HOYtyw== + +"@types/babel__core@^7.1.14": + version "7.20.5" + resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.5.tgz#3df15f27ba85319caa07ba08d0721889bb39c017" + integrity sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA== + dependencies: + "@babel/parser" "^7.20.7" + "@babel/types" "^7.20.7" + "@types/babel__generator" "*" + "@types/babel__template" "*" + "@types/babel__traverse" "*" + +"@types/babel__generator@*": + version "7.6.8" + resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.8.tgz#f836c61f48b1346e7d2b0d93c6dacc5b9535d3ab" + integrity sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw== + dependencies: + "@babel/types" "^7.0.0" + +"@types/babel__template@*": + version "7.4.4" + resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.4.tgz#5672513701c1b2199bc6dad636a9d7491586766f" + integrity sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A== + dependencies: + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.0" + +"@types/babel__traverse@*", "@types/babel__traverse@^7.0.6": + version "7.20.6" + resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.20.6.tgz#8dc9f0ae0f202c08d8d4dab648912c8d6038e3f7" + integrity sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg== + dependencies: + "@babel/types" "^7.20.7" + +"@types/estree@^1.0.6": + version "1.0.6" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.6.tgz#628effeeae2064a1b4e79f78e81d87b7e5fc7b50" + integrity sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw== + +"@types/graceful-fs@^4.1.3": + version "4.1.9" + resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.9.tgz#2a06bc0f68a20ab37b3e36aa238be6abdf49e8b4" + integrity sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ== + dependencies: + "@types/node" "*" + +"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": + version "2.0.6" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz#7739c232a1fee9b4d3ce8985f314c0c6d33549d7" + integrity sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w== + +"@types/istanbul-lib-report@*": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz#53047614ae72e19fc0401d872de3ae2b4ce350bf" + integrity sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA== + dependencies: + "@types/istanbul-lib-coverage" "*" + +"@types/istanbul-reports@^3.0.0": + version "3.0.4" + resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz#0f03e3d2f670fbdac586e34b433783070cc16f54" + integrity sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ== + dependencies: + "@types/istanbul-lib-report" "*" + +"@types/jest@^29.5.12": + version "29.5.14" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.14.tgz#2b910912fa1d6856cadcd0c1f95af7df1d6049e5" + integrity sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ== + dependencies: + expect "^29.0.0" + pretty-format "^29.0.0" + +"@types/json-schema@^7.0.15": + version "7.0.15" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" + integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== + +"@types/node@*": + version "22.10.1" + resolved "https://registry.yarnpkg.com/@types/node/-/node-22.10.1.tgz#41ffeee127b8975a05f8c4f83fb89bcb2987d766" + integrity sha512-qKgsUwfHZV2WCWLAnVP1JqnpE6Im6h3Y0+fYgMTasNQ7V++CBX5OT1as0g0f+OyubbFqhf6XVNIsmN4IIhEgGQ== + dependencies: + undici-types "~6.20.0" + +"@types/node@22.5.4": + version "22.5.4" + resolved "https://registry.yarnpkg.com/@types/node/-/node-22.5.4.tgz#83f7d1f65bc2ed223bdbf57c7884f1d5a4fa84e8" + integrity sha512-FDuKUJQm/ju9fT/SeX/6+gBzoPzlVCzfzmGkwKvRHQVxi4BntVbyIwf6a4Xn62mrvndLiml6z/UBXIdEVjQLXg== + dependencies: + undici-types "~6.19.2" + +"@types/nodemailer@^6.4.14": + version "6.4.17" + resolved "https://registry.yarnpkg.com/@types/nodemailer/-/nodemailer-6.4.17.tgz#5c82a42aee16a3dd6ea31446a1bd6a447f1ac1a4" + integrity sha512-I9CCaIp6DTldEg7vyUTZi8+9Vo0hi1/T8gv3C89yk1rSAAzoKQ8H8ki/jBYJSFoH/BisgLP8tkZMlQ91CIquww== + dependencies: + "@types/node" "*" + +"@types/prop-types@*": + version "15.7.14" + resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.14.tgz#1433419d73b2a7ebfc6918dcefd2ec0d5cd698f2" + integrity sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ== + +"@types/react@^18.3.12": + version "18.3.14" + resolved "https://registry.yarnpkg.com/@types/react/-/react-18.3.14.tgz#7ce43bbca0e15e1c4f67ad33ea3f83d75aa6756b" + integrity sha512-NzahNKvjNhVjuPBQ+2G7WlxstQ+47kXZNHlUvFakDViuIEfGY926GqhMueQFZ7woG+sPiQKlF36XfrIUVSUfFg== + dependencies: + "@types/prop-types" "*" + csstype "^3.0.2" + +"@types/sinon@^17.0.3": + version "17.0.3" + resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-17.0.3.tgz#9aa7e62f0a323b9ead177ed23a36ea757141a5fa" + integrity sha512-j3uovdn8ewky9kRBG19bOwaZbexJu/XjtkHyjvUgt4xfPFz18dcORIMqnYh66Fx3Powhcr85NT5+er3+oViapw== + dependencies: + "@types/sinonjs__fake-timers" "*" + +"@types/sinonjs__fake-timers@*": + version "8.1.5" + resolved "https://registry.yarnpkg.com/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.5.tgz#5fd3592ff10c1e9695d377020c033116cc2889f2" + integrity sha512-mQkU2jY8jJEF7YHjHvsQO8+3ughTL1mcnn96igfhONmR+fUPSKIkefQYpSe8bsly2Ep7oQbn/6VG5/9/0qcArQ== + +"@types/stack-utils@^2.0.0": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.3.tgz#6209321eb2c1712a7e7466422b8cb1fc0d9dd5d8" + integrity sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw== + +"@types/triple-beam@^1.3.2": + version "1.3.5" + resolved "https://registry.yarnpkg.com/@types/triple-beam/-/triple-beam-1.3.5.tgz#74fef9ffbaa198eb8b588be029f38b00299caa2c" + integrity sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw== + +"@types/uuid@^9.0.1": + version "9.0.8" + resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-9.0.8.tgz#7545ba4fc3c003d6c756f651f3bf163d8f0f29ba" + integrity sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA== + +"@types/yargs-parser@*": + version "21.0.3" + resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.3.tgz#815e30b786d2e8f0dcd85fd5bcf5e1a04d008f15" + integrity sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ== + +"@types/yargs@^17.0.8": + version "17.0.33" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.33.tgz#8c32303da83eec050a84b3c7ae7b9f922d13e32d" + integrity sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA== + dependencies: + "@types/yargs-parser" "*" + +"@typescript-eslint/eslint-plugin@^8.12.2": + version "8.18.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.18.0.tgz#0901933326aea4443b81df3f740ca7dfc45c7bea" + integrity sha512-NR2yS7qUqCL7AIxdJUQf2MKKNDVNaig/dEB0GBLU7D+ZdHgK1NoH/3wsgO3OnPVipn51tG3MAwaODEGil70WEw== + dependencies: + "@eslint-community/regexpp" "^4.10.0" + "@typescript-eslint/scope-manager" "8.18.0" + "@typescript-eslint/type-utils" "8.18.0" + "@typescript-eslint/utils" "8.18.0" + "@typescript-eslint/visitor-keys" "8.18.0" + graphemer "^1.4.0" + ignore "^5.3.1" + natural-compare "^1.4.0" + ts-api-utils "^1.3.0" + +"@typescript-eslint/parser@^8.12.2": + version "8.18.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.18.0.tgz#a1c9456cbb6a089730bf1d3fc47946c5fb5fe67b" + integrity sha512-hgUZ3kTEpVzKaK3uNibExUYm6SKKOmTU2BOxBSvOYwtJEPdVQ70kZJpPjstlnhCHcuc2WGfSbpKlb/69ttyN5Q== + dependencies: + "@typescript-eslint/scope-manager" "8.18.0" + "@typescript-eslint/types" "8.18.0" + "@typescript-eslint/typescript-estree" "8.18.0" + "@typescript-eslint/visitor-keys" "8.18.0" + debug "^4.3.4" + +"@typescript-eslint/scope-manager@8.18.0": + version "8.18.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.18.0.tgz#30b040cb4557804a7e2bcc65cf8fdb630c96546f" + integrity sha512-PNGcHop0jkK2WVYGotk/hxj+UFLhXtGPiGtiaWgVBVP1jhMoMCHlTyJA+hEj4rszoSdLTK3fN4oOatrL0Cp+Xw== + dependencies: + "@typescript-eslint/types" "8.18.0" + "@typescript-eslint/visitor-keys" "8.18.0" + +"@typescript-eslint/type-utils@8.18.0": + version "8.18.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.18.0.tgz#6f0d12cf923b6fd95ae4d877708c0adaad93c471" + integrity sha512-er224jRepVAVLnMF2Q7MZJCq5CsdH2oqjP4dT7K6ij09Kyd+R21r7UVJrF0buMVdZS5QRhDzpvzAxHxabQadow== + dependencies: + "@typescript-eslint/typescript-estree" "8.18.0" + "@typescript-eslint/utils" "8.18.0" + debug "^4.3.4" + ts-api-utils "^1.3.0" + +"@typescript-eslint/types@8.18.0": + version "8.18.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.18.0.tgz#3afcd30def8756bc78541268ea819a043221d5f3" + integrity sha512-FNYxgyTCAnFwTrzpBGq+zrnoTO4x0c1CKYY5MuUTzpScqmY5fmsh2o3+57lqdI3NZucBDCzDgdEbIaNfAjAHQA== + +"@typescript-eslint/typescript-estree@8.18.0": + version "8.18.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.18.0.tgz#d8ca785799fbb9c700cdff1a79c046c3e633c7f9" + integrity sha512-rqQgFRu6yPkauz+ms3nQpohwejS8bvgbPyIDq13cgEDbkXt4LH4OkDMT0/fN1RUtzG8e8AKJyDBoocuQh8qNeg== + dependencies: + "@typescript-eslint/types" "8.18.0" + "@typescript-eslint/visitor-keys" "8.18.0" + debug "^4.3.4" + fast-glob "^3.3.2" + is-glob "^4.0.3" + minimatch "^9.0.4" + semver "^7.6.0" + ts-api-utils "^1.3.0" + +"@typescript-eslint/utils@8.18.0": + version "8.18.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.18.0.tgz#48f67205d42b65d895797bb7349d1be5c39a62f7" + integrity sha512-p6GLdY383i7h5b0Qrfbix3Vc3+J2k6QWw6UMUeY5JGfm3C5LbZ4QIZzJNoNOfgyRe0uuYKjvVOsO/jD4SJO+xg== + dependencies: + "@eslint-community/eslint-utils" "^4.4.0" + "@typescript-eslint/scope-manager" "8.18.0" + "@typescript-eslint/types" "8.18.0" + "@typescript-eslint/typescript-estree" "8.18.0" + +"@typescript-eslint/visitor-keys@8.18.0": + version "8.18.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.18.0.tgz#7b6d33534fa808e33a19951907231ad2ea5c36dd" + integrity sha512-pCh/qEA8Lb1wVIqNvBke8UaRjJ6wrAWkJO5yyIbs8Yx6TNGYyfNjOo61tLv+WwLvoLPp4BQ8B7AHKijl8NGUfw== + dependencies: + "@typescript-eslint/types" "8.18.0" + eslint-visitor-keys "^4.2.0" + +"@usewaypoint/block-avatar@^0.0.3": + version "0.0.3" + resolved "https://registry.yarnpkg.com/@usewaypoint/block-avatar/-/block-avatar-0.0.3.tgz#f63510ed82690a2b4b68ead62a191d80b20c2957" + integrity sha512-3BM6P4ztMmqDbSijtVQqI1canRkcENOEHZ2X9BYNv8BZGJbmitTrzANvwmmYXfFEuWPCAyABvujdZds15Zg8Qg== + +"@usewaypoint/block-button@^0.0.3": + version "0.0.3" + resolved "https://registry.yarnpkg.com/@usewaypoint/block-button/-/block-button-0.0.3.tgz#1e9257601b452ab5687ce806cd24efd88a1185f3" + integrity sha512-LXSI3FmCTv13voYX4wdHY7iJdsfyRfpDJZCFKSun5EF1j9FXrqMDGScpk/yokopkQWvWkYXQNAne7W0yWhRQlg== + +"@usewaypoint/block-columns-container@^0.0.3": + version "0.0.3" + resolved "https://registry.yarnpkg.com/@usewaypoint/block-columns-container/-/block-columns-container-0.0.3.tgz#e6d9148f06523aa964a41f937e0b295c165d59d2" + integrity sha512-r5jaojU1Fr6Svtl0a9dDlBHgslJQ04M+XaXaEO+GZ12+35fdAirpLkrEhuyBIA1FFXzRTG740wkbkr++iv1kuA== + +"@usewaypoint/block-container@^0.0.2": + version "0.0.2" + resolved "https://registry.yarnpkg.com/@usewaypoint/block-container/-/block-container-0.0.2.tgz#de06a31242c799c7d1caaf80ab91107ca4b25fc5" + integrity sha512-li9GVdiahVpJ+MNRdkoCkP6/hBTdcpaLRGpaFBSQRkVt+cYAeB7qPNIo+242hUvVTm5Qky8ceGLDVblGYSZb7A== + +"@usewaypoint/block-divider@^0.0.4": + version "0.0.4" + resolved "https://registry.yarnpkg.com/@usewaypoint/block-divider/-/block-divider-0.0.4.tgz#29a938293a76ad8a5e9738d4ac4370f9fa3efbd8" + integrity sha512-q54ydWvKdg7Zwc4hzIwE6i/mC8dFYxfPRACEEEyu2dvSNa9cbKFIsPD9ipVSntK+Ib3Ml84uT4aHQmOlzP6hZA== + +"@usewaypoint/block-heading@^0.0.3": + version "0.0.3" + resolved "https://registry.yarnpkg.com/@usewaypoint/block-heading/-/block-heading-0.0.3.tgz#a300894acfc39556d1577be17a2d7b3d58c7c95e" + integrity sha512-1dMrf1U34nq2FuwTUfsq+hBOdLQz1H+lVMEH9xvyCq5I7nSXCzpeo7QgumZ3zZEHtu3QgSEGafJaZyrj2paC0w== + +"@usewaypoint/block-html@^0.0.3": + version "0.0.3" + resolved "https://registry.yarnpkg.com/@usewaypoint/block-html/-/block-html-0.0.3.tgz#296550235b2974679afcad4772f7bc56cf8ba520" + integrity sha512-ZI9oYDibMzs5y/YzfvUwuUBzHDKHOIjiStiVCvlmIA+VtJTycqT8X/ECjn+KmwesLTg5DhG07CC4WY2SL3AnJw== + +"@usewaypoint/block-image@^0.0.5": + version "0.0.5" + resolved "https://registry.yarnpkg.com/@usewaypoint/block-image/-/block-image-0.0.5.tgz#e9e866673d2bbdb628fd57c0798ba70e3cdc3b12" + integrity sha512-b66jAXF79idsrIRc2QoBlZctIXdqg/qOAL7/QvKvENZH2KmuXoZhEUx+Z7sACvEQD/VI0u7TK5msDsA5S0/oVQ== + +"@usewaypoint/block-spacer@^0.0.3": + version "0.0.3" + resolved "https://registry.yarnpkg.com/@usewaypoint/block-spacer/-/block-spacer-0.0.3.tgz#6655628dd74085ffcf3fbcd0f0aa286e40e69a10" + integrity sha512-CCcMtwcpeC2rHvawQdh5f0Hez7o4xA/edWl/6I3RuA6Yb6STyyrGjmPFs2ZxHQsLOGUK+0OvBenuHlSTCZwuuA== + +"@usewaypoint/block-text@^0.0.4": + version "0.0.4" + resolved "https://registry.yarnpkg.com/@usewaypoint/block-text/-/block-text-0.0.4.tgz#22be615f81f91749eac78501b1de9dfb5bd95560" + integrity sha512-c+CiTkwFSrclPxRx9Gt+nE6KkAmY5tWDumBa3qQnVrxdCjCmGK0qOj9avm9vqf9hd5JxaX4tgWhG/oi2u/zMxA== + dependencies: + markdown-parser-react "^1.1.2" + +"@usewaypoint/document-core@^0.0.6": + version "0.0.6" + resolved "https://registry.yarnpkg.com/@usewaypoint/document-core/-/document-core-0.0.6.tgz#c97468c84c85ccac46a06f82ac332e20e415cfb7" + integrity sha512-Hg10gszVCZRJhA4nIWwAi2rTXuoxPL+ATMe0hU243PFBIUZOwDIQus4XZSeoHsenMCq1uBFCRiFW4hl2+tVwgA== + +"@usewaypoint/email-builder@^0.0.6": + version "0.0.6" + resolved "https://registry.yarnpkg.com/@usewaypoint/email-builder/-/email-builder-0.0.6.tgz#e6bb5ad39acb0710814df1d925ee366276bec49f" + integrity sha512-OullFjVUwlPXkq7b+HSGMPKJcSSEbH1gm6a2TQ5uIsPIHeFb/af6EmXd8IIctf0pz1cH41USg6rBR504vuuXZw== + dependencies: + "@usewaypoint/block-avatar" "^0.0.3" + "@usewaypoint/block-button" "^0.0.3" + "@usewaypoint/block-columns-container" "^0.0.3" + "@usewaypoint/block-container" "^0.0.2" + "@usewaypoint/block-divider" "^0.0.4" + "@usewaypoint/block-heading" "^0.0.3" + "@usewaypoint/block-html" "^0.0.3" + "@usewaypoint/block-image" "^0.0.5" + "@usewaypoint/block-spacer" "^0.0.3" + "@usewaypoint/block-text" "^0.0.4" + "@usewaypoint/document-core" "^0.0.6" + +"@vitest/expect@>1.6.0": + version "2.1.8" + resolved "https://registry.yarnpkg.com/@vitest/expect/-/expect-2.1.8.tgz#13fad0e8d5a0bf0feb675dcf1d1f1a36a1773bc1" + integrity sha512-8ytZ/fFHq2g4PJVAtDX57mayemKgDR6X3Oa2Foro+EygiOJHUXhCqBAAKQYYajZpFoIfvBCF1j6R6IYRSIUFuw== + dependencies: + "@vitest/spy" "2.1.8" + "@vitest/utils" "2.1.8" + chai "^5.1.2" + tinyrainbow "^1.2.0" + +"@vitest/pretty-format@2.1.8": + version "2.1.8" + resolved "https://registry.yarnpkg.com/@vitest/pretty-format/-/pretty-format-2.1.8.tgz#88f47726e5d0cf4ba873d50c135b02e4395e2bca" + integrity sha512-9HiSZ9zpqNLKlbIDRWOnAWqgcA7xu+8YxXSekhr0Ykab7PAYFkhkwoqVArPOtJhPmYeE2YHgKZlj3CP36z2AJQ== + dependencies: + tinyrainbow "^1.2.0" + +"@vitest/spy@2.1.8": + version "2.1.8" + resolved "https://registry.yarnpkg.com/@vitest/spy/-/spy-2.1.8.tgz#bc41af3e1e6a41ae3b67e51f09724136b88fa447" + integrity sha512-5swjf2q95gXeYPevtW0BLk6H8+bPlMb4Vw/9Em4hFxDcaOxS+e0LOX4yqNxoHzMR2akEB2xfpnWUzkZokmgWDg== + dependencies: + tinyspy "^3.0.2" + +"@vitest/utils@2.1.8": + version "2.1.8" + resolved "https://registry.yarnpkg.com/@vitest/utils/-/utils-2.1.8.tgz#f8ef85525f3362ebd37fd25d268745108d6ae388" + integrity sha512-dwSoui6djdwbfFmIgbIjX2ZhIoG7Ex/+xpxyiEgIGzjliY8xGkcpITKTlp6B4MgtGkF2ilvm97cPM96XZaAgcA== + dependencies: + "@vitest/pretty-format" "2.1.8" + loupe "^3.1.2" + tinyrainbow "^1.2.0" + +abbrev@1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" + integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== + +acorn-jsx@^5.3.2: + version "5.3.2" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== + +acorn-walk@^8.1.1: + version "8.3.4" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.4.tgz#794dd169c3977edf4ba4ea47583587c5866236b7" + integrity sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g== + dependencies: + acorn "^8.11.0" + +acorn@^8.11.0, acorn@^8.14.0, acorn@^8.4.1: + version "8.14.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.14.0.tgz#063e2c70cac5fb4f6467f0b11152e04c682795b0" + integrity sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA== + +ajv@^6.12.4: + version "6.12.6" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +ansi-colors@^4.1.3: + version "4.1.3" + resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.3.tgz#37611340eb2243e70cc604cad35d63270d48781b" + integrity sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw== + +ansi-escapes@^4.2.1: + version "4.3.2" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" + integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== + dependencies: + type-fest "^0.21.3" + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-regex@^6.0.1: + version "6.1.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.1.0.tgz#95ec409c69619d6cb1b8b34f14b660ef28ebd654" + integrity sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA== + +ansi-styles@^4.0.0, ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +ansi-styles@^5.0.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" + integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== + +ansi-styles@^6.1.0: + version "6.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5" + integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug== + +anymatch@^3.0.3, anymatch@~3.1.2: + version "3.1.3" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" + integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +arg@^4.1.0: + version "4.1.3" + resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" + integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== + +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + +array-each@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/array-each/-/array-each-1.0.1.tgz#a794af0c05ab1752846ee753a1f211a05ba0c44f" + integrity sha512-zHjL5SZa68hkKHBFBK6DJCTtr9sfTCPCaph/L7tMSLcTFgy+zX7E+6q5UArbtOtMBCtxdICpfTCspRse+ywyXA== + +array-slice@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/array-slice/-/array-slice-1.1.0.tgz#e368ea15f89bc7069f7ffb89aec3a6c7d4ac22d4" + integrity sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w== + +assertion-error@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b" + integrity sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw== + +assertion-error@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-2.0.1.tgz#f641a196b335690b1070bf00b6e7593fec190bf7" + integrity sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA== + +async@^2.6.0: + version "2.6.4" + resolved "https://registry.yarnpkg.com/async/-/async-2.6.4.tgz#706b7ff6084664cd7eae713f6f965433b5504221" + integrity sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA== + dependencies: + lodash "^4.17.14" + +async@^3.2.3, async@~3.2.0: + version "3.2.6" + resolved "https://registry.yarnpkg.com/async/-/async-3.2.6.tgz#1b0728e14929d51b85b449b7f06e27c1145e38ce" + integrity sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA== + +available-typed-arrays@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz#a5cc375d6a03c2efc87a553f3e0b1522def14846" + integrity sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ== + dependencies: + possible-typed-array-names "^1.0.0" + +aws-lambda@1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/aws-lambda/-/aws-lambda-1.0.7.tgz#c6b674df47458b5ecd43ab734899ad2e2d457013" + integrity sha512-9GNFMRrEMG5y3Jvv+V4azWvc+qNWdWLTjDdhf/zgMlz8haaaLWv0xeAIWxz9PuWUBawsVxy0zZotjCdR3Xq+2w== + dependencies: + aws-sdk "^2.814.0" + commander "^3.0.2" + js-yaml "^3.14.1" + watchpack "^2.0.0-beta.10" + +aws-sdk-client-mock-jest@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/aws-sdk-client-mock-jest/-/aws-sdk-client-mock-jest-4.1.0.tgz#40a3bdedd8d551cf2a836b77239038c0ca10e25c" + integrity sha512-+g4a5Hp+MmPqqNnvwfLitByggrqf+xSbk1pm6fBYHNcon6+aQjL5iB+3YB6HuGPemY+/mUKN34iP62S14R61bA== + dependencies: + "@vitest/expect" ">1.6.0" + expect ">28.1.3" + tslib "^2.1.0" + +aws-sdk-client-mock@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/aws-sdk-client-mock/-/aws-sdk-client-mock-4.1.0.tgz#ae1950b2277f8e65f9a039975d79ff9fffab39e3" + integrity sha512-h/tOYTkXEsAcV3//6C1/7U4ifSpKyJvb6auveAepqqNJl6TdZaPFEtKjBQNf8UxQdDP850knB2i/whq4zlsxJw== + dependencies: + "@types/sinon" "^17.0.3" + sinon "^18.0.1" + tslib "^2.1.0" + +aws-sdk@^2.814.0: + version "2.1692.0" + resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.1692.0.tgz#9dac5f7bfcc5ab45825cc8591b12753aa7d2902c" + integrity sha512-x511uiJ/57FIsbgUe5csJ13k3uzu25uWQE+XqfBis/sB0SFoiElJWXRkgEAUh0U6n40eT3ay5Ue4oPkRMu1LYw== + dependencies: + buffer "4.9.2" + events "1.1.1" + ieee754 "1.1.13" + jmespath "0.16.0" + querystring "0.2.0" + sax "1.2.1" + url "0.10.3" + util "^0.12.4" + uuid "8.0.0" + xml2js "0.6.2" + +babel-jest@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.7.0.tgz#f4369919225b684c56085998ac63dbd05be020d5" + integrity sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg== + dependencies: + "@jest/transform" "^29.7.0" + "@types/babel__core" "^7.1.14" + babel-plugin-istanbul "^6.1.1" + babel-preset-jest "^29.6.3" + chalk "^4.0.0" + graceful-fs "^4.2.9" + slash "^3.0.0" + +babel-plugin-istanbul@^6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz#fa88ec59232fd9b4e36dbbc540a8ec9a9b47da73" + integrity sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@istanbuljs/load-nyc-config" "^1.0.0" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-instrument "^5.0.4" + test-exclude "^6.0.0" + +babel-plugin-jest-hoist@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz#aadbe943464182a8922c3c927c3067ff40d24626" + integrity sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg== + dependencies: + "@babel/template" "^7.3.3" + "@babel/types" "^7.3.3" + "@types/babel__core" "^7.1.14" + "@types/babel__traverse" "^7.0.6" + +babel-preset-current-node-syntax@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.0.tgz#9a929eafece419612ef4ae4f60b1862ebad8ef30" + integrity sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw== + dependencies: + "@babel/plugin-syntax-async-generators" "^7.8.4" + "@babel/plugin-syntax-bigint" "^7.8.3" + "@babel/plugin-syntax-class-properties" "^7.12.13" + "@babel/plugin-syntax-class-static-block" "^7.14.5" + "@babel/plugin-syntax-import-attributes" "^7.24.7" + "@babel/plugin-syntax-import-meta" "^7.10.4" + "@babel/plugin-syntax-json-strings" "^7.8.3" + "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + "@babel/plugin-syntax-numeric-separator" "^7.10.4" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + "@babel/plugin-syntax-private-property-in-object" "^7.14.5" + "@babel/plugin-syntax-top-level-await" "^7.14.5" + +babel-preset-jest@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz#fa05fa510e7d493896d7b0dd2033601c840f171c" + integrity sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA== + dependencies: + babel-plugin-jest-hoist "^29.6.3" + babel-preset-current-node-syntax "^1.0.0" + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +base64-js@^1.0.2: + version "1.5.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + +binary-extensions@^2.0.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522" + integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw== + +body@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/body/-/body-5.1.0.tgz#e4ba0ce410a46936323367609ecb4e6553125069" + integrity sha512-chUsBxGRtuElD6fmw1gHLpvnKdVLK302peeFa9ZqAEk8TyzZ3fygLyUEDDPTJvL9+Bor0dIwn6ePOsRM2y0zQQ== + dependencies: + continuable-cache "^0.3.1" + error "^7.0.0" + raw-body "~1.1.0" + safe-json-parse "~1.0.1" + +bowser@^2.11.0: + version "2.11.0" + resolved "https://registry.yarnpkg.com/bowser/-/bowser-2.11.0.tgz#5ca3c35757a7aa5771500c70a73a9f91ef420a8f" + integrity sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA== + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +brace-expansion@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" + integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== + dependencies: + balanced-match "^1.0.0" + +braces@^3.0.3, braces@~3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" + integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== + dependencies: + fill-range "^7.1.1" + +browser-stdout@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" + integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw== + +browserslist@^4.24.0: + version "4.24.2" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.24.2.tgz#f5845bc91069dbd55ee89faf9822e1d885d16580" + integrity sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg== + dependencies: + caniuse-lite "^1.0.30001669" + electron-to-chromium "^1.5.41" + node-releases "^2.0.18" + update-browserslist-db "^1.1.1" + +bs-logger@^0.2.6: + version "0.2.6" + resolved "https://registry.yarnpkg.com/bs-logger/-/bs-logger-0.2.6.tgz#eb7d365307a72cf974cc6cda76b68354ad336bd8" + integrity sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog== + dependencies: + fast-json-stable-stringify "2.x" + +bser@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" + integrity sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ== + dependencies: + node-int64 "^0.4.0" + +buffer-from@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" + integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== + +buffer@4.9.2: + version "4.9.2" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.2.tgz#230ead344002988644841ab0244af8c44bbe3ef8" + integrity sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg== + dependencies: + base64-js "^1.0.2" + ieee754 "^1.1.4" + isarray "^1.0.0" + +bytes@1: + version "1.0.0" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-1.0.0.tgz#3569ede8ba34315fab99c3e92cb04c7220de1fa8" + integrity sha512-/x68VkHLeTl3/Ll8IvxdwzhrT+IyKc52e/oyHhA2RwqPqswSnjVbSddfPRwAsJtbilMAPSRWwAlpxdYsSWOTKQ== + +call-bind-apply-helpers@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz#32e5892e6361b29b0b545ba6f7763378daca2840" + integrity sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g== + dependencies: + es-errors "^1.3.0" + function-bind "^1.1.2" + +call-bind@^1.0.2, call-bind@^1.0.7: + version "1.0.8" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.8.tgz#0736a9660f537e3388826f440d5ec45f744eaa4c" + integrity sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww== + dependencies: + call-bind-apply-helpers "^1.0.0" + es-define-property "^1.0.0" + get-intrinsic "^1.2.4" + set-function-length "^1.2.2" + +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + +camelcase@^5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" + integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== + +camelcase@^6.0.0, camelcase@^6.2.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" + integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== + +caniuse-lite@^1.0.30001669: + version "1.0.30001687" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001687.tgz#d0ac634d043648498eedf7a3932836beba90ebae" + integrity sha512-0S/FDhf4ZiqrTUiQ39dKeUjYRjkv7lOZU1Dgif2rIqrTzX/1wV2hfKu9TOm1IHkdSijfLswxTFzl/cvir+SLSQ== + +chai-match-pattern@^1.1.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/chai-match-pattern/-/chai-match-pattern-1.3.0.tgz#cefd4437de465860f4f87922c31049eb9d979104" + integrity sha512-DflyfI8lZ56YuYAZMTBPWghjqFQfqY1IR0ZZXrjlGZJuRvtN0TjJMBpLsrMfc45kjivXJ06iayuP7lzG6ij1bQ== + dependencies: + lodash-match-pattern "^2.3.1" + +chai@^4.1.2: + version "4.5.0" + resolved "https://registry.yarnpkg.com/chai/-/chai-4.5.0.tgz#707e49923afdd9b13a8b0b47d33d732d13812fd8" + integrity sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw== + dependencies: + assertion-error "^1.1.0" + check-error "^1.0.3" + deep-eql "^4.1.3" + get-func-name "^2.0.2" + loupe "^2.3.6" + pathval "^1.1.1" + type-detect "^4.1.0" + +chai@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/chai/-/chai-5.1.2.tgz#3afbc340b994ae3610ca519a6c70ace77ad4378d" + integrity sha512-aGtmf24DW6MLHHG5gCx4zaI3uBq3KRtxeVs0DjFH6Z0rDNbsvTxFASFvdj79pxjxZ8/5u3PIiN3IwEIQkiiuPw== + dependencies: + assertion-error "^2.0.1" + check-error "^2.1.1" + deep-eql "^5.0.1" + loupe "^3.1.0" + pathval "^2.0.0" + +chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.0, chalk@^4.1.2, chalk@~4.1.0: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +char-regex@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" + integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== + +check-error@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.3.tgz#a6502e4312a7ee969f646e83bb3ddd56281bd694" + integrity sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg== + dependencies: + get-func-name "^2.0.2" + +check-error@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/check-error/-/check-error-2.1.1.tgz#87eb876ae71ee388fa0471fe423f494be1d96ccc" + integrity sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw== + +checkit@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/checkit/-/checkit-0.7.0.tgz#14979abc93018346bfcfdcbabc19ab54c0bfd74a" + integrity sha512-QgiWB8gMdF/CbmWyuxCk+f2MPQe0G1DfJfHCTbrfZlY3FnJWdnW+EGsRJctcYz/IrXxPYJmjRjdgmKUkyIZl/Q== + dependencies: + inherits "^2.0.1" + lodash "^4.0.0" + +chokidar@^3.5.3: + version "3.6.0" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b" + integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw== + dependencies: + anymatch "~3.1.2" + braces "~3.0.2" + glob-parent "~5.1.2" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.6.0" + optionalDependencies: + fsevents "~2.3.2" + +ci-info@^3.2.0: + version "3.9.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4" + integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ== + +cjs-module-lexer@^1.0.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.4.1.tgz#707413784dbb3a72aa11c2f2b042a0bef4004170" + integrity sha512-cuSVIHi9/9E/+821Qjdvngor+xpnlwnuwIyZOaLmHBVdXL+gP+I6QQB9VkO7RI77YIcTV+S1W9AreJ5eN63JBA== + +cliui@^7.0.2: + version "7.0.4" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" + integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.0" + wrap-ansi "^7.0.0" + +cliui@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" + integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.1" + wrap-ansi "^7.0.0" + +co@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" + integrity sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ== + +collect-v8-coverage@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz#c0b29bcd33bcd0779a1344c2136051e6afd3d9e9" + integrity sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q== + +color-convert@^1.9.3: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== + +color-name@^1.0.0, color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +color-string@^1.6.0: + version "1.9.1" + resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.9.1.tgz#4467f9146f036f855b764dfb5bf8582bf342c7a4" + integrity sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg== + dependencies: + color-name "^1.0.0" + simple-swizzle "^0.2.2" + +color@^3.1.3: + version "3.2.1" + resolved "https://registry.yarnpkg.com/color/-/color-3.2.1.tgz#3544dc198caf4490c3ecc9a790b54fe9ff45e164" + integrity sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA== + dependencies: + color-convert "^1.9.3" + color-string "^1.6.0" + +colors@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/colors/-/colors-1.1.2.tgz#168a4701756b6a7f51a12ce0c97bfa28c084ed63" + integrity sha512-ENwblkFQpqqia6b++zLD/KUWafYlVY/UNnAp7oz7LY7E924wmpye416wBOmvv/HMWzl8gL1kJlfvId/1Dg176w== + +colorspace@1.1.x: + version "1.1.4" + resolved "https://registry.yarnpkg.com/colorspace/-/colorspace-1.1.4.tgz#8d442d1186152f60453bf8070cd66eb364e59243" + integrity sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w== + dependencies: + color "^3.1.3" + text-hex "1.0.x" + +commander@^10.0.1: + version "10.0.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-10.0.1.tgz#881ee46b4f77d1c1dccc5823433aa39b022cbe06" + integrity sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug== + +commander@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/commander/-/commander-3.0.2.tgz#6837c3fb677ad9933d1cfba42dd14d5117d6b39e" + integrity sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + +continuable-cache@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/continuable-cache/-/continuable-cache-0.3.1.tgz#bd727a7faed77e71ff3985ac93351a912733ad0f" + integrity sha512-TF30kpKhTH8AGCG3dut0rdd/19B7Z+qCnrMoBLpyQu/2drZdNrrpcjPEoJeSVsQM+8KmWG5O56oPDjSSUsuTyA== + +convert-source-map@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" + integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== + +create-jest@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/create-jest/-/create-jest-29.7.0.tgz#a355c5b3cb1e1af02ba177fe7afd7feee49a5320" + integrity sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q== + dependencies: + "@jest/types" "^29.6.3" + chalk "^4.0.0" + exit "^0.1.2" + graceful-fs "^4.2.9" + jest-config "^29.7.0" + jest-util "^29.7.0" + prompts "^2.0.1" + +create-require@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" + integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== + +cross-spawn@^7.0.0, cross-spawn@^7.0.3, cross-spawn@^7.0.5: + version "7.0.6" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" + integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +csstype@^3.0.2: + version "3.1.3" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81" + integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw== + +dateformat@~4.6.2: + version "4.6.3" + resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-4.6.3.tgz#556fa6497e5217fedb78821424f8a1c22fa3f4b5" + integrity sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA== + +debug@^3.1.0: + version "3.2.7" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" + integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== + dependencies: + ms "^2.1.1" + +debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@^4.3.5: + version "4.4.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.0.tgz#2b3f2aea2ffeb776477460267377dc8710faba8a" + integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA== + dependencies: + ms "^2.1.3" + +decamelize@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-4.0.0.tgz#aa472d7bf660eb15f3494efd531cab7f2a709837" + integrity sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ== + +dedent@^1.0.0: + version "1.5.3" + resolved "https://registry.yarnpkg.com/dedent/-/dedent-1.5.3.tgz#99aee19eb9bae55a67327717b6e848d0bf777e5a" + integrity sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ== + +deep-eql@^4.1.3: + version "4.1.4" + resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-4.1.4.tgz#d0d3912865911bb8fac5afb4e3acfa6a28dc72b7" + integrity sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg== + dependencies: + type-detect "^4.0.0" + +deep-eql@^5.0.1: + version "5.0.2" + resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-5.0.2.tgz#4b756d8d770a9257300825d52a2c2cff99c3a341" + integrity sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q== + +deep-is@^0.1.3: + version "0.1.4" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" + integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== + +deepmerge@^4.2.2: + version "4.3.1" + resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" + integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== + +define-data-property@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e" + integrity sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A== + dependencies: + es-define-property "^1.0.0" + es-errors "^1.3.0" + gopd "^1.0.1" + +detect-file@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/detect-file/-/detect-file-1.0.0.tgz#f0d66d03672a825cb1b73bdb3fe62310c8e552b7" + integrity sha512-DtCOLG98P007x7wiiOmfI0fi3eIKyWiLTGJ2MDnVi/E04lWGbf+JzrRHMm0rgIIZJGtHpKpbVgLWHrv8xXpc3Q== + +detect-newline@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" + integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== + +diff-sequences@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.6.3.tgz#4deaf894d11407c51efc8418012f9e70b84ea921" + integrity sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q== + +diff@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" + integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== + +diff@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/diff/-/diff-5.2.0.tgz#26ded047cd1179b78b9537d5ef725503ce1ae531" + integrity sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A== + +dotenv@^16.3.1: + version "16.4.7" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.7.tgz#0e20c5b82950140aa99be360a8a5f52335f53c26" + integrity sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ== + +dunder-proto@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.0.tgz#c2fce098b3c8f8899554905f4377b6d85dabaa80" + integrity sha512-9+Sj30DIu+4KvHqMfLUGLFYL2PkURSYMVXJyXe92nFRvlYq5hBjLEhblKB+vkd/WVlUYMWigiY07T91Fkk0+4A== + dependencies: + call-bind-apply-helpers "^1.0.0" + es-errors "^1.3.0" + gopd "^1.2.0" + +eastasianwidth@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" + integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== + +ejs@^3.1.10: + version "3.1.10" + resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.10.tgz#69ab8358b14e896f80cc39e62087b88500c3ac3b" + integrity sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA== + dependencies: + jake "^10.8.5" + +electron-to-chromium@^1.5.41: + version "1.5.71" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.71.tgz#d8b5dba1e55b320f2f4e9b1ca80738f53fcfec2b" + integrity sha512-dB68l59BI75W1BUGVTAEJy45CEVuEGy9qPVVQ8pnHyHMn36PLPPoE1mjLH+lo9rKulO3HC2OhbACI/8tCqJBcA== + +emittery@^0.13.1: + version "0.13.1" + resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.13.1.tgz#c04b8c3457490e0847ae51fced3af52d338e3dad" + integrity sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +emoji-regex@^9.2.2: + version "9.2.2" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" + integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== + +enabled@2.0.x: + version "2.0.0" + resolved "https://registry.yarnpkg.com/enabled/-/enabled-2.0.0.tgz#f9dd92ec2d6f4bbc0d5d1e64e21d61cd4665e7c2" + integrity sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ== + +error-ex@^1.3.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" + integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== + dependencies: + is-arrayish "^0.2.1" + +error@^7.0.0: + version "7.2.1" + resolved "https://registry.yarnpkg.com/error/-/error-7.2.1.tgz#eab21a4689b5f684fc83da84a0e390de82d94894" + integrity sha512-fo9HBvWnx3NGUKMvMwB/CBCMMrfEJgbDTVDEkPygA3Bdd3lM1OyCd+rbQ8BwnpF6GdVeOLDNmyL4N5Bg80ZvdA== + dependencies: + string-template "~0.2.1" + +es-define-property@^1.0.0, es-define-property@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa" + integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== + +es-errors@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" + integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== + +esbuild@0.24.0: + version "0.24.0" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.24.0.tgz#f2d470596885fcb2e91c21eb3da3b3c89c0b55e7" + integrity sha512-FuLPevChGDshgSicjisSooU0cemp/sGXR841D5LHMB7mTVOmsEHcAxaH3irL53+8YDIeVNQEySh4DaYU/iuPqQ== + optionalDependencies: + "@esbuild/aix-ppc64" "0.24.0" + "@esbuild/android-arm" "0.24.0" + "@esbuild/android-arm64" "0.24.0" + "@esbuild/android-x64" "0.24.0" + "@esbuild/darwin-arm64" "0.24.0" + "@esbuild/darwin-x64" "0.24.0" + "@esbuild/freebsd-arm64" "0.24.0" + "@esbuild/freebsd-x64" "0.24.0" + "@esbuild/linux-arm" "0.24.0" + "@esbuild/linux-arm64" "0.24.0" + "@esbuild/linux-ia32" "0.24.0" + "@esbuild/linux-loong64" "0.24.0" + "@esbuild/linux-mips64el" "0.24.0" + "@esbuild/linux-ppc64" "0.24.0" + "@esbuild/linux-riscv64" "0.24.0" + "@esbuild/linux-s390x" "0.24.0" + "@esbuild/linux-x64" "0.24.0" + "@esbuild/netbsd-x64" "0.24.0" + "@esbuild/openbsd-arm64" "0.24.0" + "@esbuild/openbsd-x64" "0.24.0" + "@esbuild/sunos-x64" "0.24.0" + "@esbuild/win32-arm64" "0.24.0" + "@esbuild/win32-ia32" "0.24.0" + "@esbuild/win32-x64" "0.24.0" + +esbuild@~0.23.0: + version "0.23.1" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.23.1.tgz#40fdc3f9265ec0beae6f59824ade1bd3d3d2dab8" + integrity sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg== + optionalDependencies: + "@esbuild/aix-ppc64" "0.23.1" + "@esbuild/android-arm" "0.23.1" + "@esbuild/android-arm64" "0.23.1" + "@esbuild/android-x64" "0.23.1" + "@esbuild/darwin-arm64" "0.23.1" + "@esbuild/darwin-x64" "0.23.1" + "@esbuild/freebsd-arm64" "0.23.1" + "@esbuild/freebsd-x64" "0.23.1" + "@esbuild/linux-arm" "0.23.1" + "@esbuild/linux-arm64" "0.23.1" + "@esbuild/linux-ia32" "0.23.1" + "@esbuild/linux-loong64" "0.23.1" + "@esbuild/linux-mips64el" "0.23.1" + "@esbuild/linux-ppc64" "0.23.1" + "@esbuild/linux-riscv64" "0.23.1" + "@esbuild/linux-s390x" "0.23.1" + "@esbuild/linux-x64" "0.23.1" + "@esbuild/netbsd-x64" "0.23.1" + "@esbuild/openbsd-arm64" "0.23.1" + "@esbuild/openbsd-x64" "0.23.1" + "@esbuild/sunos-x64" "0.23.1" + "@esbuild/win32-arm64" "0.23.1" + "@esbuild/win32-ia32" "0.23.1" + "@esbuild/win32-x64" "0.23.1" + +escalade@^3.1.1, escalade@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" + integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== + +escape-string-regexp@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" + integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== + +escape-string-regexp@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + +eslint-scope@^8.2.0: + version "8.2.0" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-8.2.0.tgz#377aa6f1cb5dc7592cfd0b7f892fd0cf352ce442" + integrity sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A== + dependencies: + esrecurse "^4.3.0" + estraverse "^5.2.0" + +eslint-visitor-keys@^3.4.3: + version "3.4.3" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" + integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== + +eslint-visitor-keys@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz#687bacb2af884fcdda8a6e7d65c606f46a14cd45" + integrity sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw== + +eslint@^9.0.0, eslint@^9.13.0: + version "9.16.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.16.0.tgz#66832e66258922ac0a626f803a9273e37747f2a6" + integrity sha512-whp8mSQI4C8VXd+fLgSM0lh3UlmcFtVwUQjyKCFfsp+2ItAIYhlq/hqGahGqHE6cv9unM41VlqKk2VtKYR2TaA== + dependencies: + "@eslint-community/eslint-utils" "^4.2.0" + "@eslint-community/regexpp" "^4.12.1" + "@eslint/config-array" "^0.19.0" + "@eslint/core" "^0.9.0" + "@eslint/eslintrc" "^3.2.0" + "@eslint/js" "9.16.0" + "@eslint/plugin-kit" "^0.2.3" + "@humanfs/node" "^0.16.6" + "@humanwhocodes/module-importer" "^1.0.1" + "@humanwhocodes/retry" "^0.4.1" + "@types/estree" "^1.0.6" + "@types/json-schema" "^7.0.15" + ajv "^6.12.4" + chalk "^4.0.0" + cross-spawn "^7.0.5" + debug "^4.3.2" + escape-string-regexp "^4.0.0" + eslint-scope "^8.2.0" + eslint-visitor-keys "^4.2.0" + espree "^10.3.0" + esquery "^1.5.0" + esutils "^2.0.2" + fast-deep-equal "^3.1.3" + file-entry-cache "^8.0.0" + find-up "^5.0.0" + glob-parent "^6.0.2" + ignore "^5.2.0" + imurmurhash "^0.1.4" + is-glob "^4.0.0" + json-stable-stringify-without-jsonify "^1.0.1" + lodash.merge "^4.6.2" + minimatch "^3.1.2" + natural-compare "^1.4.0" + optionator "^0.9.3" + +espree@^10.0.1, espree@^10.3.0: + version "10.3.0" + resolved "https://registry.yarnpkg.com/espree/-/espree-10.3.0.tgz#29267cf5b0cb98735b65e64ba07e0ed49d1eed8a" + integrity sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg== + dependencies: + acorn "^8.14.0" + acorn-jsx "^5.3.2" + eslint-visitor-keys "^4.2.0" + +esprima@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +esquery@^1.5.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.6.0.tgz#91419234f804d852a82dceec3e16cdc22cf9dae7" + integrity sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg== + dependencies: + estraverse "^5.1.0" + +esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + +estraverse@^5.1.0, estraverse@^5.2.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +eventemitter2@~0.4.13: + version "0.4.14" + resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-0.4.14.tgz#8f61b75cde012b2e9eb284d4545583b5643b61ab" + integrity sha512-K7J4xq5xAD5jHsGM5ReWXRTFa3JRGofHiMcVgQ8PRwgWxzjHpMWCIzsmyf60+mh8KLsqYPcjUMa0AC4hd6lPyQ== + +events@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924" + integrity sha512-kEcvvCBByWXGnZy6JUlgAp2gBIUjfCAV6P6TgT1/aaQKcmuAEC4OZTV1I4EWQLz2gxZw76atuVyvHhTxvi0Flw== + +execa@^5.0.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" + integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== + dependencies: + cross-spawn "^7.0.3" + get-stream "^6.0.0" + human-signals "^2.1.0" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.1" + onetime "^5.1.2" + signal-exit "^3.0.3" + strip-final-newline "^2.0.0" + +exit@^0.1.2, exit@~0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" + integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ== + +expand-tilde@^2.0.0, expand-tilde@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/expand-tilde/-/expand-tilde-2.0.2.tgz#97e801aa052df02454de46b02bf621642cdc8502" + integrity sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw== + dependencies: + homedir-polyfill "^1.0.1" + +expect@>28.1.3, expect@^29.0.0, expect@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/expect/-/expect-29.7.0.tgz#578874590dcb3214514084c08115d8aee61e11bc" + integrity sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw== + 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" + +extend@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" + integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== + +fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-glob@^3.3.2: + version "3.3.2" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129" + integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.4" + +fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fast-levenshtein@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== + +fast-xml-parser@4.4.1: + version "4.4.1" + resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-4.4.1.tgz#86dbf3f18edf8739326447bcaac31b4ae7f6514f" + integrity sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw== + dependencies: + strnum "^1.0.5" + +fastq@^1.6.0: + version "1.17.1" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.17.1.tgz#2a523f07a4e7b1e81a42b91b8bf2254107753b47" + integrity sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w== + dependencies: + reusify "^1.0.4" + +faye-websocket@~0.10.0: + version "0.10.0" + resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.10.0.tgz#4e492f8d04dfb6f89003507f6edbf2d501e7c6f4" + integrity sha512-Xhj93RXbMSq8urNCUq4p9l0P6hnySJ/7YNRhYNug0bLOuii7pKO7xQFb5mx9xZXWCar88pLPb805PvUkwrLZpQ== + dependencies: + websocket-driver ">=0.5.1" + +fb-watchman@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.2.tgz#e9524ee6b5c77e9e5001af0f85f3adbb8623255c" + integrity sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA== + dependencies: + bser "2.1.1" + +fecha@^4.2.0: + version "4.2.3" + resolved "https://registry.yarnpkg.com/fecha/-/fecha-4.2.3.tgz#4d9ccdbc61e8629b259fdca67e65891448d569fd" + integrity sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw== + +file-entry-cache@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-8.0.0.tgz#7787bddcf1131bffb92636c69457bbc0edd6d81f" + integrity sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ== + dependencies: + flat-cache "^4.0.0" + +filelist@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/filelist/-/filelist-1.0.4.tgz#f78978a1e944775ff9e62e744424f215e58352b5" + integrity sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q== + dependencies: + minimatch "^5.0.1" + +fill-range@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" + integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== + dependencies: + to-regex-range "^5.0.1" + +find-up@^4.0.0, find-up@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" + integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== + dependencies: + locate-path "^5.0.0" + path-exists "^4.0.0" + +find-up@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" + integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== + dependencies: + locate-path "^6.0.0" + path-exists "^4.0.0" + +findup-sync@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-4.0.0.tgz#956c9cdde804052b881b428512905c4a5f2cdef0" + integrity sha512-6jvvn/12IC4quLBL1KNokxC7wWTvYncaVUYSoxWw7YykPLuRrnv4qdHcSOywOI5RpkOVGeQRtWM8/q+G6W6qfQ== + dependencies: + detect-file "^1.0.0" + is-glob "^4.0.0" + micromatch "^4.0.2" + resolve-dir "^1.0.1" + +findup-sync@~5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-5.0.0.tgz#54380ad965a7edca00cc8f63113559aadc541bd2" + integrity sha512-MzwXju70AuyflbgeOhzvQWAvvQdo1XL0A9bVvlXsYcFEBM87WR4OakL4OfZq+QRmr+duJubio+UtNQCPsVESzQ== + dependencies: + detect-file "^1.0.0" + is-glob "^4.0.3" + micromatch "^4.0.4" + resolve-dir "^1.0.1" + +fined@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/fined/-/fined-1.2.0.tgz#d00beccf1aa2b475d16d423b0238b713a2c4a37b" + integrity sha512-ZYDqPLGxDkDhDZBjZBb+oD1+j0rA4E0pXY50eplAAOPg2N/gUBSSk5IM1/QhPfyVo19lJ+CvXpqfvk+b2p/8Ng== + dependencies: + expand-tilde "^2.0.2" + is-plain-object "^2.0.3" + object.defaults "^1.1.0" + object.pick "^1.2.0" + parse-filepath "^1.0.1" + +flagged-respawn@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/flagged-respawn/-/flagged-respawn-1.0.1.tgz#e7de6f1279ddd9ca9aac8a5971d618606b3aab41" + integrity sha512-lNaHNVymajmk0OJMBn8fVUAU1BtDeKIqKoVhk4xAALB57aALg6b4W0MfJ/cUE0g9YBXy5XhSlPIpYIJ7HaY/3Q== + +flat-cache@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-4.0.1.tgz#0ece39fcb14ee012f4b0410bd33dd9c1f011127c" + integrity sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw== + dependencies: + flatted "^3.2.9" + keyv "^4.5.4" + +flat@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241" + integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== + +flatted@^3.2.9: + version "3.3.2" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.2.tgz#adba1448a9841bec72b42c532ea23dbbedef1a27" + integrity sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA== + +fn.name@1.x.x: + version "1.1.0" + resolved "https://registry.yarnpkg.com/fn.name/-/fn.name-1.1.0.tgz#26cad8017967aea8731bc42961d04a3d5988accc" + integrity sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw== + +for-each@^0.3.3: + version "0.3.3" + resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e" + integrity sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw== + dependencies: + is-callable "^1.1.3" + +for-in@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" + integrity sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ== + +for-own@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/for-own/-/for-own-1.0.0.tgz#c63332f415cedc4b04dbfe70cf836494c53cb44b" + integrity sha512-0OABksIGrxKK8K4kynWkQ7y1zounQxP+CWnyclVwj81KW3vlLlGUx57DKGcP/LH216GzqnstnPocF16Nxs0Ycg== + dependencies: + for-in "^1.0.1" + +foreground-child@^3.1.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.3.0.tgz#0ac8644c06e431439f8561db8ecf29a7b5519c77" + integrity sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg== + dependencies: + cross-spawn "^7.0.0" + signal-exit "^4.0.1" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== + +fsevents@^2.3.2, fsevents@~2.3.2, fsevents@~2.3.3: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + +function-bind@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" + integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== + +gaze@^1.1.0: + version "1.1.3" + resolved "https://registry.yarnpkg.com/gaze/-/gaze-1.1.3.tgz#c441733e13b927ac8c0ff0b4c3b033f28812924a" + integrity sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g== + dependencies: + globule "^1.0.0" + +gensync@^1.0.0-beta.2: + version "1.0.0-beta.2" + resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" + integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== + +get-caller-file@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + +get-func-name@^2.0.1, get-func-name@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.2.tgz#0d7cf20cd13fda808669ffa88f4ffc7a3943fc41" + integrity sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ== + +get-intrinsic@^1.2.4: + version "1.2.5" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.5.tgz#dfe7dd1b30761b464fe51bf4bb00ac7c37b681e7" + integrity sha512-Y4+pKa7XeRUPWFNvOOYHkRYrfzW07oraURSvjDmRVOJ748OrVmeXtpE4+GCEHncjCjkTxPNRt8kEbxDhsn6VTg== + dependencies: + call-bind-apply-helpers "^1.0.0" + dunder-proto "^1.0.0" + es-define-property "^1.0.1" + es-errors "^1.3.0" + function-bind "^1.1.2" + gopd "^1.2.0" + has-symbols "^1.1.0" + hasown "^2.0.2" + +get-package-type@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" + integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== + +get-stream@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" + integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== + +get-tsconfig@^4.7.5: + version "4.8.1" + resolved "https://registry.yarnpkg.com/get-tsconfig/-/get-tsconfig-4.8.1.tgz#8995eb391ae6e1638d251118c7b56de7eb425471" + integrity sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg== + dependencies: + resolve-pkg-maps "^1.0.0" + +getobject@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/getobject/-/getobject-1.0.2.tgz#25ec87a50370f6dcc3c6ba7ef43c4c16215c4c89" + integrity sha512-2zblDBaFcb3rB4rF77XVnuINOE2h2k/OnqXAiy0IrTxUfV1iFp3la33oAQVY9pCpWU268WFYVt2t71hlMuLsOg== + +glob-parent@^5.1.2, glob-parent@~5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +glob-parent@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" + integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== + dependencies: + is-glob "^4.0.3" + +glob-to-regexp@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" + integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== + +glob@^10.4.5: + version "10.4.5" + resolved "https://registry.yarnpkg.com/glob/-/glob-10.4.5.tgz#f4d9f0b90ffdbab09c9d77f5f29b4262517b0956" + integrity sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg== + dependencies: + foreground-child "^3.1.0" + jackspeak "^3.1.2" + minimatch "^9.0.4" + minipass "^7.1.2" + package-json-from-dist "^1.0.0" + path-scurry "^1.11.1" + +glob@^7.1.3, glob@^7.1.4: + version "7.2.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.1.1" + once "^1.3.0" + path-is-absolute "^1.0.0" + +glob@~7.1.1, glob@~7.1.6: + version "7.1.7" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90" + integrity sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +global-modules@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-1.0.0.tgz#6d770f0eb523ac78164d72b5e71a8877265cc3ea" + integrity sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg== + dependencies: + global-prefix "^1.0.1" + is-windows "^1.0.1" + resolve-dir "^1.0.0" + +global-prefix@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-1.0.2.tgz#dbf743c6c14992593c655568cb66ed32c0122ebe" + integrity sha512-5lsx1NUDHtSjfg0eHlmYvZKv8/nVqX4ckFbM+FrGcQ+04KWcWFo9P5MxPZYSzUvyzmdTbI7Eix8Q4IbELDqzKg== + dependencies: + expand-tilde "^2.0.2" + homedir-polyfill "^1.0.1" + ini "^1.3.4" + is-windows "^1.0.1" + which "^1.2.14" + +globals@^11.1.0: + version "11.12.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" + integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== + +globals@^14.0.0: + version "14.0.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-14.0.0.tgz#898d7413c29babcf6bafe56fcadded858ada724e" + integrity sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ== + +globule@^1.0.0: + version "1.3.4" + resolved "https://registry.yarnpkg.com/globule/-/globule-1.3.4.tgz#7c11c43056055a75a6e68294453c17f2796170fb" + integrity sha512-OPTIfhMBh7JbBYDpa5b+Q5ptmMWKwcNcFSR/0c6t8V4f3ZAVBEsKNY37QdVqmLRYSMhOUGYrY0QhSoEpzGr/Eg== + dependencies: + glob "~7.1.1" + lodash "^4.17.21" + minimatch "~3.0.2" + +gopd@^1.0.1, gopd@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1" + integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== + +graceful-fs@^4.1.2, graceful-fs@^4.2.9: + version "4.2.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" + integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== + +graphemer@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" + integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== + +grunt-cli@~1.4.3: + version "1.4.3" + resolved "https://registry.yarnpkg.com/grunt-cli/-/grunt-cli-1.4.3.tgz#22c9f1a3d2780bf9b0d206e832e40f8f499175ff" + integrity sha512-9Dtx/AhVeB4LYzsViCjUQkd0Kw0McN2gYpdmGYKtE2a5Yt7v1Q+HYZVWhqXc/kGnxlMtqKDxSwotiGeFmkrCoQ== + dependencies: + grunt-known-options "~2.0.0" + interpret "~1.1.0" + liftup "~3.0.1" + nopt "~4.0.1" + v8flags "~3.2.0" + +grunt-contrib-watch@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/grunt-contrib-watch/-/grunt-contrib-watch-1.1.0.tgz#c143ca5b824b288a024b856639a5345aedb78ed4" + integrity sha512-yGweN+0DW5yM+oo58fRu/XIRrPcn3r4tQx+nL7eMRwjpvk+rQY6R8o94BPK0i2UhTg9FN21hS+m8vR8v9vXfeg== + dependencies: + async "^2.6.0" + gaze "^1.1.0" + lodash "^4.17.10" + tiny-lr "^1.1.1" + +grunt-eslint@^25.0.0: + version "25.0.0" + resolved "https://registry.yarnpkg.com/grunt-eslint/-/grunt-eslint-25.0.0.tgz#e04d21fdc8e772511dae201d4c13e8f4f60823bc" + integrity sha512-JIV5IPgOuacorFLmYtUTq0n+0qGIL9FSQJ4KVnNfCg/8Fm+K1t6OWrzXXI8TxWTwq2K9E3parFVXCpn1sGLbKQ== + dependencies: + chalk "^4.1.2" + eslint "^9.0.0" + +grunt-known-options@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/grunt-known-options/-/grunt-known-options-2.0.0.tgz#cac641e897f9a0a680b8c9839803d35f3325103c" + integrity sha512-GD7cTz0I4SAede1/+pAbmJRG44zFLPipVtdL9o3vqx9IEyb7b4/Y3s7r6ofI3CchR5GvYJ+8buCSioDv5dQLiA== + +grunt-legacy-log-utils@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/grunt-legacy-log-utils/-/grunt-legacy-log-utils-2.1.0.tgz#49a8c7dc74051476dcc116c32faf9db8646856ef" + integrity sha512-lwquaPXJtKQk0rUM1IQAop5noEpwFqOXasVoedLeNzaibf/OPWjKYvvdqnEHNmU+0T0CaReAXIbGo747ZD+Aaw== + dependencies: + chalk "~4.1.0" + lodash "~4.17.19" + +grunt-legacy-log@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/grunt-legacy-log/-/grunt-legacy-log-3.0.0.tgz#1c6eaf92371ea415af31ea84ce50d434ef6d39c4" + integrity sha512-GHZQzZmhyq0u3hr7aHW4qUH0xDzwp2YXldLPZTCjlOeGscAOWWPftZG3XioW8MasGp+OBRIu39LFx14SLjXRcA== + dependencies: + colors "~1.1.2" + grunt-legacy-log-utils "~2.1.0" + hooker "~0.2.3" + lodash "~4.17.19" + +grunt-legacy-util@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/grunt-legacy-util/-/grunt-legacy-util-2.0.1.tgz#0f929d13a2faf9988c9917c82bff609e2d9ba255" + integrity sha512-2bQiD4fzXqX8rhNdXkAywCadeqiPiay0oQny77wA2F3WF4grPJXCvAcyoWUJV+po/b15glGkxuSiQCK299UC2w== + dependencies: + async "~3.2.0" + exit "~0.1.2" + getobject "~1.0.0" + hooker "~0.2.3" + lodash "~4.17.21" + underscore.string "~3.3.5" + which "~2.0.2" + +grunt@^1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/grunt/-/grunt-1.6.1.tgz#0b4dd1524f26676dcf45d8f636b8d9061a8ede16" + integrity sha512-/ABUy3gYWu5iBmrUSRBP97JLpQUm0GgVveDCp6t3yRNIoltIYw7rEj3g5y1o2PGPR2vfTRGa7WC/LZHLTXnEzA== + dependencies: + dateformat "~4.6.2" + eventemitter2 "~0.4.13" + exit "~0.1.2" + findup-sync "~5.0.0" + glob "~7.1.6" + grunt-cli "~1.4.3" + grunt-known-options "~2.0.0" + grunt-legacy-log "~3.0.0" + grunt-legacy-util "~2.0.1" + iconv-lite "~0.6.3" + js-yaml "~3.14.0" + minimatch "~3.0.4" + nopt "~3.0.6" + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +has-property-descriptors@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz#963ed7d071dc7bf5f084c5bfbe0d1b6222586854" + integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg== + dependencies: + es-define-property "^1.0.0" + +has-symbols@^1.0.3, has-symbols@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338" + integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== + +has-tostringtag@^1.0.0, has-tostringtag@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc" + integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw== + dependencies: + has-symbols "^1.0.3" + +hasown@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" + integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== + dependencies: + function-bind "^1.1.2" + +he@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" + integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== + +homedir-polyfill@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz#743298cef4e5af3e194161fbadcc2151d3a058e8" + integrity sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA== + dependencies: + parse-passwd "^1.0.0" + +hooker@~0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/hooker/-/hooker-0.2.3.tgz#b834f723cc4a242aa65963459df6d984c5d3d959" + integrity sha512-t+UerCsQviSymAInD01Pw+Dn/usmz1sRO+3Zk1+lx8eg+WKpD2ulcwWqHHL0+aseRBr+3+vIhiG1K1JTwaIcTA== + +html-escaper@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" + integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== + +http-parser-js@>=0.5.1: + version "0.5.8" + resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.8.tgz#af23090d9ac4e24573de6f6aecc9d84a48bf20e3" + integrity sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q== + +human-signals@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" + integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== + +iconv-lite@~0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" + integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== + dependencies: + safer-buffer ">= 2.1.2 < 3.0.0" + +ieee754@1.1.13: + version "1.1.13" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84" + integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg== + +ieee754@^1.1.4: + version "1.2.1" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== + +ignore@^5.2.0, ignore@^5.3.1: + version "5.3.2" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5" + integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== + +import-fresh@^3.2.1: + version "3.3.0" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" + integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + +import-local@^3.0.2: + version "3.2.0" + resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.2.0.tgz#c3d5c745798c02a6f8b897726aba5100186ee260" + integrity sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA== + dependencies: + pkg-dir "^4.2.0" + resolve-cwd "^3.0.0" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@^2.0.1, inherits@^2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +ini@^1.3.4: + version "1.3.8" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" + integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== + +interpret@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.1.0.tgz#7ed1b1410c6a0e0f78cf95d3b8440c63f78b8614" + integrity sha512-CLM8SNMDu7C5psFCn6Wg/tgpj/bKAg7hc2gWqcuR9OD5Ft9PhBpIu8PLicPeis+xDd6YX2ncI8MCA64I9tftIA== + +is-absolute@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-absolute/-/is-absolute-1.0.0.tgz#395e1ae84b11f26ad1795e73c17378e48a301576" + integrity sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA== + dependencies: + is-relative "^1.0.0" + is-windows "^1.0.1" + +is-arguments@^1.0.4: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.1.1.tgz#15b3f88fda01f2a97fec84ca761a560f123efa9b" + integrity sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA== + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" + +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== + +is-arrayish@^0.3.1: + version "0.3.2" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03" + integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== + +is-binary-path@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== + dependencies: + binary-extensions "^2.0.0" + +is-callable@^1.1.3: + version "1.2.7" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" + integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== + +is-core-module@^2.13.0: + version "2.15.1" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.15.1.tgz#a7363a25bee942fefab0de13bf6aa372c82dcc37" + integrity sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ== + dependencies: + hasown "^2.0.2" + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-generator-fn@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" + integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== + +is-generator-function@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.0.10.tgz#f1558baf1ac17e0deea7c0415c438351ff2b3c72" + integrity sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A== + dependencies: + has-tostringtag "^1.0.0" + +is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-plain-obj@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287" + integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA== + +is-plain-object@^2.0.3, is-plain-object@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" + integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== + dependencies: + isobject "^3.0.1" + +is-relative@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-relative/-/is-relative-1.0.0.tgz#a1bb6935ce8c5dba1e8b9754b9b2dcc020e2260d" + integrity sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA== + dependencies: + is-unc-path "^1.0.0" + +is-stream@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" + integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== + +is-typed-array@^1.1.3: + version "1.1.13" + resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.13.tgz#d6c5ca56df62334959322d7d7dd1cca50debe229" + integrity sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw== + dependencies: + which-typed-array "^1.1.14" + +is-unc-path@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-unc-path/-/is-unc-path-1.0.0.tgz#d731e8898ed090a12c352ad2eaed5095ad322c9d" + integrity sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ== + dependencies: + unc-path-regex "^0.1.2" + +is-unicode-supported@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" + integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== + +is-windows@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" + integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== + +isarray@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== + +isobject@^3.0.0, isobject@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" + integrity sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg== + +istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: + version "3.2.2" + resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz#2d166c4b0644d43a39f04bf6c2edd1e585f31756" + integrity sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg== + +istanbul-lib-instrument@^5.0.4: + version "5.2.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz#d10c8885c2125574e1c231cacadf955675e1ce3d" + integrity sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg== + dependencies: + "@babel/core" "^7.12.3" + "@babel/parser" "^7.14.7" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-coverage "^3.2.0" + semver "^6.3.0" + +istanbul-lib-instrument@^6.0.0: + version "6.0.3" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz#fa15401df6c15874bcb2105f773325d78c666765" + integrity sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q== + dependencies: + "@babel/core" "^7.23.9" + "@babel/parser" "^7.23.9" + "@istanbuljs/schema" "^0.1.3" + istanbul-lib-coverage "^3.2.0" + semver "^7.5.4" + +istanbul-lib-report@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz#908305bac9a5bd175ac6a74489eafd0fc2445a7d" + integrity sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw== + dependencies: + istanbul-lib-coverage "^3.0.0" + make-dir "^4.0.0" + supports-color "^7.1.0" + +istanbul-lib-source-maps@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz#895f3a709fcfba34c6de5a42939022f3e4358551" + integrity sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw== + dependencies: + debug "^4.1.1" + istanbul-lib-coverage "^3.0.0" + source-map "^0.6.1" + +istanbul-reports@^3.1.3: + version "3.1.7" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.7.tgz#daed12b9e1dca518e15c056e1e537e741280fa0b" + integrity sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g== + dependencies: + html-escaper "^2.0.0" + istanbul-lib-report "^3.0.0" + +jackspeak@^3.1.2: + version "3.4.3" + resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-3.4.3.tgz#8833a9d89ab4acde6188942bd1c53b6390ed5a8a" + integrity sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw== + dependencies: + "@isaacs/cliui" "^8.0.2" + optionalDependencies: + "@pkgjs/parseargs" "^0.11.0" + +jake@^10.8.5: + version "10.9.2" + resolved "https://registry.yarnpkg.com/jake/-/jake-10.9.2.tgz#6ae487e6a69afec3a5e167628996b59f35ae2b7f" + integrity sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA== + dependencies: + async "^3.2.3" + chalk "^4.0.2" + filelist "^1.0.4" + minimatch "^3.1.2" + +jest-changed-files@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-29.7.0.tgz#1c06d07e77c78e1585d020424dedc10d6e17ac3a" + integrity sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w== + dependencies: + execa "^5.0.0" + jest-util "^29.7.0" + p-limit "^3.1.0" + +jest-circus@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-29.7.0.tgz#b6817a45fcc835d8b16d5962d0c026473ee3668a" + integrity sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/expect" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + co "^4.6.0" + dedent "^1.0.0" + is-generator-fn "^2.0.0" + jest-each "^29.7.0" + jest-matcher-utils "^29.7.0" + jest-message-util "^29.7.0" + jest-runtime "^29.7.0" + jest-snapshot "^29.7.0" + jest-util "^29.7.0" + p-limit "^3.1.0" + pretty-format "^29.7.0" + pure-rand "^6.0.0" + slash "^3.0.0" + stack-utils "^2.0.3" + +jest-cli@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-29.7.0.tgz#5592c940798e0cae677eec169264f2d839a37995" + integrity sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg== + dependencies: + "@jest/core" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/types" "^29.6.3" + chalk "^4.0.0" + create-jest "^29.7.0" + exit "^0.1.2" + import-local "^3.0.2" + jest-config "^29.7.0" + jest-util "^29.7.0" + jest-validate "^29.7.0" + yargs "^17.3.1" + +jest-config@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-29.7.0.tgz#bcbda8806dbcc01b1e316a46bb74085a84b0245f" + integrity sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ== + dependencies: + "@babel/core" "^7.11.6" + "@jest/test-sequencer" "^29.7.0" + "@jest/types" "^29.6.3" + babel-jest "^29.7.0" + chalk "^4.0.0" + ci-info "^3.2.0" + deepmerge "^4.2.2" + glob "^7.1.3" + graceful-fs "^4.2.9" + jest-circus "^29.7.0" + 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" + jest-util "^29.7.0" + jest-validate "^29.7.0" + micromatch "^4.0.4" + parse-json "^5.2.0" + pretty-format "^29.7.0" + slash "^3.0.0" + strip-json-comments "^3.1.1" + +jest-diff@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.7.0.tgz#017934a66ebb7ecf6f205e84699be10afd70458a" + integrity sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw== + dependencies: + chalk "^4.0.0" + diff-sequences "^29.6.3" + jest-get-type "^29.6.3" + pretty-format "^29.7.0" + +jest-docblock@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-29.7.0.tgz#8fddb6adc3cdc955c93e2a87f61cfd350d5d119a" + integrity sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g== + dependencies: + detect-newline "^3.0.0" + +jest-each@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-29.7.0.tgz#162a9b3f2328bdd991beaabffbb74745e56577d1" + integrity sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ== + dependencies: + "@jest/types" "^29.6.3" + chalk "^4.0.0" + jest-get-type "^29.6.3" + jest-util "^29.7.0" + pretty-format "^29.7.0" + +jest-environment-node@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-29.7.0.tgz#0b93e111dda8ec120bc8300e6d1fb9576e164376" + integrity sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/fake-timers" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + jest-mock "^29.7.0" + jest-util "^29.7.0" + +jest-get-type@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.6.3.tgz#36f499fdcea197c1045a127319c0481723908fd1" + integrity sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw== + +jest-haste-map@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-29.7.0.tgz#3c2396524482f5a0506376e6c858c3bbcc17b104" + integrity sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA== + dependencies: + "@jest/types" "^29.6.3" + "@types/graceful-fs" "^4.1.3" + "@types/node" "*" + anymatch "^3.0.3" + fb-watchman "^2.0.0" + graceful-fs "^4.2.9" + jest-regex-util "^29.6.3" + jest-util "^29.7.0" + jest-worker "^29.7.0" + micromatch "^4.0.4" + walker "^1.0.8" + optionalDependencies: + fsevents "^2.3.2" + +jest-leak-detector@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz#5b7ec0dadfdfec0ca383dc9aa016d36b5ea4c728" + integrity sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw== + dependencies: + jest-get-type "^29.6.3" + pretty-format "^29.7.0" + +jest-matcher-utils@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz#ae8fec79ff249fd592ce80e3ee474e83a6c44f12" + integrity sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g== + dependencies: + chalk "^4.0.0" + jest-diff "^29.7.0" + jest-get-type "^29.6.3" + pretty-format "^29.7.0" + +jest-message-util@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.7.0.tgz#8bc392e204e95dfe7564abbe72a404e28e51f7f3" + integrity sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w== + dependencies: + "@babel/code-frame" "^7.12.13" + "@jest/types" "^29.6.3" + "@types/stack-utils" "^2.0.0" + chalk "^4.0.0" + graceful-fs "^4.2.9" + micromatch "^4.0.4" + pretty-format "^29.7.0" + slash "^3.0.0" + stack-utils "^2.0.3" + +jest-mock@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.7.0.tgz#4e836cf60e99c6fcfabe9f99d017f3fdd50a6347" + integrity sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw== + dependencies: + "@jest/types" "^29.6.3" + "@types/node" "*" + jest-util "^29.7.0" + +jest-pnp-resolver@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz#930b1546164d4ad5937d5540e711d4d38d4cad2e" + integrity sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w== + +jest-regex-util@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-29.6.3.tgz#4a556d9c776af68e1c5f48194f4d0327d24e8a52" + integrity sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg== + +jest-resolve-dependencies@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz#1b04f2c095f37fc776ff40803dc92921b1e88428" + integrity sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA== + dependencies: + jest-regex-util "^29.6.3" + jest-snapshot "^29.7.0" + +jest-resolve@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-29.7.0.tgz#64d6a8992dd26f635ab0c01e5eef4399c6bcbc30" + integrity sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA== + dependencies: + chalk "^4.0.0" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + jest-pnp-resolver "^1.2.2" + jest-util "^29.7.0" + jest-validate "^29.7.0" + resolve "^1.20.0" + resolve.exports "^2.0.0" + slash "^3.0.0" + +jest-runner@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-29.7.0.tgz#809af072d408a53dcfd2e849a4c976d3132f718e" + integrity sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ== + dependencies: + "@jest/console" "^29.7.0" + "@jest/environment" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + emittery "^0.13.1" + graceful-fs "^4.2.9" + 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" + 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" + +jest-runtime@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-29.7.0.tgz#efecb3141cf7d3767a3a0cc8f7c9990587d3d817" + integrity sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/fake-timers" "^29.7.0" + "@jest/globals" "^29.7.0" + "@jest/source-map" "^29.6.3" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + cjs-module-lexer "^1.0.0" + collect-v8-coverage "^1.0.0" + glob "^7.1.3" + graceful-fs "^4.2.9" + 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" + jest-util "^29.7.0" + slash "^3.0.0" + strip-bom "^4.0.0" + +jest-snapshot@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-29.7.0.tgz#c2c574c3f51865da1bb329036778a69bf88a6be5" + integrity sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw== + dependencies: + "@babel/core" "^7.11.6" + "@babel/generator" "^7.7.2" + "@babel/plugin-syntax-jsx" "^7.7.2" + "@babel/plugin-syntax-typescript" "^7.7.2" + "@babel/types" "^7.3.3" + "@jest/expect-utils" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + babel-preset-current-node-syntax "^1.0.0" + chalk "^4.0.0" + expect "^29.7.0" + graceful-fs "^4.2.9" + 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.5.3" + +jest-util@^29.0.0, jest-util@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.7.0.tgz#23c2b62bfb22be82b44de98055802ff3710fc0bc" + integrity sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA== + dependencies: + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + ci-info "^3.2.0" + graceful-fs "^4.2.9" + picomatch "^2.2.3" + +jest-validate@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-29.7.0.tgz#7bf705511c64da591d46b15fce41400d52147d9c" + integrity sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw== + dependencies: + "@jest/types" "^29.6.3" + camelcase "^6.2.0" + chalk "^4.0.0" + jest-get-type "^29.6.3" + leven "^3.1.0" + pretty-format "^29.7.0" + +jest-watcher@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-29.7.0.tgz#7810d30d619c3a62093223ce6bb359ca1b28a2f2" + integrity sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g== + dependencies: + "@jest/test-result" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + emittery "^0.13.1" + jest-util "^29.7.0" + string-length "^4.0.1" + +jest-worker@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.7.0.tgz#acad073acbbaeb7262bd5389e1bcf43e10058d4a" + integrity sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw== + dependencies: + "@types/node" "*" + jest-util "^29.7.0" + merge-stream "^2.0.0" + supports-color "^8.0.0" + +jest@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest/-/jest-29.7.0.tgz#994676fc24177f088f1c5e3737f5697204ff2613" + integrity sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw== + dependencies: + "@jest/core" "^29.7.0" + "@jest/types" "^29.6.3" + import-local "^3.0.2" + jest-cli "^29.7.0" + +jit-grunt@^0.10.0: + version "0.10.0" + resolved "https://registry.yarnpkg.com/jit-grunt/-/jit-grunt-0.10.0.tgz#008c3a7fe1e96bd0d84e260ea1fa1783457f79c2" + integrity sha512-eT/f4c9wgZ3buXB7X1JY1w6uNtAV0bhrbOGf/mFmBb0CDNLUETJ/VRoydayWOI54tOoam0cz9RooVCn3QY1WoA== + +jmespath@0.16.0: + version "0.16.0" + resolved "https://registry.yarnpkg.com/jmespath/-/jmespath-0.16.0.tgz#b15b0a85dfd4d930d43e69ed605943c802785076" + integrity sha512-9FzQjJ7MATs1tSpnco1K6ayiYE3figslrXA72G2HQ/n76RzvYlofyi5QM+iX4YRs/pu3yzxlVQSST23+dMDknw== + +"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-yaml@^3.13.1, js-yaml@^3.14.1, js-yaml@~3.14.0: + version "3.14.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" + integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +js-yaml@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + +jsesc@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.0.2.tgz#bb8b09a6597ba426425f2e4a07245c3d00b9343e" + integrity sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g== + +json-buffer@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" + integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== + +json-parse-even-better-errors@^2.3.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" + integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== + +json5@^2.2.3: + version "2.2.3" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" + integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== + +just-extend@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/just-extend/-/just-extend-6.2.0.tgz#b816abfb3d67ee860482e7401564672558163947" + integrity sha512-cYofQu2Xpom82S6qD778jBDpwvvy39s1l/hrYij2u9AMdQcGRpaBu6kY4mVhuno5kJVi1DAz4aiphA2WI1/OAw== + +keyv@^4.5.4: + version "4.5.4" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" + integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== + dependencies: + json-buffer "3.0.1" + +kind-of@^6.0.2: + version "6.0.3" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" + integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== + +kleur@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" + integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== + +kuler@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/kuler/-/kuler-2.0.0.tgz#e2c570a3800388fb44407e851531c1d670b061b3" + integrity sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A== + +lambda-local@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/lambda-local/-/lambda-local-2.2.0.tgz#733d183a4c3f2b16c6499b9ea72cec2f13278eef" + integrity sha512-bPcgpIXbHnVGfI/omZIlgucDqlf4LrsunwoKue5JdZeGybt8L6KyJz2Zu19ffuZwIwLj2NAI2ZyaqNT6/cetcg== + dependencies: + commander "^10.0.1" + dotenv "^16.3.1" + winston "^3.10.0" + +leven@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" + integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== + +levn@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" + integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== + dependencies: + prelude-ls "^1.2.1" + type-check "~0.4.0" + +liftup@~3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/liftup/-/liftup-3.0.1.tgz#1cb81aff0f368464ed3a5f1a7286372d6b1a60ce" + integrity sha512-yRHaiQDizWSzoXk3APcA71eOI/UuhEkNN9DiW2Tt44mhYzX4joFoCZlxsSOF7RyeLlfqzFLQI1ngFq3ggMPhOw== + dependencies: + extend "^3.0.2" + findup-sync "^4.0.0" + fined "^1.2.0" + flagged-respawn "^1.0.1" + is-plain-object "^2.0.4" + object.map "^1.0.1" + rechoir "^0.7.0" + resolve "^1.19.0" + +lines-and-columns@^1.1.6: + version "1.2.4" + resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" + integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== + +livereload-js@^2.3.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/livereload-js/-/livereload-js-2.4.0.tgz#447c31cf1ea9ab52fc20db615c5ddf678f78009c" + integrity sha512-XPQH8Z2GDP/Hwz2PCDrh2mth4yFejwA1OZ/81Ti3LgKyhDcEjsSsqFWZojHG0va/duGd+WyosY7eXLDoOyqcPw== + +locate-path@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" + integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== + dependencies: + p-locate "^4.1.0" + +locate-path@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" + integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== + dependencies: + p-locate "^5.0.0" + +lodash-checkit@^2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/lodash-checkit/-/lodash-checkit-2.4.1.tgz#8b09c6b359a5d4de86f752ff9c231f1db5d23fd4" + integrity sha512-OAg5CqY04/dnsO8izxXqlleuj7z/dOk6yV0pm0TVtRaUwG5v2PGw4XWSIG/dLK0UWYk7g0/TCk8OCf50oVwv6w== + dependencies: + checkit "^0.7.0" + lodash "^4.17.21" + +lodash-match-pattern@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/lodash-match-pattern/-/lodash-match-pattern-2.3.1.tgz#d38f455a8b310bd91f7b2b4378297102a9b473c8" + integrity sha512-dpltpxoTqs94gGFm24VwHDyFh3/eNtqNjKrlnifIBLtnzYq0nAlNM6BIeLdGAfCWC/BwNtiLL1eKZTQpLVnY6A== + dependencies: + chalk "^4.1.0" + he "^1.2.0" + lodash-checkit "^2.4.1" + +lodash.get@^4.4.2: + version "4.4.2" + resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" + integrity sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ== + +lodash.memoize@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" + integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== + +lodash.merge@^4.6.2: + version "4.6.2" + resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" + integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== + +lodash@^4.0.0, lodash@^4.17.10, lodash@^4.17.14, lodash@^4.17.21, lodash@~4.17.19, lodash@~4.17.21: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + +log-symbols@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" + integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== + dependencies: + chalk "^4.1.0" + is-unicode-supported "^0.1.0" + +logform@^2.7.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/logform/-/logform-2.7.0.tgz#cfca97528ef290f2e125a08396805002b2d060d1" + integrity sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ== + dependencies: + "@colors/colors" "1.6.0" + "@types/triple-beam" "^1.3.2" + fecha "^4.2.0" + ms "^2.1.1" + safe-stable-stringify "^2.3.1" + triple-beam "^1.3.0" + +loose-envify@^1.1.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" + integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== + dependencies: + js-tokens "^3.0.0 || ^4.0.0" + +loupe@^2.3.6: + version "2.3.7" + resolved "https://registry.yarnpkg.com/loupe/-/loupe-2.3.7.tgz#6e69b7d4db7d3ab436328013d37d1c8c3540c697" + integrity sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA== + dependencies: + get-func-name "^2.0.1" + +loupe@^3.1.0, loupe@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/loupe/-/loupe-3.1.2.tgz#c86e0696804a02218f2206124c45d8b15291a240" + integrity sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg== + +lru-cache@^10.2.0: + version "10.4.3" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119" + integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ== + +lru-cache@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" + integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== + dependencies: + yallist "^3.0.2" + +make-dir@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-4.0.0.tgz#c3c2307a771277cd9638305f915c29ae741b614e" + integrity sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw== + dependencies: + semver "^7.5.3" + +make-error@^1.1.1, make-error@^1.3.6: + version "1.3.6" + resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" + integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== + +make-iterator@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/make-iterator/-/make-iterator-1.0.1.tgz#29b33f312aa8f547c4a5e490f56afcec99133ad6" + integrity sha512-pxiuXh0iVEq7VM7KMIhs5gxsfxCux2URptUQaXo4iZZJxBAzTPOLE2BumO5dbfVYq/hBJFBR/a1mFDmOx5AGmw== + dependencies: + kind-of "^6.0.2" + +makeerror@1.0.12: + version "1.0.12" + resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.12.tgz#3e5dd2079a82e812e983cc6610c4a2cb0eaa801a" + integrity sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg== + dependencies: + tmpl "1.0.5" + +map-cache@^0.2.0: + version "0.2.2" + resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" + integrity sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg== + +markdown-parser-react@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/markdown-parser-react/-/markdown-parser-react-1.1.2.tgz#5817a708ea1edc33579f436346cba866d58d4792" + integrity sha512-MNLHekU1xOwKZLJK4NMWJDL9pNnJdKx2jdsHfAF4+Y5rF4tD/S/OuNehd4X46/KcJzBfas19pePVcwQoibpeNg== + dependencies: + react "^18.2.0" + +merge-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" + integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== + +merge2@^1.3.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" + integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== + +micromatch@^4.0.2, micromatch@^4.0.4: + version "4.0.8" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" + integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== + dependencies: + braces "^3.0.3" + picomatch "^2.3.1" + +mimic-fn@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" + integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + +minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +minimatch@^5.0.1, minimatch@^5.1.6: + version "5.1.6" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96" + integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== + dependencies: + brace-expansion "^2.0.1" + +minimatch@^9.0.4: + version "9.0.5" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5" + integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow== + dependencies: + brace-expansion "^2.0.1" + +minimatch@~3.0.2, minimatch@~3.0.4: + version "3.0.8" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.8.tgz#5e6a59bd11e2ab0de1cfb843eb2d82e546c321c1" + integrity sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q== + dependencies: + brace-expansion "^1.1.7" + +"minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.1.2: + version "7.1.2" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.2.tgz#93a9626ce5e5e66bd4db86849e7515e92340a707" + integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw== + +mnemonist@0.38.3: + version "0.38.3" + resolved "https://registry.yarnpkg.com/mnemonist/-/mnemonist-0.38.3.tgz#35ec79c1c1f4357cfda2fe264659c2775ccd7d9d" + integrity sha512-2K9QYubXx/NAjv4VLq1d1Ly8pWNC5L3BrixtdkyTegXWJIqY+zLNDhhX/A+ZwWt70tB1S8H4BE8FLYEFyNoOBw== + dependencies: + obliterator "^1.6.1" + +mocha@^11.0.0: + version "11.0.1" + resolved "https://registry.yarnpkg.com/mocha/-/mocha-11.0.1.tgz#85c1c0e806275fe2479245be4ac4a0d81f533aa8" + integrity sha512-+3GkODfsDG71KSCQhc4IekSW+ItCK/kiez1Z28ksWvYhKXV/syxMlerR/sC7whDp7IyreZ4YxceMLdTs5hQE8A== + dependencies: + ansi-colors "^4.1.3" + browser-stdout "^1.3.1" + chokidar "^3.5.3" + debug "^4.3.5" + diff "^5.2.0" + escape-string-regexp "^4.0.0" + find-up "^5.0.0" + glob "^10.4.5" + he "^1.2.0" + js-yaml "^4.1.0" + log-symbols "^4.1.0" + minimatch "^5.1.6" + ms "^2.1.3" + serialize-javascript "^6.0.2" + strip-json-comments "^3.1.1" + supports-color "^8.1.1" + workerpool "^6.5.1" + yargs "^16.2.0" + yargs-parser "^20.2.9" + yargs-unparser "^2.0.0" + +ms@^2.1.1, ms@^2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== + +nise@^6.0.0: + version "6.1.1" + resolved "https://registry.yarnpkg.com/nise/-/nise-6.1.1.tgz#78ea93cc49be122e44cb7c8fdf597b0e8778b64a" + integrity sha512-aMSAzLVY7LyeM60gvBS423nBmIPP+Wy7St7hsb+8/fc1HmeoHJfLO8CKse4u3BtOZvQLJghYPI2i/1WZrEj5/g== + dependencies: + "@sinonjs/commons" "^3.0.1" + "@sinonjs/fake-timers" "^13.0.1" + "@sinonjs/text-encoding" "^0.7.3" + just-extend "^6.2.0" + path-to-regexp "^8.1.0" + +node-int64@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" + integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw== + +node-releases@^2.0.18: + version "2.0.18" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.18.tgz#f010e8d35e2fe8d6b2944f03f70213ecedc4ca3f" + integrity sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g== + +nodemailer@^6.9.12: + version "6.9.16" + resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-6.9.16.tgz#3ebdf6c6f477c571c0facb0727b33892635e0b8b" + integrity sha512-psAuZdTIRN08HKVd/E8ObdV6NO7NTBY3KsC30F7M4H1OnmLCUNaS56FpYxyb26zWLSyYF9Ozch9KYHhHegsiOQ== + +nopt@~3.0.6: + version "3.0.6" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9" + integrity sha512-4GUt3kSEYmk4ITxzB/b9vaIDfUVWN/Ml1Fwl11IlnIG2iaJ9O6WXZ9SrYM9NLI8OCBieN2Y8SWC2oJV0RQ7qYg== + dependencies: + abbrev "1" + +nopt@~4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.3.tgz#a375cad9d02fd921278d954c2254d5aa57e15e48" + integrity sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg== + dependencies: + abbrev "1" + osenv "^0.1.4" + +normalize-path@^3.0.0, normalize-path@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +npm-run-path@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" + integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== + dependencies: + path-key "^3.0.0" + +object-assign@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== + +object-inspect@^1.13.1: + version "1.13.3" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.3.tgz#f14c183de51130243d6d18ae149375ff50ea488a" + integrity sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA== + +object.defaults@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/object.defaults/-/object.defaults-1.1.0.tgz#3a7f868334b407dea06da16d88d5cd29e435fecf" + integrity sha512-c/K0mw/F11k4dEUBMW8naXUuBuhxRCfG7W+yFy8EcijU/rSmazOUd1XAEEe6bC0OuXY4HUKjTJv7xbxIMqdxrA== + dependencies: + array-each "^1.0.1" + array-slice "^1.0.0" + for-own "^1.0.0" + isobject "^3.0.0" + +object.map@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/object.map/-/object.map-1.0.1.tgz#cf83e59dc8fcc0ad5f4250e1f78b3b81bd801d37" + integrity sha512-3+mAJu2PLfnSVGHwIWubpOFLscJANBKuB/6A4CxBstc4aqwQY0FWcsppuy4jU5GSB95yES5JHSI+33AWuS4k6w== + dependencies: + for-own "^1.0.0" + make-iterator "^1.0.0" + +object.pick@^1.2.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747" + integrity sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ== + dependencies: + isobject "^3.0.1" + +obliterator@^1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/obliterator/-/obliterator-1.6.1.tgz#dea03e8ab821f6c4d96a299e17aef6a3af994ef3" + integrity sha512-9WXswnqINnnhOG/5SLimUlzuU1hFJUc8zkwyD59Sd+dPOMf05PmnYG/d6Q7HZ+KmgkZJa1PxRso6QdM3sTNHig== + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + +one-time@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/one-time/-/one-time-1.0.0.tgz#e06bc174aed214ed58edede573b433bbf827cb45" + integrity sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g== + dependencies: + fn.name "1.x.x" + +onetime@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" + integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== + dependencies: + mimic-fn "^2.1.0" + +optionator@^0.9.3: + version "0.9.4" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.4.tgz#7ea1c1a5d91d764fb282139c88fe11e182a3a734" + integrity sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g== + dependencies: + deep-is "^0.1.3" + fast-levenshtein "^2.0.6" + levn "^0.4.1" + prelude-ls "^1.2.1" + type-check "^0.4.0" + word-wrap "^1.2.5" + +os-homedir@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" + integrity sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ== + +os-tmpdir@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" + integrity sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g== + +osenv@^0.1.4: + version "0.1.5" + resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.5.tgz#85cdfafaeb28e8677f416e287592b5f3f49ea410" + integrity sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g== + dependencies: + os-homedir "^1.0.0" + os-tmpdir "^1.0.0" + +p-limit@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" + integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== + dependencies: + p-try "^2.0.0" + +p-limit@^3.0.2, p-limit@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" + integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== + dependencies: + yocto-queue "^0.1.0" + +p-locate@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" + integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== + dependencies: + p-limit "^2.2.0" + +p-locate@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" + integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== + dependencies: + p-limit "^3.0.2" + +p-try@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== + +package-json-from-dist@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz#4f1471a010827a86f94cfd9b0727e36d267de505" + integrity sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw== + +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + dependencies: + callsites "^3.0.0" + +parse-filepath@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/parse-filepath/-/parse-filepath-1.0.2.tgz#a632127f53aaf3d15876f5872f3ffac763d6c891" + integrity sha512-FwdRXKCohSVeXqwtYonZTXtbGJKrn+HNyWDYVcp5yuJlesTwNH4rsmRZ+GrKAPJ5bLpRxESMeS+Rl0VCHRvB2Q== + dependencies: + is-absolute "^1.0.0" + map-cache "^0.2.0" + path-root "^0.1.1" + +parse-json@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" + integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== + dependencies: + "@babel/code-frame" "^7.0.0" + error-ex "^1.3.1" + json-parse-even-better-errors "^2.3.0" + lines-and-columns "^1.1.6" + +parse-passwd@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6" + integrity sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q== + +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== + +path-key@^3.0.0, path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +path-parse@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + +path-root-regex@^0.1.0: + version "0.1.2" + resolved "https://registry.yarnpkg.com/path-root-regex/-/path-root-regex-0.1.2.tgz#bfccdc8df5b12dc52c8b43ec38d18d72c04ba96d" + integrity sha512-4GlJ6rZDhQZFE0DPVKh0e9jmZ5egZfxTkp7bcRDuPlJXbAwhxcl2dINPUAsjLdejqaLsCeg8axcLjIbvBjN4pQ== + +path-root@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/path-root/-/path-root-0.1.1.tgz#9a4a6814cac1c0cd73360a95f32083c8ea4745b7" + integrity sha512-QLcPegTHF11axjfojBIoDygmS2E3Lf+8+jI6wOVmNVenrKSo3mFdSGiIgdSHenczw3wPtlVMQaFVwGmM7BJdtg== + dependencies: + path-root-regex "^0.1.0" + +path-scurry@^1.11.1: + version "1.11.1" + resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.11.1.tgz#7960a668888594a0720b12a911d1a742ab9f11d2" + integrity sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA== + dependencies: + lru-cache "^10.2.0" + minipass "^5.0.0 || ^6.0.2 || ^7.0.0" + +path-to-regexp@^8.1.0: + version "8.2.0" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-8.2.0.tgz#73990cc29e57a3ff2a0d914095156df5db79e8b4" + integrity sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ== + +pathval@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.1.tgz#8534e77a77ce7ac5a2512ea21e0fdb8fcf6c3d8d" + integrity sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ== + +pathval@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/pathval/-/pathval-2.0.0.tgz#7e2550b422601d4f6b8e26f1301bc8f15a741a25" + integrity sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA== + +picocolors@^1.0.0, picocolors@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" + integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== + +picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3, picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +pirates@^4.0.4: + version "4.0.6" + resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.6.tgz#3018ae32ecfcff6c29ba2267cbf21166ac1f36b9" + integrity sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg== + +pkg-dir@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" + integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== + dependencies: + find-up "^4.0.0" + +possible-typed-array-names@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz#89bb63c6fada2c3e90adc4a647beeeb39cc7bf8f" + integrity sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q== + +prelude-ls@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" + integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== + +pretty-format@^29.0.0, pretty-format@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.7.0.tgz#ca42c758310f365bfa71a0bda0a807160b776812" + integrity sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ== + dependencies: + "@jest/schemas" "^29.6.3" + ansi-styles "^5.0.0" + react-is "^18.0.0" + +prompts@^2.0.1: + version "2.4.2" + resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" + integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q== + dependencies: + kleur "^3.0.3" + sisteransi "^1.0.5" + +punycode@1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" + integrity sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw== + +punycode@^2.1.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" + integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== + +pure-rand@^6.0.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/pure-rand/-/pure-rand-6.1.0.tgz#d173cf23258231976ccbdb05247c9787957604f2" + integrity sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA== + +qs@^6.4.0: + version "6.13.1" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.13.1.tgz#3ce5fc72bd3a8171b85c99b93c65dd20b7d1b16e" + integrity sha512-EJPeIn0CYrGu+hli1xilKAPXODtJ12T0sP63Ijx2/khC2JtuaN3JyNIpvmnkmaEtha9ocbG4A4cMcr+TvqvwQg== + dependencies: + side-channel "^1.0.6" + +querystring@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" + integrity sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g== + +queue-microtask@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" + integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== + +randombytes@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" + integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== + dependencies: + safe-buffer "^5.1.0" + +raw-body@~1.1.0: + version "1.1.7" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-1.1.7.tgz#1d027c2bfa116acc6623bca8f00016572a87d425" + integrity sha512-WmJJU2e9Y6M5UzTOkHaM7xJGAPQD8PNzx3bAd2+uhZAim6wDk6dAZxPVYLF67XhbR4hmKGh33Lpmh4XWrCH5Mg== + dependencies: + bytes "1" + string_decoder "0.10" + +react-dom@^18.3.1: + version "18.3.1" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.3.1.tgz#c2265d79511b57d479b3dd3fdfa51536494c5cb4" + integrity sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw== + dependencies: + loose-envify "^1.1.0" + scheduler "^0.23.2" + +react-is@^18.0.0: + version "18.3.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e" + integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg== + +react@^18.2.0, react@^18.3.1: + version "18.3.1" + resolved "https://registry.yarnpkg.com/react/-/react-18.3.1.tgz#49ab892009c53933625bd16b2533fc754cab2891" + integrity sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ== + dependencies: + loose-envify "^1.1.0" + +readable-stream@^3.4.0, readable-stream@^3.6.2: + version "3.6.2" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" + integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + +readdirp@~3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" + integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== + dependencies: + picomatch "^2.2.1" + +rechoir@^0.7.0: + version "0.7.1" + resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.7.1.tgz#9478a96a1ca135b5e88fc027f03ee92d6c645686" + integrity sha512-/njmZ8s1wVeR6pjTZ+0nCnv8SpZNRMT2D1RLOJQESlYFDBvwpTA4KWJpZ+sBJ4+vhjILRcK7JIFdGCdxEAAitg== + dependencies: + resolve "^1.9.0" + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== + +resolve-cwd@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" + integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== + dependencies: + resolve-from "^5.0.0" + +resolve-dir@^1.0.0, resolve-dir@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/resolve-dir/-/resolve-dir-1.0.1.tgz#79a40644c362be82f26effe739c9bb5382046f43" + integrity sha512-R7uiTjECzvOsWSfdM0QKFNBVFcK27aHOUwdvK53BcW8zqnGdYp0Fbj82cy54+2A4P2tFM22J5kRfe1R+lM/1yg== + dependencies: + expand-tilde "^2.0.0" + global-modules "^1.0.0" + +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + +resolve-from@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" + integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== + +resolve-pkg-maps@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz#616b3dc2c57056b5588c31cdf4b3d64db133720f" + integrity sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw== + +resolve.exports@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-2.0.3.tgz#41955e6f1b4013b7586f873749a635dea07ebe3f" + integrity sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A== + +resolve@^1.19.0, resolve@^1.20.0, resolve@^1.9.0: + version "1.22.8" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d" + integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== + dependencies: + is-core-module "^2.13.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +reusify@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" + integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== + +run-parallel@^1.1.9: + version "1.2.0" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" + integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== + dependencies: + queue-microtask "^1.2.2" + +safe-buffer@>=5.1.0, safe-buffer@^5.1.0, safe-buffer@~5.2.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +safe-json-parse@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/safe-json-parse/-/safe-json-parse-1.0.1.tgz#3e76723e38dfdda13c9b1d29a1e07ffee4b30b57" + integrity sha512-o0JmTu17WGUaUOHa1l0FPGXKBfijbxK6qoHzlkihsDXxzBHvJcA7zgviKR92Xs841rX9pK16unfphLq0/KqX7A== + +safe-stable-stringify@^2.3.1: + version "2.5.0" + resolved "https://registry.yarnpkg.com/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz#4ca2f8e385f2831c432a719b108a3bf7af42a1dd" + integrity sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA== + +"safer-buffer@>= 2.1.2 < 3.0.0": + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +sax@1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.1.tgz#7b8e656190b228e81a66aea748480d828cd2d37a" + integrity sha512-8I2a3LovHTOpm7NV5yOyO8IHqgVsfK4+UuySrXU8YXkSRX7k6hCV9b3HrkKCr3nMpgj+0bmocaJJWpvp1oc7ZA== + +sax@>=0.6.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.4.1.tgz#44cc8988377f126304d3b3fc1010c733b929ef0f" + integrity sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg== + +scheduler@^0.23.2: + version "0.23.2" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.2.tgz#414ba64a3b282892e944cf2108ecc078d115cdc3" + integrity sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ== + dependencies: + loose-envify "^1.1.0" + +semver@^6.3.0, semver@^6.3.1: + version "6.3.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" + integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== + +semver@^7.5.3, semver@^7.5.4, semver@^7.6.0, semver@^7.6.3: + version "7.6.3" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143" + integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== + +serialize-javascript@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.2.tgz#defa1e055c83bf6d59ea805d8da862254eb6a6c2" + integrity sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g== + dependencies: + randombytes "^2.1.0" + +set-function-length@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449" + integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg== + dependencies: + define-data-property "^1.1.4" + es-errors "^1.3.0" + function-bind "^1.1.2" + get-intrinsic "^1.2.4" + gopd "^1.0.1" + has-property-descriptors "^1.0.2" + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +side-channel@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.6.tgz#abd25fb7cd24baf45466406b1096b7831c9215f2" + integrity sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA== + dependencies: + call-bind "^1.0.7" + es-errors "^1.3.0" + get-intrinsic "^1.2.4" + object-inspect "^1.13.1" + +signal-exit@^3.0.3, signal-exit@^3.0.7: + version "3.0.7" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" + integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== + +signal-exit@^4.0.1: + version "4.1.0" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" + integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== + +simple-swizzle@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" + integrity sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg== + dependencies: + is-arrayish "^0.3.1" + +sinon@^18.0.1: + version "18.0.1" + resolved "https://registry.yarnpkg.com/sinon/-/sinon-18.0.1.tgz#464334cdfea2cddc5eda9a4ea7e2e3f0c7a91c5e" + integrity sha512-a2N2TDY1uGviajJ6r4D1CyRAkzE9NNVlYOV1wX5xQDuAk0ONgzgRl0EjCQuRCPxOwp13ghsMwt9Gdldujs39qw== + dependencies: + "@sinonjs/commons" "^3.0.1" + "@sinonjs/fake-timers" "11.2.2" + "@sinonjs/samsam" "^8.0.0" + diff "^5.2.0" + nise "^6.0.0" + supports-color "^7" + +sisteransi@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" + integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== + +slash@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" + integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== + +source-map-support@0.5.13: + version "0.5.13" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.13.tgz#31b24a9c2e73c2de85066c0feb7d44767ed52932" + integrity sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map@^0.6.0, source-map@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +sprintf-js@^1.1.1: + version "1.1.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.3.tgz#4914b903a2f8b685d17fdf78a70e917e872e444a" + integrity sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA== + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== + +stack-trace@0.0.x: + version "0.0.10" + resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0" + integrity sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg== + +stack-utils@^2.0.3: + version "2.0.6" + resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.6.tgz#aaf0748169c02fc33c8232abccf933f54a1cc34f" + integrity sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ== + dependencies: + escape-string-regexp "^2.0.0" + +string-length@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a" + integrity sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ== + dependencies: + char-regex "^1.0.2" + strip-ansi "^6.0.0" + +string-template@~0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/string-template/-/string-template-0.2.1.tgz#42932e598a352d01fc22ec3367d9d84eec6c9add" + integrity sha512-Yptehjogou2xm4UJbxJ4CxgZx12HBfeystp0y3x7s4Dj32ltVVG1Gg8YhKjHZkHicuKpZX/ffilA8505VbUbpw== + +"string-width-cjs@npm:string-width@^4.2.0": + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string-width@^5.0.1, string-width@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" + integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA== + dependencies: + eastasianwidth "^0.2.0" + emoji-regex "^9.2.2" + strip-ansi "^7.0.1" + +string_decoder@0.10: + version "0.10.31" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" + integrity sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ== + +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@^7.0.1: + version "7.1.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" + integrity sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ== + dependencies: + ansi-regex "^6.0.1" + +strip-bom@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" + integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== + +strip-final-newline@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" + integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== + +strip-json-comments@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + +strnum@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/strnum/-/strnum-1.0.5.tgz#5c4e829fe15ad4ff0d20c3db5ac97b73c9b072db" + integrity sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA== + +supports-color@^7, supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +supports-color@^8.0.0, supports-color@^8.1.1: + version "8.1.1" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== + dependencies: + has-flag "^4.0.0" + +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + +test-exclude@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" + integrity sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w== + dependencies: + "@istanbuljs/schema" "^0.1.2" + glob "^7.1.4" + minimatch "^3.0.4" + +text-hex@1.0.x: + version "1.0.0" + resolved "https://registry.yarnpkg.com/text-hex/-/text-hex-1.0.0.tgz#69dc9c1b17446ee79a92bf5b884bb4b9127506f5" + integrity sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg== + +tiny-lr@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/tiny-lr/-/tiny-lr-1.1.1.tgz#9fa547412f238fedb068ee295af8b682c98b2aab" + integrity sha512-44yhA3tsaRoMOjQQ+5v5mVdqef+kH6Qze9jTpqtVufgYjYt08zyZAwNwwVBj3i1rJMnR52IxOW0LK0vBzgAkuA== + dependencies: + body "^5.1.0" + debug "^3.1.0" + faye-websocket "~0.10.0" + livereload-js "^2.3.0" + object-assign "^4.1.0" + qs "^6.4.0" + +tinyrainbow@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/tinyrainbow/-/tinyrainbow-1.2.0.tgz#5c57d2fc0fb3d1afd78465c33ca885d04f02abb5" + integrity sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ== + +tinyspy@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/tinyspy/-/tinyspy-3.0.2.tgz#86dd3cf3d737b15adcf17d7887c84a75201df20a" + integrity sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q== + +tmpl@1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" + integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw== + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +triple-beam@^1.3.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/triple-beam/-/triple-beam-1.4.1.tgz#6fde70271dc6e5d73ca0c3b24e2d92afb7441984" + integrity sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg== + +ts-api-utils@^1.3.0: + version "1.4.3" + resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.4.3.tgz#bfc2215fe6528fecab2b0fba570a2e8a4263b064" + integrity sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw== + +ts-jest@^29.2.5: + version "29.2.5" + resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.2.5.tgz#591a3c108e1f5ebd013d3152142cb5472b399d63" + integrity sha512-KD8zB2aAZrcKIdGk4OwpJggeLcH1FgrICqDSROWqlnJXGCXK4Mn6FcdK2B6670Xr73lHMG1kHw8R87A0ecZ+vA== + dependencies: + bs-logger "^0.2.6" + ejs "^3.1.10" + fast-json-stable-stringify "^2.1.0" + jest-util "^29.0.0" + json5 "^2.2.3" + lodash.memoize "^4.1.2" + make-error "^1.3.6" + semver "^7.6.3" + yargs-parser "^21.1.1" + +ts-node@^10.9.2: + version "10.9.2" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.2.tgz#70f021c9e185bccdca820e26dc413805c101c71f" + integrity sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ== + dependencies: + "@cspotcode/source-map-support" "^0.8.0" + "@tsconfig/node10" "^1.0.7" + "@tsconfig/node12" "^1.0.7" + "@tsconfig/node14" "^1.0.0" + "@tsconfig/node16" "^1.0.2" + acorn "^8.4.1" + acorn-walk "^8.1.1" + arg "^4.1.0" + create-require "^1.1.0" + diff "^4.0.1" + make-error "^1.1.1" + v8-compile-cache-lib "^3.0.1" + yn "3.1.1" + +tslib@^2.1.0, tslib@^2.6.2, tslib@^2.8.0: + version "2.8.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" + integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== + +tsx@^4.19.2: + version "4.19.2" + resolved "https://registry.yarnpkg.com/tsx/-/tsx-4.19.2.tgz#2d7814783440e0ae42354d0417d9c2989a2ae92c" + integrity sha512-pOUl6Vo2LUq/bSa8S5q7b91cgNSjctn9ugq/+Mvow99qW6x/UZYwzxy/3NmqoT66eHYfCVvFvACC58UBPFf28g== + dependencies: + esbuild "~0.23.0" + get-tsconfig "^4.7.5" + optionalDependencies: + fsevents "~2.3.3" + +type-check@^0.4.0, type-check@~0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" + integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== + dependencies: + prelude-ls "^1.2.1" + +type-detect@4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" + integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== + +type-detect@^4.0.0, type-detect@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.1.0.tgz#deb2453e8f08dcae7ae98c626b13dddb0155906c" + integrity sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw== + +type-fest@^0.21.3: + version "0.21.3" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" + integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== + +typescript@5.6.3: + version "5.6.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.6.3.tgz#5f3449e31c9d94febb17de03cc081dd56d81db5b" + integrity sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw== + +unc-path-regex@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/unc-path-regex/-/unc-path-regex-0.1.2.tgz#e73dd3d7b0d7c5ed86fbac6b0ae7d8c6a69d50fa" + integrity sha512-eXL4nmJT7oCpkZsHZUOJo8hcX3GbsiDOa0Qu9F646fi8dT3XuSVopVqAcEiVzSKKH7UoDti23wNX3qGFxcW5Qg== + +underscore.string@~3.3.5: + version "3.3.6" + resolved "https://registry.yarnpkg.com/underscore.string/-/underscore.string-3.3.6.tgz#ad8cf23d7423cb3b53b898476117588f4e2f9159" + integrity sha512-VoC83HWXmCrF6rgkyxS9GHv8W9Q5nhMKho+OadDJGzL2oDYbYEppBaCMH6pFlwLeqj2QS+hhkw2kpXkSdD1JxQ== + dependencies: + sprintf-js "^1.1.1" + util-deprecate "^1.0.2" + +undici-types@~6.19.2: + version "6.19.8" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.19.8.tgz#35111c9d1437ab83a7cdc0abae2f26d88eda0a02" + integrity sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw== + +undici-types@~6.20.0: + version "6.20.0" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.20.0.tgz#8171bf22c1f588d1554d55bf204bc624af388433" + integrity sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg== + +update-browserslist-db@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz#80846fba1d79e82547fb661f8d141e0945755fe5" + integrity sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A== + dependencies: + escalade "^3.2.0" + picocolors "^1.1.0" + +uri-js@^4.2.2: + version "4.4.1" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + +url@0.10.3: + version "0.10.3" + resolved "https://registry.yarnpkg.com/url/-/url-0.10.3.tgz#021e4d9c7705f21bbf37d03ceb58767402774c64" + integrity sha512-hzSUW2q06EqL1gKM/a+obYHLIO6ct2hwPuviqTTOcfFVc61UbfJ2Q32+uGL/HCPxKqrdGB5QUwIe7UqlDgwsOQ== + dependencies: + punycode "1.3.2" + querystring "0.2.0" + +util-deprecate@^1.0.1, util-deprecate@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== + +util@^0.12.4: + version "0.12.5" + resolved "https://registry.yarnpkg.com/util/-/util-0.12.5.tgz#5f17a6059b73db61a875668781a1c2b136bd6fbc" + integrity sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA== + dependencies: + inherits "^2.0.3" + is-arguments "^1.0.4" + is-generator-function "^1.0.7" + is-typed-array "^1.1.3" + which-typed-array "^1.1.2" + +uuid@8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.0.0.tgz#bc6ccf91b5ff0ac07bbcdbf1c7c4e150db4dbb6c" + integrity sha512-jOXGuXZAWdsTH7eZLtyXMqUb9EcWMGZNbL9YcGBJl4MH4nrxHmZJhEHvyLFrkxo+28uLb/NYRcStH48fnD0Vzw== + +uuid@^9.0.1: + version "9.0.1" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.1.tgz#e188d4c8853cc722220392c424cd637f32293f30" + integrity sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA== + +v8-compile-cache-lib@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" + integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== + +v8-to-istanbul@^9.0.1: + version "9.3.0" + resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz#b9572abfa62bd556c16d75fdebc1a411d5ff3175" + integrity sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA== + dependencies: + "@jridgewell/trace-mapping" "^0.3.12" + "@types/istanbul-lib-coverage" "^2.0.1" + convert-source-map "^2.0.0" + +v8flags@~3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/v8flags/-/v8flags-3.2.0.tgz#b243e3b4dfd731fa774e7492128109a0fe66d656" + integrity sha512-mH8etigqMfiGWdeXpaaqGfs6BndypxusHHcv2qSHyZkGEznCd/qAXCWWRzeowtL54147cktFOC4P5y+kl8d8Jg== + dependencies: + homedir-polyfill "^1.0.1" + +walker@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f" + integrity sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ== + dependencies: + makeerror "1.0.12" + +watchpack@^2.0.0-beta.10: + version "2.4.2" + resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.2.tgz#2feeaed67412e7c33184e5a79ca738fbd38564da" + integrity sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw== + dependencies: + glob-to-regexp "^0.4.1" + graceful-fs "^4.1.2" + +websocket-driver@>=0.5.1: + version "0.7.4" + resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.4.tgz#89ad5295bbf64b480abcba31e4953aca706f5760" + integrity sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg== + dependencies: + http-parser-js ">=0.5.1" + safe-buffer ">=5.1.0" + websocket-extensions ">=0.1.1" + +websocket-extensions@>=0.1.1: + version "0.1.4" + resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42" + integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg== + +which-typed-array@^1.1.14, which-typed-array@^1.1.2: + version "1.1.16" + resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.16.tgz#db4db429c4706feca2f01677a144278e4a8c216b" + integrity sha512-g+N+GAWiRj66DngFwHvISJd+ITsyphZvD1vChfVg6cEdnzy53GzB3oy0fUNlvhz7H7+MiqhYr26qxQShCpKTTQ== + dependencies: + available-typed-arrays "^1.0.7" + call-bind "^1.0.7" + for-each "^0.3.3" + gopd "^1.0.1" + has-tostringtag "^1.0.2" + +which@^1.2.14: + version "1.3.1" + resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" + integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== + dependencies: + isexe "^2.0.0" + +which@^2.0.1, which@~2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +winston-transport@^4.9.0: + version "4.9.0" + resolved "https://registry.yarnpkg.com/winston-transport/-/winston-transport-4.9.0.tgz#3bba345de10297654ea6f33519424560003b3bf9" + integrity sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A== + dependencies: + logform "^2.7.0" + readable-stream "^3.6.2" + triple-beam "^1.3.0" + +winston@^3.10.0: + version "3.17.0" + resolved "https://registry.yarnpkg.com/winston/-/winston-3.17.0.tgz#74b8665ce9b4ea7b29d0922cfccf852a08a11423" + integrity sha512-DLiFIXYC5fMPxaRg832S6F5mJYvePtmO5G9v9IgUFPhXm9/GkXarH/TUrBAVzhTCzAj9anE/+GjrgXp/54nOgw== + dependencies: + "@colors/colors" "^1.6.0" + "@dabh/diagnostics" "^2.0.2" + async "^3.2.3" + is-stream "^2.0.0" + logform "^2.7.0" + one-time "^1.0.0" + readable-stream "^3.4.0" + safe-stable-stringify "^2.3.1" + stack-trace "0.0.x" + triple-beam "^1.3.0" + winston-transport "^4.9.0" + +word-wrap@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" + integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== + +workerpool@^6.5.1: + version "6.5.1" + resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.5.1.tgz#060f73b39d0caf97c6db64da004cd01b4c099544" + integrity sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA== + +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrap-ansi@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" + integrity sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ== + dependencies: + ansi-styles "^6.1.0" + string-width "^5.0.1" + strip-ansi "^7.0.1" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + +write-file-atomic@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-4.0.2.tgz#a9df01ae5b77858a027fd2e80768ee433555fcfd" + integrity sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg== + dependencies: + imurmurhash "^0.1.4" + signal-exit "^3.0.7" + +xml2js@0.6.2: + version "0.6.2" + resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.6.2.tgz#dd0b630083aa09c161e25a4d0901e2b2a929b499" + integrity sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA== + dependencies: + sax ">=0.6.0" + xmlbuilder "~11.0.0" + +xmlbuilder@~11.0.0: + version "11.0.1" + resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-11.0.1.tgz#be9bae1c8a046e76b31127726347d0ad7002beb3" + integrity sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA== + +y18n@^5.0.5: + version "5.0.8" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== + +yallist@^3.0.2: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" + integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== + +yargs-parser@^20.2.2, yargs-parser@^20.2.9: + version "20.2.9" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" + integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== + +yargs-parser@^21.1.1: + version "21.1.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" + integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== + +yargs-unparser@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/yargs-unparser/-/yargs-unparser-2.0.0.tgz#f131f9226911ae5d9ad38c432fe809366c2325eb" + integrity sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA== + dependencies: + camelcase "^6.0.0" + decamelize "^4.0.0" + flat "^5.0.2" + is-plain-obj "^2.1.0" + +yargs@^16.2.0: + version "16.2.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" + integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== + dependencies: + cliui "^7.0.2" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.0" + y18n "^5.0.5" + yargs-parser "^20.2.2" + +yargs@^17.3.1: + version "17.7.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" + integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== + dependencies: + cliui "^8.0.1" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.3" + y18n "^5.0.5" + yargs-parser "^21.1.1" + +yn@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" + integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== + +yocto-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== + +zod@^3.23.8: + version "3.23.8" + resolved "https://registry.yarnpkg.com/zod/-/zod-3.23.8.tgz#e37b957b5d52079769fb8097099b592f0ef4067d" + integrity sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g== diff --git a/backend/compact-connect-ui-app/pipeline/__init__.py b/backend/compact-connect-ui-app/pipeline/__init__.py new file mode 100644 index 000000000..eacc3d6dc --- /dev/null +++ b/backend/compact-connect-ui-app/pipeline/__init__.py @@ -0,0 +1,399 @@ +import json + +from aws_cdk import Environment, RemovalPolicy +from aws_cdk.aws_iam import Effect, PolicyStatement +from aws_cdk.aws_kms import IKey, Key +from aws_cdk.aws_s3 import IBucket +from aws_cdk.aws_sns import ITopic +from aws_cdk.aws_ssm import StringParameter +from aws_cdk.pipelines import CodePipeline as CdkCodePipeline +from cdk_nag import NagSuppressions +from common_constructs.access_logs_bucket import AccessLogsBucket +from common_constructs.alarm_topic import AlarmTopic +from common_constructs.stack import Stack +from constructs import Construct + +from pipeline.frontend_pipeline import FrontendPipeline +from pipeline.frontend_stage import FrontendStage +from pipeline.synth_substitute_stage import SynthSubstituteStage + +TEST_ENVIRONMENT_NAME = 'test' +BETA_ENVIRONMENT_NAME = 'beta' +PROD_ENVIRONMENT_NAME = 'prod' + +ALLOWED_ENVIRONMENT_NAMES = [TEST_ENVIRONMENT_NAME, BETA_ENVIRONMENT_NAME, PROD_ENVIRONMENT_NAME] + +DEPLOY_ENVIRONMENT_NAME = 'deploy' + +# Action constants +ACTION_CONTEXT_KEY = 'action' +PIPELINE_STACK_CONTEXT_KEY = 'pipelineStack' +PIPELINE_SYNTH_ACTION = 'pipelineSynth' +BOOTSTRAP_DEPLOY_ACTION = 'bootstrapDeploy' + + +class DeploymentResourcesStack(Stack): + """Stack that manages all shared resources for all pipeline stacks.""" + + def __init__( + self, + scope: Construct, + construct_id: str, + **kwargs, + ): + super().__init__(scope, construct_id, environment_name='deploy', **kwargs) + + pipeline_context_parameter_name = f'{DEPLOY_ENVIRONMENT_NAME}-compact-connect-context' + + # Fetch ssm_context if not provided locally + self.parameter = StringParameter.from_string_parameter_name( + self, + 'PipelineContext', + string_parameter_name=pipeline_context_parameter_name, + ) + value = StringParameter.value_from_lookup(self, self.parameter.parameter_name) + # When CDK runs for the first time, it synthesizes fully without actually retrieving the SSM Parameter + # value. It, instead, populates parameters and other look-ups with dummy values, synthesizes, collects all + # the look-ups together, populates them for real, then re-synthesizes with real values. + # To accommodate this pattern, we have to replace this dummy value with one that will actually + # let CDK complete its first round of synthesis, so that it can get to its second, real, synthesis. + if value != f'dummy-value-for-{pipeline_context_parameter_name}': + self.ssm_context = json.loads(value) + else: + with open(f'cdk.context.{DEPLOY_ENVIRONMENT_NAME}-example.json') as f: + self.ssm_context = json.load(f)['ssm_context'] + + self.deploy_environment_context = self.ssm_context['environments'][DEPLOY_ENVIRONMENT_NAME] + + self.pipeline_shared_encryption_key = Key( + self, + 'PipelineSharedEncryptionKey', + enable_key_rotation=True, + alias='pipeline-shared-encryption-key', + removal_policy=RemovalPolicy.RETAIN, + ) + + notifications = self.deploy_environment_context.get('notifications', {}) + self.pipeline_alarm_topic = AlarmTopic( + self, + 'AlarmTopic', + master_key=self.pipeline_shared_encryption_key, + email_subscriptions=notifications.get('email', []), + slack_subscriptions=notifications.get('slack', []), + ) + + self.pipeline_access_logs_bucket = AccessLogsBucket( + self, + 'AccessLogsBucket', + removal_policy=RemovalPolicy.RETAIN, + auto_delete_objects=False, + ) + + NagSuppressions.add_resource_suppressions_by_path( + self, + f'{self.pipeline_access_logs_bucket.node.path}/Resource', + suppressions=[ + { + 'id': 'HIPAA.Security-S3BucketLoggingEnabled', + 'reason': 'This is the access logging bucket.', + }, + ], + ) + + +class BasePipelineStack(Stack): + """Base stack with common functionality for all pipeline stacks (both backend and frontend).""" + + def __init__( + self, + scope: Construct, + construct_id: str, + environment_name: str, + env: Environment, + removal_policy: RemovalPolicy, + pipeline_access_logs_bucket: IBucket, + **kwargs, + ): + super().__init__(scope, construct_id, environment_name='pipeline', env=env, **kwargs) + + self.env = env + self.environment_name = environment_name + self.removal_policy = removal_policy + self.access_logs_bucket = pipeline_access_logs_bucket + + pipeline_context_parameter_name = f'{self.environment_name}-compact-connect-context' + + # Fetch ssm_context if not provided locally + self.parameter = StringParameter.from_string_parameter_name( + self, + 'PipelineContext', + string_parameter_name=pipeline_context_parameter_name, + ) + value = StringParameter.value_from_lookup(self, self.parameter.parameter_name) + # When CDK runs for the first time, it synthesizes fully without actually retrieving the SSM Parameter + # value. It, instead, populates parameters and other look-ups with dummy values, synthesizes, collects all + # the look-ups together, populates them for real, then re-synthesizes with real values. + # To accommodate this pattern, we have to replace this dummy value with one that will actually + # let CDK complete its first round of synthesis, so that it can get to its second, real, synthesis. + if value != f'dummy-value-for-{pipeline_context_parameter_name}': + self.ssm_context = json.loads(value) + else: + with open(f'cdk.context.{environment_name}-example.json') as f: + self.ssm_context = json.load(f)['ssm_context'] + + self.pipeline_environment_context = self.ssm_context['environments']['pipeline'] + self.connection_arn = self.pipeline_environment_context['connection_arn'] + self.github_repo_string = self.ssm_context['github_repo_string'] + self.backup_config = self.ssm_context.get('backup_config', {}) + self.app_name = self.ssm_context['app_name'] + + def _add_pipeline_cdk_assume_role_policy(self, pipeline: CdkCodePipeline): + pipeline.synth_project.add_to_role_policy( + PolicyStatement( + effect=Effect.ALLOW, + actions=['sts:AssumeRole'], + resources=[ + self.format_arn( + partition=self.partition, + service='iam', + region='', + account='*', + resource='role', + resource_name='cdk-hnb659fds-lookup-role-*', + ), + ], + ), + ) + + def _get_frontend_pipeline_name(self): + if self.environment_name not in ALLOWED_ENVIRONMENT_NAMES: + raise ValueError(f'Environment name must be one of {ALLOWED_ENVIRONMENT_NAMES}') + + return f'{self.environment_name}-compactConnect-frontendPipeline' + + def _get_frontend_pipeline_arn(self): + pipeline_name = self._get_frontend_pipeline_name() + + return self.format_arn( + partition=self.partition, + service='codepipeline', + region=self.env.region, + account=self.env.account, + resource=pipeline_name, + ) + + +class BaseFrontendPipelineStack(BasePipelineStack): + """ + Base class for frontend pipeline stacks. + Implements common functionality for all frontend pipeline stacks. + """ + + def __init__( + self, + scope: Construct, + construct_id: str, + environment_name: str, + env: Environment, + removal_policy: RemovalPolicy, + pipeline_access_logs_bucket: IBucket, + **kwargs, + ): + super().__init__( + scope, + construct_id, + environment_name=environment_name, + env=env, + removal_policy=removal_policy, + pipeline_access_logs_bucket=pipeline_access_logs_bucket, + **kwargs, + ) + + def _determine_frontend_stage(self, construct_id, environment_name, environment_context): + """ + Return either a real FrontendStage or a SynthSubstituteStage depending on pipeline synthesis context. + + This method centralizes the stage creation logic to conditionally create a lightweight substitute + stage during pipeline synthesis when the stage is not part of the pipeline being synthesized. + """ + # Check if we're in pipeline synthesis mode and if we're synthesizing this specific pipeline + action = self.node.try_get_context('action') + pipeline_stack_name = self.node.try_get_context('pipelineStack') + + # If we're in pipeline synthesis mode and this is not the pipeline being synthesized, + # use a lightweight substitute stage + if ( + action == PIPELINE_SYNTH_ACTION and pipeline_stack_name != self.stack_name + ) or action == BOOTSTRAP_DEPLOY_ACTION: + return SynthSubstituteStage( + self, + 'SubstituteFrontendStage', + environment_context=environment_context, + ) + + # Otherwise, use the real stage for deployment + return FrontendStage( + self, + construct_id, + environment_name=environment_name, + environment_context=environment_context, + ) + + +class TestFrontendPipelineStack(BaseFrontendPipelineStack): + """Pipeline stack for the test frontend environment.""" + + def __init__( + self, + scope: Construct, + construct_id: str, + *, + pipeline_shared_encryption_key: IKey, + pipeline_alarm_topic: ITopic, + pipeline_access_logs_bucket: IBucket, + cdk_path: str, + **kwargs, + ): + super().__init__( + scope, + construct_id, + environment_name=TEST_ENVIRONMENT_NAME, + removal_policy=RemovalPolicy.DESTROY, + pipeline_access_logs_bucket=pipeline_access_logs_bucket, + **kwargs, + ) + + # Allows us to override the default branching scheme for the test environment, via context variable + pre_prod_trigger_branch = self.pipeline_environment_context.get('pre_prod_trigger_branch', 'development') + + self.pre_prod_frontend_pipeline = FrontendPipeline( + self, + 'TestFrontendPipeline', + pipeline_name=self._get_frontend_pipeline_name(), + github_repo_string=self.github_repo_string, + cdk_path=cdk_path, + connection_arn=self.connection_arn, + source_branch=pre_prod_trigger_branch, + encryption_key=pipeline_shared_encryption_key, + alarm_topic=pipeline_alarm_topic, + access_logs_bucket=self.access_logs_bucket, + ssm_parameter=self.parameter, + pipeline_stack_name=self.stack_name, + environment_context=self.pipeline_environment_context, + self_mutation=True, + removal_policy=self.removal_policy, + ) + + self.pre_prod_frontend_stage = self._determine_frontend_stage( + construct_id='TestFrontend', + environment_name=TEST_ENVIRONMENT_NAME, + environment_context=self.ssm_context['environments'][TEST_ENVIRONMENT_NAME], + ) + + self.pre_prod_frontend_pipeline.add_stage(self.pre_prod_frontend_stage) + self.pre_prod_frontend_pipeline.build_pipeline() + self._add_pipeline_cdk_assume_role_policy(self.pre_prod_frontend_pipeline) + + +class BetaFrontendPipelineStack(BaseFrontendPipelineStack): + """Pipeline stack for the beta frontend environment.""" + + def __init__( + self, + scope: Construct, + construct_id: str, + *, + pipeline_shared_encryption_key: IKey, + pipeline_alarm_topic: ITopic, + pipeline_access_logs_bucket: IBucket, + cdk_path: str, + **kwargs, + ): + super().__init__( + scope, + construct_id, + environment_name=BETA_ENVIRONMENT_NAME, + removal_policy=RemovalPolicy.RETAIN, + pipeline_access_logs_bucket=pipeline_access_logs_bucket, + **kwargs, + ) + + self.beta_frontend_pipeline = FrontendPipeline( + self, + 'BetaFrontendPipeline', + pipeline_name=self._get_frontend_pipeline_name(), + github_repo_string=self.github_repo_string, + cdk_path=cdk_path, + connection_arn=self.connection_arn, + source_branch='main', + encryption_key=pipeline_shared_encryption_key, + alarm_topic=pipeline_alarm_topic, + access_logs_bucket=self.access_logs_bucket, + ssm_parameter=self.parameter, + pipeline_stack_name=self.stack_name, + environment_context=self.pipeline_environment_context, + self_mutation=True, + removal_policy=self.removal_policy, + ) + + self.beta_frontend_stage = self._determine_frontend_stage( + construct_id='BetaFrontend', + environment_name=BETA_ENVIRONMENT_NAME, + environment_context=self.ssm_context['environments'][BETA_ENVIRONMENT_NAME], + ) + + self.beta_frontend_pipeline.add_stage(self.beta_frontend_stage) + self.beta_frontend_pipeline.build_pipeline() + self._add_pipeline_cdk_assume_role_policy(self.beta_frontend_pipeline) + + +class ProdFrontendPipelineStack(BaseFrontendPipelineStack): + """Pipeline stack for the production frontend environment.""" + + def __init__( + self, + scope: Construct, + construct_id: str, + *, + pipeline_shared_encryption_key: IKey, + pipeline_alarm_topic: ITopic, + pipeline_access_logs_bucket: IBucket, + cdk_path: str, + **kwargs, + ): + super().__init__( + scope, + construct_id, + environment_name=PROD_ENVIRONMENT_NAME, + removal_policy=RemovalPolicy.RETAIN, + pipeline_access_logs_bucket=pipeline_access_logs_bucket, + **kwargs, + ) + + self.prod_frontend_pipeline = FrontendPipeline( + self, + 'ProdFrontendPipeline', + pipeline_name=self._get_frontend_pipeline_name(), + github_repo_string=self.github_repo_string, + cdk_path=cdk_path, + connection_arn=self.connection_arn, + source_branch='main', + encryption_key=pipeline_shared_encryption_key, + alarm_topic=pipeline_alarm_topic, + access_logs_bucket=self.access_logs_bucket, + ssm_parameter=self.parameter, + pipeline_stack_name=self.stack_name, + environment_context=self.pipeline_environment_context, + self_mutation=True, + removal_policy=self.removal_policy, + ) + + self.prod_frontend_stage = self._determine_frontend_stage( + construct_id='ProdFrontend', + environment_name=PROD_ENVIRONMENT_NAME, + environment_context=self.ssm_context['environments'][PROD_ENVIRONMENT_NAME], + ) + + self.prod_frontend_pipeline.add_stage(self.prod_frontend_stage) + self.prod_frontend_pipeline.build_pipeline() + self._add_pipeline_cdk_assume_role_policy(self.prod_frontend_pipeline) diff --git a/backend/compact-connect-ui-app/pipeline/design/.!35012!pipeline-architecture.pdf b/backend/compact-connect-ui-app/pipeline/design/.!35012!pipeline-architecture.pdf new file mode 100644 index 0000000000000000000000000000000000000000..e284855032cb0402825a714221c3a4a48b24824f GIT binary patch literal 353 zcmZ9H&1!={6ovOb#cTrUB3?(0p(P0UL7FxY(RL+S=s2ipoROL9qPzaQnNOCnU0Axf zaJl!K!*^!!K3Y(FWzB|<;dA&JzAZ`|B8!W)Uc<|K$05!uu3;y8frP?yQ|in!lH?qQ z78m6x^(V{5*iv{+4&%&g#7;i|R1MaBVKdnR)73(%$^^Du&TuD%;0*7=&|Cy^fqMS$ z1&?rm#E3HL-rEjY`hgt~*ISKy;Mx8e(!Riv_1K8FU+Rqz5+Oy^GmLX(at-`es*=$kT literal 0 HcmV?d00001 diff --git a/backend/compact-connect-ui-app/pipeline/design/README.md b/backend/compact-connect-ui-app/pipeline/design/README.md new file mode 100644 index 000000000..26d8ad82d --- /dev/null +++ b/backend/compact-connect-ui-app/pipeline/design/README.md @@ -0,0 +1,96 @@ +# CDK Pipeline Architecture Design + +[View Pipeline Architecture (PDF)](./pipeline-architecture.pdf) + +## Overview + +The CompactConnect CI/CD pipeline architecture implements an optimized deployment strategy built around AWS CDK Pipelines (see https://docs.aws.amazon.com/cdk/v2/guide/cdk_pipeline.html). It follows a multi-pipeline approach with separate backend and frontend pipelines to improve deployment speed, reliability, and security. + +## Key Components + +- **Backend Pipelines**: Deploy infrastructure resources and backend components first +- **Frontend Pipelines**: Deploy frontend applications with backend configuration values +- **Deployment Resources Stack**: Shared resources used by all pipeline stacks across all environments +- **Environments**: Test, Beta, and Production environments + +## Pipeline Flow + +1. GitHub push → Backend Pipeline +2. Backend Pipeline successful completion → Trigger Frontend Pipeline +3. Frontend Pipeline deploys web application using configuration values from Backend + +Commits pushed to the 'development' branch trigger the test pipelines. Commits pushed to the 'main' branch trigger the beta and prod pipelines. + +## Self-Mutation Feature and Optimization + +### Understanding CDK Pipeline Self-Mutation + +AWS CDK Pipelines include a powerful "self-mutation" feature that allows the pipeline to update itself. When code changes affecting the pipeline's structure are pushed, the pipeline: + +1. Executes with its current configuration +2. Synthesizes CloudFormation templates for all stacks in the app +3. Deploys a "self-mutation" step that updates the pipeline's own definition +4. Continues deployment with the updated pipeline definition + +While powerful, this feature presents challenges: + +1. **Performance Impact**: By default, CDK synthesizes all stacks in the application even when only one pipeline needs to be updated. This can be extremely slow, especially for complex applications. + +2. **Unnecessary Processing**: Every pipeline synthesis includes bundling operations (like frontend builds) even when those components aren't changing. + +### The SynthSubstituteStage and SynthSubstituteStack Solution + +To address these challenges, we've implemented the `SynthSubstituteStage` and `SynthSubstituteStack` classes that act as lightweight placeholders during the synthesis process: + +#### How It Works + +1. During pipeline synthesis, we pass in cdk context variables to determine which specific pipeline is being synthesized. + +2. For any stage that isn't part of the current pipeline being synthesized, we replace it with a `SynthSubstituteStage` containing a minimal `SynthSubstituteStack`. + +3. The substitute stack synths a single SSM parameter resource, dramatically reducing synthesis time compared to full application stacks. + +## Implementation Details + +The substitution mechanism relies on CDK context values which we pass in during the CDK synth step of the pipeline definition (see the [BackendPipeline](../backend_pipeline.py) and [FrontendPipeline](../frontend_pipeline.py) class constructors, specifically the `synth.commands` property): + +```python +commands=[ + ... other commands + # Only synthesize the specific pipeline stack needed + f'cdk synth --context pipelineStack={pipeline_stack_name} --context action=pipelineSynth', +], +``` +The following context values are used to determine which pipeline to fully synthesize: + +- `action`: Specifies the current action (e.g., `pipelineSynth`, `bootstrapDeploy`) +- `pipelineStack`: The specific pipeline stack being synthesized + +In the pipeline stack classes, the `_determine_backend_stage` and `_determine_frontend_stage` methods handle the stage substitution logic: + +```python +def _determine_backend_stage(self, construct_id, app_name, environment_name, environment_context): + # Check if we're in pipeline synthesis mode and if we're synthesizing this specific pipeline + action = self.node.try_get_context('action') + pipeline_stack_name = self.node.try_get_context('pipelineStack') + + # Use substitute stage if not synthesizing this specific pipeline or during bootstrap + if (action == PIPELINE_SYNTH_ACTION and pipeline_stack_name != self.stack_name) or action == BOOTSTRAP_DEPLOY_ACTION: + return SynthSubstituteStage( + self, + 'SubstituteBackendStage', + environment_context=environment_context, + ) + + # Otherwise, use the real stage + return BackendStage( + self, + construct_id, + app_name=app_name, + environment_name=environment_name, + environment_context=environment_context, + ) +``` + +# Bootstrapping the piplines +See this [README.md](../../README.md) for details on performing a bootstrap deployment of the pipelines. diff --git a/backend/compact-connect-ui-app/pipeline/design/pipeline-architecture.pdf b/backend/compact-connect-ui-app/pipeline/design/pipeline-architecture.pdf new file mode 100644 index 0000000000000000000000000000000000000000..c5146c2e2864567e8077cee448b9ef6524bb8bc7 GIT binary patch literal 89240 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%0A=wxbf58988*?(rcE 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

lVHA>R}}g8ztm5Bg=(YkiTeF1 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 literal 0 HcmV?d00001 diff --git a/backend/compact-connect/pipeline/frontend_pipeline.py b/backend/compact-connect-ui-app/pipeline/frontend_pipeline.py similarity index 100% rename from backend/compact-connect/pipeline/frontend_pipeline.py rename to backend/compact-connect-ui-app/pipeline/frontend_pipeline.py diff --git a/backend/compact-connect/pipeline/frontend_stage.py b/backend/compact-connect-ui-app/pipeline/frontend_stage.py similarity index 99% rename from backend/compact-connect/pipeline/frontend_stage.py rename to backend/compact-connect-ui-app/pipeline/frontend_stage.py index e4aef258e..1e34a981d 100644 --- a/backend/compact-connect/pipeline/frontend_stage.py +++ b/backend/compact-connect-ui-app/pipeline/frontend_stage.py @@ -1,6 +1,7 @@ from aws_cdk import Environment, Stage from common_constructs.stack import StandardTags from constructs import Construct + from stacks.frontend_deployment_stack import FrontendDeploymentStack diff --git a/backend/compact-connect-ui-app/pipeline/synth_substitute_stack.py b/backend/compact-connect-ui-app/pipeline/synth_substitute_stack.py new file mode 100644 index 000000000..c5dda2739 --- /dev/null +++ b/backend/compact-connect-ui-app/pipeline/synth_substitute_stack.py @@ -0,0 +1,30 @@ +from aws_cdk import Stack, aws_ssm +from constructs import Construct + + +class SynthSubstituteStack(Stack): + """ + A lightweight stack used as a substitute during pipeline synthesis. + + This stack is used to optimize CDK pipeline synthesis by replacing + heavyweight stacks with a minimal stack that contains just a dummy + SSM parameter. This dramatically reduces synthesis time when only + a specific pipeline's stacks need to be synthesized. + """ + + def __init__( + self, + scope: Construct, + construct_id: str, + **kwargs, + ): + super().__init__(scope, construct_id, **kwargs) + + # Create a simple SSM parameter as a lightweight substitute + self.dummy_parameter = aws_ssm.StringParameter( + self, + 'DummyParameter', + parameter_name=f'/compact-connect/{construct_id}/dummy-parameter', + string_value='dummy parameter value', + description='Dummy parameter used for CDK synthesis optimization', + ) diff --git a/backend/compact-connect-ui-app/pipeline/synth_substitute_stage.py b/backend/compact-connect-ui-app/pipeline/synth_substitute_stage.py new file mode 100644 index 000000000..d7aca239c --- /dev/null +++ b/backend/compact-connect-ui-app/pipeline/synth_substitute_stage.py @@ -0,0 +1,38 @@ +from aws_cdk import Environment, Stage +from constructs import Construct + +from pipeline.synth_substitute_stack import SynthSubstituteStack + + +class SynthSubstituteStage(Stage): + """ + A lightweight stage used as a substitute during pipeline synthesis. + + This stage is used to optimize CDK pipeline synthesis by replacing + heavyweight stages with a minimal stage that contains just a single + SynthSubstituteStack. This dramatically reduces synthesis time when + only a specific pipeline's stages need to be synthesized. + + Using a separate stage rather than conditional logic within existing + stages provides an additional safety layer - preventing accidental + deletion of production resources due to typos in pipeline names. + """ + + def __init__( + self, + scope: Construct, + construct_id: str, + *, + environment_context: dict, + **kwargs, + ): + super().__init__(scope, construct_id, **kwargs) + + environment = Environment(account=environment_context['account_id'], region=environment_context['region']) + + # Create a simple substitute stack + self.substitute_stack = SynthSubstituteStack( + self, + 'SubstituteStack', + env=environment, + ) diff --git a/backend/compact-connect-ui-app/requirements-dev.in b/backend/compact-connect-ui-app/requirements-dev.in new file mode 100644 index 000000000..34e2caeca --- /dev/null +++ b/backend/compact-connect-ui-app/requirements-dev.in @@ -0,0 +1,6 @@ +pytest>=6.2.5 +pytest-cov +coverage +ruff +pip-tools +pip-audit diff --git a/backend/compact-connect-ui-app/requirements-dev.txt b/backend/compact-connect-ui-app/requirements-dev.txt new file mode 100644 index 000000000..8a13d2148 --- /dev/null +++ b/backend/compact-connect-ui-app/requirements-dev.txt @@ -0,0 +1,108 @@ +# +# This file is autogenerated by pip-compile with Python 3.12 +# by the following command: +# +# pip-compile --no-emit-index-url compact-connect/requirements-dev.in +# +boolean-py==5.0 + # via license-expression +build==1.3.0 + # via pip-tools +cachecontrol[filecache]==0.14.3 + # via + # cachecontrol + # pip-audit +certifi==2025.8.3 + # via requests +charset-normalizer==3.4.3 + # via requests +click==8.2.1 + # via pip-tools +coverage[toml]==7.10.5 + # via + # -r compact-connect/requirements-dev.in + # pytest-cov +cyclonedx-python-lib==9.1.0 + # via pip-audit +defusedxml==0.7.1 + # via py-serializable +faker==28.4.1 + # via -r compact-connect/requirements-dev.in +filelock==3.19.1 + # via cachecontrol +idna==3.10 + # via requests +iniconfig==2.1.0 + # via pytest +license-expression==30.4.4 + # via cyclonedx-python-lib +markdown-it-py==4.0.0 + # via rich +mdurl==0.1.2 + # via markdown-it-py +msgpack==1.1.1 + # via cachecontrol +packageurl-python==0.17.5 + # via cyclonedx-python-lib +packaging==25.0 + # via + # build + # pip-audit + # pip-requirements-parser + # pytest +pip-api==0.0.34 + # via pip-audit +pip-audit==2.9.0 + # via -r compact-connect/requirements-dev.in +pip-requirements-parser==32.0.1 + # via pip-audit +pip-tools==7.5.0 + # via -r compact-connect/requirements-dev.in +platformdirs==4.4.0 + # via pip-audit +pluggy==1.6.0 + # via + # pytest + # pytest-cov +py-serializable==2.1.0 + # via cyclonedx-python-lib +pygments==2.19.2 + # via + # pytest + # rich +pyparsing==3.2.3 + # via pip-requirements-parser +pyproject-hooks==1.2.0 + # via + # build + # pip-tools +pytest==8.4.1 + # via + # -r compact-connect/requirements-dev.in + # pytest-cov +pytest-cov==6.2.1 + # via -r compact-connect/requirements-dev.in +python-dateutil==2.9.0.post0 + # via faker +requests==2.32.5 + # via + # cachecontrol + # pip-audit +rich==14.1.0 + # via pip-audit +ruff==0.12.10 + # via -r compact-connect/requirements-dev.in +six==1.17.0 + # via python-dateutil +sortedcontainers==2.4.0 + # via cyclonedx-python-lib +toml==0.10.2 + # via pip-audit +urllib3==2.5.0 + # via requests +wheel==0.45.1 + # via pip-tools + +# The following packages are considered to be unsafe in a requirements file: +# pip +# setuptools diff --git a/backend/compact-connect-ui-app/requirements.in b/backend/compact-connect-ui-app/requirements.in new file mode 100644 index 000000000..0ae037f07 --- /dev/null +++ b/backend/compact-connect-ui-app/requirements.in @@ -0,0 +1,6 @@ +aws-cdk-lib>=2.140.0 +aws-cdk-aws-lambda-python-alpha>=2.142.0a0 +constructs>=10.0.0,<11.0.0 +cdk-nag>=2.28.10, <3 +# pyyaml required for compact configuration uploader +pyyaml>=6.0.2, <7 diff --git a/backend/compact-connect-ui-app/requirements.txt b/backend/compact-connect-ui-app/requirements.txt new file mode 100644 index 000000000..681812b16 --- /dev/null +++ b/backend/compact-connect-ui-app/requirements.txt @@ -0,0 +1,74 @@ +# +# This file is autogenerated by pip-compile with Python 3.12 +# by the following command: +# +# pip-compile --no-emit-index-url compact-connect/requirements.in +# +attrs==25.3.0 + # via + # cattrs + # jsii +aws-cdk-asset-awscli-v1==2.2.242 + # via aws-cdk-lib +aws-cdk-asset-node-proxy-agent-v6==2.1.0 + # via aws-cdk-lib +aws-cdk-aws-lambda-python-alpha==2.212.0a0 + # via -r compact-connect/requirements.in +aws-cdk-cloud-assembly-schema==48.5.0 + # via aws-cdk-lib +aws-cdk-lib==2.212.0 + # via + # -r compact-connect/requirements.in + # aws-cdk-aws-lambda-python-alpha + # cdk-nag +cattrs==25.1.1 + # via jsii +cdk-nag==2.37.9 + # via -r compact-connect/requirements.in +constructs==10.4.2 + # via + # -r compact-connect/requirements.in + # aws-cdk-aws-lambda-python-alpha + # aws-cdk-lib + # cdk-nag +importlib-resources==6.5.2 + # via jsii +jsii==1.113.0 + # via + # aws-cdk-asset-awscli-v1 + # aws-cdk-asset-node-proxy-agent-v6 + # aws-cdk-aws-lambda-python-alpha + # aws-cdk-cloud-assembly-schema + # aws-cdk-lib + # cdk-nag + # constructs +publication==0.0.3 + # via + # aws-cdk-asset-awscli-v1 + # aws-cdk-asset-node-proxy-agent-v6 + # aws-cdk-aws-lambda-python-alpha + # aws-cdk-cloud-assembly-schema + # aws-cdk-lib + # cdk-nag + # constructs + # jsii +python-dateutil==2.9.0.post0 + # via jsii +pyyaml==6.0.2 + # via -r compact-connect/requirements.in +six==1.17.0 + # via python-dateutil +typeguard==2.13.3 + # via + # aws-cdk-asset-awscli-v1 + # aws-cdk-asset-node-proxy-agent-v6 + # aws-cdk-aws-lambda-python-alpha + # aws-cdk-cloud-assembly-schema + # aws-cdk-lib + # cdk-nag + # constructs + # jsii +typing-extensions==4.15.0 + # via + # cattrs + # jsii diff --git a/backend/compact-connect-ui-app/ruff.toml b/backend/compact-connect-ui-app/ruff.toml new file mode 100644 index 000000000..66682be80 --- /dev/null +++ b/backend/compact-connect-ui-app/ruff.toml @@ -0,0 +1,112 @@ +# Exclude a variety of commonly ignored directories. +exclude = [ + ".bzr", + ".direnv", + ".eggs", + ".git", + ".git-rewrite", + ".hg", + ".ipynb_checkpoints", + ".mypy_cache", + ".nox", + ".pants.d", + ".pyenv", + ".pytest_cache", + ".pytype", + ".ruff_cache", + ".svn", + ".tox", + ".venv", + ".vscode", + "__pypackages__", + "_build", + "buck-out", + "build", + "dist", + "node_modules", + "site-packages", + "venv" +] + +line-length = 120 +indent-width = 4 + +# Assume Python 3.12 +target-version = "py312" + +[lint] +select = [ + "A", # flake8-builtins + "ARG", # flake8-unused-arguments + "B", # flake8-bugbear + "BLE", # flake8-blind-except + "DTZ", # flake8-datetimez + "E", # pycodestyle + "F", # Pyflakes + "FIX", # flake8-fixme + "FA", # flake8-future-annotations + "G", # flake8-logging-format + "I", # isort + "N", # pep8-naming + "PIE", # flake8-pie + "SLF", # flake8-self + "RET", # flake8-return + "RSE", # flake8-raise + "S", # flake8-bandit + "T", # flake8-print + "UP", # pyupgrade + "W", # warning +] +ignore = [ + "S311", # suspicious-non-cryptographic-random-usage + "N818", # error-suffix-on-exception-name + # the following are ignored by recommendation of ruff documentation + # due to conflicts with the ruff formatter see https://docs.astral.sh/ruff/formatter/#conflicting-lint-rules + "W191", # indentation contains tabs + "E111", # indentation-with-invalid-multiple + "E114", # indentation-with-invalid-multiple-comment + "E117", # over-indented + "D206", # indent-with-spaces + "D300", # triple-single-quotes + "Q000", # bad-quotes-inline-string + "Q001", # bad-quotes-multiline-string + "Q002", # bad-quotes-docstring + "Q003", # avoidable-escaped-quote + "COM812", # missing-trailing-comma + "COM819", # prohibited-trailing-comma + "ISC001", # single-line-implicit-string-concatenation + "ISC002", # multi-line-implicit-string-concatenation +] + +# Allow fix for all enabled rules (when `--fix`) is provided. +fixable = ["ALL"] +unfixable = [] + +# Allow unused variables when underscore-prefixed. +dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" + +[format] +quote-style = "single" + +# Like Black, indent with spaces, rather than tabs. +indent-style = "space" + +# Like Black, respect magic trailing commas. +skip-magic-trailing-comma = false + +# Like Black, automatically detect the appropriate line ending. +line-ending = "auto" + +# Enable auto-formatting of code examples in docstrings. Markdown, +# reStructuredText code/literal blocks and doctests are all supported. +# +# This is currently disabled by default, but it is planned for this +# to be opt-out in the future. +docstring-code-format = false + +# Set the line length limit used when formatting code snippets in +# docstrings. +# +# This only has an effect when the `docstring-code-format` setting is +# enabled. +docstring-code-line-length = "dynamic" diff --git a/backend/compact-connect-ui-app/stacks/__init__.py b/backend/compact-connect-ui-app/stacks/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/backend/compact-connect/stacks/frontend_deployment_stack/__init__.py b/backend/compact-connect-ui-app/stacks/frontend_deployment_stack/__init__.py similarity index 100% rename from backend/compact-connect/stacks/frontend_deployment_stack/__init__.py rename to backend/compact-connect-ui-app/stacks/frontend_deployment_stack/__init__.py diff --git a/backend/compact-connect/stacks/frontend_deployment_stack/deployment.py b/backend/compact-connect-ui-app/stacks/frontend_deployment_stack/deployment.py similarity index 100% rename from backend/compact-connect/stacks/frontend_deployment_stack/deployment.py rename to backend/compact-connect-ui-app/stacks/frontend_deployment_stack/deployment.py diff --git a/backend/compact-connect/stacks/frontend_deployment_stack/distribution.py b/backend/compact-connect-ui-app/stacks/frontend_deployment_stack/distribution.py similarity index 100% rename from backend/compact-connect/stacks/frontend_deployment_stack/distribution.py rename to backend/compact-connect-ui-app/stacks/frontend_deployment_stack/distribution.py diff --git a/backend/compact-connect-ui-app/tests/__init__.py b/backend/compact-connect-ui-app/tests/__init__.py new file mode 100644 index 000000000..f56f03fdc --- /dev/null +++ b/backend/compact-connect-ui-app/tests/__init__.py @@ -0,0 +1,5 @@ +import os +import sys + +# Make the `common_constructs` namespace package under `common-cdk` available to Python +sys.path.insert(0, os.path.abspath(os.path.join('..', 'common-cdk'))) diff --git a/backend/compact-connect-ui-app/tests/app/__init__.py b/backend/compact-connect-ui-app/tests/app/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/backend/compact-connect-ui-app/tests/app/base.py b/backend/compact-connect-ui-app/tests/app/base.py new file mode 100644 index 000000000..9bcefeaed --- /dev/null +++ b/backend/compact-connect-ui-app/tests/app/base.py @@ -0,0 +1,149 @@ +import json +import os +import sys +from abc import ABC, abstractmethod +from collections.abc import Mapping +from unittest.mock import patch + +from aws_cdk.assertions import Annotations, Match, Template +from aws_cdk.aws_cloudfront import CfnDistribution +from aws_cdk.aws_lambda import CfnFunction +from aws_cdk.aws_s3 import CfnBucket +from common_constructs.stack import Stack + +from app import CompactConnectApp +from pipeline import FrontendStage +from stacks.frontend_deployment_stack import FrontendDeploymentStack + + +class _AppSynthesizer: + """ + A helper class to cache apps based on context. + This is useful to avoid re-synthesizing the app for each test. + """ + + def __init__(self): + super().__init__() + self._cached_apps: dict[int, CompactConnectApp] = {} + + def get_app(self, context: Mapping) -> CompactConnectApp: + context_hash = self._get_context_hash(context) + if context_hash not in self._cached_apps.keys(): + self._cached_apps[context_hash] = CompactConnectApp(context=context) + return self._cached_apps[context_hash] + + def _get_context_hash(self, context: Mapping) -> int: + return hash(json.dumps(context, sort_keys=True)) + + +_app_synthesizer = _AppSynthesizer() + + +class TstAppABC(ABC): + """ + Base class for common test elements across configurations. + + Note: Concrete classes must also inherit from TestCase + """ + + @classmethod + @abstractmethod + def get_context(cls) -> Mapping: + pass + + @classmethod + @patch.dict(os.environ, {'CDK_DEFAULT_ACCOUNT': '000000000000', 'CDK_DEFAULT_REGION': 'us-east-1'}) + def setUpClass(cls): # pylint: disable=invalid-name + """ + We build the app once per TestCase, to save compute time in the test suite + """ + cls.context = cls.get_context() + cls.app = _app_synthesizer.get_app(cls.context) + + @staticmethod + def get_resource_properties_by_logical_id(logical_id: str, resources: Mapping[str, Mapping]) -> Mapping: + """ + Helper function to retrieve a resource from a CloudFormation template by its logical ID. + """ + try: + return resources[logical_id]['Properties'] + except KeyError as exc: + raise RuntimeError(f'{logical_id} not found in resources!') from exc + + def _inspect_frontend_deployment_stack(self, ui_stack: FrontendDeploymentStack): + with self.subTest(ui_stack.stack_name): + ui_stack_template = Template.from_stack(ui_stack) + # Ensure we have a CloudFront distribution + ui_stack_template.resource_count_is('AWS::CloudFront::Distribution', 1) + # This stack is not anticipated to do much changing, so we'll just snapshot-test key resources + ui_bucket = ui_stack_template.find_resources(CfnBucket.CFN_RESOURCE_TYPE_NAME)[ + ui_stack.get_logical_id(ui_stack.ui_bucket.node.default_child) + ] + self.compare_snapshot(ui_bucket, snapshot_name=f'{ui_stack.stack_name}-UI_BUCKET', overwrite_snapshot=False) + distribution = ui_stack_template.find_resources(CfnDistribution.CFN_RESOURCE_TYPE_NAME) + self.assertEqual(len(distribution), 1) + self.compare_snapshot(distribution, f'{ui_stack.stack_name}-UI_DISTRIBUTION', overwrite_snapshot=False) + # take a snapshot of the lambda@edge code to ensure placeholder values are being injected + distribution_function = ui_stack_template.find_resources(CfnFunction.CFN_RESOURCE_TYPE_NAME)[ + ui_stack.get_logical_id(ui_stack.distribution.csp_function.node.default_child) + ] + self.compare_snapshot( + distribution_function, + f'{ui_stack.stack_name}-UI_DISTRIBUTION_LAMBDA_FUNCTION', + overwrite_snapshot=False, + ) + + def _check_no_stack_annotations(self, stack: Stack): + with self.subTest(f'Security Rules: {stack.stack_name}'): + errors = Annotations.from_stack(stack).find_error('*', Match.string_like_regexp('.*')) + self.assertEqual(0, len(errors), msg='\n'.join(f'{err.id}: {err.entry.data.strip()}' for err in errors)) + + warnings = Annotations.from_stack(stack).find_warning('*', Match.string_like_regexp('.*')) + self.assertEqual( + 0, len(warnings), msg='\n'.join(f'{warn.id}: {warn.entry.data.strip()}' for warn in warnings) + ) + + def _check_no_frontend_stage_annotations(self, stage: FrontendStage): + self._check_no_stack_annotations(stage.frontend_deployment_stack) + + def _count_stack_resources(self, stack: Stack) -> int: + """ + Count the number of resources in a CloudFormation stack. + + :param stack: The CDK Stack to analyze + :returns: Number of resources in the stack + """ + template = Template.from_stack(stack) + # Get template as dictionary and count resources + template_dict = template.to_json() + resources = template_dict.get('Resources', {}) + return len(resources) + + def compare_snapshot(self, actual: Mapping | list, snapshot_name: str, overwrite_snapshot: bool = False): + """ + Compare the actual dictionary to the snapshot with the given name. + If overwrite_snapshot is True, overwrite the snapshot with the actual data. + """ + snapshot_path = os.path.join('tests', 'resources', 'snapshots', f'{snapshot_name}.json') + + if os.path.exists(snapshot_path): + with open(snapshot_path) as f: + snapshot = json.load(f) + else: + sys.stdout.write(f"Snapshot at path '{snapshot_path}' does not exist.") + snapshot = None + + if snapshot != actual and overwrite_snapshot: + with open(snapshot_path, 'w') as f: + json.dump(actual, f, indent=2) + # So the data files will end with a newline + f.write('\n') + sys.stdout.write(f"Snapshot '{snapshot_name}' has been overwritten.") + else: + self.maxDiff = None # pylint: disable=invalid-name,attribute-defined-outside-init + self.assertEqual( + snapshot, + actual, + f"Snapshot '{snapshot_name}' does not match the actual data. " + 'To overwrite the snapshot, set overwrite_snapshot=True.', + ) diff --git a/backend/compact-connect-ui-app/tests/app/test_pipeline.py b/backend/compact-connect-ui-app/tests/app/test_pipeline.py new file mode 100644 index 000000000..08b20dfa0 --- /dev/null +++ b/backend/compact-connect-ui-app/tests/app/test_pipeline.py @@ -0,0 +1,41 @@ +import json +from unittest import TestCase + +from tests.app.base import TstAppABC + + +class TestFrontendPipeline(TstAppABC, TestCase): + @classmethod + def get_context(cls): + with open('cdk.json') as f: + context = json.load(f)['context'] + # For pipeline deployments, the pipelines pull their CDK context values from SSM Parameter Store, rather + # than the cdk.context.json files used in local development. We can override the context values used in the + # tests by adding values here. + + # Suppresses lambda bundling for tests + context['aws:cdk:bundling-stacks'] = [] + + return context + + def test_synth_pipeline(self): + """ + Test infrastructure as deployed via the pipeline + """ + # Identify any findings from our AwsSolutions rule sets + self._check_no_stack_annotations(self.app.deployment_resources_stack) + self._check_no_stack_annotations(self.app.test_frontend_pipeline_stack) + self._check_no_stack_annotations(self.app.prod_frontend_pipeline_stack) + for stage in ( + self.app.test_frontend_pipeline_stack.pre_prod_frontend_stage, + self.app.beta_frontend_pipeline_stack.beta_frontend_stage, + self.app.prod_frontend_pipeline_stack.prod_frontend_stage, + ): + self._check_no_frontend_stage_annotations(stage) + + for frontend_deployment_stack in ( + self.app.test_frontend_pipeline_stack.pre_prod_frontend_stage.frontend_deployment_stack, + self.app.beta_frontend_pipeline_stack.beta_frontend_stage.frontend_deployment_stack, + self.app.prod_frontend_pipeline_stack.prod_frontend_stage.frontend_deployment_stack, + ): + self._inspect_frontend_deployment_stack(frontend_deployment_stack) diff --git a/backend/compact-connect/tests/resources/snapshots/BetaFrontend-FrontendDeploymentStack-UI_BUCKET.json b/backend/compact-connect-ui-app/tests/resources/snapshots/BetaFrontend-FrontendDeploymentStack-UI_BUCKET.json similarity index 100% rename from backend/compact-connect/tests/resources/snapshots/BetaFrontend-FrontendDeploymentStack-UI_BUCKET.json rename to backend/compact-connect-ui-app/tests/resources/snapshots/BetaFrontend-FrontendDeploymentStack-UI_BUCKET.json diff --git a/backend/compact-connect/tests/resources/snapshots/BetaFrontend-FrontendDeploymentStack-UI_DISTRIBUTION.json b/backend/compact-connect-ui-app/tests/resources/snapshots/BetaFrontend-FrontendDeploymentStack-UI_DISTRIBUTION.json similarity index 100% rename from backend/compact-connect/tests/resources/snapshots/BetaFrontend-FrontendDeploymentStack-UI_DISTRIBUTION.json rename to backend/compact-connect-ui-app/tests/resources/snapshots/BetaFrontend-FrontendDeploymentStack-UI_DISTRIBUTION.json diff --git a/backend/compact-connect/tests/resources/snapshots/BetaFrontend-FrontendDeploymentStack-UI_DISTRIBUTION_LAMBDA_FUNCTION.json b/backend/compact-connect-ui-app/tests/resources/snapshots/BetaFrontend-FrontendDeploymentStack-UI_DISTRIBUTION_LAMBDA_FUNCTION.json similarity index 100% rename from backend/compact-connect/tests/resources/snapshots/BetaFrontend-FrontendDeploymentStack-UI_DISTRIBUTION_LAMBDA_FUNCTION.json rename to backend/compact-connect-ui-app/tests/resources/snapshots/BetaFrontend-FrontendDeploymentStack-UI_DISTRIBUTION_LAMBDA_FUNCTION.json diff --git a/backend/compact-connect/tests/resources/snapshots/ProdFrontend-FrontendDeploymentStack-UI_BUCKET.json b/backend/compact-connect-ui-app/tests/resources/snapshots/ProdFrontend-FrontendDeploymentStack-UI_BUCKET.json similarity index 100% rename from backend/compact-connect/tests/resources/snapshots/ProdFrontend-FrontendDeploymentStack-UI_BUCKET.json rename to backend/compact-connect-ui-app/tests/resources/snapshots/ProdFrontend-FrontendDeploymentStack-UI_BUCKET.json diff --git a/backend/compact-connect/tests/resources/snapshots/ProdFrontend-FrontendDeploymentStack-UI_DISTRIBUTION.json b/backend/compact-connect-ui-app/tests/resources/snapshots/ProdFrontend-FrontendDeploymentStack-UI_DISTRIBUTION.json similarity index 100% rename from backend/compact-connect/tests/resources/snapshots/ProdFrontend-FrontendDeploymentStack-UI_DISTRIBUTION.json rename to backend/compact-connect-ui-app/tests/resources/snapshots/ProdFrontend-FrontendDeploymentStack-UI_DISTRIBUTION.json diff --git a/backend/compact-connect/tests/resources/snapshots/ProdFrontend-FrontendDeploymentStack-UI_DISTRIBUTION_LAMBDA_FUNCTION.json b/backend/compact-connect-ui-app/tests/resources/snapshots/ProdFrontend-FrontendDeploymentStack-UI_DISTRIBUTION_LAMBDA_FUNCTION.json similarity index 100% rename from backend/compact-connect/tests/resources/snapshots/ProdFrontend-FrontendDeploymentStack-UI_DISTRIBUTION_LAMBDA_FUNCTION.json rename to backend/compact-connect-ui-app/tests/resources/snapshots/ProdFrontend-FrontendDeploymentStack-UI_DISTRIBUTION_LAMBDA_FUNCTION.json diff --git a/backend/compact-connect/tests/resources/snapshots/TestFrontend-FrontendDeploymentStack-UI_BUCKET.json b/backend/compact-connect-ui-app/tests/resources/snapshots/TestFrontend-FrontendDeploymentStack-UI_BUCKET.json similarity index 100% rename from backend/compact-connect/tests/resources/snapshots/TestFrontend-FrontendDeploymentStack-UI_BUCKET.json rename to backend/compact-connect-ui-app/tests/resources/snapshots/TestFrontend-FrontendDeploymentStack-UI_BUCKET.json diff --git a/backend/compact-connect/tests/resources/snapshots/TestFrontend-FrontendDeploymentStack-UI_DISTRIBUTION.json b/backend/compact-connect-ui-app/tests/resources/snapshots/TestFrontend-FrontendDeploymentStack-UI_DISTRIBUTION.json similarity index 100% rename from backend/compact-connect/tests/resources/snapshots/TestFrontend-FrontendDeploymentStack-UI_DISTRIBUTION.json rename to backend/compact-connect-ui-app/tests/resources/snapshots/TestFrontend-FrontendDeploymentStack-UI_DISTRIBUTION.json diff --git a/backend/compact-connect/tests/resources/snapshots/TestFrontend-FrontendDeploymentStack-UI_DISTRIBUTION_LAMBDA_FUNCTION.json b/backend/compact-connect-ui-app/tests/resources/snapshots/TestFrontend-FrontendDeploymentStack-UI_DISTRIBUTION_LAMBDA_FUNCTION.json similarity index 100% rename from backend/compact-connect/tests/resources/snapshots/TestFrontend-FrontendDeploymentStack-UI_DISTRIBUTION_LAMBDA_FUNCTION.json rename to backend/compact-connect-ui-app/tests/resources/snapshots/TestFrontend-FrontendDeploymentStack-UI_DISTRIBUTION_LAMBDA_FUNCTION.json diff --git a/backend/compact-connect-ui-app/tests/resources/test_files/.!35287!military_affiliation.pdf b/backend/compact-connect-ui-app/tests/resources/test_files/.!35287!military_affiliation.pdf new file mode 100644 index 0000000000000000000000000000000000000000..2fbf8bb39211c9538298936703d12721cce882c4 GIT binary patch literal 9 QcmY!laB)k={E&s+53& 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 iDAE(DbxP*{oBYvR6rdZZpK=P61_F@*gH5bVW&Q 0: + pytest_args.append(f'-{"v" * args.verbose}') + + # Run tests for each directory with the correct working directory + for test_dir in TEST_DIRS: + dir_path = APP_DIR / test_dir + if not dir_path.exists(): + sys.stdout.write(f'Warning: Directory {dir_path} does not exist, skipping\n') + continue + + tests_dir = dir_path / 'tests' + if not tests_dir.exists(): + sys.stdout.write(f'Warning: Tests directory {tests_dir} does not exist, skipping\n') + continue + + sys.stdout.write('\n' + '=' * 80 + '\n') + sys.stdout.write(f'Running tests in {test_dir}\n') + sys.stdout.write('=' * 80 + '\n') + + # Save the original working directory and sys.path + original_dir = os.getcwd() + original_path = sys.path.copy() + original_env = os.environ.copy() + + try: + # Change to the test directory + os.chdir(dir_path) + + # Set up PYTHONPATH for common code if needed + if test_dir != 'lambdas/python/common' and 'python' in test_dir: + common_path = APP_DIR / 'lambdas/python/common' + if str(common_path) not in sys.path: + sys.path.insert(0, str(common_path)) + + # Clean up modules before running tests + clean_modules() + + # Run pytest for this directory + test_result = pytest.main(pytest_args) + + # Update exit code if any tests fail + if test_result != 0 and exit_code == 0: + return test_result + + # Restore the original environment + os.environ = original_env # noqa: B003 + + # Thorough module cleanup after each test suite + clean_modules() + + finally: + # Restore the original working directory + os.chdir(original_dir) + + # Restore the original sys.path + sys.path = original_path + finally: + # Stop coverage measurement + cov.stop() + cov.save() + + return exit_code + + +def main(): + parser = ArgumentParser(description='Run all Python tests in a unified pytest session') + parser.add_argument('--report', action='store_true', help='Generate HTML coverage report') + parser.add_argument('--open-report', action='store_true', help='Open HTML coverage report after generation') + parser.add_argument('--fail-under', type=float, default=90, help='Fail if coverage is under this percentage') + parser.add_argument('--verbose', '-v', action='count', default=0, help='Increase verbosity') + args = parser.parse_args() + + cov = get_coverage() + exit_code = run_tests(cov, args) + + # Generate coverage report if requested + if args.report: + sys.stdout.write('\nGenerating coverage report...\n') + cov.html_report() + + if args.open_report: + # Open the coverage report + report_path = APP_DIR / 'coverage' / 'index.html' + webbrowser.open(f'file://{report_path}') + + # Check coverage against threshold + sys.stdout.write('\nCalculating coverage...\n') + coverage_percent = cov.report() + if coverage_percent < args.fail_under: + sys.stdout.write( + f'Coverage {coverage_percent:.2f}% is below the required threshold of {args.fail_under:.2f}%\n' + ) + exit_code = 1 + + if exit_code > 0: + sys.stdout.write('\n================= TESTS FAILED =================\n') + return exit_code + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/backend/compact-connect/bin/run_tests.sh b/backend/compact-connect/bin/run_tests.sh new file mode 100755 index 000000000..ad8dcb5ee --- /dev/null +++ b/backend/compact-connect/bin/run_tests.sh @@ -0,0 +1,129 @@ +#!/bin/bash +set -e + +# Default values +LANGUAGE="all" +REPORT=true +OPEN_REPORT=true +VERBOSE="" +FAIL_UNDER=90 + +# Print usage information +usage() { + echo "Usage: $0 [options]" + echo "Options:" + echo " -l, --language LANG Specify language to test (nodejs, python, all) [default: all]" + echo " -n, --no-report Don't generate coverage report" + echo " -o, --no-open Don't open coverage report in browser" + echo " -v, --verbose Increase python verbosity (can be used multiple times: -vv)" + echo " -f, --fail-under PCT Set minimum python coverage percentage [default: 90]" + echo " -h, --help Display this help message" + exit 1 +} + +# Parse command line arguments +while getopts "l:nov:f:h-:" opt; do + case $opt in + -) + case "${OPTARG}" in + language) + LANGUAGE="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 )) + ;; + no-report) + REPORT=false + ;; + no-open) + OPEN_REPORT=false + ;; + verbose) + VERBOSE="v${VERBOSE}" + ;; + fail-under) + FAIL_UNDER="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 )) + ;; + help) + usage + ;; + *) + echo "Invalid option: --${OPTARG}" >&2 + usage + ;; + esac + ;; + l) + LANGUAGE="$OPTARG" + ;; + n) + REPORT=false + ;; + o) + OPEN_REPORT=false + ;; + v) + VERBOSE="v${VERBOSE}" + ;; + f) + FAIL_UNDER="$OPTARG" + ;; + h) + usage + ;; + \?) + echo "Invalid option: -$OPTARG" >&2 + usage + ;; + esac +done + +# Validate language argument +if [[ "$LANGUAGE" != "nodejs" && "$LANGUAGE" != "python" && "$LANGUAGE" != "all" ]]; then + echo "Error: Language must be 'nodejs', 'python', or 'all'" >&2 + usage +fi + +# Set this to 1 ahead of running tests, so this script will fail if neither node or python tests ran +EXIT=1 + +# Run NodeJS tests if requested +if [[ "$LANGUAGE" == 'nodejs' || "$LANGUAGE" == 'all' ]]; then + echo "Running NodeJS tests..." + ( + cd lambdas/nodejs + yarn test || exit "$?" + if [[ "$REPORT" == true && "$OPEN_REPORT" == true ]]; then + open 'coverage/lcov-report/index.html' + fi + ) || exit "$?" + # If this didn't exit already, we'll set our exit status to success for now + EXIT=0 +fi + +# Run Python tests if requested +if [[ "$LANGUAGE" == 'python' || "$LANGUAGE" == 'all' ]]; then + echo "Running Python tests..." + + # Build Python test arguments + PYTHON_ARGS=() + + # Add report flags + if [[ "$REPORT" == true ]]; then + PYTHON_ARGS+=("--report") + if [[ "$OPEN_REPORT" == true ]]; then + PYTHON_ARGS+=("--open-report") + fi + fi + + # Add verbosity flag + if [[ -n "$VERBOSE" ]]; then + PYTHON_ARGS+=("-${VERBOSE}") + fi + + # Add fail-under threshold + PYTHON_ARGS+=("--fail-under" "$FAIL_UNDER") + + # Run the Python tests + python3 bin/run_python_tests.py "${PYTHON_ARGS[@]}" || exit "$?" + EXIT=0 +fi + +exit "$EXIT" diff --git a/backend/compact-connect/bin/sync_deps.sh b/backend/compact-connect/bin/sync_deps.sh new file mode 100755 index 000000000..9a9d80983 --- /dev/null +++ b/backend/compact-connect/bin/sync_deps.sh @@ -0,0 +1,28 @@ +( + cd compact-connect/lambdas/nodejs + yarn install +) + +pip-sync \ + requirements-dev.txt \ + requirements.txt \ + lambdas/python/cognito-backup/requirements-dev.txt \ + lambdas/python/cognito-backup/requirements.txt \ + lambdas/python/compact-configuration/requirements-dev.txt \ + lambdas/python/compact-configuration/requirements.txt \ + lambdas/python/common/requirements-dev.txt \ + lambdas/python/common/requirements.txt \ + lambdas/python/custom-resources/requirements-dev.txt \ + lambdas/python/custom-resources/requirements.txt \ + lambdas/python/data-events/requirements-dev.txt \ + lambdas/python/data-events/requirements.txt \ + lambdas/python/disaster-recovery/requirements-dev.txt \ + lambdas/python/disaster-recovery/requirements.txt \ + lambdas/python/provider-data-v1/requirements-dev.txt \ + lambdas/python/provider-data-v1/requirements.txt \ + lambdas/python/purchases/requirements-dev.txt \ + lambdas/python/purchases/requirements.txt \ + lambdas/python/staff-user-pre-token/requirements-dev.txt \ + lambdas/python/staff-user-pre-token/requirements.txt \ + lambdas/python/staff-users/requirements-dev.txt \ + lambdas/python/staff-users/requirements.txt diff --git a/backend/compact-connect/common_constructs/README.md b/backend/compact-connect/common_constructs/README.md new file mode 100644 index 000000000..2e486b201 --- /dev/null +++ b/backend/compact-connect/common_constructs/README.md @@ -0,0 +1,8 @@ +# Common Constructs + +This is the application node of a [namespace package](https://docs.python.org/3/glossary.html#term-namespace-package), which +houses common CDK constructs that are used across different apps in the CompactConnect project. Modules in this package +will be merged in Python with similarly-named namespace packages in each app's specific folder. + +> **Note: Do not add an `__init__.py` file to any of the `common_constructs` packages, or they will break the +> import behavior. diff --git a/backend/compact-connect/common_constructs/cc_api.py b/backend/compact-connect/common_constructs/cc_api.py index a42fb727c..49d1d1fe6 100644 --- a/backend/compact-connect/common_constructs/cc_api.py +++ b/backend/compact-connect/common_constructs/cc_api.py @@ -27,11 +27,11 @@ from aws_cdk.aws_route53_targets import ApiGateway from cdk_nag import NagSuppressions from constructs import IConstruct -from stacks import persistent_stack as ps from common_constructs.security_profile import SecurityProfile from common_constructs.stack import AppStack from common_constructs.webacl import WebACL, WebACLScope +from stacks import persistent_stack as ps MD_FORMAT = r'^[01]{1}[0-9]{1}-[0-3]{1}[0-9]{1}$' YMD_FORMAT = r'^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$' diff --git a/backend/compact-connect/common_constructs/cognito_user_backup.py b/backend/compact-connect/common_constructs/cognito_user_backup.py index b883ce187..b821bd273 100644 --- a/backend/compact-connect/common_constructs/cognito_user_backup.py +++ b/backend/compact-connect/common_constructs/cognito_user_backup.py @@ -22,13 +22,13 @@ from aws_cdk.aws_sns import ITopic from cdk_nag import NagSuppressions from constructs import Construct -from stacks.backup_infrastructure_stack import BackupInfrastructureStack from common_constructs.access_logs_bucket import AccessLogsBucket from common_constructs.backup_plan import CCBackupPlan from common_constructs.bucket import Bucket from common_constructs.python_function import PythonFunction from common_constructs.stack import Stack +from stacks.backup_infrastructure_stack import BackupInfrastructureStack class CognitoUserBackup(Construct): diff --git a/backend/compact-connect/common_constructs/resource_scope_mixin.py b/backend/compact-connect/common_constructs/resource_scope_mixin.py index ee1f6fbe5..be20a536e 100644 --- a/backend/compact-connect/common_constructs/resource_scope_mixin.py +++ b/backend/compact-connect/common_constructs/resource_scope_mixin.py @@ -1,6 +1,7 @@ from __future__ import annotations from aws_cdk.aws_cognito import ResourceServerScope, UserPoolResourceServer + from stacks import persistent_stack as ps diff --git a/backend/compact-connect/lambdas/nodejs/cloudfront-csp/yarn.lock b/backend/compact-connect/lambdas/nodejs/cloudfront-csp/yarn.lock deleted file mode 100644 index 6b1673444..000000000 --- a/backend/compact-connect/lambdas/nodejs/cloudfront-csp/yarn.lock +++ /dev/null @@ -1,2971 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -"@babel/code-frame@7.12.11": - version "7.12.11" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.11.tgz#f4ad435aa263db935b8f10f2c552d23fb716a63f" - integrity sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw== - dependencies: - "@babel/highlight" "^7.10.4" - -"@babel/helper-validator-identifier@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz#75b889cfaf9e35c2aaf42cf0d72c8e91719251db" - integrity sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w== - -"@babel/highlight@^7.10.4": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.24.7.tgz#a05ab1df134b286558aae0ed41e6c5f731bf409d" - integrity sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw== - dependencies: - "@babel/helper-validator-identifier" "^7.24.7" - chalk "^2.4.2" - js-tokens "^4.0.0" - picocolors "^1.0.0" - -"@colors/colors@1.6.0", "@colors/colors@^1.6.0": - version "1.6.0" - resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.6.0.tgz#ec6cd237440700bc23ca23087f513c75508958b0" - integrity sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA== - -"@dabh/diagnostics@^2.0.2": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@dabh/diagnostics/-/diagnostics-2.0.3.tgz#7f7e97ee9a725dffc7808d93668cc984e1dc477a" - integrity sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA== - dependencies: - colorspace "1.1.x" - enabled "2.0.x" - kuler "^2.0.0" - -"@eslint-community/eslint-utils@^4.2.0": - version "4.4.0" - resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" - integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA== - dependencies: - eslint-visitor-keys "^3.3.0" - -"@eslint-community/regexpp@^4.6.1": - version "4.11.0" - resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.11.0.tgz#b0ffd0312b4a3fd2d6f77237e7248a5ad3a680ae" - integrity sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A== - -"@eslint/eslintrc@^0.4.3": - version "0.4.3" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.4.3.tgz#9e42981ef035beb3dd49add17acb96e8ff6f394c" - integrity sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw== - dependencies: - ajv "^6.12.4" - debug "^4.1.1" - espree "^7.3.0" - globals "^13.9.0" - ignore "^4.0.6" - import-fresh "^3.2.1" - js-yaml "^3.13.1" - minimatch "^3.0.4" - strip-json-comments "^3.1.1" - -"@eslint/eslintrc@^2.1.4": - version "2.1.4" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.4.tgz#388a269f0f25c1b6adc317b5a2c55714894c70ad" - integrity sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ== - dependencies: - ajv "^6.12.4" - debug "^4.3.2" - espree "^9.6.0" - globals "^13.19.0" - ignore "^5.2.0" - import-fresh "^3.2.1" - js-yaml "^4.1.0" - minimatch "^3.1.2" - strip-json-comments "^3.1.1" - -"@eslint/js@8.57.0": - version "8.57.0" - resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.57.0.tgz#a5417ae8427873f1dd08b70b3574b453e67b5f7f" - integrity sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g== - -"@humanwhocodes/config-array@^0.11.14": - version "0.11.14" - resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.14.tgz#d78e481a039f7566ecc9660b4ea7fe6b1fec442b" - integrity sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg== - dependencies: - "@humanwhocodes/object-schema" "^2.0.2" - debug "^4.3.1" - minimatch "^3.0.5" - -"@humanwhocodes/config-array@^0.5.0": - version "0.5.0" - resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.5.0.tgz#1407967d4c6eecd7388f83acf1eaf4d0c6e58ef9" - integrity sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg== - dependencies: - "@humanwhocodes/object-schema" "^1.2.0" - debug "^4.1.1" - minimatch "^3.0.4" - -"@humanwhocodes/module-importer@^1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" - integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== - -"@humanwhocodes/object-schema@^1.2.0": - version "1.2.1" - resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" - integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== - -"@humanwhocodes/object-schema@^2.0.2": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz#4a2868d75d6d6963e423bcf90b7fd1be343409d3" - integrity sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA== - -"@nodelib/fs.scandir@2.1.5": - version "2.1.5" - resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" - integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== - dependencies: - "@nodelib/fs.stat" "2.0.5" - run-parallel "^1.1.9" - -"@nodelib/fs.stat@2.0.5": - version "2.0.5" - resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" - integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== - -"@nodelib/fs.walk@^1.2.8": - version "1.2.8" - resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" - integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== - dependencies: - "@nodelib/fs.scandir" "2.1.5" - fastq "^1.6.0" - -"@types/json5@^0.0.29": - version "0.0.29" - resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" - integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== - -"@types/triple-beam@^1.3.2": - version "1.3.5" - resolved "https://registry.yarnpkg.com/@types/triple-beam/-/triple-beam-1.3.5.tgz#74fef9ffbaa198eb8b588be029f38b00299caa2c" - integrity sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw== - -"@ungap/structured-clone@^1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406" - integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ== - -abbrev@1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" - integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== - -acorn-jsx@^5.3.1, acorn-jsx@^5.3.2: - version "5.3.2" - resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" - integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== - -acorn@^7.4.0: - version "7.4.1" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" - integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== - -acorn@^8.9.0: - version "8.12.1" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.12.1.tgz#71616bdccbe25e27a54439e0046e89ca76df2248" - integrity sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg== - -ajv@^6.10.0, ajv@^6.12.4: - version "6.12.6" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" - integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== - dependencies: - fast-deep-equal "^3.1.1" - fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.4.1" - uri-js "^4.2.2" - -ajv@^8.0.1: - version "8.17.1" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.17.1.tgz#37d9a5c776af6bc92d7f4f9510eba4c0a60d11a6" - integrity sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g== - dependencies: - fast-deep-equal "^3.1.3" - fast-uri "^3.0.1" - json-schema-traverse "^1.0.0" - require-from-string "^2.0.2" - -ansi-colors@^4.1.1, ansi-colors@^4.1.3: - version "4.1.3" - resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.3.tgz#37611340eb2243e70cc604cad35d63270d48781b" - integrity sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw== - -ansi-regex@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" - integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== - -ansi-styles@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" - integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== - dependencies: - color-convert "^1.9.0" - -ansi-styles@^4.0.0, ansi-styles@^4.1.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" - integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== - dependencies: - color-convert "^2.0.1" - -anymatch@~3.1.2: - version "3.1.3" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" - integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== - dependencies: - normalize-path "^3.0.0" - picomatch "^2.0.4" - -argparse@^1.0.7: - version "1.0.10" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" - integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== - dependencies: - sprintf-js "~1.0.2" - -argparse@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" - integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== - -array-buffer-byte-length@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz#1e5583ec16763540a27ae52eed99ff899223568f" - integrity sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg== - dependencies: - call-bind "^1.0.5" - is-array-buffer "^3.0.4" - -array-each@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/array-each/-/array-each-1.0.1.tgz#a794af0c05ab1752846ee753a1f211a05ba0c44f" - integrity sha512-zHjL5SZa68hkKHBFBK6DJCTtr9sfTCPCaph/L7tMSLcTFgy+zX7E+6q5UArbtOtMBCtxdICpfTCspRse+ywyXA== - -array-includes@^3.1.7: - version "3.1.8" - resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.8.tgz#5e370cbe172fdd5dd6530c1d4aadda25281ba97d" - integrity sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ== - dependencies: - call-bind "^1.0.7" - define-properties "^1.2.1" - es-abstract "^1.23.2" - es-object-atoms "^1.0.0" - get-intrinsic "^1.2.4" - is-string "^1.0.7" - -array-slice@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/array-slice/-/array-slice-1.1.0.tgz#e368ea15f89bc7069f7ffb89aec3a6c7d4ac22d4" - integrity sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w== - -array.prototype.findlastindex@^1.2.3: - version "1.2.5" - resolved "https://registry.yarnpkg.com/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.5.tgz#8c35a755c72908719453f87145ca011e39334d0d" - integrity sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ== - dependencies: - call-bind "^1.0.7" - define-properties "^1.2.1" - es-abstract "^1.23.2" - es-errors "^1.3.0" - es-object-atoms "^1.0.0" - es-shim-unscopables "^1.0.2" - -array.prototype.flat@^1.3.2: - version "1.3.2" - resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz#1476217df8cff17d72ee8f3ba06738db5b387d18" - integrity sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA== - dependencies: - call-bind "^1.0.2" - define-properties "^1.2.0" - es-abstract "^1.22.1" - es-shim-unscopables "^1.0.0" - -array.prototype.flatmap@^1.3.2: - version "1.3.2" - resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz#c9a7c6831db8e719d6ce639190146c24bbd3e527" - integrity sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ== - dependencies: - call-bind "^1.0.2" - define-properties "^1.2.0" - es-abstract "^1.22.1" - es-shim-unscopables "^1.0.0" - -arraybuffer.prototype.slice@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz#097972f4255e41bc3425e37dc3f6421cf9aefde6" - integrity sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A== - dependencies: - array-buffer-byte-length "^1.0.1" - call-bind "^1.0.5" - define-properties "^1.2.1" - es-abstract "^1.22.3" - es-errors "^1.2.1" - get-intrinsic "^1.2.3" - is-array-buffer "^3.0.4" - is-shared-array-buffer "^1.0.2" - -assertion-error@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b" - integrity sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw== - -astral-regex@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31" - integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ== - -async@^2.6.0: - version "2.6.4" - resolved "https://registry.yarnpkg.com/async/-/async-2.6.4.tgz#706b7ff6084664cd7eae713f6f965433b5504221" - integrity sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA== - dependencies: - lodash "^4.17.14" - -async@^3.2.3, async@~3.2.0: - version "3.2.5" - resolved "https://registry.yarnpkg.com/async/-/async-3.2.5.tgz#ebd52a8fdaf7a2289a24df399f8d8485c8a46b66" - integrity sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg== - -available-typed-arrays@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz#a5cc375d6a03c2efc87a553f3e0b1522def14846" - integrity sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ== - dependencies: - possible-typed-array-names "^1.0.0" - -balanced-match@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" - integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== - -binary-extensions@^2.0.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522" - integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw== - -body@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/body/-/body-5.1.0.tgz#e4ba0ce410a46936323367609ecb4e6553125069" - integrity sha512-chUsBxGRtuElD6fmw1gHLpvnKdVLK302peeFa9ZqAEk8TyzZ3fygLyUEDDPTJvL9+Bor0dIwn6ePOsRM2y0zQQ== - dependencies: - continuable-cache "^0.3.1" - error "^7.0.0" - raw-body "~1.1.0" - safe-json-parse "~1.0.1" - -brace-expansion@^1.1.7: - version "1.1.11" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" - integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== - dependencies: - balanced-match "^1.0.0" - concat-map "0.0.1" - -brace-expansion@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" - integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== - dependencies: - balanced-match "^1.0.0" - -braces@^3.0.3, braces@~3.0.2: - version "3.0.3" - resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" - integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== - dependencies: - fill-range "^7.1.1" - -browser-stdout@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" - integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw== - -bytes@1: - version "1.0.0" - resolved "https://registry.yarnpkg.com/bytes/-/bytes-1.0.0.tgz#3569ede8ba34315fab99c3e92cb04c7220de1fa8" - integrity sha512-/x68VkHLeTl3/Ll8IvxdwzhrT+IyKc52e/oyHhA2RwqPqswSnjVbSddfPRwAsJtbilMAPSRWwAlpxdYsSWOTKQ== - -call-bind@^1.0.2, call-bind@^1.0.5, call-bind@^1.0.6, call-bind@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.7.tgz#06016599c40c56498c18769d2730be242b6fa3b9" - integrity sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w== - dependencies: - es-define-property "^1.0.0" - es-errors "^1.3.0" - function-bind "^1.1.2" - get-intrinsic "^1.2.4" - set-function-length "^1.2.1" - -callsites@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" - integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== - -camelcase@^6.0.0: - version "6.3.0" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" - integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== - -chai-match-pattern@^1.1.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/chai-match-pattern/-/chai-match-pattern-1.3.0.tgz#cefd4437de465860f4f87922c31049eb9d979104" - integrity sha512-DflyfI8lZ56YuYAZMTBPWghjqFQfqY1IR0ZZXrjlGZJuRvtN0TjJMBpLsrMfc45kjivXJ06iayuP7lzG6ij1bQ== - dependencies: - lodash-match-pattern "^2.3.1" - -chai@^4.1.2: - version "4.4.1" - resolved "https://registry.yarnpkg.com/chai/-/chai-4.4.1.tgz#3603fa6eba35425b0f2ac91a009fe924106e50d1" - integrity sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g== - dependencies: - assertion-error "^1.1.0" - check-error "^1.0.3" - deep-eql "^4.1.3" - get-func-name "^2.0.2" - loupe "^2.3.6" - pathval "^1.1.1" - type-detect "^4.0.8" - -chalk@^2.4.2: - version "2.4.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" - integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== - dependencies: - ansi-styles "^3.2.1" - escape-string-regexp "^1.0.5" - supports-color "^5.3.0" - -chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.2, chalk@~4.1.0: - version "4.1.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" - integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - -check-error@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.3.tgz#a6502e4312a7ee969f646e83bb3ddd56281bd694" - integrity sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg== - dependencies: - get-func-name "^2.0.2" - -checkit@^0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/checkit/-/checkit-0.7.0.tgz#14979abc93018346bfcfdcbabc19ab54c0bfd74a" - integrity sha512-QgiWB8gMdF/CbmWyuxCk+f2MPQe0G1DfJfHCTbrfZlY3FnJWdnW+EGsRJctcYz/IrXxPYJmjRjdgmKUkyIZl/Q== - dependencies: - inherits "^2.0.1" - lodash "^4.0.0" - -chokidar@^3.5.3: - version "3.6.0" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b" - integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw== - dependencies: - anymatch "~3.1.2" - braces "~3.0.2" - glob-parent "~5.1.2" - is-binary-path "~2.1.0" - is-glob "~4.0.1" - normalize-path "~3.0.0" - readdirp "~3.6.0" - optionalDependencies: - fsevents "~2.3.2" - -cliui@^7.0.2: - version "7.0.4" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" - integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== - dependencies: - string-width "^4.2.0" - strip-ansi "^6.0.0" - wrap-ansi "^7.0.0" - -color-convert@^1.9.0, color-convert@^1.9.3: - version "1.9.3" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" - integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== - dependencies: - color-name "1.1.3" - -color-convert@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" - integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== - dependencies: - color-name "~1.1.4" - -color-name@1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" - integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== - -color-name@^1.0.0, color-name@~1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" - integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== - -color-string@^1.6.0: - version "1.9.1" - resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.9.1.tgz#4467f9146f036f855b764dfb5bf8582bf342c7a4" - integrity sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg== - dependencies: - color-name "^1.0.0" - simple-swizzle "^0.2.2" - -color@^3.1.3: - version "3.2.1" - resolved "https://registry.yarnpkg.com/color/-/color-3.2.1.tgz#3544dc198caf4490c3ecc9a790b54fe9ff45e164" - integrity sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA== - dependencies: - color-convert "^1.9.3" - color-string "^1.6.0" - -colors@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/colors/-/colors-1.1.2.tgz#168a4701756b6a7f51a12ce0c97bfa28c084ed63" - integrity sha512-ENwblkFQpqqia6b++zLD/KUWafYlVY/UNnAp7oz7LY7E924wmpye416wBOmvv/HMWzl8gL1kJlfvId/1Dg176w== - -colorspace@1.1.x: - version "1.1.4" - resolved "https://registry.yarnpkg.com/colorspace/-/colorspace-1.1.4.tgz#8d442d1186152f60453bf8070cd66eb364e59243" - integrity sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w== - dependencies: - color "^3.1.3" - text-hex "1.0.x" - -commander@^10.0.1: - version "10.0.1" - resolved "https://registry.yarnpkg.com/commander/-/commander-10.0.1.tgz#881ee46b4f77d1c1dccc5823433aa39b022cbe06" - integrity sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug== - -concat-map@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" - integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== - -confusing-browser-globals@^1.0.10: - version "1.0.11" - resolved "https://registry.yarnpkg.com/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz#ae40e9b57cdd3915408a2805ebd3a5585608dc81" - integrity sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA== - -continuable-cache@^0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/continuable-cache/-/continuable-cache-0.3.1.tgz#bd727a7faed77e71ff3985ac93351a912733ad0f" - integrity sha512-TF30kpKhTH8AGCG3dut0rdd/19B7Z+qCnrMoBLpyQu/2drZdNrrpcjPEoJeSVsQM+8KmWG5O56oPDjSSUsuTyA== - -cross-spawn@^7.0.2: - version "7.0.6" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" - integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== - dependencies: - path-key "^3.1.0" - shebang-command "^2.0.0" - which "^2.0.1" - -data-view-buffer@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/data-view-buffer/-/data-view-buffer-1.0.1.tgz#8ea6326efec17a2e42620696e671d7d5a8bc66b2" - integrity sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA== - dependencies: - call-bind "^1.0.6" - es-errors "^1.3.0" - is-data-view "^1.0.1" - -data-view-byte-length@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz#90721ca95ff280677eb793749fce1011347669e2" - integrity sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ== - dependencies: - call-bind "^1.0.7" - es-errors "^1.3.0" - is-data-view "^1.0.1" - -data-view-byte-offset@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz#5e0bbfb4828ed2d1b9b400cd8a7d119bca0ff18a" - integrity sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA== - dependencies: - call-bind "^1.0.6" - es-errors "^1.3.0" - is-data-view "^1.0.1" - -dateformat@~4.6.2: - version "4.6.3" - resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-4.6.3.tgz#556fa6497e5217fedb78821424f8a1c22fa3f4b5" - integrity sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA== - -debug@^3.1.0, debug@^3.2.7: - version "3.2.7" - resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" - integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== - dependencies: - ms "^2.1.1" - -debug@^4.0.1, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.5: - version "4.3.5" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.5.tgz#e83444eceb9fedd4a1da56d671ae2446a01a6e1e" - integrity sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg== - dependencies: - ms "2.1.2" - -decamelize@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-4.0.0.tgz#aa472d7bf660eb15f3494efd531cab7f2a709837" - integrity sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ== - -deep-eql@^4.1.3: - version "4.1.4" - resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-4.1.4.tgz#d0d3912865911bb8fac5afb4e3acfa6a28dc72b7" - integrity sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg== - dependencies: - type-detect "^4.0.0" - -deep-is@^0.1.3: - version "0.1.4" - resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" - integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== - -define-data-property@^1.0.1, define-data-property@^1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e" - integrity sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A== - dependencies: - es-define-property "^1.0.0" - es-errors "^1.3.0" - gopd "^1.0.1" - -define-properties@^1.2.0, define-properties@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.1.tgz#10781cc616eb951a80a034bafcaa7377f6af2b6c" - integrity sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg== - dependencies: - define-data-property "^1.0.1" - has-property-descriptors "^1.0.0" - object-keys "^1.1.1" - -detect-file@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/detect-file/-/detect-file-1.0.0.tgz#f0d66d03672a825cb1b73bdb3fe62310c8e552b7" - integrity sha512-DtCOLG98P007x7wiiOmfI0fi3eIKyWiLTGJ2MDnVi/E04lWGbf+JzrRHMm0rgIIZJGtHpKpbVgLWHrv8xXpc3Q== - -diff@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/diff/-/diff-5.2.0.tgz#26ded047cd1179b78b9537d5ef725503ce1ae531" - integrity sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A== - -doctrine@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d" - integrity sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw== - dependencies: - esutils "^2.0.2" - -doctrine@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" - integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== - dependencies: - esutils "^2.0.2" - -dotenv@^16.3.1: - version "16.4.5" - resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.5.tgz#cdd3b3b604cb327e286b4762e13502f717cb099f" - integrity sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg== - -emoji-regex@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" - integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== - -enabled@2.0.x: - version "2.0.0" - resolved "https://registry.yarnpkg.com/enabled/-/enabled-2.0.0.tgz#f9dd92ec2d6f4bbc0d5d1e64e21d61cd4665e7c2" - integrity sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ== - -enquirer@^2.3.5: - version "2.4.1" - resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.4.1.tgz#93334b3fbd74fc7097b224ab4a8fb7e40bf4ae56" - integrity sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ== - dependencies: - ansi-colors "^4.1.1" - strip-ansi "^6.0.1" - -error@^7.0.0: - version "7.2.1" - resolved "https://registry.yarnpkg.com/error/-/error-7.2.1.tgz#eab21a4689b5f684fc83da84a0e390de82d94894" - integrity sha512-fo9HBvWnx3NGUKMvMwB/CBCMMrfEJgbDTVDEkPygA3Bdd3lM1OyCd+rbQ8BwnpF6GdVeOLDNmyL4N5Bg80ZvdA== - dependencies: - string-template "~0.2.1" - -es-abstract@^1.22.1, es-abstract@^1.22.3, es-abstract@^1.23.0, es-abstract@^1.23.2: - version "1.23.3" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.23.3.tgz#8f0c5a35cd215312573c5a27c87dfd6c881a0aa0" - integrity sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A== - dependencies: - array-buffer-byte-length "^1.0.1" - arraybuffer.prototype.slice "^1.0.3" - available-typed-arrays "^1.0.7" - call-bind "^1.0.7" - data-view-buffer "^1.0.1" - data-view-byte-length "^1.0.1" - data-view-byte-offset "^1.0.0" - es-define-property "^1.0.0" - es-errors "^1.3.0" - es-object-atoms "^1.0.0" - es-set-tostringtag "^2.0.3" - es-to-primitive "^1.2.1" - function.prototype.name "^1.1.6" - get-intrinsic "^1.2.4" - get-symbol-description "^1.0.2" - globalthis "^1.0.3" - gopd "^1.0.1" - has-property-descriptors "^1.0.2" - has-proto "^1.0.3" - has-symbols "^1.0.3" - hasown "^2.0.2" - internal-slot "^1.0.7" - is-array-buffer "^3.0.4" - is-callable "^1.2.7" - is-data-view "^1.0.1" - is-negative-zero "^2.0.3" - is-regex "^1.1.4" - is-shared-array-buffer "^1.0.3" - is-string "^1.0.7" - is-typed-array "^1.1.13" - is-weakref "^1.0.2" - object-inspect "^1.13.1" - object-keys "^1.1.1" - object.assign "^4.1.5" - regexp.prototype.flags "^1.5.2" - safe-array-concat "^1.1.2" - safe-regex-test "^1.0.3" - string.prototype.trim "^1.2.9" - string.prototype.trimend "^1.0.8" - string.prototype.trimstart "^1.0.8" - typed-array-buffer "^1.0.2" - typed-array-byte-length "^1.0.1" - typed-array-byte-offset "^1.0.2" - typed-array-length "^1.0.6" - unbox-primitive "^1.0.2" - which-typed-array "^1.1.15" - -es-define-property@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.0.tgz#c7faefbdff8b2696cf5f46921edfb77cc4ba3845" - integrity sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ== - dependencies: - get-intrinsic "^1.2.4" - -es-errors@^1.2.1, es-errors@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" - integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== - -es-object-atoms@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.0.0.tgz#ddb55cd47ac2e240701260bc2a8e31ecb643d941" - integrity sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw== - dependencies: - es-errors "^1.3.0" - -es-set-tostringtag@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz#8bb60f0a440c2e4281962428438d58545af39777" - integrity sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ== - dependencies: - get-intrinsic "^1.2.4" - has-tostringtag "^1.0.2" - hasown "^2.0.1" - -es-shim-unscopables@^1.0.0, es-shim-unscopables@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz#1f6942e71ecc7835ed1c8a83006d8771a63a3763" - integrity sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw== - dependencies: - hasown "^2.0.0" - -es-to-primitive@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" - integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== - dependencies: - is-callable "^1.1.4" - is-date-object "^1.0.1" - is-symbol "^1.0.2" - -escalade@^3.1.1: - version "3.1.2" - resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.2.tgz#54076e9ab29ea5bf3d8f1ed62acffbb88272df27" - integrity sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA== - -escape-string-regexp@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" - integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== - -escape-string-regexp@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" - integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== - -eslint-config-airbnb-base@^15.0.0: - version "15.0.0" - resolved "https://registry.yarnpkg.com/eslint-config-airbnb-base/-/eslint-config-airbnb-base-15.0.0.tgz#6b09add90ac79c2f8d723a2580e07f3925afd236" - integrity sha512-xaX3z4ZZIcFLvh2oUNvcX5oEofXda7giYmuplVxoOg5A7EXJMrUyqRgR+mhDhPK8LZ4PttFOBvCYDbX3sUoUig== - dependencies: - confusing-browser-globals "^1.0.10" - object.assign "^4.1.2" - object.entries "^1.1.5" - semver "^6.3.0" - -eslint-import-resolver-node@^0.3.9: - version "0.3.9" - resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz#d4eaac52b8a2e7c3cd1903eb00f7e053356118ac" - integrity sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g== - dependencies: - debug "^3.2.7" - is-core-module "^2.13.0" - resolve "^1.22.4" - -eslint-module-utils@^2.8.0: - version "2.8.1" - resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.8.1.tgz#52f2404300c3bd33deece9d7372fb337cc1d7c34" - integrity sha512-rXDXR3h7cs7dy9RNpUlQf80nX31XWJEyGq1tRMo+6GsO5VmTe4UTwtmonAD4ZkAsrfMVDA2wlGJ3790Ys+D49Q== - dependencies: - debug "^3.2.7" - -eslint-plugin-import@^2.25.3: - version "2.29.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz#d45b37b5ef5901d639c15270d74d46d161150643" - integrity sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw== - dependencies: - array-includes "^3.1.7" - array.prototype.findlastindex "^1.2.3" - array.prototype.flat "^1.3.2" - array.prototype.flatmap "^1.3.2" - debug "^3.2.7" - doctrine "^2.1.0" - eslint-import-resolver-node "^0.3.9" - eslint-module-utils "^2.8.0" - hasown "^2.0.0" - is-core-module "^2.13.1" - is-glob "^4.0.3" - minimatch "^3.1.2" - object.fromentries "^2.0.7" - object.groupby "^1.0.1" - object.values "^1.1.7" - semver "^6.3.1" - tsconfig-paths "^3.15.0" - -eslint-scope@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" - integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== - dependencies: - esrecurse "^4.3.0" - estraverse "^4.1.1" - -eslint-scope@^7.2.2: - version "7.2.2" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.2.2.tgz#deb4f92563390f32006894af62a22dba1c46423f" - integrity sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg== - dependencies: - esrecurse "^4.3.0" - estraverse "^5.2.0" - -eslint-utils@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-2.1.0.tgz#d2de5e03424e707dc10c74068ddedae708741b27" - integrity sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg== - dependencies: - eslint-visitor-keys "^1.1.0" - -eslint-visitor-keys@^1.1.0, eslint-visitor-keys@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e" - integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ== - -eslint-visitor-keys@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303" - integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== - -eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.3: - version "3.4.3" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" - integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== - -eslint@^7.32.0: - version "7.32.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.32.0.tgz#c6d328a14be3fb08c8d1d21e12c02fdb7a2a812d" - integrity sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA== - dependencies: - "@babel/code-frame" "7.12.11" - "@eslint/eslintrc" "^0.4.3" - "@humanwhocodes/config-array" "^0.5.0" - ajv "^6.10.0" - chalk "^4.0.0" - cross-spawn "^7.0.2" - debug "^4.0.1" - doctrine "^3.0.0" - enquirer "^2.3.5" - escape-string-regexp "^4.0.0" - eslint-scope "^5.1.1" - eslint-utils "^2.1.0" - eslint-visitor-keys "^2.0.0" - espree "^7.3.1" - esquery "^1.4.0" - esutils "^2.0.2" - fast-deep-equal "^3.1.3" - file-entry-cache "^6.0.1" - functional-red-black-tree "^1.0.1" - glob-parent "^5.1.2" - globals "^13.6.0" - ignore "^4.0.6" - import-fresh "^3.0.0" - imurmurhash "^0.1.4" - is-glob "^4.0.0" - js-yaml "^3.13.1" - json-stable-stringify-without-jsonify "^1.0.1" - levn "^0.4.1" - lodash.merge "^4.6.2" - minimatch "^3.0.4" - natural-compare "^1.4.0" - optionator "^0.9.1" - progress "^2.0.0" - regexpp "^3.1.0" - semver "^7.2.1" - strip-ansi "^6.0.0" - strip-json-comments "^3.1.0" - table "^6.0.9" - text-table "^0.2.0" - v8-compile-cache "^2.0.3" - -eslint@^8.44.0: - version "8.57.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.57.0.tgz#c786a6fd0e0b68941aaf624596fb987089195668" - integrity sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ== - dependencies: - "@eslint-community/eslint-utils" "^4.2.0" - "@eslint-community/regexpp" "^4.6.1" - "@eslint/eslintrc" "^2.1.4" - "@eslint/js" "8.57.0" - "@humanwhocodes/config-array" "^0.11.14" - "@humanwhocodes/module-importer" "^1.0.1" - "@nodelib/fs.walk" "^1.2.8" - "@ungap/structured-clone" "^1.2.0" - ajv "^6.12.4" - chalk "^4.0.0" - cross-spawn "^7.0.2" - debug "^4.3.2" - doctrine "^3.0.0" - escape-string-regexp "^4.0.0" - eslint-scope "^7.2.2" - eslint-visitor-keys "^3.4.3" - espree "^9.6.1" - esquery "^1.4.2" - esutils "^2.0.2" - fast-deep-equal "^3.1.3" - file-entry-cache "^6.0.1" - find-up "^5.0.0" - glob-parent "^6.0.2" - globals "^13.19.0" - graphemer "^1.4.0" - ignore "^5.2.0" - imurmurhash "^0.1.4" - is-glob "^4.0.0" - is-path-inside "^3.0.3" - js-yaml "^4.1.0" - json-stable-stringify-without-jsonify "^1.0.1" - levn "^0.4.1" - lodash.merge "^4.6.2" - minimatch "^3.1.2" - natural-compare "^1.4.0" - optionator "^0.9.3" - strip-ansi "^6.0.1" - text-table "^0.2.0" - -espree@^7.3.0, espree@^7.3.1: - version "7.3.1" - resolved "https://registry.yarnpkg.com/espree/-/espree-7.3.1.tgz#f2df330b752c6f55019f8bd89b7660039c1bbbb6" - integrity sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g== - dependencies: - acorn "^7.4.0" - acorn-jsx "^5.3.1" - eslint-visitor-keys "^1.3.0" - -espree@^9.6.0, espree@^9.6.1: - version "9.6.1" - resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f" - integrity sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ== - dependencies: - acorn "^8.9.0" - acorn-jsx "^5.3.2" - eslint-visitor-keys "^3.4.1" - -esprima@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" - integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== - -esquery@^1.4.0, esquery@^1.4.2: - version "1.6.0" - resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.6.0.tgz#91419234f804d852a82dceec3e16cdc22cf9dae7" - integrity sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg== - dependencies: - estraverse "^5.1.0" - -esrecurse@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" - integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== - dependencies: - estraverse "^5.2.0" - -estraverse@^4.1.1: - version "4.3.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" - integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== - -estraverse@^5.1.0, estraverse@^5.2.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" - integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== - -esutils@^2.0.2: - version "2.0.3" - resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" - integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== - -eventemitter2@~0.4.13: - version "0.4.14" - resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-0.4.14.tgz#8f61b75cde012b2e9eb284d4545583b5643b61ab" - integrity sha512-K7J4xq5xAD5jHsGM5ReWXRTFa3JRGofHiMcVgQ8PRwgWxzjHpMWCIzsmyf60+mh8KLsqYPcjUMa0AC4hd6lPyQ== - -exit@~0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" - integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ== - -expand-tilde@^2.0.0, expand-tilde@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/expand-tilde/-/expand-tilde-2.0.2.tgz#97e801aa052df02454de46b02bf621642cdc8502" - integrity sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw== - dependencies: - homedir-polyfill "^1.0.1" - -extend@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" - integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== - -fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: - version "3.1.3" - resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" - integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== - -fast-json-stable-stringify@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" - integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== - -fast-levenshtein@^2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" - integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== - -fast-uri@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.0.1.tgz#cddd2eecfc83a71c1be2cc2ef2061331be8a7134" - integrity sha512-MWipKbbYiYI0UC7cl8m/i/IWTqfC8YXsqjzybjddLsFjStroQzsHXkc73JutMvBiXmOvapk+axIl79ig5t55Bw== - -fastq@^1.6.0: - version "1.17.1" - resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.17.1.tgz#2a523f07a4e7b1e81a42b91b8bf2254107753b47" - integrity sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w== - dependencies: - reusify "^1.0.4" - -faye-websocket@~0.10.0: - version "0.10.0" - resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.10.0.tgz#4e492f8d04dfb6f89003507f6edbf2d501e7c6f4" - integrity sha512-Xhj93RXbMSq8urNCUq4p9l0P6hnySJ/7YNRhYNug0bLOuii7pKO7xQFb5mx9xZXWCar88pLPb805PvUkwrLZpQ== - dependencies: - websocket-driver ">=0.5.1" - -fecha@^4.2.0: - version "4.2.3" - resolved "https://registry.yarnpkg.com/fecha/-/fecha-4.2.3.tgz#4d9ccdbc61e8629b259fdca67e65891448d569fd" - integrity sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw== - -file-entry-cache@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" - integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== - dependencies: - flat-cache "^3.0.4" - -fill-range@^7.1.1: - version "7.1.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" - integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== - dependencies: - to-regex-range "^5.0.1" - -find-up@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" - integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== - dependencies: - locate-path "^6.0.0" - path-exists "^4.0.0" - -findup-sync@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-4.0.0.tgz#956c9cdde804052b881b428512905c4a5f2cdef0" - integrity sha512-6jvvn/12IC4quLBL1KNokxC7wWTvYncaVUYSoxWw7YykPLuRrnv4qdHcSOywOI5RpkOVGeQRtWM8/q+G6W6qfQ== - dependencies: - detect-file "^1.0.0" - is-glob "^4.0.0" - micromatch "^4.0.2" - resolve-dir "^1.0.1" - -findup-sync@~5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-5.0.0.tgz#54380ad965a7edca00cc8f63113559aadc541bd2" - integrity sha512-MzwXju70AuyflbgeOhzvQWAvvQdo1XL0A9bVvlXsYcFEBM87WR4OakL4OfZq+QRmr+duJubio+UtNQCPsVESzQ== - dependencies: - detect-file "^1.0.0" - is-glob "^4.0.3" - micromatch "^4.0.4" - resolve-dir "^1.0.1" - -fined@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/fined/-/fined-1.2.0.tgz#d00beccf1aa2b475d16d423b0238b713a2c4a37b" - integrity sha512-ZYDqPLGxDkDhDZBjZBb+oD1+j0rA4E0pXY50eplAAOPg2N/gUBSSk5IM1/QhPfyVo19lJ+CvXpqfvk+b2p/8Ng== - dependencies: - expand-tilde "^2.0.2" - is-plain-object "^2.0.3" - object.defaults "^1.1.0" - object.pick "^1.2.0" - parse-filepath "^1.0.1" - -flagged-respawn@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/flagged-respawn/-/flagged-respawn-1.0.1.tgz#e7de6f1279ddd9ca9aac8a5971d618606b3aab41" - integrity sha512-lNaHNVymajmk0OJMBn8fVUAU1BtDeKIqKoVhk4xAALB57aALg6b4W0MfJ/cUE0g9YBXy5XhSlPIpYIJ7HaY/3Q== - -flat-cache@^3.0.4: - version "3.2.0" - resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.2.0.tgz#2c0c2d5040c99b1632771a9d105725c0115363ee" - integrity sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw== - dependencies: - flatted "^3.2.9" - keyv "^4.5.3" - rimraf "^3.0.2" - -flat@^5.0.2: - version "5.0.2" - resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241" - integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== - -flatted@^3.2.9: - version "3.3.1" - resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.1.tgz#21db470729a6734d4997002f439cb308987f567a" - integrity sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw== - -fn.name@1.x.x: - version "1.1.0" - resolved "https://registry.yarnpkg.com/fn.name/-/fn.name-1.1.0.tgz#26cad8017967aea8731bc42961d04a3d5988accc" - integrity sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw== - -for-each@^0.3.3: - version "0.3.3" - resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e" - integrity sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw== - dependencies: - is-callable "^1.1.3" - -for-in@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" - integrity sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ== - -for-own@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/for-own/-/for-own-1.0.0.tgz#c63332f415cedc4b04dbfe70cf836494c53cb44b" - integrity sha512-0OABksIGrxKK8K4kynWkQ7y1zounQxP+CWnyclVwj81KW3vlLlGUx57DKGcP/LH216GzqnstnPocF16Nxs0Ycg== - dependencies: - for-in "^1.0.1" - -fs.realpath@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" - integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== - -fsevents@~2.3.2: - version "2.3.3" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" - integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== - -function-bind@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" - integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== - -function.prototype.name@^1.1.6: - version "1.1.6" - resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.6.tgz#cdf315b7d90ee77a4c6ee216c3c3362da07533fd" - integrity sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg== - dependencies: - call-bind "^1.0.2" - define-properties "^1.2.0" - es-abstract "^1.22.1" - functions-have-names "^1.2.3" - -functional-red-black-tree@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" - integrity sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g== - -functions-have-names@^1.2.3: - version "1.2.3" - resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" - integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== - -gaze@^1.1.0: - version "1.1.3" - resolved "https://registry.yarnpkg.com/gaze/-/gaze-1.1.3.tgz#c441733e13b927ac8c0ff0b4c3b033f28812924a" - integrity sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g== - dependencies: - globule "^1.0.0" - -get-caller-file@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" - integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== - -get-func-name@^2.0.1, get-func-name@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.2.tgz#0d7cf20cd13fda808669ffa88f4ffc7a3943fc41" - integrity sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ== - -get-intrinsic@^1.1.3, get-intrinsic@^1.2.1, get-intrinsic@^1.2.3, get-intrinsic@^1.2.4: - version "1.2.4" - resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.4.tgz#e385f5a4b5227d449c3eabbad05494ef0abbeadd" - integrity sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ== - dependencies: - es-errors "^1.3.0" - function-bind "^1.1.2" - has-proto "^1.0.1" - has-symbols "^1.0.3" - hasown "^2.0.0" - -get-symbol-description@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.2.tgz#533744d5aa20aca4e079c8e5daf7fd44202821f5" - integrity sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg== - dependencies: - call-bind "^1.0.5" - es-errors "^1.3.0" - get-intrinsic "^1.2.4" - -getobject@~1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/getobject/-/getobject-1.0.2.tgz#25ec87a50370f6dcc3c6ba7ef43c4c16215c4c89" - integrity sha512-2zblDBaFcb3rB4rF77XVnuINOE2h2k/OnqXAiy0IrTxUfV1iFp3la33oAQVY9pCpWU268WFYVt2t71hlMuLsOg== - -glob-parent@^5.1.2, glob-parent@~5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" - integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== - dependencies: - is-glob "^4.0.1" - -glob-parent@^6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" - integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== - dependencies: - is-glob "^4.0.3" - -glob@^7.1.3: - version "7.2.3" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" - integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.1.1" - once "^1.3.0" - path-is-absolute "^1.0.0" - -glob@^8.1.0: - version "8.1.0" - resolved "https://registry.yarnpkg.com/glob/-/glob-8.1.0.tgz#d388f656593ef708ee3e34640fdfb99a9fd1c33e" - integrity sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^5.0.1" - once "^1.3.0" - -glob@~7.1.1, glob@~7.1.6: - version "7.1.7" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90" - integrity sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.4" - once "^1.3.0" - path-is-absolute "^1.0.0" - -global-modules@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-1.0.0.tgz#6d770f0eb523ac78164d72b5e71a8877265cc3ea" - integrity sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg== - dependencies: - global-prefix "^1.0.1" - is-windows "^1.0.1" - resolve-dir "^1.0.0" - -global-prefix@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-1.0.2.tgz#dbf743c6c14992593c655568cb66ed32c0122ebe" - integrity sha512-5lsx1NUDHtSjfg0eHlmYvZKv8/nVqX4ckFbM+FrGcQ+04KWcWFo9P5MxPZYSzUvyzmdTbI7Eix8Q4IbELDqzKg== - dependencies: - expand-tilde "^2.0.2" - homedir-polyfill "^1.0.1" - ini "^1.3.4" - is-windows "^1.0.1" - which "^1.2.14" - -globals@^13.19.0, globals@^13.6.0, globals@^13.9.0: - version "13.24.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-13.24.0.tgz#8432a19d78ce0c1e833949c36adb345400bb1171" - integrity sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ== - dependencies: - type-fest "^0.20.2" - -globalthis@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.4.tgz#7430ed3a975d97bfb59bcce41f5cabbafa651236" - integrity sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ== - dependencies: - define-properties "^1.2.1" - gopd "^1.0.1" - -globule@^1.0.0: - version "1.3.4" - resolved "https://registry.yarnpkg.com/globule/-/globule-1.3.4.tgz#7c11c43056055a75a6e68294453c17f2796170fb" - integrity sha512-OPTIfhMBh7JbBYDpa5b+Q5ptmMWKwcNcFSR/0c6t8V4f3ZAVBEsKNY37QdVqmLRYSMhOUGYrY0QhSoEpzGr/Eg== - dependencies: - glob "~7.1.1" - lodash "^4.17.21" - minimatch "~3.0.2" - -gopd@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c" - integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA== - dependencies: - get-intrinsic "^1.1.3" - -graphemer@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" - integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== - -grunt-cli@~1.4.3: - version "1.4.3" - resolved "https://registry.yarnpkg.com/grunt-cli/-/grunt-cli-1.4.3.tgz#22c9f1a3d2780bf9b0d206e832e40f8f499175ff" - integrity sha512-9Dtx/AhVeB4LYzsViCjUQkd0Kw0McN2gYpdmGYKtE2a5Yt7v1Q+HYZVWhqXc/kGnxlMtqKDxSwotiGeFmkrCoQ== - dependencies: - grunt-known-options "~2.0.0" - interpret "~1.1.0" - liftup "~3.0.1" - nopt "~4.0.1" - v8flags "~3.2.0" - -grunt-contrib-watch@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/grunt-contrib-watch/-/grunt-contrib-watch-1.1.0.tgz#c143ca5b824b288a024b856639a5345aedb78ed4" - integrity sha512-yGweN+0DW5yM+oo58fRu/XIRrPcn3r4tQx+nL7eMRwjpvk+rQY6R8o94BPK0i2UhTg9FN21hS+m8vR8v9vXfeg== - dependencies: - async "^2.6.0" - gaze "^1.1.0" - lodash "^4.17.10" - tiny-lr "^1.1.1" - -grunt-eslint@^24.0.0: - version "24.3.0" - resolved "https://registry.yarnpkg.com/grunt-eslint/-/grunt-eslint-24.3.0.tgz#2b82d107f6963de91daf58cf8311221a806a6de5" - integrity sha512-dUPiRgX8fhmh4uwTAn9xrzg7HV5j5DhGmZZGJdHfjy/AN9G4jD+5IjfbcAJ209JcIG8m4B7xz3crIhuDSm3siQ== - dependencies: - chalk "^4.1.2" - eslint "^8.44.0" - -grunt-known-options@~2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/grunt-known-options/-/grunt-known-options-2.0.0.tgz#cac641e897f9a0a680b8c9839803d35f3325103c" - integrity sha512-GD7cTz0I4SAede1/+pAbmJRG44zFLPipVtdL9o3vqx9IEyb7b4/Y3s7r6ofI3CchR5GvYJ+8buCSioDv5dQLiA== - -grunt-legacy-log-utils@~2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/grunt-legacy-log-utils/-/grunt-legacy-log-utils-2.1.0.tgz#49a8c7dc74051476dcc116c32faf9db8646856ef" - integrity sha512-lwquaPXJtKQk0rUM1IQAop5noEpwFqOXasVoedLeNzaibf/OPWjKYvvdqnEHNmU+0T0CaReAXIbGo747ZD+Aaw== - dependencies: - chalk "~4.1.0" - lodash "~4.17.19" - -grunt-legacy-log@~3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/grunt-legacy-log/-/grunt-legacy-log-3.0.0.tgz#1c6eaf92371ea415af31ea84ce50d434ef6d39c4" - integrity sha512-GHZQzZmhyq0u3hr7aHW4qUH0xDzwp2YXldLPZTCjlOeGscAOWWPftZG3XioW8MasGp+OBRIu39LFx14SLjXRcA== - dependencies: - colors "~1.1.2" - grunt-legacy-log-utils "~2.1.0" - hooker "~0.2.3" - lodash "~4.17.19" - -grunt-legacy-util@~2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/grunt-legacy-util/-/grunt-legacy-util-2.0.1.tgz#0f929d13a2faf9988c9917c82bff609e2d9ba255" - integrity sha512-2bQiD4fzXqX8rhNdXkAywCadeqiPiay0oQny77wA2F3WF4grPJXCvAcyoWUJV+po/b15glGkxuSiQCK299UC2w== - dependencies: - async "~3.2.0" - exit "~0.1.2" - getobject "~1.0.0" - hooker "~0.2.3" - lodash "~4.17.21" - underscore.string "~3.3.5" - which "~2.0.2" - -grunt@^1.5.3: - version "1.6.1" - resolved "https://registry.yarnpkg.com/grunt/-/grunt-1.6.1.tgz#0b4dd1524f26676dcf45d8f636b8d9061a8ede16" - integrity sha512-/ABUy3gYWu5iBmrUSRBP97JLpQUm0GgVveDCp6t3yRNIoltIYw7rEj3g5y1o2PGPR2vfTRGa7WC/LZHLTXnEzA== - dependencies: - dateformat "~4.6.2" - eventemitter2 "~0.4.13" - exit "~0.1.2" - findup-sync "~5.0.0" - glob "~7.1.6" - grunt-cli "~1.4.3" - grunt-known-options "~2.0.0" - grunt-legacy-log "~3.0.0" - grunt-legacy-util "~2.0.1" - iconv-lite "~0.6.3" - js-yaml "~3.14.0" - minimatch "~3.0.4" - nopt "~3.0.6" - -has-bigints@^1.0.1, has-bigints@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa" - integrity sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ== - -has-flag@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" - integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== - -has-flag@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" - integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== - -has-property-descriptors@^1.0.0, has-property-descriptors@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz#963ed7d071dc7bf5f084c5bfbe0d1b6222586854" - integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg== - dependencies: - es-define-property "^1.0.0" - -has-proto@^1.0.1, has-proto@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.3.tgz#b31ddfe9b0e6e9914536a6ab286426d0214f77fd" - integrity sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q== - -has-symbols@^1.0.2, has-symbols@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" - integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== - -has-tostringtag@^1.0.0, has-tostringtag@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc" - integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw== - dependencies: - has-symbols "^1.0.3" - -hasown@^2.0.0, hasown@^2.0.1, hasown@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" - integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== - dependencies: - function-bind "^1.1.2" - -he@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" - integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== - -homedir-polyfill@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz#743298cef4e5af3e194161fbadcc2151d3a058e8" - integrity sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA== - dependencies: - parse-passwd "^1.0.0" - -hooker@~0.2.3: - version "0.2.3" - resolved "https://registry.yarnpkg.com/hooker/-/hooker-0.2.3.tgz#b834f723cc4a242aa65963459df6d984c5d3d959" - integrity sha512-t+UerCsQviSymAInD01Pw+Dn/usmz1sRO+3Zk1+lx8eg+WKpD2ulcwWqHHL0+aseRBr+3+vIhiG1K1JTwaIcTA== - -http-parser-js@>=0.5.1: - version "0.5.8" - resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.8.tgz#af23090d9ac4e24573de6f6aecc9d84a48bf20e3" - integrity sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q== - -iconv-lite@~0.6.3: - version "0.6.3" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" - integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== - dependencies: - safer-buffer ">= 2.1.2 < 3.0.0" - -ignore@^4.0.6: - version "4.0.6" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" - integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== - -ignore@^5.2.0: - version "5.3.1" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.1.tgz#5073e554cd42c5b33b394375f538b8593e34d4ef" - integrity sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw== - -import-fresh@^3.0.0, import-fresh@^3.2.1: - version "3.3.0" - resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" - integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== - dependencies: - parent-module "^1.0.0" - resolve-from "^4.0.0" - -imurmurhash@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" - integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== - -inflight@^1.0.4: - version "1.0.6" - resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" - integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== - dependencies: - once "^1.3.0" - wrappy "1" - -inherits@2, inherits@^2.0.1, inherits@^2.0.3: - version "2.0.4" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" - integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== - -ini@^1.3.4: - version "1.3.8" - resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" - integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== - -internal-slot@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.7.tgz#c06dcca3ed874249881007b0a5523b172a190802" - integrity sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g== - dependencies: - es-errors "^1.3.0" - hasown "^2.0.0" - side-channel "^1.0.4" - -interpret@~1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.1.0.tgz#7ed1b1410c6a0e0f78cf95d3b8440c63f78b8614" - integrity sha512-CLM8SNMDu7C5psFCn6Wg/tgpj/bKAg7hc2gWqcuR9OD5Ft9PhBpIu8PLicPeis+xDd6YX2ncI8MCA64I9tftIA== - -is-absolute@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-absolute/-/is-absolute-1.0.0.tgz#395e1ae84b11f26ad1795e73c17378e48a301576" - integrity sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA== - dependencies: - is-relative "^1.0.0" - is-windows "^1.0.1" - -is-array-buffer@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.4.tgz#7a1f92b3d61edd2bc65d24f130530ea93d7fae98" - integrity sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw== - dependencies: - call-bind "^1.0.2" - get-intrinsic "^1.2.1" - -is-arrayish@^0.3.1: - version "0.3.2" - resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03" - integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== - -is-bigint@^1.0.1: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3" - integrity sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg== - dependencies: - has-bigints "^1.0.1" - -is-binary-path@~2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" - integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== - dependencies: - binary-extensions "^2.0.0" - -is-boolean-object@^1.1.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.2.tgz#5c6dc200246dd9321ae4b885a114bb1f75f63719" - integrity sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA== - dependencies: - call-bind "^1.0.2" - has-tostringtag "^1.0.0" - -is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.7: - version "1.2.7" - resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" - integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== - -is-core-module@^2.13.0, is-core-module@^2.13.1: - version "2.15.0" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.15.0.tgz#71c72ec5442ace7e76b306e9d48db361f22699ea" - integrity sha512-Dd+Lb2/zvk9SKy1TGCt1wFJFo/MWBPMX5x7KcvLajWTGuomczdQX61PvY5yK6SVACwpoexWo81IfFyoKY2QnTA== - dependencies: - hasown "^2.0.2" - -is-data-view@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-data-view/-/is-data-view-1.0.1.tgz#4b4d3a511b70f3dc26d42c03ca9ca515d847759f" - integrity sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w== - dependencies: - is-typed-array "^1.1.13" - -is-date-object@^1.0.1: - version "1.0.5" - resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f" - integrity sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ== - dependencies: - has-tostringtag "^1.0.0" - -is-extglob@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" - integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== - -is-fullwidth-code-point@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" - integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== - -is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: - version "4.0.3" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" - integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== - dependencies: - is-extglob "^2.1.1" - -is-negative-zero@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.3.tgz#ced903a027aca6381b777a5743069d7376a49747" - integrity sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw== - -is-number-object@^1.0.4: - version "1.0.7" - resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.7.tgz#59d50ada4c45251784e9904f5246c742f07a42fc" - integrity sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ== - dependencies: - has-tostringtag "^1.0.0" - -is-number@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" - integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== - -is-path-inside@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" - integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== - -is-plain-obj@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287" - integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA== - -is-plain-object@^2.0.3, is-plain-object@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" - integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== - dependencies: - isobject "^3.0.1" - -is-regex@^1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958" - integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg== - dependencies: - call-bind "^1.0.2" - has-tostringtag "^1.0.0" - -is-relative@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-relative/-/is-relative-1.0.0.tgz#a1bb6935ce8c5dba1e8b9754b9b2dcc020e2260d" - integrity sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA== - dependencies: - is-unc-path "^1.0.0" - -is-shared-array-buffer@^1.0.2, is-shared-array-buffer@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz#1237f1cba059cdb62431d378dcc37d9680181688" - integrity sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg== - dependencies: - call-bind "^1.0.7" - -is-stream@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" - integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== - -is-string@^1.0.5, is-string@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.7.tgz#0dd12bf2006f255bb58f695110eff7491eebc0fd" - integrity sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg== - dependencies: - has-tostringtag "^1.0.0" - -is-symbol@^1.0.2, is-symbol@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.4.tgz#a6dac93b635b063ca6872236de88910a57af139c" - integrity sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg== - dependencies: - has-symbols "^1.0.2" - -is-typed-array@^1.1.13: - version "1.1.13" - resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.13.tgz#d6c5ca56df62334959322d7d7dd1cca50debe229" - integrity sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw== - dependencies: - which-typed-array "^1.1.14" - -is-unc-path@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-unc-path/-/is-unc-path-1.0.0.tgz#d731e8898ed090a12c352ad2eaed5095ad322c9d" - integrity sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ== - dependencies: - unc-path-regex "^0.1.2" - -is-unicode-supported@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" - integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== - -is-weakref@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.0.2.tgz#9529f383a9338205e89765e0392efc2f100f06f2" - integrity sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ== - dependencies: - call-bind "^1.0.2" - -is-windows@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" - integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== - -isarray@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" - integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== - -isexe@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" - integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== - -isobject@^3.0.0, isobject@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" - integrity sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg== - -jit-grunt@^0.10.0: - version "0.10.0" - resolved "https://registry.yarnpkg.com/jit-grunt/-/jit-grunt-0.10.0.tgz#008c3a7fe1e96bd0d84e260ea1fa1783457f79c2" - integrity sha512-eT/f4c9wgZ3buXB7X1JY1w6uNtAV0bhrbOGf/mFmBb0CDNLUETJ/VRoydayWOI54tOoam0cz9RooVCn3QY1WoA== - -js-tokens@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" - integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== - -js-yaml@^3.13.1, js-yaml@~3.14.0: - version "3.14.1" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" - integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== - dependencies: - argparse "^1.0.7" - esprima "^4.0.0" - -js-yaml@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" - integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== - dependencies: - argparse "^2.0.1" - -json-buffer@3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" - integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== - -json-schema-traverse@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" - integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== - -json-schema-traverse@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" - integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== - -json-stable-stringify-without-jsonify@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" - integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== - -json5@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.2.tgz#63d98d60f21b313b77c4d6da18bfa69d80e1d593" - integrity sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA== - dependencies: - minimist "^1.2.0" - -keyv@^4.5.3: - version "4.5.4" - resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" - integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== - dependencies: - json-buffer "3.0.1" - -kind-of@^6.0.2: - version "6.0.3" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" - integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== - -kuler@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/kuler/-/kuler-2.0.0.tgz#e2c570a3800388fb44407e851531c1d670b061b3" - integrity sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A== - -lambda-local@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/lambda-local/-/lambda-local-2.2.0.tgz#733d183a4c3f2b16c6499b9ea72cec2f13278eef" - integrity sha512-bPcgpIXbHnVGfI/omZIlgucDqlf4LrsunwoKue5JdZeGybt8L6KyJz2Zu19ffuZwIwLj2NAI2ZyaqNT6/cetcg== - dependencies: - commander "^10.0.1" - dotenv "^16.3.1" - winston "^3.10.0" - -levn@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" - integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== - dependencies: - prelude-ls "^1.2.1" - type-check "~0.4.0" - -liftup@~3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/liftup/-/liftup-3.0.1.tgz#1cb81aff0f368464ed3a5f1a7286372d6b1a60ce" - integrity sha512-yRHaiQDizWSzoXk3APcA71eOI/UuhEkNN9DiW2Tt44mhYzX4joFoCZlxsSOF7RyeLlfqzFLQI1ngFq3ggMPhOw== - dependencies: - extend "^3.0.2" - findup-sync "^4.0.0" - fined "^1.2.0" - flagged-respawn "^1.0.1" - is-plain-object "^2.0.4" - object.map "^1.0.1" - rechoir "^0.7.0" - resolve "^1.19.0" - -livereload-js@^2.3.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/livereload-js/-/livereload-js-2.4.0.tgz#447c31cf1ea9ab52fc20db615c5ddf678f78009c" - integrity sha512-XPQH8Z2GDP/Hwz2PCDrh2mth4yFejwA1OZ/81Ti3LgKyhDcEjsSsqFWZojHG0va/duGd+WyosY7eXLDoOyqcPw== - -locate-path@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" - integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== - dependencies: - p-locate "^5.0.0" - -lodash-checkit@^2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/lodash-checkit/-/lodash-checkit-2.4.1.tgz#8b09c6b359a5d4de86f752ff9c231f1db5d23fd4" - integrity sha512-OAg5CqY04/dnsO8izxXqlleuj7z/dOk6yV0pm0TVtRaUwG5v2PGw4XWSIG/dLK0UWYk7g0/TCk8OCf50oVwv6w== - dependencies: - checkit "^0.7.0" - lodash "^4.17.21" - -lodash-match-pattern@^2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/lodash-match-pattern/-/lodash-match-pattern-2.3.1.tgz#d38f455a8b310bd91f7b2b4378297102a9b473c8" - integrity sha512-dpltpxoTqs94gGFm24VwHDyFh3/eNtqNjKrlnifIBLtnzYq0nAlNM6BIeLdGAfCWC/BwNtiLL1eKZTQpLVnY6A== - dependencies: - chalk "^4.1.0" - he "^1.2.0" - lodash-checkit "^2.4.1" - -lodash.merge@^4.6.2: - version "4.6.2" - resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" - integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== - -lodash.truncate@^4.4.2: - version "4.4.2" - resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193" - integrity sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw== - -lodash@^4.0.0, lodash@^4.17.10, lodash@^4.17.14, lodash@^4.17.21, lodash@~4.17.19, lodash@~4.17.21: - version "4.17.21" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" - integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== - -log-symbols@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" - integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== - dependencies: - chalk "^4.1.0" - is-unicode-supported "^0.1.0" - -logform@^2.6.0, logform@^2.6.1: - version "2.6.1" - resolved "https://registry.yarnpkg.com/logform/-/logform-2.6.1.tgz#71403a7d8cae04b2b734147963236205db9b3df0" - integrity sha512-CdaO738xRapbKIMVn2m4F6KTj4j7ooJ8POVnebSgKo3KBz5axNXRAL7ZdRjIV6NOr2Uf4vjtRkxrFETOioCqSA== - dependencies: - "@colors/colors" "1.6.0" - "@types/triple-beam" "^1.3.2" - fecha "^4.2.0" - ms "^2.1.1" - safe-stable-stringify "^2.3.1" - triple-beam "^1.3.0" - -loupe@^2.3.6: - version "2.3.7" - resolved "https://registry.yarnpkg.com/loupe/-/loupe-2.3.7.tgz#6e69b7d4db7d3ab436328013d37d1c8c3540c697" - integrity sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA== - dependencies: - get-func-name "^2.0.1" - -make-iterator@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/make-iterator/-/make-iterator-1.0.1.tgz#29b33f312aa8f547c4a5e490f56afcec99133ad6" - integrity sha512-pxiuXh0iVEq7VM7KMIhs5gxsfxCux2URptUQaXo4iZZJxBAzTPOLE2BumO5dbfVYq/hBJFBR/a1mFDmOx5AGmw== - dependencies: - kind-of "^6.0.2" - -map-cache@^0.2.0: - version "0.2.2" - resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" - integrity sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg== - -micromatch@^4.0.2, micromatch@^4.0.4: - version "4.0.8" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" - integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== - dependencies: - braces "^3.0.3" - picomatch "^2.3.1" - -minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" - integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== - dependencies: - brace-expansion "^1.1.7" - -minimatch@^5.0.1, minimatch@^5.1.6: - version "5.1.6" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96" - integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== - dependencies: - brace-expansion "^2.0.1" - -minimatch@~3.0.2, minimatch@~3.0.4: - version "3.0.8" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.8.tgz#5e6a59bd11e2ab0de1cfb843eb2d82e546c321c1" - integrity sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q== - dependencies: - brace-expansion "^1.1.7" - -minimist@^1.2.0, minimist@^1.2.6: - version "1.2.8" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" - integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== - -mocha@^10.0.0: - version "10.6.0" - resolved "https://registry.yarnpkg.com/mocha/-/mocha-10.6.0.tgz#465fc66c52613088e10018989a3b98d5e11954b9" - integrity sha512-hxjt4+EEB0SA0ZDygSS015t65lJw/I2yRCS3Ae+SJ5FrbzrXgfYwJr96f0OvIXdj7h4lv/vLCrH3rkiuizFSvw== - dependencies: - ansi-colors "^4.1.3" - browser-stdout "^1.3.1" - chokidar "^3.5.3" - debug "^4.3.5" - diff "^5.2.0" - escape-string-regexp "^4.0.0" - find-up "^5.0.0" - glob "^8.1.0" - he "^1.2.0" - js-yaml "^4.1.0" - log-symbols "^4.1.0" - minimatch "^5.1.6" - ms "^2.1.3" - serialize-javascript "^6.0.2" - strip-json-comments "^3.1.1" - supports-color "^8.1.1" - workerpool "^6.5.1" - yargs "^16.2.0" - yargs-parser "^20.2.9" - yargs-unparser "^2.0.0" - -ms@2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" - integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== - -ms@^2.1.1, ms@^2.1.3: - version "2.1.3" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" - integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== - -natural-compare@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" - integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== - -nopt@~3.0.6: - version "3.0.6" - resolved "https://registry.yarnpkg.com/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9" - integrity sha512-4GUt3kSEYmk4ITxzB/b9vaIDfUVWN/Ml1Fwl11IlnIG2iaJ9O6WXZ9SrYM9NLI8OCBieN2Y8SWC2oJV0RQ7qYg== - dependencies: - abbrev "1" - -nopt@~4.0.1: - version "4.0.3" - resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.3.tgz#a375cad9d02fd921278d954c2254d5aa57e15e48" - integrity sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg== - dependencies: - abbrev "1" - osenv "^0.1.4" - -normalize-path@^3.0.0, normalize-path@~3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" - integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== - -object-assign@^4.1.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" - integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== - -object-inspect@^1.13.1: - version "1.13.2" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.2.tgz#dea0088467fb991e67af4058147a24824a3043ff" - integrity sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g== - -object-keys@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" - integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== - -object.assign@^4.1.2, object.assign@^4.1.5: - version "4.1.5" - resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.5.tgz#3a833f9ab7fdb80fc9e8d2300c803d216d8fdbb0" - integrity sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ== - dependencies: - call-bind "^1.0.5" - define-properties "^1.2.1" - has-symbols "^1.0.3" - object-keys "^1.1.1" - -object.defaults@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/object.defaults/-/object.defaults-1.1.0.tgz#3a7f868334b407dea06da16d88d5cd29e435fecf" - integrity sha512-c/K0mw/F11k4dEUBMW8naXUuBuhxRCfG7W+yFy8EcijU/rSmazOUd1XAEEe6bC0OuXY4HUKjTJv7xbxIMqdxrA== - dependencies: - array-each "^1.0.1" - array-slice "^1.0.0" - for-own "^1.0.0" - isobject "^3.0.0" - -object.entries@^1.1.5: - version "1.1.8" - resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.8.tgz#bffe6f282e01f4d17807204a24f8edd823599c41" - integrity sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ== - dependencies: - call-bind "^1.0.7" - define-properties "^1.2.1" - es-object-atoms "^1.0.0" - -object.fromentries@^2.0.7: - version "2.0.8" - resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.8.tgz#f7195d8a9b97bd95cbc1999ea939ecd1a2b00c65" - integrity sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ== - dependencies: - call-bind "^1.0.7" - define-properties "^1.2.1" - es-abstract "^1.23.2" - es-object-atoms "^1.0.0" - -object.groupby@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/object.groupby/-/object.groupby-1.0.3.tgz#9b125c36238129f6f7b61954a1e7176148d5002e" - integrity sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ== - dependencies: - call-bind "^1.0.7" - define-properties "^1.2.1" - es-abstract "^1.23.2" - -object.map@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/object.map/-/object.map-1.0.1.tgz#cf83e59dc8fcc0ad5f4250e1f78b3b81bd801d37" - integrity sha512-3+mAJu2PLfnSVGHwIWubpOFLscJANBKuB/6A4CxBstc4aqwQY0FWcsppuy4jU5GSB95yES5JHSI+33AWuS4k6w== - dependencies: - for-own "^1.0.0" - make-iterator "^1.0.0" - -object.pick@^1.2.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747" - integrity sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ== - dependencies: - isobject "^3.0.1" - -object.values@^1.1.7: - version "1.2.0" - resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.2.0.tgz#65405a9d92cee68ac2d303002e0b8470a4d9ab1b" - integrity sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ== - dependencies: - call-bind "^1.0.7" - define-properties "^1.2.1" - es-object-atoms "^1.0.0" - -once@^1.3.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" - integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== - dependencies: - wrappy "1" - -one-time@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/one-time/-/one-time-1.0.0.tgz#e06bc174aed214ed58edede573b433bbf827cb45" - integrity sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g== - dependencies: - fn.name "1.x.x" - -optionator@^0.9.1, optionator@^0.9.3: - version "0.9.4" - resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.4.tgz#7ea1c1a5d91d764fb282139c88fe11e182a3a734" - integrity sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g== - dependencies: - deep-is "^0.1.3" - fast-levenshtein "^2.0.6" - levn "^0.4.1" - prelude-ls "^1.2.1" - type-check "^0.4.0" - word-wrap "^1.2.5" - -os-homedir@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" - integrity sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ== - -os-tmpdir@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" - integrity sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g== - -osenv@^0.1.4: - version "0.1.5" - resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.5.tgz#85cdfafaeb28e8677f416e287592b5f3f49ea410" - integrity sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g== - dependencies: - os-homedir "^1.0.0" - os-tmpdir "^1.0.0" - -p-limit@^3.0.2: - version "3.1.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" - integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== - dependencies: - yocto-queue "^0.1.0" - -p-locate@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" - integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== - dependencies: - p-limit "^3.0.2" - -parent-module@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" - integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== - dependencies: - callsites "^3.0.0" - -parse-filepath@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/parse-filepath/-/parse-filepath-1.0.2.tgz#a632127f53aaf3d15876f5872f3ffac763d6c891" - integrity sha512-FwdRXKCohSVeXqwtYonZTXtbGJKrn+HNyWDYVcp5yuJlesTwNH4rsmRZ+GrKAPJ5bLpRxESMeS+Rl0VCHRvB2Q== - dependencies: - is-absolute "^1.0.0" - map-cache "^0.2.0" - path-root "^0.1.1" - -parse-passwd@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6" - integrity sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q== - -path-exists@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" - integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== - -path-is-absolute@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" - integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== - -path-key@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" - integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== - -path-parse@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" - integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== - -path-root-regex@^0.1.0: - version "0.1.2" - resolved "https://registry.yarnpkg.com/path-root-regex/-/path-root-regex-0.1.2.tgz#bfccdc8df5b12dc52c8b43ec38d18d72c04ba96d" - integrity sha512-4GlJ6rZDhQZFE0DPVKh0e9jmZ5egZfxTkp7bcRDuPlJXbAwhxcl2dINPUAsjLdejqaLsCeg8axcLjIbvBjN4pQ== - -path-root@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/path-root/-/path-root-0.1.1.tgz#9a4a6814cac1c0cd73360a95f32083c8ea4745b7" - integrity sha512-QLcPegTHF11axjfojBIoDygmS2E3Lf+8+jI6wOVmNVenrKSo3mFdSGiIgdSHenczw3wPtlVMQaFVwGmM7BJdtg== - dependencies: - path-root-regex "^0.1.0" - -pathval@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.1.tgz#8534e77a77ce7ac5a2512ea21e0fdb8fcf6c3d8d" - integrity sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ== - -picocolors@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.1.tgz#a8ad579b571952f0e5d25892de5445bcfe25aaa1" - integrity sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew== - -picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" - integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== - -possible-typed-array-names@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz#89bb63c6fada2c3e90adc4a647beeeb39cc7bf8f" - integrity sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q== - -prelude-ls@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" - integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== - -progress@^2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" - integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== - -punycode@^2.1.0: - version "2.3.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" - integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== - -qs@^6.4.0: - version "6.12.3" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.12.3.tgz#e43ce03c8521b9c7fd7f1f13e514e5ca37727754" - integrity sha512-AWJm14H1vVaO/iNZ4/hO+HyaTehuy9nRqVdkTqlJt0HWvBiBIEXFmb4C0DGeYo3Xes9rrEW+TxHsaigCbN5ICQ== - dependencies: - side-channel "^1.0.6" - -queue-microtask@^1.2.2: - version "1.2.3" - resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" - integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== - -randombytes@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" - integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== - dependencies: - safe-buffer "^5.1.0" - -raw-body@~1.1.0: - version "1.1.7" - resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-1.1.7.tgz#1d027c2bfa116acc6623bca8f00016572a87d425" - integrity sha512-WmJJU2e9Y6M5UzTOkHaM7xJGAPQD8PNzx3bAd2+uhZAim6wDk6dAZxPVYLF67XhbR4hmKGh33Lpmh4XWrCH5Mg== - dependencies: - bytes "1" - string_decoder "0.10" - -readable-stream@^3.4.0, readable-stream@^3.6.2: - version "3.6.2" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" - integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== - dependencies: - inherits "^2.0.3" - string_decoder "^1.1.1" - util-deprecate "^1.0.1" - -readdirp@~3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" - integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== - dependencies: - picomatch "^2.2.1" - -rechoir@^0.7.0: - version "0.7.1" - resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.7.1.tgz#9478a96a1ca135b5e88fc027f03ee92d6c645686" - integrity sha512-/njmZ8s1wVeR6pjTZ+0nCnv8SpZNRMT2D1RLOJQESlYFDBvwpTA4KWJpZ+sBJ4+vhjILRcK7JIFdGCdxEAAitg== - dependencies: - resolve "^1.9.0" - -regexp.prototype.flags@^1.5.2: - version "1.5.2" - resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz#138f644a3350f981a858c44f6bb1a61ff59be334" - integrity sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw== - dependencies: - call-bind "^1.0.6" - define-properties "^1.2.1" - es-errors "^1.3.0" - set-function-name "^2.0.1" - -regexpp@^3.1.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2" - integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg== - -require-directory@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" - integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== - -require-from-string@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" - integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== - -resolve-dir@^1.0.0, resolve-dir@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/resolve-dir/-/resolve-dir-1.0.1.tgz#79a40644c362be82f26effe739c9bb5382046f43" - integrity sha512-R7uiTjECzvOsWSfdM0QKFNBVFcK27aHOUwdvK53BcW8zqnGdYp0Fbj82cy54+2A4P2tFM22J5kRfe1R+lM/1yg== - dependencies: - expand-tilde "^2.0.0" - global-modules "^1.0.0" - -resolve-from@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" - integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== - -resolve@^1.19.0, resolve@^1.22.4, resolve@^1.9.0: - version "1.22.8" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d" - integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== - dependencies: - is-core-module "^2.13.0" - path-parse "^1.0.7" - supports-preserve-symlinks-flag "^1.0.0" - -reusify@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" - integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== - -rimraf@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" - integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== - dependencies: - glob "^7.1.3" - -run-parallel@^1.1.9: - version "1.2.0" - resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" - integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== - dependencies: - queue-microtask "^1.2.2" - -safe-array-concat@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.1.2.tgz#81d77ee0c4e8b863635227c721278dd524c20edb" - integrity sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q== - dependencies: - call-bind "^1.0.7" - get-intrinsic "^1.2.4" - has-symbols "^1.0.3" - isarray "^2.0.5" - -safe-buffer@>=5.1.0, safe-buffer@^5.1.0, safe-buffer@~5.2.0: - version "5.2.1" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" - integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== - -safe-json-parse@~1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/safe-json-parse/-/safe-json-parse-1.0.1.tgz#3e76723e38dfdda13c9b1d29a1e07ffee4b30b57" - integrity sha512-o0JmTu17WGUaUOHa1l0FPGXKBfijbxK6qoHzlkihsDXxzBHvJcA7zgviKR92Xs841rX9pK16unfphLq0/KqX7A== - -safe-regex-test@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.0.3.tgz#a5b4c0f06e0ab50ea2c395c14d8371232924c377" - integrity sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw== - dependencies: - call-bind "^1.0.6" - es-errors "^1.3.0" - is-regex "^1.1.4" - -safe-stable-stringify@^2.3.1: - version "2.4.3" - resolved "https://registry.yarnpkg.com/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz#138c84b6f6edb3db5f8ef3ef7115b8f55ccbf886" - integrity sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g== - -"safer-buffer@>= 2.1.2 < 3.0.0": - version "2.1.2" - resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" - integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== - -semver@^6.3.0, semver@^6.3.1: - version "6.3.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" - integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== - -semver@^7.2.1: - version "7.6.3" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143" - integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== - -serialize-javascript@^6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.2.tgz#defa1e055c83bf6d59ea805d8da862254eb6a6c2" - integrity sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g== - dependencies: - randombytes "^2.1.0" - -set-function-length@^1.2.1: - version "1.2.2" - resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449" - integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg== - dependencies: - define-data-property "^1.1.4" - es-errors "^1.3.0" - function-bind "^1.1.2" - get-intrinsic "^1.2.4" - gopd "^1.0.1" - has-property-descriptors "^1.0.2" - -set-function-name@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/set-function-name/-/set-function-name-2.0.2.tgz#16a705c5a0dc2f5e638ca96d8a8cd4e1c2b90985" - integrity sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ== - dependencies: - define-data-property "^1.1.4" - es-errors "^1.3.0" - functions-have-names "^1.2.3" - has-property-descriptors "^1.0.2" - -shebang-command@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" - integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== - dependencies: - shebang-regex "^3.0.0" - -shebang-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" - integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== - -side-channel@^1.0.4, side-channel@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.6.tgz#abd25fb7cd24baf45466406b1096b7831c9215f2" - integrity sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA== - dependencies: - call-bind "^1.0.7" - es-errors "^1.3.0" - get-intrinsic "^1.2.4" - object-inspect "^1.13.1" - -simple-swizzle@^0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" - integrity sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg== - dependencies: - is-arrayish "^0.3.1" - -slice-ansi@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-4.0.0.tgz#500e8dd0fd55b05815086255b3195adf2a45fe6b" - integrity sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ== - dependencies: - ansi-styles "^4.0.0" - astral-regex "^2.0.0" - is-fullwidth-code-point "^3.0.0" - -sprintf-js@^1.1.1: - version "1.1.3" - resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.3.tgz#4914b903a2f8b685d17fdf78a70e917e872e444a" - integrity sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA== - -sprintf-js@~1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" - integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== - -stack-trace@0.0.x: - version "0.0.10" - resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0" - integrity sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg== - -string-template@~0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/string-template/-/string-template-0.2.1.tgz#42932e598a352d01fc22ec3367d9d84eec6c9add" - integrity sha512-Yptehjogou2xm4UJbxJ4CxgZx12HBfeystp0y3x7s4Dj32ltVVG1Gg8YhKjHZkHicuKpZX/ffilA8505VbUbpw== - -string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -string.prototype.trim@^1.2.9: - version "1.2.9" - resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz#b6fa326d72d2c78b6df02f7759c73f8f6274faa4" - integrity sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw== - dependencies: - call-bind "^1.0.7" - define-properties "^1.2.1" - es-abstract "^1.23.0" - es-object-atoms "^1.0.0" - -string.prototype.trimend@^1.0.8: - version "1.0.8" - resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz#3651b8513719e8a9f48de7f2f77640b26652b229" - integrity sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ== - dependencies: - call-bind "^1.0.7" - define-properties "^1.2.1" - es-object-atoms "^1.0.0" - -string.prototype.trimstart@^1.0.8: - version "1.0.8" - resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz#7ee834dda8c7c17eff3118472bb35bfedaa34dde" - integrity sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg== - dependencies: - call-bind "^1.0.7" - define-properties "^1.2.1" - es-object-atoms "^1.0.0" - -string_decoder@0.10: - version "0.10.31" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" - integrity sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ== - -string_decoder@^1.1.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" - integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== - dependencies: - safe-buffer "~5.2.0" - -strip-ansi@^6.0.0, strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-bom@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" - integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA== - -strip-json-comments@^3.1.0, strip-json-comments@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" - integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== - -supports-color@^5.3.0: - version "5.5.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" - integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== - dependencies: - has-flag "^3.0.0" - -supports-color@^7.1.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" - integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== - dependencies: - has-flag "^4.0.0" - -supports-color@^8.1.1: - version "8.1.1" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" - integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== - dependencies: - has-flag "^4.0.0" - -supports-preserve-symlinks-flag@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" - integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== - -table@^6.0.9: - version "6.8.2" - resolved "https://registry.yarnpkg.com/table/-/table-6.8.2.tgz#c5504ccf201213fa227248bdc8c5569716ac6c58" - integrity sha512-w2sfv80nrAh2VCbqR5AK27wswXhqcck2AhfnNW76beQXskGZ1V12GwS//yYVa3d3fcvAip2OUnbDAjW2k3v9fA== - dependencies: - ajv "^8.0.1" - lodash.truncate "^4.4.2" - slice-ansi "^4.0.0" - string-width "^4.2.3" - strip-ansi "^6.0.1" - -text-hex@1.0.x: - version "1.0.0" - resolved "https://registry.yarnpkg.com/text-hex/-/text-hex-1.0.0.tgz#69dc9c1b17446ee79a92bf5b884bb4b9127506f5" - integrity sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg== - -text-table@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" - integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== - -tiny-lr@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/tiny-lr/-/tiny-lr-1.1.1.tgz#9fa547412f238fedb068ee295af8b682c98b2aab" - integrity sha512-44yhA3tsaRoMOjQQ+5v5mVdqef+kH6Qze9jTpqtVufgYjYt08zyZAwNwwVBj3i1rJMnR52IxOW0LK0vBzgAkuA== - dependencies: - body "^5.1.0" - debug "^3.1.0" - faye-websocket "~0.10.0" - livereload-js "^2.3.0" - object-assign "^4.1.0" - qs "^6.4.0" - -to-regex-range@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" - integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== - dependencies: - is-number "^7.0.0" - -triple-beam@^1.3.0: - version "1.4.1" - resolved "https://registry.yarnpkg.com/triple-beam/-/triple-beam-1.4.1.tgz#6fde70271dc6e5d73ca0c3b24e2d92afb7441984" - integrity sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg== - -tsconfig-paths@^3.15.0: - version "3.15.0" - resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz#5299ec605e55b1abb23ec939ef15edaf483070d4" - integrity sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg== - dependencies: - "@types/json5" "^0.0.29" - json5 "^1.0.2" - minimist "^1.2.6" - strip-bom "^3.0.0" - -type-check@^0.4.0, type-check@~0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" - integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== - dependencies: - prelude-ls "^1.2.1" - -type-detect@^4.0.0, type-detect@^4.0.8: - version "4.0.8" - resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" - integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== - -type-fest@^0.20.2: - version "0.20.2" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" - integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== - -typed-array-buffer@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz#1867c5d83b20fcb5ccf32649e5e2fc7424474ff3" - integrity sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ== - dependencies: - call-bind "^1.0.7" - es-errors "^1.3.0" - is-typed-array "^1.1.13" - -typed-array-byte-length@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz#d92972d3cff99a3fa2e765a28fcdc0f1d89dec67" - integrity sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw== - dependencies: - call-bind "^1.0.7" - for-each "^0.3.3" - gopd "^1.0.1" - has-proto "^1.0.3" - is-typed-array "^1.1.13" - -typed-array-byte-offset@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz#f9ec1acb9259f395093e4567eb3c28a580d02063" - integrity sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA== - dependencies: - available-typed-arrays "^1.0.7" - call-bind "^1.0.7" - for-each "^0.3.3" - gopd "^1.0.1" - has-proto "^1.0.3" - is-typed-array "^1.1.13" - -typed-array-length@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.6.tgz#57155207c76e64a3457482dfdc1c9d1d3c4c73a3" - integrity sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g== - dependencies: - call-bind "^1.0.7" - for-each "^0.3.3" - gopd "^1.0.1" - has-proto "^1.0.3" - is-typed-array "^1.1.13" - possible-typed-array-names "^1.0.0" - -unbox-primitive@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz#29032021057d5e6cdbd08c5129c226dff8ed6f9e" - integrity sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw== - dependencies: - call-bind "^1.0.2" - has-bigints "^1.0.2" - has-symbols "^1.0.3" - which-boxed-primitive "^1.0.2" - -unc-path-regex@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/unc-path-regex/-/unc-path-regex-0.1.2.tgz#e73dd3d7b0d7c5ed86fbac6b0ae7d8c6a69d50fa" - integrity sha512-eXL4nmJT7oCpkZsHZUOJo8hcX3GbsiDOa0Qu9F646fi8dT3XuSVopVqAcEiVzSKKH7UoDti23wNX3qGFxcW5Qg== - -underscore.string@~3.3.5: - version "3.3.6" - resolved "https://registry.yarnpkg.com/underscore.string/-/underscore.string-3.3.6.tgz#ad8cf23d7423cb3b53b898476117588f4e2f9159" - integrity sha512-VoC83HWXmCrF6rgkyxS9GHv8W9Q5nhMKho+OadDJGzL2oDYbYEppBaCMH6pFlwLeqj2QS+hhkw2kpXkSdD1JxQ== - dependencies: - sprintf-js "^1.1.1" - util-deprecate "^1.0.2" - -uri-js@^4.2.2: - version "4.4.1" - resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" - integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== - dependencies: - punycode "^2.1.0" - -util-deprecate@^1.0.1, util-deprecate@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" - integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== - -v8-compile-cache@^2.0.3: - version "2.4.0" - resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.4.0.tgz#cdada8bec61e15865f05d097c5f4fd30e94dc128" - integrity sha512-ocyWc3bAHBB/guyqJQVI5o4BZkPhznPYUG2ea80Gond/BgNWpap8TOmLSeeQG7bnh2KMISxskdADG59j7zruhw== - -v8flags@~3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/v8flags/-/v8flags-3.2.0.tgz#b243e3b4dfd731fa774e7492128109a0fe66d656" - integrity sha512-mH8etigqMfiGWdeXpaaqGfs6BndypxusHHcv2qSHyZkGEznCd/qAXCWWRzeowtL54147cktFOC4P5y+kl8d8Jg== - dependencies: - homedir-polyfill "^1.0.1" - -websocket-driver@>=0.5.1: - version "0.7.4" - resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.4.tgz#89ad5295bbf64b480abcba31e4953aca706f5760" - integrity sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg== - dependencies: - http-parser-js ">=0.5.1" - safe-buffer ">=5.1.0" - websocket-extensions ">=0.1.1" - -websocket-extensions@>=0.1.1: - version "0.1.4" - resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42" - integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg== - -which-boxed-primitive@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" - integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg== - dependencies: - is-bigint "^1.0.1" - is-boolean-object "^1.1.0" - is-number-object "^1.0.4" - is-string "^1.0.5" - is-symbol "^1.0.3" - -which-typed-array@^1.1.14, which-typed-array@^1.1.15: - version "1.1.15" - resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.15.tgz#264859e9b11a649b388bfaaf4f767df1f779b38d" - integrity sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA== - dependencies: - available-typed-arrays "^1.0.7" - call-bind "^1.0.7" - for-each "^0.3.3" - gopd "^1.0.1" - has-tostringtag "^1.0.2" - -which@^1.2.14: - version "1.3.1" - resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" - integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== - dependencies: - isexe "^2.0.0" - -which@^2.0.1, which@~2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" - integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== - dependencies: - isexe "^2.0.0" - -winston-transport@^4.7.0: - version "4.7.1" - resolved "https://registry.yarnpkg.com/winston-transport/-/winston-transport-4.7.1.tgz#52ff1bcfe452ad89991a0aaff9c3b18e7f392569" - integrity sha512-wQCXXVgfv/wUPOfb2x0ruxzwkcZfxcktz6JIMUaPLmcNhO4bZTwA/WtDWK74xV3F2dKu8YadrFv0qhwYjVEwhA== - dependencies: - logform "^2.6.1" - readable-stream "^3.6.2" - triple-beam "^1.3.0" - -winston@^3.10.0: - version "3.13.1" - resolved "https://registry.yarnpkg.com/winston/-/winston-3.13.1.tgz#53ddadb9c2332eb12cff8306413b3480dc82b6c3" - integrity sha512-SvZit7VFNvXRzbqGHsv5KSmgbEYR5EiQfDAL9gxYkRqa934Hnk++zze0wANKtMHcy/gI4W/3xmSDwlhf865WGw== - dependencies: - "@colors/colors" "^1.6.0" - "@dabh/diagnostics" "^2.0.2" - async "^3.2.3" - is-stream "^2.0.0" - logform "^2.6.0" - one-time "^1.0.0" - readable-stream "^3.4.0" - safe-stable-stringify "^2.3.1" - stack-trace "0.0.x" - triple-beam "^1.3.0" - winston-transport "^4.7.0" - -word-wrap@^1.2.5: - version "1.2.5" - resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" - integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== - -workerpool@^6.5.1: - version "6.5.1" - resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.5.1.tgz#060f73b39d0caf97c6db64da004cd01b4c099544" - integrity sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA== - -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - -wrappy@1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" - integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== - -y18n@^5.0.5: - version "5.0.8" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" - integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== - -yargs-parser@^20.2.2, yargs-parser@^20.2.9: - version "20.2.9" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" - integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== - -yargs-unparser@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/yargs-unparser/-/yargs-unparser-2.0.0.tgz#f131f9226911ae5d9ad38c432fe809366c2325eb" - integrity sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA== - dependencies: - camelcase "^6.0.0" - decamelize "^4.0.0" - flat "^5.0.2" - is-plain-obj "^2.1.0" - -yargs@^16.2.0: - version "16.2.0" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" - integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== - dependencies: - cliui "^7.0.2" - escalade "^3.1.1" - get-caller-file "^2.0.5" - require-directory "^2.1.1" - string-width "^4.2.0" - y18n "^5.0.5" - yargs-parser "^20.2.2" - -yocto-queue@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" - integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== diff --git a/backend/compact-connect/lambdas/nodejs/eslint.config.mjs b/backend/compact-connect/lambdas/nodejs/eslint.config.mjs index 7482c561b..bea30f936 100644 --- a/backend/compact-connect/lambdas/nodejs/eslint.config.mjs +++ b/backend/compact-connect/lambdas/nodejs/eslint.config.mjs @@ -1,11 +1,3 @@ -// -// eslint.config.mjs -// Adapted from IA Website (all vue-related stuff removed): -// https://github.com/InspiringApps/IA-Website/blob/cfbaf96be5841cb762a7dcd00a4ccca10fafa672/2021v2/webroot/.eslintrc.js -// -// Created by InspiringApps on 10/18/2024. -// Copyright © 2024. All rights reserved. -// import typescriptParser from '@typescript-eslint/parser'; import typescriptPlugin from '@typescript-eslint/eslint-plugin'; diff --git a/backend/compact-connect/lambdas/nodejs/package.json b/backend/compact-connect/lambdas/nodejs/package.json index a45544447..2b74aae5d 100644 --- a/backend/compact-connect/lambdas/nodejs/package.json +++ b/backend/compact-connect/lambdas/nodejs/package.json @@ -12,7 +12,7 @@ "test:csp": "mocha cloudfront-csp/test", "lint:csp": "grunt check", "lint": "eslint '**/*.ts' && grunt check", - "test": "NODE_OPTIONS=\"$NODE_OPTIONS --experimental-vm-modules\" jest --coverage && mocha cloudfront-csp/test", + "test": "NODE_OPTIONS=\"$NODE_OPTIONS --experimental-vm-modules\" jest --coverage", "audit:dependencies": "yarn audit --groups dependencies --level moderate", "grunt": "grunt" }, diff --git a/backend/compact-connect/lambdas/python/compact-configuration/.coveragerc b/backend/compact-connect/lambdas/python/compact-configuration/.coveragerc deleted file mode 100644 index 99c409d65..000000000 --- a/backend/compact-connect/lambdas/python/compact-configuration/.coveragerc +++ /dev/null @@ -1,10 +0,0 @@ -[run] -data_file = ../../../.coverage - -omit = - */cdk.out/* - */smoke-test/* - */tests/* - -[report] -skip_empty = true diff --git a/backend/compact-connect/lambdas/python/custom-resources/.coveragerc b/backend/compact-connect/lambdas/python/custom-resources/.coveragerc deleted file mode 100644 index 193e8ec8a..000000000 --- a/backend/compact-connect/lambdas/python/custom-resources/.coveragerc +++ /dev/null @@ -1,10 +0,0 @@ -[run] -data_file = ../../../../.coverage - -omit = - */cdk.out/* - */smoke-test/* - */tests/* - -[report] -skip_empty = true diff --git a/backend/compact-connect/lambdas/python/data-events/.coveragerc b/backend/compact-connect/lambdas/python/data-events/.coveragerc deleted file mode 100644 index 193e8ec8a..000000000 --- a/backend/compact-connect/lambdas/python/data-events/.coveragerc +++ /dev/null @@ -1,10 +0,0 @@ -[run] -data_file = ../../../../.coverage - -omit = - */cdk.out/* - */smoke-test/* - */tests/* - -[report] -skip_empty = true diff --git a/backend/compact-connect/lambdas/python/provider-data-v1/.coveragerc b/backend/compact-connect/lambdas/python/provider-data-v1/.coveragerc deleted file mode 100644 index 193e8ec8a..000000000 --- a/backend/compact-connect/lambdas/python/provider-data-v1/.coveragerc +++ /dev/null @@ -1,10 +0,0 @@ -[run] -data_file = ../../../../.coverage - -omit = - */cdk.out/* - */smoke-test/* - */tests/* - -[report] -skip_empty = true diff --git a/backend/compact-connect/lambdas/python/purchases/.coveragerc b/backend/compact-connect/lambdas/python/purchases/.coveragerc deleted file mode 100644 index 193e8ec8a..000000000 --- a/backend/compact-connect/lambdas/python/purchases/.coveragerc +++ /dev/null @@ -1,10 +0,0 @@ -[run] -data_file = ../../../../.coverage - -omit = - */cdk.out/* - */smoke-test/* - */tests/* - -[report] -skip_empty = true diff --git a/backend/compact-connect/lambdas/python/staff-user-pre-token/.coveragerc b/backend/compact-connect/lambdas/python/staff-user-pre-token/.coveragerc deleted file mode 100644 index 193e8ec8a..000000000 --- a/backend/compact-connect/lambdas/python/staff-user-pre-token/.coveragerc +++ /dev/null @@ -1,10 +0,0 @@ -[run] -data_file = ../../../../.coverage - -omit = - */cdk.out/* - */smoke-test/* - */tests/* - -[report] -skip_empty = true diff --git a/backend/compact-connect/lambdas/python/staff-users/.coveragerc b/backend/compact-connect/lambdas/python/staff-users/.coveragerc deleted file mode 100644 index 193e8ec8a..000000000 --- a/backend/compact-connect/lambdas/python/staff-users/.coveragerc +++ /dev/null @@ -1,10 +0,0 @@ -[run] -data_file = ../../../../.coverage - -omit = - */cdk.out/* - */smoke-test/* - */tests/* - -[report] -skip_empty = true diff --git a/backend/compact-connect/pipeline/__init__.py b/backend/compact-connect/pipeline/__init__.py index c188f221c..3bdcc2b1f 100644 --- a/backend/compact-connect/pipeline/__init__.py +++ b/backend/compact-connect/pipeline/__init__.py @@ -9,15 +9,13 @@ from aws_cdk.pipelines import CodeBuildStep from aws_cdk.pipelines import CodePipeline as CdkCodePipeline from cdk_nag import NagSuppressions +from constructs import Construct + from common_constructs.access_logs_bucket import AccessLogsBucket from common_constructs.alarm_topic import AlarmTopic from common_constructs.stack import Stack -from constructs import Construct - from pipeline.backend_pipeline import BackendPipeline from pipeline.backend_stage import BackendStage -from pipeline.frontend_pipeline import FrontendPipeline -from pipeline.frontend_stage import FrontendStage from pipeline.synth_substitute_stage import SynthSubstituteStage TEST_ENVIRONMENT_NAME = 'test' @@ -309,63 +307,6 @@ def _add_nag_suppressions_for_trigger_pipeline_step_role(self, trigger_pipeline_ ) -class BaseFrontendPipelineStack(BasePipelineStack): - """ - Base class for frontend pipeline stacks. - Implements common functionality for all frontend pipeline stacks. - """ - - def __init__( - self, - scope: Construct, - construct_id: str, - environment_name: str, - env: Environment, - removal_policy: RemovalPolicy, - pipeline_access_logs_bucket: IBucket, - **kwargs, - ): - super().__init__( - scope, - construct_id, - environment_name=environment_name, - env=env, - removal_policy=removal_policy, - pipeline_access_logs_bucket=pipeline_access_logs_bucket, - **kwargs, - ) - - def _determine_frontend_stage(self, construct_id, environment_name, environment_context): - """ - Return either a real FrontendStage or a SynthSubstituteStage depending on pipeline synthesis context. - - This method centralizes the stage creation logic to conditionally create a lightweight substitute - stage during pipeline synthesis when the stage is not part of the pipeline being synthesized. - """ - # Check if we're in pipeline synthesis mode and if we're synthesizing this specific pipeline - action = self.node.try_get_context('action') - pipeline_stack_name = self.node.try_get_context('pipelineStack') - - # If we're in pipeline synthesis mode and this is not the pipeline being synthesized, - # use a lightweight substitute stage - if ( - action == PIPELINE_SYNTH_ACTION and pipeline_stack_name != self.stack_name - ) or action == BOOTSTRAP_DEPLOY_ACTION: - return SynthSubstituteStage( - self, - 'SubstituteFrontendStage', - environment_context=environment_context, - ) - - # Otherwise, use the real stage for deployment - return FrontendStage( - self, - construct_id, - environment_name=environment_name, - environment_context=environment_context, - ) - - class TestBackendPipelineStack(BaseBackendPipelineStack): """Pipeline stack for the test backend environment, triggered by the development branch.""" @@ -428,61 +369,6 @@ def __init__( self._add_nag_suppressions_for_trigger_pipeline_step_role(trigger_frontend_pipeline_step) -class TestFrontendPipelineStack(BaseFrontendPipelineStack): - """Pipeline stack for the test frontend environment.""" - - def __init__( - self, - scope: Construct, - construct_id: str, - *, - pipeline_shared_encryption_key: IKey, - pipeline_alarm_topic: ITopic, - pipeline_access_logs_bucket: IBucket, - cdk_path: str, - **kwargs, - ): - super().__init__( - scope, - construct_id, - environment_name=TEST_ENVIRONMENT_NAME, - removal_policy=RemovalPolicy.DESTROY, - pipeline_access_logs_bucket=pipeline_access_logs_bucket, - **kwargs, - ) - - # Allows us to override the default branching scheme for the test environment, via context variable - pre_prod_trigger_branch = self.pipeline_environment_context.get('pre_prod_trigger_branch', 'development') - - self.pre_prod_frontend_pipeline = FrontendPipeline( - self, - 'TestFrontendPipeline', - pipeline_name=self._get_frontend_pipeline_name(), - github_repo_string=self.github_repo_string, - cdk_path=cdk_path, - connection_arn=self.connection_arn, - source_branch=pre_prod_trigger_branch, - encryption_key=pipeline_shared_encryption_key, - alarm_topic=pipeline_alarm_topic, - access_logs_bucket=self.access_logs_bucket, - ssm_parameter=self.parameter, - pipeline_stack_name=self.stack_name, - environment_context=self.pipeline_environment_context, - self_mutation=True, - removal_policy=self.removal_policy, - ) - - self.pre_prod_frontend_stage = self._determine_frontend_stage( - construct_id='TestFrontend', - environment_name=TEST_ENVIRONMENT_NAME, - environment_context=self.ssm_context['environments'][TEST_ENVIRONMENT_NAME], - ) - - self.pre_prod_frontend_pipeline.add_stage(self.pre_prod_frontend_stage) - self.pre_prod_frontend_pipeline.build_pipeline() - self._add_pipeline_cdk_assume_role_policy(self.pre_prod_frontend_pipeline) - - class BetaBackendPipelineStack(BaseBackendPipelineStack): """Pipeline stack for the beta backend environment, triggered by the main branch.""" @@ -542,58 +428,6 @@ def __init__( self._add_nag_suppressions_for_trigger_pipeline_step_role(trigger_frontend_pipeline_step) -class BetaFrontendPipelineStack(BaseFrontendPipelineStack): - """Pipeline stack for the beta frontend environment.""" - - def __init__( - self, - scope: Construct, - construct_id: str, - *, - pipeline_shared_encryption_key: IKey, - pipeline_alarm_topic: ITopic, - pipeline_access_logs_bucket: IBucket, - cdk_path: str, - **kwargs, - ): - super().__init__( - scope, - construct_id, - environment_name=BETA_ENVIRONMENT_NAME, - removal_policy=RemovalPolicy.RETAIN, - pipeline_access_logs_bucket=pipeline_access_logs_bucket, - **kwargs, - ) - - self.beta_frontend_pipeline = FrontendPipeline( - self, - 'BetaFrontendPipeline', - pipeline_name=self._get_frontend_pipeline_name(), - github_repo_string=self.github_repo_string, - cdk_path=cdk_path, - connection_arn=self.connection_arn, - source_branch='main', - encryption_key=pipeline_shared_encryption_key, - alarm_topic=pipeline_alarm_topic, - access_logs_bucket=self.access_logs_bucket, - ssm_parameter=self.parameter, - pipeline_stack_name=self.stack_name, - environment_context=self.pipeline_environment_context, - self_mutation=True, - removal_policy=self.removal_policy, - ) - - self.beta_frontend_stage = self._determine_frontend_stage( - construct_id='BetaFrontend', - environment_name=BETA_ENVIRONMENT_NAME, - environment_context=self.ssm_context['environments'][BETA_ENVIRONMENT_NAME], - ) - - self.beta_frontend_pipeline.add_stage(self.beta_frontend_stage) - self.beta_frontend_pipeline.build_pipeline() - self._add_pipeline_cdk_assume_role_policy(self.beta_frontend_pipeline) - - class ProdBackendPipelineStack(BaseBackendPipelineStack): """Pipeline stack for the production backend environment, triggered by the main branch.""" @@ -654,55 +488,3 @@ def __init__( # the following must be called after the pipeline is built self._add_pipeline_cdk_assume_role_policy(self.prod_pipeline) self._add_nag_suppressions_for_trigger_pipeline_step_role(trigger_frontend_pipeline_step) - - -class ProdFrontendPipelineStack(BaseFrontendPipelineStack): - """Pipeline stack for the production frontend environment.""" - - def __init__( - self, - scope: Construct, - construct_id: str, - *, - pipeline_shared_encryption_key: IKey, - pipeline_alarm_topic: ITopic, - pipeline_access_logs_bucket: IBucket, - cdk_path: str, - **kwargs, - ): - super().__init__( - scope, - construct_id, - environment_name=PROD_ENVIRONMENT_NAME, - removal_policy=RemovalPolicy.RETAIN, - pipeline_access_logs_bucket=pipeline_access_logs_bucket, - **kwargs, - ) - - self.prod_frontend_pipeline = FrontendPipeline( - self, - 'ProdFrontendPipeline', - pipeline_name=self._get_frontend_pipeline_name(), - github_repo_string=self.github_repo_string, - cdk_path=cdk_path, - connection_arn=self.connection_arn, - source_branch='main', - encryption_key=pipeline_shared_encryption_key, - alarm_topic=pipeline_alarm_topic, - access_logs_bucket=self.access_logs_bucket, - ssm_parameter=self.parameter, - pipeline_stack_name=self.stack_name, - environment_context=self.pipeline_environment_context, - self_mutation=True, - removal_policy=self.removal_policy, - ) - - self.prod_frontend_stage = self._determine_frontend_stage( - construct_id='ProdFrontend', - environment_name=PROD_ENVIRONMENT_NAME, - environment_context=self.ssm_context['environments'][PROD_ENVIRONMENT_NAME], - ) - - self.prod_frontend_pipeline.add_stage(self.prod_frontend_stage) - self.prod_frontend_pipeline.build_pipeline() - self._add_pipeline_cdk_assume_role_policy(self.prod_frontend_pipeline) diff --git a/backend/compact-connect/pipeline/backend_pipeline.py b/backend/compact-connect/pipeline/backend_pipeline.py index 541ae096f..367b36a99 100644 --- a/backend/compact-connect/pipeline/backend_pipeline.py +++ b/backend/compact-connect/pipeline/backend_pipeline.py @@ -11,9 +11,10 @@ from aws_cdk.pipelines import CodeBuildOptions, CodePipelineSource, ShellStep from aws_cdk.pipelines import CodePipeline as CdkCodePipeline from cdk_nag import NagSuppressions -from common_constructs.bucket import Bucket from constructs import Construct +from common_constructs.bucket import Bucket + class BackendPipeline(CdkCodePipeline): """ diff --git a/backend/compact-connect/pipeline/backend_stage.py b/backend/compact-connect/pipeline/backend_stage.py index 83bbbd0fc..ca8782998 100644 --- a/backend/compact-connect/pipeline/backend_stage.py +++ b/backend/compact-connect/pipeline/backend_stage.py @@ -1,6 +1,7 @@ from aws_cdk import Environment, Stage -from common_constructs.stack import StandardTags from constructs import Construct + +from common_constructs.stack import StandardTags from stacks.api_lambda_stack import ApiLambdaStack from stacks.api_stack import ApiStack from stacks.disaster_recovery_stack import DisasterRecoveryStack diff --git a/backend/compact-connect/ruff.toml b/backend/compact-connect/ruff.toml new file mode 100644 index 000000000..66682be80 --- /dev/null +++ b/backend/compact-connect/ruff.toml @@ -0,0 +1,112 @@ +# Exclude a variety of commonly ignored directories. +exclude = [ + ".bzr", + ".direnv", + ".eggs", + ".git", + ".git-rewrite", + ".hg", + ".ipynb_checkpoints", + ".mypy_cache", + ".nox", + ".pants.d", + ".pyenv", + ".pytest_cache", + ".pytype", + ".ruff_cache", + ".svn", + ".tox", + ".venv", + ".vscode", + "__pypackages__", + "_build", + "buck-out", + "build", + "dist", + "node_modules", + "site-packages", + "venv" +] + +line-length = 120 +indent-width = 4 + +# Assume Python 3.12 +target-version = "py312" + +[lint] +select = [ + "A", # flake8-builtins + "ARG", # flake8-unused-arguments + "B", # flake8-bugbear + "BLE", # flake8-blind-except + "DTZ", # flake8-datetimez + "E", # pycodestyle + "F", # Pyflakes + "FIX", # flake8-fixme + "FA", # flake8-future-annotations + "G", # flake8-logging-format + "I", # isort + "N", # pep8-naming + "PIE", # flake8-pie + "SLF", # flake8-self + "RET", # flake8-return + "RSE", # flake8-raise + "S", # flake8-bandit + "T", # flake8-print + "UP", # pyupgrade + "W", # warning +] +ignore = [ + "S311", # suspicious-non-cryptographic-random-usage + "N818", # error-suffix-on-exception-name + # the following are ignored by recommendation of ruff documentation + # due to conflicts with the ruff formatter see https://docs.astral.sh/ruff/formatter/#conflicting-lint-rules + "W191", # indentation contains tabs + "E111", # indentation-with-invalid-multiple + "E114", # indentation-with-invalid-multiple-comment + "E117", # over-indented + "D206", # indent-with-spaces + "D300", # triple-single-quotes + "Q000", # bad-quotes-inline-string + "Q001", # bad-quotes-multiline-string + "Q002", # bad-quotes-docstring + "Q003", # avoidable-escaped-quote + "COM812", # missing-trailing-comma + "COM819", # prohibited-trailing-comma + "ISC001", # single-line-implicit-string-concatenation + "ISC002", # multi-line-implicit-string-concatenation +] + +# Allow fix for all enabled rules (when `--fix`) is provided. +fixable = ["ALL"] +unfixable = [] + +# Allow unused variables when underscore-prefixed. +dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" + +[format] +quote-style = "single" + +# Like Black, indent with spaces, rather than tabs. +indent-style = "space" + +# Like Black, respect magic trailing commas. +skip-magic-trailing-comma = false + +# Like Black, automatically detect the appropriate line ending. +line-ending = "auto" + +# Enable auto-formatting of code examples in docstrings. Markdown, +# reStructuredText code/literal blocks and doctests are all supported. +# +# This is currently disabled by default, but it is planned for this +# to be opt-out in the future. +docstring-code-format = false + +# Set the line length limit used when formatting code snippets in +# docstrings. +# +# This only has an effect when the `docstring-code-format` setting is +# enabled. +docstring-code-line-length = "dynamic" diff --git a/backend/compact-connect/stacks/api_lambda_stack/__init__.py b/backend/compact-connect/stacks/api_lambda_stack/__init__.py index bf68bad61..ddd92358c 100644 --- a/backend/compact-connect/stacks/api_lambda_stack/__init__.py +++ b/backend/compact-connect/stacks/api_lambda_stack/__init__.py @@ -1,8 +1,8 @@ from __future__ import annotations -from common_constructs.stack import AppStack from constructs import Construct +from common_constructs.stack import AppStack from stacks import persistent_stack as ps from stacks.provider_users import ProviderUsersStack diff --git a/backend/compact-connect/stacks/api_lambda_stack/provider_users.py b/backend/compact-connect/stacks/api_lambda_stack/provider_users.py index 9fde2d555..809321727 100644 --- a/backend/compact-connect/stacks/api_lambda_stack/provider_users.py +++ b/backend/compact-connect/stacks/api_lambda_stack/provider_users.py @@ -7,9 +7,9 @@ from aws_cdk.aws_cloudwatch_actions import SnsAction from aws_cdk.aws_secretsmanager import Secret from cdk_nag import NagSuppressions + from common_constructs.python_function import PythonFunction from common_constructs.stack import Stack - from stacks import persistent_stack as ps from stacks.provider_users import ProviderUsersStack diff --git a/backend/compact-connect/stacks/api_stack/__init__.py b/backend/compact-connect/stacks/api_stack/__init__.py index 4d2578047..fdb5d4ead 100644 --- a/backend/compact-connect/stacks/api_stack/__init__.py +++ b/backend/compact-connect/stacks/api_stack/__init__.py @@ -1,9 +1,9 @@ from __future__ import annotations -from common_constructs.security_profile import SecurityProfile -from common_constructs.stack import AppStack from constructs import Construct +from common_constructs.security_profile import SecurityProfile +from common_constructs.stack import AppStack from stacks import persistent_stack as ps from stacks.api_lambda_stack import ApiLambdaStack from stacks.provider_users import ProviderUsersStack diff --git a/backend/compact-connect/stacks/api_stack/api.py b/backend/compact-connect/stacks/api_stack/api.py index 21a518cd1..351993d16 100644 --- a/backend/compact-connect/stacks/api_stack/api.py +++ b/backend/compact-connect/stacks/api_stack/api.py @@ -4,10 +4,10 @@ from functools import cached_property from aws_cdk import ArnFormat -from common_constructs.cc_api import CCApi -from common_constructs.stack import Stack from constructs import Construct +from common_constructs.cc_api import CCApi +from common_constructs.stack import Stack from stacks import persistent_stack as ps from stacks.api_lambda_stack import ApiLambdaStack from stacks.provider_users import ProviderUsersStack diff --git a/backend/compact-connect/stacks/api_stack/v1_api/api.py b/backend/compact-connect/stacks/api_stack/v1_api/api.py index 59bec1306..29b3536eb 100644 --- a/backend/compact-connect/stacks/api_stack/v1_api/api.py +++ b/backend/compact-connect/stacks/api_stack/v1_api/api.py @@ -4,10 +4,10 @@ from aws_cdk.aws_apigateway import AuthorizationType, IResource, MethodOptions from cdk_nag import NagSuppressions + from common_constructs.python_function import PythonFunction from common_constructs.ssm_parameter_utility import SSMParameterUtility from common_constructs.stack import Stack - from stacks import persistent_stack as ps from stacks.api_lambda_stack import ApiLambdaStack from stacks.api_stack.v1_api.attestations import Attestations diff --git a/backend/compact-connect/stacks/api_stack/v1_api/attestations.py b/backend/compact-connect/stacks/api_stack/v1_api/attestations.py index dbc26fac8..73e090906 100644 --- a/backend/compact-connect/stacks/api_stack/v1_api/attestations.py +++ b/backend/compact-connect/stacks/api_stack/v1_api/attestations.py @@ -5,10 +5,10 @@ from aws_cdk import Duration from aws_cdk.aws_apigateway import LambdaIntegration, MethodResponse, Resource from cdk_nag import NagSuppressions + from common_constructs.cc_api import CCApi from common_constructs.python_function import PythonFunction from common_constructs.stack import Stack - from stacks import persistent_stack as ps from .api_model import ApiModel diff --git a/backend/compact-connect/stacks/api_stack/v1_api/bulk_upload_url.py b/backend/compact-connect/stacks/api_stack/v1_api/bulk_upload_url.py index 0b164ec0a..d795928f4 100644 --- a/backend/compact-connect/stacks/api_stack/v1_api/bulk_upload_url.py +++ b/backend/compact-connect/stacks/api_stack/v1_api/bulk_upload_url.py @@ -6,6 +6,7 @@ from aws_cdk.aws_apigateway import AuthorizationType, LambdaIntegration, MethodOptions, MethodResponse, Resource from aws_cdk.aws_iam import IRole from aws_cdk.aws_s3 import IBucket + from common_constructs.cc_api import CCApi from common_constructs.python_function import PythonFunction from common_constructs.stack import Stack diff --git a/backend/compact-connect/stacks/api_stack/v1_api/compact_configuration_api.py b/backend/compact-connect/stacks/api_stack/v1_api/compact_configuration_api.py index 7fc99a4a6..4e2604ede 100644 --- a/backend/compact-connect/stacks/api_stack/v1_api/compact_configuration_api.py +++ b/backend/compact-connect/stacks/api_stack/v1_api/compact_configuration_api.py @@ -5,10 +5,10 @@ from aws_cdk import Duration from aws_cdk.aws_apigateway import LambdaIntegration, MethodOptions, MethodResponse, Resource from cdk_nag import NagSuppressions + from common_constructs.cc_api import CCApi from common_constructs.python_function import PythonFunction from common_constructs.stack import Stack - from stacks import persistent_stack as ps from .api_model import ApiModel diff --git a/backend/compact-connect/stacks/api_stack/v1_api/credentials.py b/backend/compact-connect/stacks/api_stack/v1_api/credentials.py index 63bd86abf..41bdd820d 100644 --- a/backend/compact-connect/stacks/api_stack/v1_api/credentials.py +++ b/backend/compact-connect/stacks/api_stack/v1_api/credentials.py @@ -6,10 +6,10 @@ from aws_cdk.aws_apigateway import LambdaIntegration, MethodOptions, MethodResponse, Resource from aws_cdk.aws_iam import Effect, PolicyStatement from cdk_nag import NagSuppressions + from common_constructs.cc_api import CCApi from common_constructs.python_function import PythonFunction from common_constructs.stack import Stack - from stacks import persistent_stack as ps from .api_model import ApiModel diff --git a/backend/compact-connect/stacks/api_stack/v1_api/post_licenses.py b/backend/compact-connect/stacks/api_stack/v1_api/post_licenses.py index 2c14ce5b8..e40a9c80a 100644 --- a/backend/compact-connect/stacks/api_stack/v1_api/post_licenses.py +++ b/backend/compact-connect/stacks/api_stack/v1_api/post_licenses.py @@ -7,10 +7,10 @@ from aws_cdk.aws_dynamodb import ITable from aws_cdk.aws_iam import IRole from aws_cdk.aws_sqs import IQueue + from common_constructs.cc_api import CCApi from common_constructs.python_function import PythonFunction from common_constructs.stack import Stack - from stacks import persistent_stack as ps from .api_model import ApiModel diff --git a/backend/compact-connect/stacks/api_stack/v1_api/provider_management.py b/backend/compact-connect/stacks/api_stack/v1_api/provider_management.py index e2e16d1f8..8d6f31c76 100644 --- a/backend/compact-connect/stacks/api_stack/v1_api/provider_management.py +++ b/backend/compact-connect/stacks/api_stack/v1_api/provider_management.py @@ -17,11 +17,11 @@ from aws_cdk.aws_iam import Policy, PolicyStatement from aws_cdk.aws_kms import IKey from cdk_nag import NagSuppressions + from common_constructs.cc_api import CCApi from common_constructs.nodejs_function import NodejsFunction from common_constructs.python_function import PythonFunction from common_constructs.stack import Stack - from stacks import persistent_stack as ps from stacks.persistent_stack import ProviderTable, ProviderUsersBucket, RateLimitingTable, SSNTable, StaffUsers diff --git a/backend/compact-connect/stacks/api_stack/v1_api/provider_users.py b/backend/compact-connect/stacks/api_stack/v1_api/provider_users.py index 1cea1e63b..289d8ae46 100644 --- a/backend/compact-connect/stacks/api_stack/v1_api/provider_users.py +++ b/backend/compact-connect/stacks/api_stack/v1_api/provider_users.py @@ -3,10 +3,10 @@ from aws_cdk import Duration from aws_cdk.aws_apigateway import LambdaIntegration, MethodResponse, Resource from cdk_nag import NagSuppressions + from common_constructs.cc_api import CCApi from common_constructs.python_function import PythonFunction from common_constructs.stack import Stack - from stacks.api_lambda_stack import ApiLambdaStack from .api_model import ApiModel diff --git a/backend/compact-connect/stacks/api_stack/v1_api/public_lookup_api.py b/backend/compact-connect/stacks/api_stack/v1_api/public_lookup_api.py index 63443650e..2697859fa 100644 --- a/backend/compact-connect/stacks/api_stack/v1_api/public_lookup_api.py +++ b/backend/compact-connect/stacks/api_stack/v1_api/public_lookup_api.py @@ -5,10 +5,10 @@ from aws_cdk import Duration from aws_cdk.aws_apigateway import LambdaIntegration, MethodResponse, Resource from cdk_nag import NagSuppressions + from common_constructs.cc_api import CCApi from common_constructs.python_function import PythonFunction from common_constructs.stack import Stack - from stacks import persistent_stack as ps from .api_model import ApiModel diff --git a/backend/compact-connect/stacks/api_stack/v1_api/purchases.py b/backend/compact-connect/stacks/api_stack/v1_api/purchases.py index d212ff496..d5225c261 100644 --- a/backend/compact-connect/stacks/api_stack/v1_api/purchases.py +++ b/backend/compact-connect/stacks/api_stack/v1_api/purchases.py @@ -8,10 +8,10 @@ from aws_cdk.aws_iam import Effect, PolicyStatement from aws_cdk.aws_kms import IKey from cdk_nag import NagSuppressions + from common_constructs.cc_api import CCApi from common_constructs.python_function import PythonFunction from common_constructs.stack import Stack - from stacks.persistent_stack import CompactConfigurationTable, ProviderTable from .api_model import ApiModel diff --git a/backend/compact-connect/stacks/api_stack/v1_api/staff_users.py b/backend/compact-connect/stacks/api_stack/v1_api/staff_users.py index 64e31ad1a..e5f862e18 100644 --- a/backend/compact-connect/stacks/api_stack/v1_api/staff_users.py +++ b/backend/compact-connect/stacks/api_stack/v1_api/staff_users.py @@ -21,10 +21,10 @@ from aws_cdk.aws_dynamodb import ITable from aws_cdk.aws_kms import IKey from cdk_nag import NagSuppressions + from common_constructs.cc_api import CCApi from common_constructs.python_function import PythonFunction from common_constructs.user_pool import UserPool - from stacks import persistent_stack as ps from .api_model import ApiModel diff --git a/backend/compact-connect/stacks/disaster_recovery_stack/__init__.py b/backend/compact-connect/stacks/disaster_recovery_stack/__init__.py index 901e57bf8..9cdc369df 100644 --- a/backend/compact-connect/stacks/disaster_recovery_stack/__init__.py +++ b/backend/compact-connect/stacks/disaster_recovery_stack/__init__.py @@ -2,9 +2,9 @@ from aws_cdk.aws_dynamodb import Table from aws_cdk.aws_iam import PolicyStatement, ServicePrincipal from aws_cdk.aws_kms import Key -from common_constructs.stack import AppStack from constructs import Construct +from common_constructs.stack import AppStack from stacks import persistent_stack as ps from stacks.disaster_recovery_stack.restore_dynamo_db_table_step_function import ( RestoreDynamoDbTableStepFunctionConstruct, diff --git a/backend/compact-connect/stacks/disaster_recovery_stack/restore_dynamo_db_table_step_function.py b/backend/compact-connect/stacks/disaster_recovery_stack/restore_dynamo_db_table_step_function.py index eb108772a..5609a9696 100644 --- a/backend/compact-connect/stacks/disaster_recovery_stack/restore_dynamo_db_table_step_function.py +++ b/backend/compact-connect/stacks/disaster_recovery_stack/restore_dynamo_db_table_step_function.py @@ -20,9 +20,10 @@ WaitTime, ) from cdk_nag import NagSuppressions -from common_constructs.stack import Stack from constructs import Construct +from common_constructs.stack import Stack + class RestoreDynamoDbTableStepFunctionConstruct(Construct): def __init__( diff --git a/backend/compact-connect/stacks/disaster_recovery_stack/sync_table_step_function.py b/backend/compact-connect/stacks/disaster_recovery_stack/sync_table_step_function.py index 8a10706ed..8c6e0af77 100644 --- a/backend/compact-connect/stacks/disaster_recovery_stack/sync_table_step_function.py +++ b/backend/compact-connect/stacks/disaster_recovery_stack/sync_table_step_function.py @@ -19,9 +19,10 @@ ) from aws_cdk.aws_stepfunctions_tasks import LambdaInvoke from cdk_nag import NagSuppressions +from constructs import Construct + from common_constructs.python_function import PythonFunction from common_constructs.stack import Stack -from constructs import Construct class SyncTableDataStepFunctionConstruct(Construct): diff --git a/backend/compact-connect/stacks/event_listener_stack/__init__.py b/backend/compact-connect/stacks/event_listener_stack/__init__.py index 1177a1a84..5af836006 100644 --- a/backend/compact-connect/stacks/event_listener_stack/__init__.py +++ b/backend/compact-connect/stacks/event_listener_stack/__init__.py @@ -5,12 +5,12 @@ from aws_cdk import Duration from aws_cdk.aws_events import EventBus from cdk_nag import NagSuppressions +from constructs import Construct + from common_constructs.python_function import PythonFunction from common_constructs.queue_event_listener import QueueEventListener from common_constructs.ssm_parameter_utility import SSMParameterUtility from common_constructs.stack import AppStack -from constructs import Construct - from stacks import persistent_stack as ps diff --git a/backend/compact-connect/stacks/ingest_stack.py b/backend/compact-connect/stacks/ingest_stack.py index 5391597b1..e51ea3abd 100644 --- a/backend/compact-connect/stacks/ingest_stack.py +++ b/backend/compact-connect/stacks/ingest_stack.py @@ -8,12 +8,12 @@ from aws_cdk.aws_events import EventBus, EventPattern, Rule from aws_cdk.aws_events_targets import SqsQueue from cdk_nag import NagSuppressions +from constructs import Construct + from common_constructs.python_function import PythonFunction from common_constructs.queued_lambda_processor import QueuedLambdaProcessor from common_constructs.ssm_parameter_utility import SSMParameterUtility from common_constructs.stack import AppStack, Stack -from constructs import Construct - from stacks import persistent_stack as ps diff --git a/backend/compact-connect/stacks/managed_login_stack.py b/backend/compact-connect/stacks/managed_login_stack.py index 621d55dee..8672fc14b 100644 --- a/backend/compact-connect/stacks/managed_login_stack.py +++ b/backend/compact-connect/stacks/managed_login_stack.py @@ -1,9 +1,9 @@ import json from aws_cdk.aws_cognito import CfnManagedLoginBranding -from common_constructs.stack import AppStack from constructs import Construct +from common_constructs.stack import AppStack from stacks.persistent_stack import PersistentStack from stacks.provider_users import ProviderUsersStack diff --git a/backend/compact-connect/stacks/notification_stack.py b/backend/compact-connect/stacks/notification_stack.py index a95b3471e..457bde652 100644 --- a/backend/compact-connect/stacks/notification_stack.py +++ b/backend/compact-connect/stacks/notification_stack.py @@ -8,13 +8,13 @@ from aws_cdk.aws_events import EventBus, EventPattern, IEventBus, Rule from aws_cdk.aws_events_targets import SqsQueue from cdk_nag import NagSuppressions +from constructs import Construct + from common_constructs.python_function import PythonFunction from common_constructs.queue_event_listener import QueueEventListener from common_constructs.queued_lambda_processor import QueuedLambdaProcessor from common_constructs.ssm_parameter_utility import SSMParameterUtility from common_constructs.stack import AppStack -from constructs import Construct - from stacks import persistent_stack as ps diff --git a/backend/compact-connect/stacks/persistent_stack/__init__.py b/backend/compact-connect/stacks/persistent_stack/__init__.py index 20d54bc7d..736b03320 100644 --- a/backend/compact-connect/stacks/persistent_stack/__init__.py +++ b/backend/compact-connect/stacks/persistent_stack/__init__.py @@ -8,6 +8,8 @@ from aws_cdk.aws_lambda_python_alpha import PythonLayerVersion from aws_cdk.aws_logs import QueryDefinition, QueryString from cdk_nag import NagSuppressions +from constructs import Construct + from common_constructs.access_logs_bucket import AccessLogsBucket from common_constructs.alarm_topic import AlarmTopic from common_constructs.frontend_app_config_utility import PersistentStackFrontendAppConfigUtility @@ -16,8 +18,6 @@ from common_constructs.security_profile import SecurityProfile from common_constructs.ssm_parameter_utility import SSMParameterUtility from common_constructs.stack import AppStack -from constructs import Construct - from stacks.backup_infrastructure_stack import BackupInfrastructureStack from stacks.persistent_stack.bulk_uploads_bucket import BulkUploadsBucket from stacks.persistent_stack.compact_configuration_table import CompactConfigurationTable diff --git a/backend/compact-connect/stacks/persistent_stack/bulk_uploads_bucket.py b/backend/compact-connect/stacks/persistent_stack/bulk_uploads_bucket.py index fed7da066..a1ad7565d 100644 --- a/backend/compact-connect/stacks/persistent_stack/bulk_uploads_bucket.py +++ b/backend/compact-connect/stacks/persistent_stack/bulk_uploads_bucket.py @@ -13,13 +13,13 @@ from aws_cdk.aws_s3_notifications import LambdaDestination from aws_cdk.aws_sqs import IQueue from cdk_nag import NagSuppressions +from constructs import Construct + +import stacks.persistent_stack as ps from common_constructs.access_logs_bucket import AccessLogsBucket from common_constructs.bucket import Bucket from common_constructs.python_function import PythonFunction from common_constructs.stack import Stack -from constructs import Construct - -import stacks.persistent_stack as ps class BulkUploadsBucket(Bucket): diff --git a/backend/compact-connect/stacks/persistent_stack/compact_configuration_table.py b/backend/compact-connect/stacks/persistent_stack/compact_configuration_table.py index 7bc1d3684..d9beaf991 100644 --- a/backend/compact-connect/stacks/persistent_stack/compact_configuration_table.py +++ b/backend/compact-connect/stacks/persistent_stack/compact_configuration_table.py @@ -3,9 +3,9 @@ from aws_cdk.aws_dynamodb import Attribute, AttributeType, BillingMode, Table, TableEncryption from aws_cdk.aws_kms import IKey from cdk_nag import NagSuppressions -from common_constructs.backup_plan import CCBackupPlan from constructs import Construct +from common_constructs.backup_plan import CCBackupPlan from stacks.backup_infrastructure_stack import BackupInfrastructureStack diff --git a/backend/compact-connect/stacks/persistent_stack/compact_configuration_upload.py b/backend/compact-connect/stacks/persistent_stack/compact_configuration_upload.py index 10361a258..1df685d80 100644 --- a/backend/compact-connect/stacks/persistent_stack/compact_configuration_upload.py +++ b/backend/compact-connect/stacks/persistent_stack/compact_configuration_upload.py @@ -7,9 +7,10 @@ from aws_cdk.aws_logs import RetentionDays from aws_cdk.custom_resources import Provider from cdk_nag import NagSuppressions +from constructs import Construct + from common_constructs.python_function import PythonFunction from common_constructs.stack import Stack -from constructs import Construct from .compact_configuration_table import CompactConfigurationTable diff --git a/backend/compact-connect/stacks/persistent_stack/data_event_table.py b/backend/compact-connect/stacks/persistent_stack/data_event_table.py index d0e17ff41..1e92cb65e 100644 --- a/backend/compact-connect/stacks/persistent_stack/data_event_table.py +++ b/backend/compact-connect/stacks/persistent_stack/data_event_table.py @@ -12,11 +12,11 @@ from aws_cdk.aws_kms import IKey from aws_cdk.aws_sns import ITopic from cdk_nag import NagSuppressions +from constructs import Construct + from common_constructs.backup_plan import CCBackupPlan from common_constructs.python_function import PythonFunction from common_constructs.queued_lambda_processor import QueuedLambdaProcessor -from constructs import Construct - from stacks import persistent_stack as ps from stacks.backup_infrastructure_stack import BackupInfrastructureStack diff --git a/backend/compact-connect/stacks/persistent_stack/provider_table.py b/backend/compact-connect/stacks/persistent_stack/provider_table.py index 470a51a70..00d9c9e58 100644 --- a/backend/compact-connect/stacks/persistent_stack/provider_table.py +++ b/backend/compact-connect/stacks/persistent_stack/provider_table.py @@ -3,9 +3,9 @@ from aws_cdk.aws_dynamodb import Attribute, AttributeType, BillingMode, ProjectionType, Table, TableEncryption from aws_cdk.aws_kms import Key from cdk_nag import NagSuppressions -from common_constructs.backup_plan import CCBackupPlan from constructs import Construct +from common_constructs.backup_plan import CCBackupPlan from stacks.backup_infrastructure_stack import BackupInfrastructureStack diff --git a/backend/compact-connect/stacks/persistent_stack/provider_users_bucket.py b/backend/compact-connect/stacks/persistent_stack/provider_users_bucket.py index a30406afa..8f52df3e4 100644 --- a/backend/compact-connect/stacks/persistent_stack/provider_users_bucket.py +++ b/backend/compact-connect/stacks/persistent_stack/provider_users_bucket.py @@ -12,13 +12,13 @@ from aws_cdk.aws_s3 import BucketEncryption, CorsRule, EventType, HttpMethods from aws_cdk.aws_s3_notifications import LambdaDestination from cdk_nag import NagSuppressions +from constructs import Construct + +import stacks.persistent_stack as ps from common_constructs.access_logs_bucket import AccessLogsBucket from common_constructs.backup_plan import CCBackupPlan from common_constructs.bucket import Bucket from common_constructs.python_function import PythonFunction -from constructs import Construct - -import stacks.persistent_stack as ps from stacks.backup_infrastructure_stack import BackupInfrastructureStack diff --git a/backend/compact-connect/stacks/persistent_stack/ssn_table.py b/backend/compact-connect/stacks/persistent_stack/ssn_table.py index 7ef3d2ba0..b90d64b31 100644 --- a/backend/compact-connect/stacks/persistent_stack/ssn_table.py +++ b/backend/compact-connect/stacks/persistent_stack/ssn_table.py @@ -16,12 +16,12 @@ from aws_cdk.aws_kms import Key from aws_cdk.aws_sns import ITopic from cdk_nag import NagSuppressions +from constructs import Construct + from common_constructs.backup_plan import CCBackupPlan from common_constructs.python_function import PythonFunction from common_constructs.queued_lambda_processor import QueuedLambdaProcessor from common_constructs.stack import Stack -from constructs import Construct - from stacks.backup_infrastructure_stack import BackupInfrastructureStack diff --git a/backend/compact-connect/stacks/persistent_stack/staff_users.py b/backend/compact-connect/stacks/persistent_stack/staff_users.py index 82a51d863..09e37f502 100644 --- a/backend/compact-connect/stacks/persistent_stack/staff_users.py +++ b/backend/compact-connect/stacks/persistent_stack/staff_users.py @@ -14,13 +14,13 @@ ) from aws_cdk.aws_kms import IKey from cdk_nag import NagSuppressions +from constructs import Construct + from common_constructs.cognito_user_backup import CognitoUserBackup from common_constructs.nodejs_function import NodejsFunction from common_constructs.python_function import PythonFunction from common_constructs.resource_scope_mixin import ResourceScopeMixin from common_constructs.user_pool import UserPool -from constructs import Construct - from stacks import persistent_stack as ps from stacks.backup_infrastructure_stack import BackupInfrastructureStack from stacks.persistent_stack.users_table import UsersTable diff --git a/backend/compact-connect/stacks/persistent_stack/transaction_history_table.py b/backend/compact-connect/stacks/persistent_stack/transaction_history_table.py index 55d7c782f..7bb939115 100644 --- a/backend/compact-connect/stacks/persistent_stack/transaction_history_table.py +++ b/backend/compact-connect/stacks/persistent_stack/transaction_history_table.py @@ -3,9 +3,9 @@ from aws_cdk.aws_dynamodb import Attribute, AttributeType, BillingMode, Table, TableEncryption from aws_cdk.aws_kms import IKey from cdk_nag import NagSuppressions -from common_constructs.backup_plan import CCBackupPlan from constructs import Construct +from common_constructs.backup_plan import CCBackupPlan from stacks.backup_infrastructure_stack import BackupInfrastructureStack diff --git a/backend/compact-connect/stacks/persistent_stack/transaction_reports_bucket.py b/backend/compact-connect/stacks/persistent_stack/transaction_reports_bucket.py index 51d025b88..aa1c857a0 100644 --- a/backend/compact-connect/stacks/persistent_stack/transaction_reports_bucket.py +++ b/backend/compact-connect/stacks/persistent_stack/transaction_reports_bucket.py @@ -3,9 +3,10 @@ from aws_cdk.aws_kms import IKey from aws_cdk.aws_s3 import BucketEncryption from cdk_nag import NagSuppressions +from constructs import Construct + from common_constructs.access_logs_bucket import AccessLogsBucket from common_constructs.bucket import Bucket -from constructs import Construct class TransactionReportsBucket(Bucket): diff --git a/backend/compact-connect/stacks/persistent_stack/user_email_notifications.py b/backend/compact-connect/stacks/persistent_stack/user_email_notifications.py index 04527a26a..846ea56b6 100644 --- a/backend/compact-connect/stacks/persistent_stack/user_email_notifications.py +++ b/backend/compact-connect/stacks/persistent_stack/user_email_notifications.py @@ -9,9 +9,10 @@ from aws_cdk.aws_sns import Subscription, SubscriptionProtocol, Topic from aws_cdk.custom_resources import Provider from cdk_nag import NagSuppressions +from constructs import Construct + from common_constructs.python_function import PythonFunction from common_constructs.stack import Stack -from constructs import Construct class UserEmailNotifications(Construct): diff --git a/backend/compact-connect/stacks/persistent_stack/users_table.py b/backend/compact-connect/stacks/persistent_stack/users_table.py index 2b53fb3d7..e2a28bf17 100644 --- a/backend/compact-connect/stacks/persistent_stack/users_table.py +++ b/backend/compact-connect/stacks/persistent_stack/users_table.py @@ -3,9 +3,9 @@ from aws_cdk.aws_dynamodb import Attribute, AttributeType, BillingMode, ProjectionType, Table, TableEncryption from aws_cdk.aws_kms import IKey from cdk_nag import NagSuppressions -from common_constructs.backup_plan import CCBackupPlan from constructs import Construct +from common_constructs.backup_plan import CCBackupPlan from stacks.backup_infrastructure_stack import BackupInfrastructureStack diff --git a/backend/compact-connect/stacks/provider_users/__init__.py b/backend/compact-connect/stacks/provider_users/__init__.py index 180993679..f6c661c22 100644 --- a/backend/compact-connect/stacks/provider_users/__init__.py +++ b/backend/compact-connect/stacks/provider_users/__init__.py @@ -1,10 +1,10 @@ from aws_cdk import RemovalPolicy from aws_cdk.aws_cognito import SignInAliases, UserPoolEmail from aws_cdk.aws_logs import QueryDefinition, QueryString -from common_constructs.security_profile import SecurityProfile -from common_constructs.stack import AppStack from constructs import Construct +from common_constructs.security_profile import SecurityProfile +from common_constructs.stack import AppStack from stacks.persistent_stack import PersistentStack from stacks.provider_users.provider_users import ProviderUsers diff --git a/backend/compact-connect/stacks/provider_users/provider_users.py b/backend/compact-connect/stacks/provider_users/provider_users.py index d2c1603d0..8a107ab88 100644 --- a/backend/compact-connect/stacks/provider_users/provider_users.py +++ b/backend/compact-connect/stacks/provider_users/provider_users.py @@ -12,11 +12,11 @@ UserPoolOperation, ) from aws_cdk.aws_kms import IKey +from constructs import Construct + from common_constructs.cognito_user_backup import CognitoUserBackup from common_constructs.nodejs_function import NodejsFunction from common_constructs.user_pool import UserPool -from constructs import Construct - from stacks import persistent_stack as ps diff --git a/backend/compact-connect/stacks/reporting_stack.py b/backend/compact-connect/stacks/reporting_stack.py index 3eb42228e..c4a88fbe9 100644 --- a/backend/compact-connect/stacks/reporting_stack.py +++ b/backend/compact-connect/stacks/reporting_stack.py @@ -10,11 +10,11 @@ from aws_cdk.aws_events_targets import LambdaFunction from aws_cdk.aws_logs import QueryDefinition, QueryString from cdk_nag import NagSuppressions +from constructs import Construct + from common_constructs.nodejs_function import NodejsFunction from common_constructs.python_function import PythonFunction from common_constructs.stack import AppStack -from constructs import Construct - from stacks import persistent_stack as ps diff --git a/backend/compact-connect/stacks/state_api_stack/__init__.py b/backend/compact-connect/stacks/state_api_stack/__init__.py index 35a2a2c18..12192c225 100644 --- a/backend/compact-connect/stacks/state_api_stack/__init__.py +++ b/backend/compact-connect/stacks/state_api_stack/__init__.py @@ -1,9 +1,9 @@ from __future__ import annotations -from common_constructs.security_profile import SecurityProfile -from common_constructs.stack import AppStack from constructs import Construct +from common_constructs.security_profile import SecurityProfile +from common_constructs.stack import AppStack from stacks import persistent_stack as ps from stacks.state_auth import StateAuthStack diff --git a/backend/compact-connect/stacks/state_api_stack/api.py b/backend/compact-connect/stacks/state_api_stack/api.py index 55e85432c..967008398 100644 --- a/backend/compact-connect/stacks/state_api_stack/api.py +++ b/backend/compact-connect/stacks/state_api_stack/api.py @@ -2,9 +2,9 @@ from functools import cached_property -from common_constructs.cc_api import CCApi from constructs import Construct +from common_constructs.cc_api import CCApi from stacks import persistent_stack as ps from stacks.state_auth import StateAuthStack diff --git a/backend/compact-connect/stacks/state_api_stack/v1_api/bulk_upload_url.py b/backend/compact-connect/stacks/state_api_stack/v1_api/bulk_upload_url.py index 38731983d..47c11171a 100644 --- a/backend/compact-connect/stacks/state_api_stack/v1_api/bulk_upload_url.py +++ b/backend/compact-connect/stacks/state_api_stack/v1_api/bulk_upload_url.py @@ -5,10 +5,10 @@ from aws_cdk import Duration from aws_cdk.aws_apigateway import AuthorizationType, LambdaIntegration, MethodOptions, MethodResponse, Resource from aws_cdk.aws_iam import IRole + from common_constructs.cc_api import CCApi from common_constructs.python_function import PythonFunction from common_constructs.stack import Stack - from stacks import persistent_stack as ps from .api_model import ApiModel diff --git a/backend/compact-connect/stacks/state_api_stack/v1_api/post_licenses.py b/backend/compact-connect/stacks/state_api_stack/v1_api/post_licenses.py index ae1626d2f..4940c4c43 100644 --- a/backend/compact-connect/stacks/state_api_stack/v1_api/post_licenses.py +++ b/backend/compact-connect/stacks/state_api_stack/v1_api/post_licenses.py @@ -5,10 +5,10 @@ from aws_cdk import Duration from aws_cdk.aws_apigateway import LambdaIntegration, MethodOptions, MethodResponse, Resource from aws_cdk.aws_iam import IRole + from common_constructs.cc_api import CCApi from common_constructs.python_function import PythonFunction from common_constructs.stack import Stack - from stacks import persistent_stack as ps from .api_model import ApiModel diff --git a/backend/compact-connect/stacks/state_api_stack/v1_api/provider_management.py b/backend/compact-connect/stacks/state_api_stack/v1_api/provider_management.py index 3e2579fdf..15d79b091 100644 --- a/backend/compact-connect/stacks/state_api_stack/v1_api/provider_management.py +++ b/backend/compact-connect/stacks/state_api_stack/v1_api/provider_management.py @@ -7,10 +7,10 @@ from aws_cdk.aws_dynamodb import ITable from aws_cdk.aws_kms import IKey from cdk_nag import NagSuppressions + from common_constructs.cc_api import CCApi from common_constructs.python_function import PythonFunction from common_constructs.stack import Stack - from stacks import persistent_stack as ps from stacks.persistent_stack import ProviderTable diff --git a/backend/compact-connect/stacks/state_auth/__init__.py b/backend/compact-connect/stacks/state_auth/__init__.py index 65e8a5235..c7b64bf40 100644 --- a/backend/compact-connect/stacks/state_auth/__init__.py +++ b/backend/compact-connect/stacks/state_auth/__init__.py @@ -1,7 +1,7 @@ from aws_cdk import RemovalPolicy -from common_constructs.stack import AppStack from constructs import Construct +from common_constructs.stack import AppStack from stacks.persistent_stack import PersistentStack from stacks.state_auth.state_auth_users import StateAuthUsers diff --git a/backend/compact-connect/stacks/state_auth/state_auth_users.py b/backend/compact-connect/stacks/state_auth/state_auth_users.py index b035da210..58cc79d67 100644 --- a/backend/compact-connect/stacks/state_auth/state_auth_users.py +++ b/backend/compact-connect/stacks/state_auth/state_auth_users.py @@ -11,9 +11,9 @@ UserPool, ) from cdk_nag import NagSuppressions -from common_constructs.resource_scope_mixin import ResourceScopeMixin from constructs import Construct +from common_constructs.resource_scope_mixin import ResourceScopeMixin from stacks import persistent_stack as ps diff --git a/backend/compact-connect/stacks/transaction_monitoring_stack/__init__.py b/backend/compact-connect/stacks/transaction_monitoring_stack/__init__.py index 7acd12028..018056e71 100644 --- a/backend/compact-connect/stacks/transaction_monitoring_stack/__init__.py +++ b/backend/compact-connect/stacks/transaction_monitoring_stack/__init__.py @@ -2,9 +2,9 @@ import json -from common_constructs.stack import AppStack from constructs import Construct +from common_constructs.stack import AppStack from stacks import persistent_stack as ps from .transaction_history_processing_workflow import TransactionHistoryProcessingWorkflow diff --git a/backend/compact-connect/stacks/transaction_monitoring_stack/transaction_history_processing_workflow.py b/backend/compact-connect/stacks/transaction_monitoring_stack/transaction_history_processing_workflow.py index 7c2d1bfc2..695355539 100644 --- a/backend/compact-connect/stacks/transaction_monitoring_stack/transaction_history_processing_workflow.py +++ b/backend/compact-connect/stacks/transaction_monitoring_stack/transaction_history_processing_workflow.py @@ -25,10 +25,10 @@ ) from aws_cdk.aws_stepfunctions_tasks import LambdaInvoke from cdk_nag import NagSuppressions -from common_constructs.python_function import PythonFunction -from common_constructs.stack import Stack from constructs import Construct +from common_constructs.python_function import PythonFunction +from common_constructs.stack import Stack from stacks import persistent_stack as ps diff --git a/backend/compact-connect/tests/__init__.py b/backend/compact-connect/tests/__init__.py index e69de29bb..f56f03fdc 100644 --- a/backend/compact-connect/tests/__init__.py +++ b/backend/compact-connect/tests/__init__.py @@ -0,0 +1,5 @@ +import os +import sys + +# Make the `common_constructs` namespace package under `common-cdk` available to Python +sys.path.insert(0, os.path.abspath(os.path.join('..', 'common-cdk'))) diff --git a/backend/compact-connect/tests/app/base.py b/backend/compact-connect/tests/app/base.py index 00ee9e9ee..8d59e2e5b 100644 --- a/backend/compact-connect/tests/app/base.py +++ b/backend/compact-connect/tests/app/base.py @@ -5,22 +5,20 @@ from collections.abc import Mapping from unittest.mock import patch -from app import CompactConnectApp from aws_cdk.assertions import Annotations, Match, Template from aws_cdk.aws_apigateway import CfnGatewayResponse, CfnMethod -from aws_cdk.aws_cloudfront import CfnDistribution from aws_cdk.aws_cognito import CfnUserPool, CfnUserPoolClient, CfnUserPoolDomain, CfnUserPoolResourceServer from aws_cdk.aws_dynamodb import CfnTable from aws_cdk.aws_events import CfnRule from aws_cdk.aws_kms import CfnKey -from aws_cdk.aws_lambda import CfnEventSourceMapping, CfnFunction -from aws_cdk.aws_s3 import CfnBucket +from aws_cdk.aws_lambda import CfnEventSourceMapping from aws_cdk.aws_sqs import CfnQueue + +from app import CompactConnectApp from common_constructs.backup_plan import CCBackupPlan from common_constructs.stack import Stack -from pipeline import BackendStage, FrontendStage +from pipeline import BackendStage from stacks.api_stack import ApiStack -from stacks.frontend_deployment_stack import FrontendDeploymentStack from stacks.persistent_stack import PersistentStack from stacks.provider_users import ProviderUsersStack from stacks.state_auth import StateAuthStack @@ -97,29 +95,6 @@ def get_resource_properties_by_logical_id(logical_id: str, resources: Mapping[st except KeyError as exc: raise RuntimeError(f'{logical_id} not found in resources!') from exc - def _inspect_frontend_deployment_stack(self, ui_stack: FrontendDeploymentStack): - with self.subTest(ui_stack.stack_name): - ui_stack_template = Template.from_stack(ui_stack) - # Ensure we have a CloudFront distribution - ui_stack_template.resource_count_is('AWS::CloudFront::Distribution', 1) - # This stack is not anticipated to do much changing, so we'll just snapshot-test key resources - ui_bucket = ui_stack_template.find_resources(CfnBucket.CFN_RESOURCE_TYPE_NAME)[ - ui_stack.get_logical_id(ui_stack.ui_bucket.node.default_child) - ] - self.compare_snapshot(ui_bucket, snapshot_name=f'{ui_stack.stack_name}-UI_BUCKET', overwrite_snapshot=False) - distribution = ui_stack_template.find_resources(CfnDistribution.CFN_RESOURCE_TYPE_NAME) - self.assertEqual(len(distribution), 1) - self.compare_snapshot(distribution, f'{ui_stack.stack_name}-UI_DISTRIBUTION', overwrite_snapshot=False) - # take a snapshot of the lambda@edge code to ensure placeholder values are being injected - distribution_function = ui_stack_template.find_resources(CfnFunction.CFN_RESOURCE_TYPE_NAME)[ - ui_stack.get_logical_id(ui_stack.distribution.csp_function.node.default_child) - ] - self.compare_snapshot( - distribution_function, - f'{ui_stack.stack_name}-UI_DISTRIBUTION_LAMBDA_FUNCTION', - overwrite_snapshot=False, - ) - def _inspect_provider_users_stack( self, provider_users_stack: ProviderUsersStack, @@ -544,9 +519,6 @@ def _check_no_backend_stage_annotations(self, stage: BackendStage): if stage.persistent_stack.hosted_zone: self._check_no_stack_annotations(stage.reporting_stack) - def _check_no_frontend_stage_annotations(self, stage: FrontendStage): - self._check_no_stack_annotations(stage.frontend_deployment_stack) - def _count_stack_resources(self, stack: Stack) -> int: """ Count the number of resources in a CloudFormation stack. diff --git a/backend/compact-connect/tests/app/test_api/__init__.py b/backend/compact-connect/tests/app/test_api/__init__.py index 6155aa212..6cbf1a3e8 100644 --- a/backend/compact-connect/tests/app/test_api/__init__.py +++ b/backend/compact-connect/tests/app/test_api/__init__.py @@ -3,8 +3,8 @@ from aws_cdk.assertions import Template from aws_cdk.aws_lambda import IFunction -from stacks.api_lambda_stack import ApiLambdaStack +from stacks.api_lambda_stack import ApiLambdaStack from tests.app.base import TstAppABC diff --git a/backend/compact-connect/tests/app/test_cognito_backup.py b/backend/compact-connect/tests/app/test_cognito_backup.py index a3a77e97e..451bd1ee4 100644 --- a/backend/compact-connect/tests/app/test_cognito_backup.py +++ b/backend/compact-connect/tests/app/test_cognito_backup.py @@ -12,8 +12,8 @@ from aws_cdk.aws_cloudwatch import CfnAlarm from aws_cdk.aws_events import CfnRule from aws_cdk.aws_lambda import CfnFunction -from common_constructs.cognito_user_backup import CognitoUserBackup +from common_constructs.cognito_user_backup import CognitoUserBackup from tests.app.base import TstAppABC diff --git a/backend/compact-connect/tests/app/test_pipeline.py b/backend/compact-connect/tests/app/test_pipeline.py index bbde9fce3..4e2a0257e 100644 --- a/backend/compact-connect/tests/app/test_pipeline.py +++ b/backend/compact-connect/tests/app/test_pipeline.py @@ -3,7 +3,6 @@ from unittest import TestCase from unittest.mock import patch -from app import CompactConnectApp from aws_cdk.assertions import Match, Template from aws_cdk.aws_cognito import ( CfnUserPool, @@ -14,6 +13,7 @@ from aws_cdk.aws_lambda import CfnFunction, CfnLayerVersion from aws_cdk.aws_ssm import CfnParameter +from app import CompactConnectApp from tests.app.base import TstAppABC @@ -367,40 +367,3 @@ def test_app_refuses_to_synth_with_prod_vulnerable(self): with self.assertRaises(ValueError): CompactConnectApp(context=context) - - -class TestFrontendPipeline(TstAppABC, TestCase): - @classmethod - def get_context(cls): - with open('cdk.json') as f: - context = json.load(f)['context'] - # For pipeline deployments, the pipelines pull their CDK context values from SSM Parameter Store, rather - # than the cdk.context.json files used in local development. We can override the context values used in the - # tests by adding values here. - - # Suppresses lambda bundling for tests - context['aws:cdk:bundling-stacks'] = [] - - return context - - def test_synth_pipeline(self): - """ - Test infrastructure as deployed via the pipeline - """ - # Identify any findings from our AwsSolutions rule sets - self._check_no_stack_annotations(self.app.deployment_resources_stack) - self._check_no_stack_annotations(self.app.test_frontend_pipeline_stack) - self._check_no_stack_annotations(self.app.prod_frontend_pipeline_stack) - for stage in ( - self.app.test_frontend_pipeline_stack.pre_prod_frontend_stage, - self.app.beta_frontend_pipeline_stack.beta_frontend_stage, - self.app.prod_frontend_pipeline_stack.prod_frontend_stage, - ): - self._check_no_frontend_stage_annotations(stage) - - for frontend_deployment_stack in ( - self.app.test_frontend_pipeline_stack.pre_prod_frontend_stage.frontend_deployment_stack, - self.app.beta_frontend_pipeline_stack.beta_frontend_stage.frontend_deployment_stack, - self.app.prod_frontend_pipeline_stack.prod_frontend_stage.frontend_deployment_stack, - ): - self._inspect_frontend_deployment_stack(frontend_deployment_stack) diff --git a/backend/compact-connect/tests/app/test_sandbox.py b/backend/compact-connect/tests/app/test_sandbox.py index aca16f4ee..d24ea65a0 100644 --- a/backend/compact-connect/tests/app/test_sandbox.py +++ b/backend/compact-connect/tests/app/test_sandbox.py @@ -2,7 +2,6 @@ from unittest import TestCase from app import CompactConnectApp - from tests.app.base import TstAppABC diff --git a/backend/compact-connect/tests/common_constructs/test_cognito_user_backup.py b/backend/compact-connect/tests/common_constructs/test_cognito_user_backup.py index ab5cc511b..d2935d494 100644 --- a/backend/compact-connect/tests/common_constructs/test_cognito_user_backup.py +++ b/backend/compact-connect/tests/common_constructs/test_cognito_user_backup.py @@ -18,6 +18,7 @@ from aws_cdk.aws_lambda import CfnFunction from aws_cdk.aws_s3 import CfnBucket from aws_cdk.aws_sns import Topic + from common_constructs.access_logs_bucket import AccessLogsBucket from common_constructs.cognito_user_backup import CognitoUserBackup from stacks.backup_infrastructure_stack import BackupInfrastructureStack diff --git a/backend/compact-connect/tests/common_constructs/test_queue_event_listener.py b/backend/compact-connect/tests/common_constructs/test_queue_event_listener.py index 7eeffcb1c..72a7225c1 100644 --- a/backend/compact-connect/tests/common_constructs/test_queue_event_listener.py +++ b/backend/compact-connect/tests/common_constructs/test_queue_event_listener.py @@ -8,6 +8,7 @@ from aws_cdk.aws_lambda import CfnEventSourceMapping, Code, Function, Runtime from aws_cdk.aws_sns import Topic from aws_cdk.aws_sqs import CfnQueue + from common_constructs.queue_event_listener import QueueEventListener diff --git a/backend/compact-connect/tests/common_constructs/test_queued_lambda_processor.py b/backend/compact-connect/tests/common_constructs/test_queued_lambda_processor.py index 869e05735..9b91eff79 100644 --- a/backend/compact-connect/tests/common_constructs/test_queued_lambda_processor.py +++ b/backend/compact-connect/tests/common_constructs/test_queued_lambda_processor.py @@ -6,6 +6,7 @@ from aws_cdk.aws_lambda import CfnEventSourceMapping, Code, Function, Runtime from aws_cdk.aws_sns import Topic from aws_cdk.aws_sqs import CfnQueue + from common_constructs.queued_lambda_processor import QueuedLambdaProcessor diff --git a/backend/compact-connect/lambdas/python/common/.coveragerc b/backend/multi-account/.coveragerc similarity index 51% rename from backend/compact-connect/lambdas/python/common/.coveragerc rename to backend/multi-account/.coveragerc index 193e8ec8a..6e6498b9e 100644 --- a/backend/compact-connect/lambdas/python/common/.coveragerc +++ b/backend/multi-account/.coveragerc @@ -1,10 +1,17 @@ [run] -data_file = ../../../../.coverage +include = + ./**/* + +data_file = .coverage omit = + */bin/* */cdk.out/* */smoke-test/* */tests/* [report] skip_empty = true + +[html] +directory = coverage diff --git a/backend/multi-account/backups/requirements-dev.txt b/backend/multi-account/backups/requirements-dev.txt index 80c7c50a8..16301c350 100644 --- a/backend/multi-account/backups/requirements-dev.txt +++ b/backend/multi-account/backups/requirements-dev.txt @@ -2,22 +2,22 @@ # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# pip-compile --no-emit-index-url multi-account/backups/requirements-dev.in +# pip-compile --no-emit-index-url backups/requirements-dev.in # -boto3==1.40.18 +boto3==1.40.33 # via moto -botocore==1.40.18 +botocore==1.40.33 # via # boto3 # moto # s3transfer certifi==2025.8.3 # via requests -cffi==1.17.1 +cffi==2.0.0 # via cryptography charset-normalizer==3.4.3 # via requests -cryptography==45.0.6 +cryptography==46.0.1 # via moto idna==3.10 # via requests @@ -33,18 +33,18 @@ markupsafe==3.0.2 # via # jinja2 # werkzeug -moto==5.1.11 - # via -r multi-account/backups/requirements-dev.in +moto==5.1.12 + # via -r backups/requirements-dev.in packaging==25.0 # via pytest pluggy==1.6.0 # via pytest -pycparser==2.22 +pycparser==2.23 # via cffi pygments==2.19.2 # via pytest -pytest==8.4.1 - # via -r multi-account/backups/requirements-dev.in +pytest==8.4.2 + # via -r backups/requirements-dev.in python-dateutil==2.9.0.post0 # via # botocore @@ -57,7 +57,7 @@ requests==2.32.5 # responses responses==0.25.8 # via moto -s3transfer==0.13.1 +s3transfer==0.14.0 # via boto3 six==1.17.0 # via python-dateutil @@ -68,5 +68,5 @@ urllib3==2.5.0 # responses werkzeug==3.1.3 # via moto -xmltodict==0.14.2 +xmltodict==1.0.1 # via moto diff --git a/backend/multi-account/backups/requirements.txt b/backend/multi-account/backups/requirements.txt index 9399dc14d..536510e88 100644 --- a/backend/multi-account/backups/requirements.txt +++ b/backend/multi-account/backups/requirements.txt @@ -2,7 +2,7 @@ # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# pip-compile --no-emit-index-url multi-account/backups/requirements.in +# pip-compile --no-emit-index-url backups/requirements.in # attrs==25.3.0 # via @@ -12,19 +12,19 @@ aws-cdk-asset-awscli-v1==2.2.242 # via aws-cdk-lib aws-cdk-asset-node-proxy-agent-v6==2.1.0 # via aws-cdk-lib -aws-cdk-cloud-assembly-schema==48.5.0 +aws-cdk-cloud-assembly-schema==48.10.0 # via aws-cdk-lib -aws-cdk-lib==2.212.0 - # via -r multi-account/backups/requirements.in -cattrs==25.1.1 +aws-cdk-lib==2.215.0 + # via -r backups/requirements.in +cattrs==25.2.0 # via jsii constructs==10.4.2 # via - # -r multi-account/backups/requirements.in + # -r backups/requirements.in # aws-cdk-lib importlib-resources==6.5.2 # via jsii -jsii==1.113.0 +jsii==1.114.1 # via # aws-cdk-asset-awscli-v1 # aws-cdk-asset-node-proxy-agent-v6 diff --git a/backend/multi-account/bin/compile_requirements.sh b/backend/multi-account/bin/compile_requirements.sh new file mode 100755 index 000000000..513d6e514 --- /dev/null +++ b/backend/multi-account/bin/compile_requirements.sh @@ -0,0 +1,9 @@ +set -e + +pip-compile --no-emit-index-url --upgrade --no-strip-extras backups/requirements-dev.in +pip-compile --no-emit-index-url --upgrade --no-strip-extras backups/requirements.in +pip-compile --no-emit-index-url --upgrade --no-strip-extras control-tower/requirements-dev.in +pip-compile --no-emit-index-url --upgrade --no-strip-extras control-tower/requirements.in +pip-compile --no-emit-index-url --upgrade --no-strip-extras log-aggregation/requirements-dev.in +pip-compile --no-emit-index-url --upgrade --no-strip-extras log-aggregation/requirements.in +bin/sync_deps.sh diff --git a/backend/multi-account/bin/run_python_tests.py b/backend/multi-account/bin/run_python_tests.py new file mode 100755 index 000000000..1364f1a09 --- /dev/null +++ b/backend/multi-account/bin/run_python_tests.py @@ -0,0 +1,171 @@ +#!/usr/bin/env python3 +# ruff: noqa: I001 +""" +This script runs all Python tests in a unified pytest session, which allows coverage data collection to properly merge +across multiple test suites. + +Note: This should be run from the 'backend' directory. +""" + +import os +import sys +import webbrowser +from argparse import ArgumentParser +from pathlib import Path + +import pytest + +from coverage import Coverage + +# Get the absolute path to the backend directory +BACKEND_DIR = Path(__file__).parent.parent.absolute() + +# Define the test directories to include +TEST_DIRS = ( + 'control-tower', + 'log-aggregation', + 'backups', # Data retention backup infrastructure +) + + +def get_coverage(): + # Initialize coverage.py with the existing .coveragerc + return Coverage( + config_file=BACKEND_DIR / '.coveragerc', + # Coverage data in memory + data_file=None, + ) + + +def clean_modules(): + """ + Clean up modules between test runs to prevent cross-contamination. + This is especially important for modules like config that maintain state. + """ + # List of module prefixes to clean up + modules_to_clean = ['cc_common', 'common_test', 'handlers', 'tests'] + + for module_name in list(sys.modules.keys()): + for prefix in modules_to_clean: + if module_name == prefix or module_name.startswith(f'{prefix}.'): + del sys.modules[module_name] + + # Delete local modules after each run so we don't use cached modules and hit name clashes between tests + for local_dir in [*os.listdir()]: + # Remove the .py extension from the local file name + local_dir = local_dir.split('.py', 1)[0] + if local_dir.isidentifier(): + for m in sorted(sys.modules.keys()): + if m.startswith(local_dir): + del sys.modules[m] + + +def run_tests(cov: Coverage, args): + cov.start() + + # Track overall test result + exit_code = 0 + try: + # Prepare pytest arguments + pytest_args = ['tests', '--tb=short', '-W', 'ignore'] + if args.verbose > 0: + pytest_args.append(f'-{"v" * args.verbose}') + + # Run tests for each directory with the correct working directory + for test_dir in TEST_DIRS: + dir_path = BACKEND_DIR / test_dir + if not dir_path.exists(): + sys.stdout.write(f'Warning: Directory {dir_path} does not exist, skipping\n') + continue + + tests_dir = dir_path / 'tests' + if not tests_dir.exists(): + sys.stdout.write(f'Warning: Tests directory {tests_dir} does not exist, skipping\n') + continue + + sys.stdout.write('\n' + '=' * 80 + '\n') + sys.stdout.write(f'Running tests in {test_dir}\n') + sys.stdout.write('=' * 80 + '\n') + + # Save the original working directory and sys.path + original_dir = os.getcwd() + original_path = sys.path.copy() + original_env = os.environ.copy() + + try: + # Change to the test directory + os.chdir(dir_path) + + # Set up PYTHONPATH for common code if needed + if test_dir != 'compact-connect/lambdas/python/common' and 'python' in test_dir: + common_path = BACKEND_DIR / 'compact-connect/lambdas/python/common' + if str(common_path) not in sys.path: + sys.path.insert(0, str(common_path)) + + # Clean up modules before running tests + clean_modules() + + # Run pytest for this directory + test_result = pytest.main(pytest_args) + + # Update exit code if any tests fail + if test_result != 0 and exit_code == 0: + return test_result + + # Restore the original environment + os.environ = original_env # noqa: B003 + + # Thorough module cleanup after each test suite + clean_modules() + + finally: + # Restore the original working directory + os.chdir(original_dir) + + # Restore the original sys.path + sys.path = original_path + finally: + # Stop coverage measurement + cov.stop() + cov.save() + + return exit_code + + +def main(): + parser = ArgumentParser(description='Run all Python tests in a unified pytest session') + parser.add_argument('--report', action='store_true', help='Generate HTML coverage report') + parser.add_argument('--open-report', action='store_true', help='Open HTML coverage report after generation') + parser.add_argument('--fail-under', type=float, default=90, help='Fail if coverage is under this percentage') + parser.add_argument('--verbose', '-v', action='count', default=0, help='Increase verbosity') + args = parser.parse_args() + + cov = get_coverage() + exit_code = run_tests(cov, args) + + # Generate coverage report if requested + if args.report: + sys.stdout.write('\nGenerating coverage report...\n') + cov.html_report() + + if args.open_report: + # Open the coverage report + report_path = BACKEND_DIR / 'coverage' / 'index.html' + webbrowser.open(f'file://{report_path}') + + # Check coverage against threshold + sys.stdout.write('\nCalculating coverage...\n') + coverage_percent = cov.report() + if coverage_percent < args.fail_under: + sys.stdout.write( + f'Coverage {coverage_percent:.2f}% is below the required threshold of {args.fail_under:.2f}%\n' + ) + exit_code = 1 + + if exit_code > 0: + sys.stdout.write('\n================= TESTS FAILED =================\n') + return exit_code + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/backend/multi-account/bin/run_tests.sh b/backend/multi-account/bin/run_tests.sh new file mode 100755 index 000000000..3b3c267cc --- /dev/null +++ b/backend/multi-account/bin/run_tests.sh @@ -0,0 +1,115 @@ +#!/bin/bash +set -e + +# Default values +LANGUAGE="all" +REPORT=true +OPEN_REPORT=true +VERBOSE="" +FAIL_UNDER=90 + +# Print usage information +usage() { + echo "Usage: $0 [options]" + echo "Options:" + echo " -l, --language LANG Specify language to test (nodejs, python, all) [default: all]" + echo " -n, --no-report Don't generate coverage report" + echo " -o, --no-open Don't open coverage report in browser" + echo " -v, --verbose Increase python verbosity (can be used multiple times: -vv)" + echo " -f, --fail-under PCT Set minimum python coverage percentage [default: 90]" + echo " -h, --help Display this help message" + exit 1 +} + +# Parse command line arguments +while getopts "l:nov:f:h-:" opt; do + case $opt in + -) + case "${OPTARG}" in + language) + LANGUAGE="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 )) + ;; + no-report) + REPORT=false + ;; + no-open) + OPEN_REPORT=false + ;; + verbose) + VERBOSE="v${VERBOSE}" + ;; + fail-under) + FAIL_UNDER="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 )) + ;; + help) + usage + ;; + *) + echo "Invalid option: --${OPTARG}" >&2 + usage + ;; + esac + ;; + l) + LANGUAGE="$OPTARG" + ;; + n) + REPORT=false + ;; + o) + OPEN_REPORT=false + ;; + v) + VERBOSE="v${VERBOSE}" + ;; + f) + FAIL_UNDER="$OPTARG" + ;; + h) + usage + ;; + \?) + echo "Invalid option: -$OPTARG" >&2 + usage + ;; + esac +done + +# Validate language argument +if [[ "$LANGUAGE" != "python" && "$LANGUAGE" != "all" ]]; then + echo "Error: Language must be 'python', or 'all'" >&2 + usage +fi + +# Set this to 1 ahead of running tests, so this script will fail if neither node or python tests ran +EXIT=1 + +# Run Python tests if requested +if [[ "$LANGUAGE" == 'python' || "$LANGUAGE" == 'all' ]]; then + echo "Running Python tests..." + + # Build Python test arguments + PYTHON_ARGS=() + + # Add report flags + if [[ "$REPORT" == true ]]; then + PYTHON_ARGS+=("--report") + if [[ "$OPEN_REPORT" == true ]]; then + PYTHON_ARGS+=("--open-report") + fi + fi + + # Add verbosity flag + if [[ -n "$VERBOSE" ]]; then + PYTHON_ARGS+=("-${VERBOSE}") + fi + + # Add fail-under threshold + PYTHON_ARGS+=("--fail-under" "$FAIL_UNDER") + + # Run the Python tests + python3 bin/run_python_tests.py "${PYTHON_ARGS[@]}" || exit "$?" + EXIT=0 +fi + +exit "$EXIT" diff --git a/backend/multi-account/bin/sync_deps.sh b/backend/multi-account/bin/sync_deps.sh new file mode 100755 index 000000000..8daf1c0a2 --- /dev/null +++ b/backend/multi-account/bin/sync_deps.sh @@ -0,0 +1,7 @@ +pip-sync \ + backups/requirements-dev.txt \ + backups/requirements.txt \ + control-tower/requirements-dev.txt \ + control-tower/requirements.txt \ + log-aggregation/requirements-dev.txt \ + log-aggregation/requirements.txt diff --git a/backend/multi-account/control-tower/requirements-dev.in b/backend/multi-account/control-tower/requirements-dev.in index c63164ea3..34e2caeca 100644 --- a/backend/multi-account/control-tower/requirements-dev.in +++ b/backend/multi-account/control-tower/requirements-dev.in @@ -1 +1,6 @@ pytest>=6.2.5 +pytest-cov +coverage +ruff +pip-tools +pip-audit diff --git a/backend/multi-account/control-tower/requirements-dev.txt b/backend/multi-account/control-tower/requirements-dev.txt index 553af2b5a..21864a695 100644 --- a/backend/multi-account/control-tower/requirements-dev.txt +++ b/backend/multi-account/control-tower/requirements-dev.txt @@ -2,15 +2,101 @@ # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# pip-compile --no-emit-index-url multi-account/control-tower/requirements-dev.in +# pip-compile --no-emit-index-url control-tower/requirements-dev.in # +boolean-py==5.0 + # via license-expression +build==1.3.0 + # via pip-tools +cachecontrol[filecache]==0.14.3 + # via + # cachecontrol + # pip-audit +certifi==2025.8.3 + # via requests +charset-normalizer==3.4.3 + # via requests +click==8.2.1 + # via pip-tools +coverage[toml]==7.10.6 + # via + # -r control-tower/requirements-dev.in + # pytest-cov +cyclonedx-python-lib==9.1.0 + # via pip-audit +defusedxml==0.7.1 + # via py-serializable +filelock==3.19.1 + # via cachecontrol +idna==3.10 + # via requests iniconfig==2.1.0 # via pytest +license-expression==30.4.4 + # via cyclonedx-python-lib +markdown-it-py==4.0.0 + # via rich +mdurl==0.1.2 + # via markdown-it-py +msgpack==1.1.1 + # via cachecontrol +packageurl-python==0.17.5 + # via cyclonedx-python-lib packaging==25.0 - # via pytest + # via + # build + # pip-audit + # pip-requirements-parser + # pytest +pip-api==0.0.34 + # via pip-audit +pip-audit==2.9.0 + # via -r control-tower/requirements-dev.in +pip-requirements-parser==32.0.1 + # via pip-audit +pip-tools==7.5.0 + # via -r control-tower/requirements-dev.in +platformdirs==4.4.0 + # via pip-audit pluggy==1.6.0 - # via pytest + # via + # pytest + # pytest-cov +py-serializable==2.1.0 + # via cyclonedx-python-lib pygments==2.19.2 - # via pytest -pytest==8.4.1 - # via -r multi-account/control-tower/requirements-dev.in + # via + # pytest + # rich +pyparsing==3.2.4 + # via pip-requirements-parser +pyproject-hooks==1.2.0 + # via + # build + # pip-tools +pytest==8.4.2 + # via + # -r control-tower/requirements-dev.in + # pytest-cov +pytest-cov==7.0.0 + # via -r control-tower/requirements-dev.in +requests==2.32.5 + # via + # cachecontrol + # pip-audit +rich==14.1.0 + # via pip-audit +ruff==0.13.0 + # via -r control-tower/requirements-dev.in +sortedcontainers==2.4.0 + # via cyclonedx-python-lib +toml==0.10.2 + # via pip-audit +urllib3==2.5.0 + # via requests +wheel==0.45.1 + # via pip-tools + +# The following packages are considered to be unsafe in a requirements file: +# pip +# setuptools diff --git a/backend/multi-account/control-tower/requirements.txt b/backend/multi-account/control-tower/requirements.txt index 6d7fd024a..d260f42aa 100644 --- a/backend/multi-account/control-tower/requirements.txt +++ b/backend/multi-account/control-tower/requirements.txt @@ -2,7 +2,7 @@ # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# pip-compile --no-emit-index-url multi-account/control-tower/requirements.in +# pip-compile --no-emit-index-url control-tower/requirements.in # attrs==25.3.0 # via @@ -12,19 +12,19 @@ aws-cdk-asset-awscli-v1==2.2.242 # via aws-cdk-lib aws-cdk-asset-node-proxy-agent-v6==2.1.0 # via aws-cdk-lib -aws-cdk-cloud-assembly-schema==48.5.0 +aws-cdk-cloud-assembly-schema==48.10.0 # via aws-cdk-lib -aws-cdk-lib==2.212.0 - # via -r multi-account/control-tower/requirements.in -cattrs==25.1.1 +aws-cdk-lib==2.215.0 + # via -r control-tower/requirements.in +cattrs==25.2.0 # via jsii constructs==10.4.2 # via - # -r multi-account/control-tower/requirements.in + # -r control-tower/requirements.in # aws-cdk-lib importlib-resources==6.5.2 # via jsii -jsii==1.113.0 +jsii==1.114.1 # via # aws-cdk-asset-awscli-v1 # aws-cdk-asset-node-proxy-agent-v6 diff --git a/backend/multi-account/log-aggregation/requirements-dev.txt b/backend/multi-account/log-aggregation/requirements-dev.txt index 6ba6860c1..e002df771 100644 --- a/backend/multi-account/log-aggregation/requirements-dev.txt +++ b/backend/multi-account/log-aggregation/requirements-dev.txt @@ -2,7 +2,7 @@ # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# pip-compile --no-emit-index-url multi-account/log-aggregation/requirements-dev.in +# pip-compile --no-emit-index-url log-aggregation/requirements-dev.in # iniconfig==2.1.0 # via pytest @@ -12,5 +12,5 @@ pluggy==1.6.0 # via pytest pygments==2.19.2 # via pytest -pytest==8.4.1 - # via -r multi-account/log-aggregation/requirements-dev.in +pytest==8.4.2 + # via -r log-aggregation/requirements-dev.in diff --git a/backend/multi-account/log-aggregation/requirements.txt b/backend/multi-account/log-aggregation/requirements.txt index 25a21f891..338d35ef1 100644 --- a/backend/multi-account/log-aggregation/requirements.txt +++ b/backend/multi-account/log-aggregation/requirements.txt @@ -2,7 +2,7 @@ # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# pip-compile --no-emit-index-url multi-account/log-aggregation/requirements.in +# pip-compile --no-emit-index-url log-aggregation/requirements.in # attrs==25.3.0 # via @@ -12,19 +12,19 @@ aws-cdk-asset-awscli-v1==2.2.242 # via aws-cdk-lib aws-cdk-asset-node-proxy-agent-v6==2.1.0 # via aws-cdk-lib -aws-cdk-cloud-assembly-schema==48.5.0 +aws-cdk-cloud-assembly-schema==48.10.0 # via aws-cdk-lib -aws-cdk-lib==2.212.0 - # via -r multi-account/log-aggregation/requirements.in -cattrs==25.1.1 +aws-cdk-lib==2.215.0 + # via -r log-aggregation/requirements.in +cattrs==25.2.0 # via jsii constructs==10.4.2 # via - # -r multi-account/log-aggregation/requirements.in + # -r log-aggregation/requirements.in # aws-cdk-lib importlib-resources==6.5.2 # via jsii -jsii==1.113.0 +jsii==1.114.1 # via # aws-cdk-asset-awscli-v1 # aws-cdk-asset-node-proxy-agent-v6 diff --git a/backend/multi-account/ruff.toml b/backend/multi-account/ruff.toml new file mode 100644 index 000000000..66682be80 --- /dev/null +++ b/backend/multi-account/ruff.toml @@ -0,0 +1,112 @@ +# Exclude a variety of commonly ignored directories. +exclude = [ + ".bzr", + ".direnv", + ".eggs", + ".git", + ".git-rewrite", + ".hg", + ".ipynb_checkpoints", + ".mypy_cache", + ".nox", + ".pants.d", + ".pyenv", + ".pytest_cache", + ".pytype", + ".ruff_cache", + ".svn", + ".tox", + ".venv", + ".vscode", + "__pypackages__", + "_build", + "buck-out", + "build", + "dist", + "node_modules", + "site-packages", + "venv" +] + +line-length = 120 +indent-width = 4 + +# Assume Python 3.12 +target-version = "py312" + +[lint] +select = [ + "A", # flake8-builtins + "ARG", # flake8-unused-arguments + "B", # flake8-bugbear + "BLE", # flake8-blind-except + "DTZ", # flake8-datetimez + "E", # pycodestyle + "F", # Pyflakes + "FIX", # flake8-fixme + "FA", # flake8-future-annotations + "G", # flake8-logging-format + "I", # isort + "N", # pep8-naming + "PIE", # flake8-pie + "SLF", # flake8-self + "RET", # flake8-return + "RSE", # flake8-raise + "S", # flake8-bandit + "T", # flake8-print + "UP", # pyupgrade + "W", # warning +] +ignore = [ + "S311", # suspicious-non-cryptographic-random-usage + "N818", # error-suffix-on-exception-name + # the following are ignored by recommendation of ruff documentation + # due to conflicts with the ruff formatter see https://docs.astral.sh/ruff/formatter/#conflicting-lint-rules + "W191", # indentation contains tabs + "E111", # indentation-with-invalid-multiple + "E114", # indentation-with-invalid-multiple-comment + "E117", # over-indented + "D206", # indent-with-spaces + "D300", # triple-single-quotes + "Q000", # bad-quotes-inline-string + "Q001", # bad-quotes-multiline-string + "Q002", # bad-quotes-docstring + "Q003", # avoidable-escaped-quote + "COM812", # missing-trailing-comma + "COM819", # prohibited-trailing-comma + "ISC001", # single-line-implicit-string-concatenation + "ISC002", # multi-line-implicit-string-concatenation +] + +# Allow fix for all enabled rules (when `--fix`) is provided. +fixable = ["ALL"] +unfixable = [] + +# Allow unused variables when underscore-prefixed. +dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" + +[format] +quote-style = "single" + +# Like Black, indent with spaces, rather than tabs. +indent-style = "space" + +# Like Black, respect magic trailing commas. +skip-magic-trailing-comma = false + +# Like Black, automatically detect the appropriate line ending. +line-ending = "auto" + +# Enable auto-formatting of code examples in docstrings. Markdown, +# reStructuredText code/literal blocks and doctests are all supported. +# +# This is currently disabled by default, but it is planned for this +# to be opt-out in the future. +docstring-code-format = false + +# Set the line length limit used when formatting code snippets in +# docstrings. +# +# This only has an effect when the `docstring-code-format` setting is +# enabled. +docstring-code-line-length = "dynamic" diff --git a/backend/social-work-app/README.md b/backend/social-work-app/README.md new file mode 100644 index 000000000..ab73e902a --- /dev/null +++ b/backend/social-work-app/README.md @@ -0,0 +1,3 @@ +# Social Work Compact App + +This is the future home of the CompactConnect app version, customized for SocialWork From 88dee92a90421f0f278c365fd8933996d071b9d9 Mon Sep 17 00:00:00 2001 From: Justin Frahm Date: Fri, 19 Sep 2025 09:53:23 -0600 Subject: [PATCH 02/32] Docs, common deploy resource stack --- .../deployment_resources_stack.py | 81 +++++++ backend/compact-connect-ui-app/README.md | 225 ++++-------------- backend/compact-connect-ui-app/app.py | 39 ++- .../pipeline/__init__.py | 74 +----- backend/compact-connect/README.md | 128 ++++++---- backend/compact-connect/app.py | 2 +- backend/compact-connect/docs/design/README.md | 5 + .../docs/design/pipeline-architecture.md} | 43 +++- .../docs}/design/pipeline-architecture.pdf | Bin backend/compact-connect/pipeline/__init__.py | 73 +----- .../compact-connect/pipeline/design/README.md | 96 -------- .../pipeline/design/pipeline-architecture.pdf | Bin 89240 -> 0 bytes 12 files changed, 270 insertions(+), 496 deletions(-) create mode 100644 backend/common-cdk/common_constructs/deployment_resources_stack.py rename backend/{compact-connect-ui-app/pipeline/design/README.md => compact-connect/docs/design/pipeline-architecture.md} (68%) rename backend/{compact-connect-ui-app/pipeline => compact-connect/docs}/design/pipeline-architecture.pdf (100%) delete mode 100644 backend/compact-connect/pipeline/design/README.md delete mode 100644 backend/compact-connect/pipeline/design/pipeline-architecture.pdf diff --git a/backend/common-cdk/common_constructs/deployment_resources_stack.py b/backend/common-cdk/common_constructs/deployment_resources_stack.py new file mode 100644 index 000000000..dff97e532 --- /dev/null +++ b/backend/common-cdk/common_constructs/deployment_resources_stack.py @@ -0,0 +1,81 @@ +import json + +from aws_cdk import RemovalPolicy +from aws_cdk.aws_kms import Key +from aws_cdk.aws_ssm import StringParameter +from cdk_nag import NagSuppressions +from constructs import Construct +from pipeline import DEPLOY_ENVIRONMENT_NAME + +from common_constructs.access_logs_bucket import AccessLogsBucket +from common_constructs.alarm_topic import AlarmTopic +from common_constructs.stack import Stack + + +class DeploymentResourcesStack(Stack): + """Stack that manages all shared resources for all pipeline stacks.""" + + def __init__( + self, + scope: Construct, + construct_id: str, + **kwargs, + ): + super().__init__(scope, construct_id, environment_name='deploy', **kwargs) + + pipeline_context_parameter_name = f'{DEPLOY_ENVIRONMENT_NAME}-compact-connect-context' + + # Fetch ssm_context if not provided locally + self.parameter = StringParameter.from_string_parameter_name( + self, + 'PipelineContext', + string_parameter_name=pipeline_context_parameter_name, + ) + value = StringParameter.value_from_lookup(self, self.parameter.parameter_name) + # When CDK runs for the first time, it synthesizes fully without actually retrieving the SSM Parameter + # value. It, instead, populates parameters and other look-ups with dummy values, synthesizes, collects all + # the look-ups together, populates them for real, then re-synthesizes with real values. + # To accommodate this pattern, we have to replace this dummy value with one that will actually + # let CDK complete its first round of synthesis, so that it can get to its second, real, synthesis. + if value != f'dummy-value-for-{pipeline_context_parameter_name}': + self.ssm_context = json.loads(value) + else: + with open(f'cdk.context.{DEPLOY_ENVIRONMENT_NAME}-example.json') as f: + self.ssm_context = json.load(f)['ssm_context'] + + self.deploy_environment_context = self.ssm_context['environments'][DEPLOY_ENVIRONMENT_NAME] + + self.pipeline_shared_encryption_key = Key( + self, + 'PipelineSharedEncryptionKey', + enable_key_rotation=True, + alias=f'{self.node.path}-shared-encryption-key', + removal_policy=RemovalPolicy.RETAIN, + ) + + notifications = self.deploy_environment_context.get('notifications', {}) + self.pipeline_alarm_topic = AlarmTopic( + self, + 'AlarmTopic', + master_key=self.pipeline_shared_encryption_key, + email_subscriptions=notifications.get('email', []), + slack_subscriptions=notifications.get('slack', []), + ) + + self.pipeline_access_logs_bucket = AccessLogsBucket( + self, + 'AccessLogsBucket', + removal_policy=RemovalPolicy.RETAIN, + auto_delete_objects=False, + ) + + NagSuppressions.add_resource_suppressions_by_path( + self, + f'{self.pipeline_access_logs_bucket.node.path}/Resource', + suppressions=[ + { + 'id': 'HIPAA.Security-S3BucketLoggingEnabled', + 'reason': 'This is the access logging bucket.', + }, + ], + ) diff --git a/backend/compact-connect-ui-app/README.md b/backend/compact-connect-ui-app/README.md index c577ba4f0..6bb0c6177 100644 --- a/backend/compact-connect-ui-app/README.md +++ b/backend/compact-connect-ui-app/README.md @@ -1,11 +1,15 @@ -# Compact Connect - Backend developer documentation +# Compact Connect UI - Backend developer documentation ## Looking for technical user documentation? -[Find it here](./docs/README.md) +[Find it here](../compact-connect/docs/README.md) + +## Looking for architectural design documentation? +[Find it here](../compact-connect/docs/design/README.md) ## Introduction -This is an [AWS-CDK](https://aws.amazon.com/cdk/) based project for the backend components of the licensure compact system. +This is an [AWS-CDK](https://aws.amazon.com/cdk/) based project for the frontend deploy and hosting infrastructure for +the licensure compact system. ## Table of Contents - **[Prerequisites](#prerequisites)** @@ -19,18 +23,19 @@ This is an [AWS-CDK](https://aws.amazon.com/cdk/) based project for the backend - **[More Info](#more-info)** ## Prerequisites -[Back to top](#compact-connect---backend-developer-documentation) +[Back to top](#compact-connect-ui---backend-developer-documentation) To deploy this app, you will need: 1) Access to an AWS account -2) Python>=3.12 installed on your machine, preferably through a virtual environment management tool like [pyenv](https://github.com/pyenv/pyenv), +2) Python>=3.12 installed on your machine, preferably through a virtual environment management tool like + [pyenv](https://github.com/pyenv/pyenv), for clean management of virtual environments across multiple Python versions. 3) Otherwise, follow the [Prerequisites section](https://cdkworkshop.com/15-prerequisites.html) of the CDK workshop to prepare your system to work with AWS-CDK, including a NodeJS install. 4) Follow the steps in the [Installing Dependencies](#installing-dependencies) section. ## Environment Config -[Back to top](#compact-connect---backend-developer-documentation) +[Back to top](#compact-connect-ui---backend-developer-documentation) The `cdk.json` file tells the CDK Toolkit how to execute your app, including configuration specific to any given target deployment. You can add local configuration that will be merged into the `cdk.json['context']` values with a @@ -42,7 +47,7 @@ using the tools of your choice (`pyenv` and `venv` are common). Once the virtualenv is activated, you can install the required dependencies. ## Installing Dependencies -[Back to top](#compact-connect---backend-developer-documentation) +[Back to top](#compact-connect-ui---backend-developer-documentation) Python requirements are pinned in [`requirements.txt`](requirements.txt). Install them using `pip`: @@ -50,7 +55,8 @@ Python requirements are pinned in [`requirements.txt`](requirements.txt). Instal $ pip install -r requirements.txt ``` -Node.js requirements (for some selected Lambda runtimes) are defined in [`package.json`](./lambdas/nodejs). Install them using `yarn`. +Node.js requirements (for some selected Lambda runtimes) are defined in [`package.json`](./lambdas/nodejs). Install +them using `yarn`. ```shell $ cd lambdas/nodejs @@ -70,7 +76,7 @@ To add additional dependencies, for example other CDK libraries, just add them t `pip-compile requirements.in`, then `pip install -r requirements.txt` command. ## Local Development -[Back to top](#compact-connect---backend-developer-documentation) +[Back to top](#compact-connect-ui---backend-developer-documentation) Local development can be done by editing the python code and `cdk.json`. For development purposes, this is simply a Python project that can be exercised with local tests. Be sure to install the development requirements: @@ -93,7 +99,7 @@ in human-readable format (for example, DynamoDB table names), run `bin/fetch_aws flags. ## Tests -[Back to top](#compact-connect---backend-developer-documentation) +[Back to top](#compact-connect-ui---backend-developer-documentation) Being a cloud project whose infrastructure is written in Python, establishing tests, using the python `unittest` library is critical to maintaining reliability and velocity. Be sure that any updates you add are covered @@ -101,172 +107,57 @@ by tests, so we don't introduce bugs or cost time identifying testable bugs afte unit/functional tests bundled with this app should be designed to execute with zero requirements for environmental setup (including environment variables) beyond simply installing the dependencies in `requirements*.txt` files. CDK tests are defined under the [tests](./tests) directory. Runtime code tests should be similarly bundled within the -lambda folders. Code that is common across all lambdas should be tested in the `common-python` directory, to reduce -duplication and ensure consistency across the app. +lambda folders. Code that is common across all lambdas should be abstracted to a common code asset and tested there, to +reduce duplication and ensure consistency across the app. To execute the tests, simply run `bin/sync_deps.sh` then `bin/run_tests.sh` from the `backend` directory. ## Documentation -[Back to top](#compact-connect---backend-developer-documentation) +[Back to top](#compact-connect-ui---backend-developer-documentation) -Keeping documentation current is an important part of feature development in this project. If the feature involves a non-trivial amount of architecture or other technical design, be sure that the design and considerations are captured in the [design documentation](./docs/design). If any updates are made to the API, be sure to follow these steps to keep the documentation current: -1) Export a fresh api specification (OAS 3.0) is exported from API Gateway and used to update [the Open API Specification JSON file](./docs/api-specification/latest-oas30.json). -2) Run `bin/trim_oas30.py` to organize and trim the API to include only supported API endpoints (and update the script itself, if needed). -3) If you exported the api specification from somewhere other than the CSG Test environment, be sure to set the `servers[0].url` entry back to the correct base URL for the CSG Test environment. -4) Use `bin/update_postman_collection.py` to update the [Postman Collection and Environment](./docs/postman), based on your new api spec, as appropriate. +Keeping documentation current is an important part of feature development in this project. If the feature involves a +non-trivial amount of architecture or other technical design, be sure that the design and considerations are captured +in the [design documentation](./docs/design). ## Deployment -[Back to top](#compact-connect---backend-developer-documentation) - -### AWS Service Quota Increases -Before deploying to any environment (sandbox, test, beta, or production), you'll need to request service quota increases for the following AWS services: - -#### 1. Resource Servers Per User Pool (Amazon Cognito) -The Staff Users pool in CompactConnect uses resource servers for every jurisdiction (50+ states/territories). It also has resource servers for each compact to implement granular permission scopes. As detailed in [User Architecture documentation](./docs/design/README.md#user-architecture), resource server scopes are defined at both the jurisdiction level (ie for state administrators) and the compact level (ie for compact administrators), allowing for fine-grained access control tailored to each entity's specific needs. - -**Required Steps:** -1. Visit the [AWS Service Quotas console](https://console.aws.amazon.com/servicequotas/home) in each AWS account you'll be deploying to -2. Search for "Amazon Cognito User Pools" -3. Find "Resource servers per user pool" (default value is 25) -4. Request an increase to at least 100 resource servers per user pool -5. Wait for AWS to approve the increase before attempting deployment - -This increase gives sufficient capacity for all jurisdictions (50+ states/territories) plus all compacts, with room for future expansion. - -#### 2. Concurrent Executions (AWS Lambda) -CompactConnect uses numerous Lambda functions to power its backend services. By default, new AWS accounts have a very low concurrent execution limit. - -**Required Steps:** -1. Visit the [AWS Service Quotas console](https://console.aws.amazon.com/servicequotas/home) in each AWS account you'll be deploying to -2. Search for "AWS Lambda" -3. Find "Concurrent executions" (default value is 10 for new accounts) -4. Request an increase to at least 1,000 concurrent executions -5. Wait for AWS to approve the increase before attempting deployment - -This increase ensures that your Lambda functions can scale appropriately during periods of high traffic without throttling. +[Back to top](#compact-connect-ui---backend-developer-documentation) ### First deploy to a Sandbox environment The very first deploy to a new environment (like your personal sandbox account) requires a few steps to fully set up its environment: -1) *Optional:* Create a new Route53 HostedZone in your AWS sandbox account for the DNS domain name you want to use for - your app. See [About Route53 Hosted Zones](#about-route53-hosted-zones) for more. Note: Without this step, you will - not be able to log in to the UI hosted in CloudFront. The Oauth2 authentication process requires a predictable - callback url to be pre-configured, which the domain name provides. You can still run a local UI against this app, - so long as you leave the `allow_local_ui` context value set to `true` and remove the `domain_name` param in your environment's context. -2) *Optional if testing SES email notifications with custom domain:* By default, AWS does not allow sending emails to unverified email - addresses. If you need to test SES email notifications and do not want to request AWS to remove your account from - the SES sandbox, you will need to set up a verified SES email identity for each address you want to send emails to. - See [Creating an email address identity](https://docs.aws.amazon.com/ses/latest/dg/creating-identities.html#verify-email-addresses-procedure). Alternatively, you can request AWS to remove your account - from the SES sandbox, which will allow you to send emails to addresses that are not verified. See [SES Sandbox](https://docs.aws.amazon.com/ses/latest/dg/request-production-access.html). - If you do not specify the `domain_name` field in your environment context, cognito will use its default email configuration. - See [Default User Pool Email Settings](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-email.html#user-pool-email-default) -3) Copy [cdk.context.sandbox-example.json](./cdk.context.sandbox-example.json) to `cdk.context.json`. -4) At the top level of the JSON structure update the `"environment_name"` field to your own name. -5) Update the environment entry under `ssm_context.environments` to your own name and your own AWS sandbox account id (which you can find by following these [these instructions](https://docs.aws.amazon.com/accounts/latest/reference/manage-acct-identifiers.html#FindAccountId)), - and domain name, if you set one up. **If you opted not to create a HostedZone, remove the `domain_name` field.** - The key under `environments` must match the value you put under `environment_name`. -6) Configure your aws cli to authenticate against your own account. There are several ways to do this based on the - type of authentication you use to login to your account. See the [AWS CLI Configuration Guide](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-quickstart.html). -7) Complete the [Google reCAPTCHA Setup](#google-recaptcha-setup) steps for your sandbox environment. -8) Run `cdk bootstrap` to add some base CDK support infrastructure to your AWS account. -9) Run `cdk deploy 'Sandbox/*'` to get the initial backend stack resources deployed. -10) *Optional:* If you have a domain name configured for your sandbox environment, once the backend stacks have successfully deployed, you can deploy the frontend UI by setting the 'deploy_sandbox_ui' context field to `true` and run `cdk deploy 'SandboxUI/*'`. If you do not have a domain name configured, you can still run the UI from local host (see the README.md under the webroot folder for more information about running the app on localhost). If you do have a domain name configured, the application will be accessible at the 'app' subdomain of the configured domain name (e.g., app.[configured_domain.com]). +1) Deploy the backend app. See the [backend app](../compact-connect/README.md) for details. You will need to configure + the 'optional' domain name, if you want to host your frontend in your sandbox. +2) Copy your sandbox context file from the backend app to this folder. +3) Once the backend stacks have successfully deployed with a domain name, you can deploy the frontend UI by setting the + running `cdk deploy 'SandboxUI/*'`. The application will then be accessible at the 'app' subdomain of the configured + domain name (e.g., `https://app.[configured_domain.com]`). ### Subsequent sandbox deploys: -For any future deploys, everything is set up so a simple `cdk deploy 'Sandbox/*'` should update all your infrastructure -to reflect the changes in your code. Full deployment steps are: +For any future deploys, everything is set up so a simple `cdk deploy 'SandboxUI/*'` should update all your frontend +infrastructure to reflect the changes in your code. Full deployment steps are: 1) Make sure your python environment is active. 2) Run `bin/sync_deps.sh` from `backend/` to ensure you have the latest requirements installed. 3) Configure your aws cli to authenticate against your own account. -4) Run `cdk deploy 'Sandbox/*'` to deploy the app to your AWS account. - -### Verifying SES configuration for Cognito User Notifications -If your account is in the SES sandbox, the simplest way to verify that SES is integrated with your cognito user pool is -to first go the AWS SES console and create an SES verified email identity for the email address you want to send a test -message to, See [Creating an email address identity](https://docs.aws.amazon.com/ses/latest/dg/creating-identities.html#verify-email-addresses-procedure). - -Once you have verified your email address, go to the AWS Cognito console and find your user pool. From there, you have -the option to create a new user using your verified email address, and select the option to send an email invite. Once -you create the user, you should receive an email notification from Cognito, and you can verify that -the FROM address is using your custom domain. The DMARC authentication will reject any emails from your domain that are -not properly configured using SPF and DKIM, so if you get the email notification from Cognito, you've verified that the -authentication is working as expected. +4) Run `cdk deploy 'SandboxUI/*'` to deploy the app to your AWS account. ### First deploy to the production environment The production environment requires a few steps to fully set up before deploys can be automated. Refer to the [README.md](../multi-account/README.md) for details on setting up a full multi-account architecture environment. Once that is done, perform the following steps to deploy the CI/CD pipelines into the appropriate AWS account: -- Complete the [Google reCAPTCHA Setup](#google-recaptcha-setup) steps for each environment you will be deploying to (test, beta, prod). Use the appropriate domain name for the environment (ie `app.test.compactconnect.org` for test environment, `app.beta.compactconnect.org` for beta environment, `app.compactconnect.org` for production). For the production environment, make sure to complete the billing setup steps as well. -- Have someone who has suitable permissions in the GitHub organization that hosts this code navigate to the AWS Console - for the Deploy account, go to the - [AWS CodeStar Connections](https://us-east-1.console.aws.amazon.com/codesuite/settings/connections) page and create a - connection that grants AWS permission to receive GitHub events. Note the ARN of the resulting connection for - the next step. -- Create a new Route53 hosted zone for the domain name you plan to use for the app in each of the production, beta, and test AWS - accounts. See [About Route53 hosted zones](#about-route53-hosted-zones) below for more detail. -- Request AWS to remove your account from the SES sandbox and wait for them to complete this request. - See [SES Sandbox](https://docs.aws.amazon.com/ses/latest/dg/request-production-access.html). -- With the [aws-cli](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html), set up your local machine to authenticate against the Deploy account as an administrator. -- For every environment, copy the appropriate example context file (`cdk.context.deploy-example.json` for the `Deploy` account, `cdk.context.test-example.json` for the `Test` account, `cdk.context.beta-example.json` for the `Beta` account, or `cdk.context.prod-example.json` for the `Prod` account) to `cdk.context.json` and update the values to match your respective accounts and other identifiers, including the code star connection you just created to match the identifiers for your actual accounts and resources. You will then need to run the `bin/put_ssm_context.sh ` script to push relevant content from your `cdk.context.json` script into an SSM Parameter in your Deploy account. Replace `` with the target environment. For example, to set up for the test environment: `bin/put_ssm_context.sh test`. - For example, to set up for the test environment: `bin/put_ssm_context.sh test`. -- Optional: If a Slack integration is desired for operational support, have someone with permission to install Slack - apps in your workspace and Admin access to each of the Test, Beta, Prod, and Deploy accounts log into each AWS account - and go to the Chatbot service. Select 'Slack' under the **Configure a chat client** box and click **Configure - client**, then follow the Slack authorization prompts. This will authorize AWS to integrate with the channels you - identify in your `cdk.context.json` file. For each Slack channel you want to integrate, be sure to invite your new AWS app to those channels. Update the - `notifications.slack` sections of the `cdk.context.json` file with the details for your Slack workspace and channels. - If you opt not to integrate with Slack, remove the `slack` fields from the file. -- Set cli-environment variables `CDK_DEFAULT_ACCOUNT` and `CDK_DEFAULT_REGION` to your deployment account id and `us-east-1`, respectively. -- For each environment (test, beta, prod), you need to deploy both the backend and frontend pipeline stacks: - -1. **Deploy the Backend Pipeline Stacks first (note: you will need to approve the permission change requests for each stack deployment in the terminal)**: - `cdk deploy --context action=bootstrapDeploy TestBackendPipelineStack BetaBackendPipelineStack ProdBackendPipelineStack` +1. **Deploy the Backend Pipeline Stacks and applications first. See [the backend app](../compact-connect/README.md) + for details. 2. **Then deploy the Frontend Pipeline Stacks (approve the permission change requests for each stack deployment)**: `cdk deploy --context action=bootstrapDeploy TestFrontendPipelineStack BetaFrontendPipelineStack ProdFrontendPipelineStack` -**Important**: When a pipeline stack is deployed, it will automatically trigger a deployment to its environment from the configured branch in your GitHub repo. The first time you deploy the backend pipeline, it should pass all the steps except the final trigger of the frontend pipeline, since the frontend pipeline will not exist until you deploy it. From there on, the pipelines should integrate as designed. - ### Subsequent production deploys Once the pipelines are established with the above steps, deployments will be automatically handled: -- Pushes to the `development` branch will trigger the test backend pipeline, which will then trigger the test frontend pipeline -- Pushes to the `main` branch will trigger both the beta and production backend pipelines, which will then trigger their respective frontend pipelines. - -## Google reCAPTCHA Setup -[Back to top](#compact-connect---backend-developer-documentation) - -The practitioner registration endpoint uses Google reCAPTCHA to prevent abuse. Follow these steps to set up reCAPTCHA for your environment: - -1. Visit https://www.google.com/recaptcha/ -2. Go to "v3 Admin Console" - - If needed, enter your Google account credentials -3. Create a site - - Recaptcha type is v3 (score based) - - Domain will be the frontend browser domain for the environment ('localhost' for local development) - - Google Cloud Platform may require a project name - - Submit -4. Open the Settings for the new site - - The Site Key (Public) will need to be set in the cdk.context.json for the appropriate environment under the field named `recaptcha_public_key` for deployments, or in your local `.env` file of the webroot folder (if running the app locally) - - The Secret Key (Private) will need to be manually stored in the AWS account in secrets manager, using the following secret name: - `compact-connect/env/{value of 'environment_name' in cdk.context.json}/recaptcha/token` - The value of the secret key should be in the following format: - ``` - { - "token": "" - } - ``` - You can run the following aws cli command to create the secret (make sure you are logged in to the same AWS account you want to store the secret in, under the us-east-1 region): - ``` - aws secretsmanager create-secret --name compact-connect/env/{value of 'environment_name' in cdk.context.json}/recaptcha/token --secret-string '{"token": ""}' - ``` - -For Production environments, additional billing setup is required: -1. In the Settings for a reCAPTCHA site, click "View in Cloud Console" -2. From the main nav, go to Billing -3. If you have an existing billing account, you may link it. Otherwise, you can create a New Billing account, where you will add payment information -4. More info on Google Recaptcha billing: https://cloud.google.com/recaptcha/docs/billing-information +- Pushes to the `development` branch will trigger the test backend pipeline, which will then trigger the test frontend + pipeline +- Pushes to the `main` branch will trigger both the beta and production backend pipelines, which will then trigger + their respective frontend pipelines. ### Useful commands @@ -277,42 +168,16 @@ For Production environments, additional billing setup is required: * `cdk docs` open CDK documentation ## Decommissioning -[Back to top](#compact-connect---backend-developer-documentation) +[Back to top](#compact-connect-ui---backend-developer-documentation) You can tear down resources associated with any of the CloudFormation stacks for this application with -`cdk destroy `. Most persistent resources with data remain in the Persistent stack, so you can freely -destroy the others without losing users or data. If you wish to destroy the Persistent stack as well, be aware that -some resources may be left behind as CloudFormation is designed to err on the side of orphaning resources over data -loss. You can identify any resources that weren't destroyed by watching the stack deletion from the AWS CloudFormation +`cdk destroy `. Most resources in frontend infrastructure can be deleted with the stack, however some data +storage resources, such as the access logs bucket, may not be configured to delete with the stack, depending on your. +You can identify any resources that weren't destroyed by watching the stack deletion from the AWS CloudFormation Console, then looking at the resources after its delete is complete, to look for any with a `Delete Skipped` status. -## About Route53 hosted zones - -A Hosted Zone in Route53 represents a collection of DNS records for a particular domain and its subdomains, managed -together. See the [Route53 FAQs for more](https://aws.amazon.com/route53/faqs/). When creating a hosted zone, you have -to also configure the domain name registrar (be it AWS or some other vendor) to point to the name servers associated -with your hosted zone, before the records in the zone will have any effect. When deploying this app, creating a hosted -zone in the AWS account for the UI and API domains is part of the environment setup. If you use the common approach -of having your test environments be a subdomain of your production environments (i.e. `compcatconnect.org` for prod -and `test.compcatconnect.org` for test), you need to delegate nameserver authority from your production hosted zone -(`compactconnect.org` in this example) to your test account's hosted zone (`test.compactconnect.org`). To do this, you -need to create your production hosted zone (`compactconnect.org`) in your production account first, then create your -test hosted zone (`test.compactconnect.org`) in your test account second, then delegate name server authority to your -test subdomain. To do this, find the NS record associated with your test hosted zone and copy its value, which should -look something like: -```text -ns-1.awsdns-19.co.uk. -ns-2.awsdns-18.com. -ns-5.awsdns-15.net. -ns-6.awsdns-16.org. -``` - -Copy those name server values and, back in your production hosted zone, create a new NS record that matches the test -one, with the same value (i.e. Record Name: `test.compcatconnect.org`, Type: `NS`, Value: ``). Once that -is done, your test hosted zone is ready for use by the app. You will need to perform this action for your beta environment as well, should you choose to deploy one. - ## More Info -[Back to top](#compact-connect---backend-developer-documentation) +[Back to top](#compact-connect-ui---backend-developer-documentation) -- [cdk-workshop](https://cdkworkshop.com/): If you are new to CDK, I highly recommend you go through the CDK Workshop for a quick - introduction to the technology and its concepts before getting too deep into any particular project. +- [cdk-workshop](https://cdkworkshop.com/): If you are new to CDK, I highly recommend you go through the CDK Workshop + for a quick introduction to the technology and its concepts before getting too deep into any particular project. diff --git a/backend/compact-connect-ui-app/app.py b/backend/compact-connect-ui-app/app.py index be006f967..dd18b8210 100644 --- a/backend/compact-connect-ui-app/app.py +++ b/backend/compact-connect-ui-app/app.py @@ -7,13 +7,14 @@ # Make the `common_constructs` namespace package under `common-cdk` available to Python sys.path.insert(0, os.path.abspath(os.path.join('..', 'common-cdk'))) +from common_constructs.deployment_resources_stack import DeploymentResourcesStack from common_constructs.stack import StandardTags + from pipeline import ( ACTION_CONTEXT_KEY, PIPELINE_STACK_CONTEXT_KEY, PIPELINE_SYNTH_ACTION, BetaFrontendPipelineStack, - DeploymentResourcesStack, ProdFrontendPipelineStack, TestFrontendPipelineStack, ) @@ -23,7 +24,7 @@ TEST_FRONTEND_PIPELINE_STACK = 'TestFrontendPipelineStack' BETA_FRONTEND_PIPELINE_STACK = 'BetaFrontendPipelineStack' PROD_FRONTEND_PIPELINE_STACK = 'ProdFrontendPipelineStack' -DEPLOYMENT_RESOURCES_STACK = 'DeploymentResourcesStack' +DEPLOYMENT_RESOURCES_STACK = 'FrontendDeploymentResourcesStack' # CDK path CDK_PATH = 'backend/compact-connect-ui-app' @@ -66,33 +67,24 @@ def __init__(self, *args, **kwargs): def _setup_sandbox_environment(self): """Set up sandbox environment stacks""" # ssm_context must be provided locally for a sandbox deploy - # TODO: review what context is still needed in this reduced app ssm_context = self.node.get_context('ssm_context') environment_name = self.node.get_context('environment_name') environment_context = ssm_context['environments'][environment_name] - # TODO: review this toggle flow for what can be simplified - # TODO: update pipeline docs - # NOTE: for first-time sandbox deployments, ensure you deploy the backend stage successfully first - # by running `cdk deploy 'Sandbox/*'`, then if you have a domain name configured and want to deploy the UI for - # your sandbox environment, set the 'deploy_sandbox_ui' field to true and deploy this stack by running - # `cdk deploy 'SandboxUI/*'. This ensures the user pool values are configured to be bundled with the UI build - # artifact. - if environment_context.get('deploy_sandbox_ui', False): - if not environment_context.get('domain_name'): - raise ValueError( - 'Cannot deploy sandbox UI if domain name is not configured for your environment. ' - 'You may still run the app from localhost. See README.md in the webroot folder for ' - 'more information about running the app from localhost.' - ) - - self.sandbox_frontend_stage = FrontendStage( - self, - 'SandboxUI', - environment_name=environment_name, - environment_context=environment_context, + if not environment_context.get('domain_name'): + raise ValueError( + 'Cannot deploy sandbox UI if domain name is not configured for your environment. ' + 'You may still run the app from localhost. See README.md in the webroot folder for ' + 'more information about running the app from localhost.' ) + self.sandbox_frontend_stage = FrontendStage( + self, + 'SandboxUI', + environment_name=environment_name, + environment_context=environment_context, + ) + def _setup_pipeline_environment(self): """ Set up pipeline environment stacks based on action and pipeline stack context @@ -131,7 +123,6 @@ def add_all_pipeline_stacks(self): stack resources in every environment. """ # This stack must be declared first, as all other pipeline stacks depend on it. - # TODO: decide how we will handle these common deployment resources after breaking up into multiple apps self.add_deployment_resources_stack() self.add_test_frontend_pipeline_stack() diff --git a/backend/compact-connect-ui-app/pipeline/__init__.py b/backend/compact-connect-ui-app/pipeline/__init__.py index eacc3d6dc..edf4c6b89 100644 --- a/backend/compact-connect-ui-app/pipeline/__init__.py +++ b/backend/compact-connect-ui-app/pipeline/__init__.py @@ -2,14 +2,11 @@ from aws_cdk import Environment, RemovalPolicy from aws_cdk.aws_iam import Effect, PolicyStatement -from aws_cdk.aws_kms import IKey, Key +from aws_cdk.aws_kms import IKey from aws_cdk.aws_s3 import IBucket from aws_cdk.aws_sns import ITopic from aws_cdk.aws_ssm import StringParameter from aws_cdk.pipelines import CodePipeline as CdkCodePipeline -from cdk_nag import NagSuppressions -from common_constructs.access_logs_bucket import AccessLogsBucket -from common_constructs.alarm_topic import AlarmTopic from common_constructs.stack import Stack from constructs import Construct @@ -32,75 +29,6 @@ BOOTSTRAP_DEPLOY_ACTION = 'bootstrapDeploy' -class DeploymentResourcesStack(Stack): - """Stack that manages all shared resources for all pipeline stacks.""" - - def __init__( - self, - scope: Construct, - construct_id: str, - **kwargs, - ): - super().__init__(scope, construct_id, environment_name='deploy', **kwargs) - - pipeline_context_parameter_name = f'{DEPLOY_ENVIRONMENT_NAME}-compact-connect-context' - - # Fetch ssm_context if not provided locally - self.parameter = StringParameter.from_string_parameter_name( - self, - 'PipelineContext', - string_parameter_name=pipeline_context_parameter_name, - ) - value = StringParameter.value_from_lookup(self, self.parameter.parameter_name) - # When CDK runs for the first time, it synthesizes fully without actually retrieving the SSM Parameter - # value. It, instead, populates parameters and other look-ups with dummy values, synthesizes, collects all - # the look-ups together, populates them for real, then re-synthesizes with real values. - # To accommodate this pattern, we have to replace this dummy value with one that will actually - # let CDK complete its first round of synthesis, so that it can get to its second, real, synthesis. - if value != f'dummy-value-for-{pipeline_context_parameter_name}': - self.ssm_context = json.loads(value) - else: - with open(f'cdk.context.{DEPLOY_ENVIRONMENT_NAME}-example.json') as f: - self.ssm_context = json.load(f)['ssm_context'] - - self.deploy_environment_context = self.ssm_context['environments'][DEPLOY_ENVIRONMENT_NAME] - - self.pipeline_shared_encryption_key = Key( - self, - 'PipelineSharedEncryptionKey', - enable_key_rotation=True, - alias='pipeline-shared-encryption-key', - removal_policy=RemovalPolicy.RETAIN, - ) - - notifications = self.deploy_environment_context.get('notifications', {}) - self.pipeline_alarm_topic = AlarmTopic( - self, - 'AlarmTopic', - master_key=self.pipeline_shared_encryption_key, - email_subscriptions=notifications.get('email', []), - slack_subscriptions=notifications.get('slack', []), - ) - - self.pipeline_access_logs_bucket = AccessLogsBucket( - self, - 'AccessLogsBucket', - removal_policy=RemovalPolicy.RETAIN, - auto_delete_objects=False, - ) - - NagSuppressions.add_resource_suppressions_by_path( - self, - f'{self.pipeline_access_logs_bucket.node.path}/Resource', - suppressions=[ - { - 'id': 'HIPAA.Security-S3BucketLoggingEnabled', - 'reason': 'This is the access logging bucket.', - }, - ], - ) - - class BasePipelineStack(Stack): """Base stack with common functionality for all pipeline stacks (both backend and frontend).""" diff --git a/backend/compact-connect/README.md b/backend/compact-connect/README.md index 5ad8bb955..9d9486cc3 100644 --- a/backend/compact-connect/README.md +++ b/backend/compact-connect/README.md @@ -23,8 +23,9 @@ This is an [AWS-CDK](https://aws.amazon.com/cdk/) based project for the backend To deploy this app, you will need: 1) Access to an AWS account -2) Python>=3.12 installed on your machine, preferably through a virtual environment management tool like [pyenv](https://github.com/pyenv/pyenv), - for clean management of virtual environments across multiple Python versions. +2) Python>=3.12 installed on your machine, preferably through a virtual environment management tool like + [pyenv](https://github.com/pyenv/pyenv), for clean management of virtual environments across multiple Python + versions. 3) Otherwise, follow the [Prerequisites section](https://cdkworkshop.com/15-prerequisites.html) of the CDK workshop to prepare your system to work with AWS-CDK, including a NodeJS install. 4) Follow the steps in the [Installing Dependencies](#installing-dependencies) section. @@ -101,28 +102,40 @@ by tests, so we don't introduce bugs or cost time identifying testable bugs afte unit/functional tests bundled with this app should be designed to execute with zero requirements for environmental setup (including environment variables) beyond simply installing the dependencies in `requirements*.txt` files. CDK tests are defined under the [tests](./tests) directory. Runtime code tests should be similarly bundled within the -lambda folders. Code that is common across all lambdas should be tested in the `common-python` directory, to reduce -duplication and ensure consistency across the app. +lambda folders. Code that is common across all lambdas should be abstracted to a common code asset and tested there, to +reduce duplication and ensure consistency across the app. To execute the tests, simply run `bin/sync_deps.sh` then `bin/run_tests.sh` from the `backend` directory. ## Documentation [Back to top](#compact-connect---backend-developer-documentation) -Keeping documentation current is an important part of feature development in this project. If the feature involves a non-trivial amount of architecture or other technical design, be sure that the design and considerations are captured in the [design documentation](./docs/design). If any updates are made to the API, be sure to follow these steps to keep the documentation current: -1) Export a fresh api specification (OAS 3.0) is exported from API Gateway and used to update [the Open API Specification JSON file](./docs/api-specification/latest-oas30.json). -2) Run `bin/trim_oas30.py` to organize and trim the API to include only supported API endpoints (and update the script itself, if needed). -3) If you exported the api specification from somewhere other than the CSG Test environment, be sure to set the `servers[0].url` entry back to the correct base URL for the CSG Test environment. -4) Use `bin/update_postman_collection.py` to update the [Postman Collection and Environment](./docs/postman), based on your new api spec, as appropriate. +Keeping documentation current is an important part of feature development in this project. If the feature involves a +non-trivial amount of architecture or other technical design, be sure that the design and considerations are captured +in the [design documentation](./docs/design). If any updates are made to the API, be sure to follow these steps to keep +the documentation current: +1) Export a fresh api specification (OAS 3.0) is exported from API Gateway and used to update + [the Open API Specification JSON file](./docs/api-specification/latest-oas30.json). +2) Run `bin/trim_oas30.py` to organize and trim the API to include only supported API endpoints (and update the script + itself, if needed). +3) If you exported the api specification from somewhere other than the CSG Test environment, be sure to set the + `servers[0].url` entry back to the correct base URL for the CSG Test environment. +4) Use `bin/update_postman_collection.py` to update the [Postman Collection and Environment](./docs/postman), based on + your new api spec, as appropriate. ## Deployment [Back to top](#compact-connect---backend-developer-documentation) ### AWS Service Quota Increases -Before deploying to any environment (sandbox, test, beta, or production), you'll need to request service quota increases for the following AWS services: +Before deploying to any environment (sandbox, test, beta, or production), you'll need to request service quota +increases for the following AWS services: #### 1. Resource Servers Per User Pool (Amazon Cognito) -The Staff Users pool in CompactConnect uses resource servers for every jurisdiction (50+ states/territories). It also has resource servers for each compact to implement granular permission scopes. As detailed in [User Architecture documentation](./docs/design/README.md#user-architecture), resource server scopes are defined at both the jurisdiction level (ie for state administrators) and the compact level (ie for compact administrators), allowing for fine-grained access control tailored to each entity's specific needs. +The Staff Users pool in CompactConnect uses resource servers for every jurisdiction (50+ states/territories). It also +has resource servers for each compact to implement granular permission scopes. As detailed in +[User Architecture documentation](./docs/design/README.md#user-architecture), resource server scopes are defined at +both the jurisdiction level (ie for state administrators) and the compact level (ie for compact administrators), +allowing for fine-grained access control tailored to each entity's specific needs. **Required Steps:** 1. Visit the [AWS Service Quotas console](https://console.aws.amazon.com/servicequotas/home) in each AWS account you'll be deploying to @@ -131,10 +144,12 @@ The Staff Users pool in CompactConnect uses resource servers for every jurisdict 4. Request an increase to at least 100 resource servers per user pool 5. Wait for AWS to approve the increase before attempting deployment -This increase gives sufficient capacity for all jurisdictions (50+ states/territories) plus all compacts, with room for future expansion. +This increase gives sufficient capacity for all jurisdictions (50+ states/territories) plus all compacts, with room for +future expansion. #### 2. Concurrent Executions (AWS Lambda) -CompactConnect uses numerous Lambda functions to power its backend services. By default, new AWS accounts have a very low concurrent execution limit. +CompactConnect uses numerous Lambda functions to power its backend services. By default, new AWS accounts have a very +low concurrent execution limit. **Required Steps:** 1. Visit the [AWS Service Quotas console](https://console.aws.amazon.com/servicequotas/home) in each AWS account you'll be deploying to @@ -152,17 +167,23 @@ its environment: your app. See [About Route53 Hosted Zones](#about-route53-hosted-zones) for more. Note: Without this step, you will not be able to log in to the UI hosted in CloudFront. The Oauth2 authentication process requires a predictable callback url to be pre-configured, which the domain name provides. You can still run a local UI against this app, - so long as you leave the `allow_local_ui` context value set to `true` and remove the `domain_name` param in your environment's context. -2) *Optional if testing SES email notifications with custom domain:* By default, AWS does not allow sending emails to unverified email + so long as you leave the `allow_local_ui` context value set to `true` and remove the `domain_name` param in your + environment's context. +2) *Optional if testing SES email notifications with custom domain:* By default, AWS does not allow sending emails to + unverified email addresses. If you need to test SES email notifications and do not want to request AWS to remove your account from the SES sandbox, you will need to set up a verified SES email identity for each address you want to send emails to. See [Creating an email address identity](https://docs.aws.amazon.com/ses/latest/dg/creating-identities.html#verify-email-addresses-procedure). Alternatively, you can request AWS to remove your account - from the SES sandbox, which will allow you to send emails to addresses that are not verified. See [SES Sandbox](https://docs.aws.amazon.com/ses/latest/dg/request-production-access.html). - If you do not specify the `domain_name` field in your environment context, cognito will use its default email configuration. + from the SES sandbox, which will allow you to send emails to addresses that are not verified. See + [SES Sandbox](https://docs.aws.amazon.com/ses/latest/dg/request-production-access.html). + If you do not specify the `domain_name` field in your environment context, cognito will use its default email + configuration. See [Default User Pool Email Settings](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-email.html#user-pool-email-default) 3) Copy [cdk.context.sandbox-example.json](./cdk.context.sandbox-example.json) to `cdk.context.json`. 4) At the top level of the JSON structure update the `"environment_name"` field to your own name. -5) Update the environment entry under `ssm_context.environments` to your own name and your own AWS sandbox account id (which you can find by following these [these instructions](https://docs.aws.amazon.com/accounts/latest/reference/manage-acct-identifiers.html#FindAccountId)), +5) Update the environment entry under `ssm_context.environments` to your own name and your own AWS sandbox account id + (which you can find by following + [these instructions](https://docs.aws.amazon.com/accounts/latest/reference/manage-acct-identifiers.html#FindAccountId)), and domain name, if you set one up. **If you opted not to create a HostedZone, remove the `domain_name` field.** The key under `environments` must match the value you put under `environment_name`. 6) Configure your aws cli to authenticate against your own account. There are several ways to do this based on the @@ -170,7 +191,9 @@ its environment: 7) Complete the [Google reCAPTCHA Setup](#google-recaptcha-setup) steps for your sandbox environment. 8) Run `cdk bootstrap` to add some base CDK support infrastructure to your AWS account. 9) Run `cdk deploy 'Sandbox/*'` to get the initial backend stack resources deployed. -10) *Optional:* If you have a domain name configured for your sandbox environment, once the backend stacks have successfully deployed, you can deploy the frontend UI by setting the 'deploy_sandbox_ui' context field to `true` and run `cdk deploy 'SandboxUI/*'`. If you do not have a domain name configured, you can still run the UI from local host (see the README.md under the webroot folder for more information about running the app on localhost). If you do have a domain name configured, the application will be accessible at the 'app' subdomain of the configured domain name (e.g., app.[configured_domain.com]). +10) *Optional:* If you have a domain name configured for your sandbox environment, once the backend stacks have + successfully deployed, you can deploy the frontend UI app as well. See the + [UI app for details](../compact-connect-ui-app/README.md). ### Subsequent sandbox deploys: For any future deploys, everything is set up so a simple `cdk deploy 'Sandbox/*'` should update all your infrastructure @@ -196,48 +219,67 @@ authentication is working as expected. The production environment requires a few steps to fully set up before deploys can be automated. Refer to the [README.md](../multi-account/README.md) for details on setting up a full multi-account architecture environment. Once that is done, perform the following steps to deploy the CI/CD pipelines into the appropriate AWS account: -- Complete the [Google reCAPTCHA Setup](#google-recaptcha-setup) steps for each environment you will be deploying to (test, beta, prod). Use the appropriate domain name for the environment (ie `app.test.compactconnect.org` for test environment, `app.beta.compactconnect.org` for beta environment, `app.compactconnect.org` for production). For the production environment, make sure to complete the billing setup steps as well. -- Have someone who has suitable permissions in the GitHub organization that hosts this code navigate to the AWS Console +- Complete the [Google reCAPTCHA Setup](#google-recaptcha-setup) steps for each environment you will be deploying to (test, beta, prod). + Use the appropriate domain name for the environment (ie `app.test.compactconnect.org` for test environment, + `app.beta.compactconnect.org` for beta environment, `app.compactconnect.org` for production). For the production + environment, make sure to complete the billing setup steps as well. +- Have someone with suitable permissions in the GitHub organization that hosts this code navigate to the AWS Console for the Deploy account, go to the [AWS CodeStar Connections](https://us-east-1.console.aws.amazon.com/codesuite/settings/connections) page and create a connection that grants AWS permission to receive GitHub events. Note the ARN of the resulting connection for the next step. -- Create a new Route53 hosted zone for the domain name you plan to use for the app in each of the production, beta, and test AWS - accounts. See [About Route53 hosted zones](#about-route53-hosted-zones) below for more detail. +- Create a new Route53 hosted zone for the domain name you plan to use for the app in each of the production, beta, and + test AWS accounts. See [About Route53 hosted zones](#about-route53-hosted-zones) below for more detail. - Request AWS to remove your account from the SES sandbox and wait for them to complete this request. See [SES Sandbox](https://docs.aws.amazon.com/ses/latest/dg/request-production-access.html). - With the [aws-cli](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html), set up your local machine to authenticate against the Deploy account as an administrator. -- For every environment, copy the appropriate example context file (`cdk.context.deploy-example.json` for the `Deploy` account, `cdk.context.test-example.json` for the `Test` account, `cdk.context.beta-example.json` for the `Beta` account, or `cdk.context.prod-example.json` for the `Prod` account) to `cdk.context.json` and update the values to match your respective accounts and other identifiers, including the code star connection you just created to match the identifiers for your actual accounts and resources. You will then need to run the `bin/put_ssm_context.sh ` script to push relevant content from your `cdk.context.json` script into an SSM Parameter in your Deploy account. Replace `` with the target environment. For example, to set up for the test environment: `bin/put_ssm_context.sh test`. - For example, to set up for the test environment: `bin/put_ssm_context.sh test`. +- For every environment, copy the appropriate example context file (`cdk.context.deploy-example.json` for the `Deploy` + account, `cdk.context.test-example.json` for the `Test` account, `cdk.context.beta-example.json` for the `Beta` + account, or `cdk.context.prod-example.json` for the `Prod` account) to `cdk.context.json` and update the values to + match your respective accounts and other identifiers, including the code star connection you just created to match + the identifiers for your actual accounts and resources. You will then need to run the + `bin/put_ssm_context.sh ` script to push relevant content from your `cdk.context.json` script into an + SSM Parameter in your Deploy account. Replace `` with the target environment. For example, to set up for + the test environment: `bin/put_ssm_context.sh test`. + For example, to set up for the test environment: `bin/put_ssm_context.sh test`. - Optional: If a Slack integration is desired for operational support, have someone with permission to install Slack apps in your workspace and Admin access to each of the Test, Beta, Prod, and Deploy accounts log into each AWS account and go to the Chatbot service. Select 'Slack' under the **Configure a chat client** box and click **Configure client**, then follow the Slack authorization prompts. This will authorize AWS to integrate with the channels you - identify in your `cdk.context.json` file. For each Slack channel you want to integrate, be sure to invite your new AWS app to those channels. Update the - `notifications.slack` sections of the `cdk.context.json` file with the details for your Slack workspace and channels. + identify in your `cdk.context.json` file. For each Slack channel you want to integrate, be sure to invite your new + AWS app to those channels. Update the `notifications.slack` sections of the `cdk.context.json` file with the details + for your Slack workspace and channels. If you opt not to integrate with Slack, remove the `slack` fields from the file. -- Set cli-environment variables `CDK_DEFAULT_ACCOUNT` and `CDK_DEFAULT_REGION` to your deployment account id and `us-east-1`, respectively. +- Set cli-environment variables `CDK_DEFAULT_ACCOUNT` and `CDK_DEFAULT_REGION` to your deployment account id and + `us-east-1`, respectively. - For each environment (test, beta, prod), you need to deploy both the backend and frontend pipeline stacks: -1. **Deploy the Backend Pipeline Stacks first (note: you will need to approve the permission change requests for each stack deployment in the terminal)**: +1. **Deploy the Backend Pipeline Stacks first (note: you will need to approve the permission change requests for each + stack deployment in the terminal)**: `cdk deploy --context action=bootstrapDeploy TestBackendPipelineStack BetaBackendPipelineStack ProdBackendPipelineStack` -2. **Then deploy the Frontend Pipeline Stacks (approve the permission change requests for each stack deployment)**: - `cdk deploy --context action=bootstrapDeploy TestFrontendPipelineStack BetaFrontendPipelineStack ProdFrontendPipelineStack` +2. **Then deploy the Frontend App**: + See the [UI app for details](../compact-connect-ui-app/README.md). -**Important**: When a pipeline stack is deployed, it will automatically trigger a deployment to its environment from the configured branch in your GitHub repo. The first time you deploy the backend pipeline, it should pass all the steps except the final trigger of the frontend pipeline, since the frontend pipeline will not exist until you deploy it. From there on, the pipelines should integrate as designed. +**Important**: When a pipeline stack is deployed, it will automatically trigger a deployment to its environment from +the configured branch in your GitHub repo. The first time you deploy the backend pipeline, it should pass all the steps +except the final trigger of the frontend pipeline, since the frontend pipeline will not exist until you deploy it. From +there on, the pipelines should integrate as designed. ### Subsequent production deploys Once the pipelines are established with the above steps, deployments will be automatically handled: -- Pushes to the `development` branch will trigger the test backend pipeline, which will then trigger the test frontend pipeline -- Pushes to the `main` branch will trigger both the beta and production backend pipelines, which will then trigger their respective frontend pipelines. +- Pushes to the `development` branch will trigger the test backend pipeline, which will then trigger the test frontend + pipeline +- Pushes to the `main` branch will trigger both the beta and production backend pipelines, which will then trigger + their respective frontend pipelines. ## Google reCAPTCHA Setup [Back to top](#compact-connect---backend-developer-documentation) -The practitioner registration endpoint uses Google reCAPTCHA to prevent abuse. Follow these steps to set up reCAPTCHA for your environment: +The practitioner registration endpoint uses Google reCAPTCHA to prevent abuse. Follow these steps to set up reCAPTCHA +for your environment: 1. Visit https://www.google.com/recaptcha/ 2. Go to "v3 Admin Console" @@ -248,8 +290,11 @@ The practitioner registration endpoint uses Google reCAPTCHA to prevent abuse. F - Google Cloud Platform may require a project name - Submit 4. Open the Settings for the new site - - The Site Key (Public) will need to be set in the cdk.context.json for the appropriate environment under the field named `recaptcha_public_key` for deployments, or in your local `.env` file of the webroot folder (if running the app locally) - - The Secret Key (Private) will need to be manually stored in the AWS account in secrets manager, using the following secret name: + - The Site Key (Public) will need to be set in the cdk.context.json for the appropriate environment under the field + named `recaptcha_public_key` for deployments, or in your local `.env` file of the webroot folder (if running the + app locally) + - The Secret Key (Private) will need to be manually stored in the AWS account in secrets manager, using the + following secret name: `compact-connect/env/{value of 'environment_name' in cdk.context.json}/recaptcha/token` The value of the secret key should be in the following format: ``` @@ -257,7 +302,8 @@ The practitioner registration endpoint uses Google reCAPTCHA to prevent abuse. F "token": "" } ``` - You can run the following aws cli command to create the secret (make sure you are logged in to the same AWS account you want to store the secret in, under the us-east-1 region): + You can run the following aws cli command to create the secret (make sure you are logged in to the same AWS account + you want to store the secret in, under the us-east-1 region): ``` aws secretsmanager create-secret --name compact-connect/env/{value of 'environment_name' in cdk.context.json}/recaptcha/token --secret-string '{"token": ""}' ``` @@ -265,7 +311,8 @@ The practitioner registration endpoint uses Google reCAPTCHA to prevent abuse. F For Production environments, additional billing setup is required: 1. In the Settings for a reCAPTCHA site, click "View in Cloud Console" 2. From the main nav, go to Billing -3. If you have an existing billing account, you may link it. Otherwise, you can create a New Billing account, where you will add payment information +3. If you have an existing billing account, you may link it. Otherwise, you can create a New Billing account, where you + will add payment information 4. More info on Google Recaptcha billing: https://cloud.google.com/recaptcha/docs/billing-information ### Useful commands @@ -309,7 +356,8 @@ ns-6.awsdns-16.org. Copy those name server values and, back in your production hosted zone, create a new NS record that matches the test one, with the same value (i.e. Record Name: `test.compcatconnect.org`, Type: `NS`, Value: ``). Once that -is done, your test hosted zone is ready for use by the app. You will need to perform this action for your beta environment as well, should you choose to deploy one. +is done, your test hosted zone is ready for use by the app. You will need to perform this action for your beta +environment as well, should you choose to deploy one. ## More Info [Back to top](#compact-connect---backend-developer-documentation) diff --git a/backend/compact-connect/app.py b/backend/compact-connect/app.py index fa7468b5e..c8036ca5e 100644 --- a/backend/compact-connect/app.py +++ b/backend/compact-connect/app.py @@ -7,13 +7,13 @@ # Make the `common_constructs` namespace package under `common-cdk` available to Python sys.path.insert(0, os.path.abspath(os.path.join('..', 'common-cdk'))) +from common_constructs.deployment_resources_stack import DeploymentResourcesStack from common_constructs.stack import StandardTags from pipeline import ( ACTION_CONTEXT_KEY, PIPELINE_STACK_CONTEXT_KEY, PIPELINE_SYNTH_ACTION, BetaBackendPipelineStack, - DeploymentResourcesStack, ProdBackendPipelineStack, TestBackendPipelineStack, ) diff --git a/backend/compact-connect/docs/design/README.md b/backend/compact-connect/docs/design/README.md index ac73766ae..84d89add1 100644 --- a/backend/compact-connect/docs/design/README.md +++ b/backend/compact-connect/docs/design/README.md @@ -675,6 +675,11 @@ For this reason, we use the batch settlement time as the timestamp for the trans transaction history table. This ensures that any transactions that are in a batch which fails to settle will eventually be processed and stored in the transaction history table. +## CI/CD Pipelines + +This project leverages AWS CodePipeline to deploy the backend and frontend infrastructure. See the +[pipeline architecture docs](./pipeline-architecture.md) for detailed discussion. + ## Audit Logging [Back to top](#backend-design) diff --git a/backend/compact-connect-ui-app/pipeline/design/README.md b/backend/compact-connect/docs/design/pipeline-architecture.md similarity index 68% rename from backend/compact-connect-ui-app/pipeline/design/README.md rename to backend/compact-connect/docs/design/pipeline-architecture.md index 26d8ad82d..b432588ea 100644 --- a/backend/compact-connect-ui-app/pipeline/design/README.md +++ b/backend/compact-connect/docs/design/pipeline-architecture.md @@ -4,12 +4,28 @@ ## Overview -The CompactConnect CI/CD pipeline architecture implements an optimized deployment strategy built around AWS CDK Pipelines (see https://docs.aws.amazon.com/cdk/v2/guide/cdk_pipeline.html). It follows a multi-pipeline approach with separate backend and frontend pipelines to improve deployment speed, reliability, and security. +The CompactConnect CI/CD pipeline architecture implements an optimized deployment strategy built around AWS CDK +Pipelines (see https://docs.aws.amazon.com/cdk/v2/guide/cdk_pipeline.html). It follows a multi-pipeline approach with +separate backend and frontend pipelines to improve deployment speed, reliability, and security. ## Key Components -- **Backend Pipelines**: Deploy infrastructure resources and backend components first -- **Frontend Pipelines**: Deploy frontend applications with backend configuration values +### Backend Pipelines + +There are different backend pipelines for each environment, defined as part of this CDK app. Those pipelines deploy +infrastructure resources and backend components to environment-specific application AWS accounts. + +### Frontend Pipelines + +There are also different frontend pipelines for each environment. These pipelines are defined as part of the separate +[CompactConnect UI App](../../../compact-connect-ui-app/README.md). The frontend pipelines deploy application hosting +infrastructure to the environment-specific application AWS accounts, based on backend configuration values, provide +by the backend deploy process. + +### Deployment Resources Stack + + + - **Deployment Resources Stack**: Shared resources used by all pipeline stacks across all environments - **Environments**: Test, Beta, and Production environments @@ -19,13 +35,15 @@ The CompactConnect CI/CD pipeline architecture implements an optimized deploymen 2. Backend Pipeline successful completion → Trigger Frontend Pipeline 3. Frontend Pipeline deploys web application using configuration values from Backend -Commits pushed to the 'development' branch trigger the test pipelines. Commits pushed to the 'main' branch trigger the beta and prod pipelines. +Commits pushed to the 'development' branch trigger the test pipelines. Commits pushed to the 'main' branch trigger the +beta and prod pipelines. ## Self-Mutation Feature and Optimization ### Understanding CDK Pipeline Self-Mutation -AWS CDK Pipelines include a powerful "self-mutation" feature that allows the pipeline to update itself. When code changes affecting the pipeline's structure are pushed, the pipeline: +AWS CDK Pipelines include a powerful "self-mutation" feature that allows the pipeline to update itself. When code +changes affecting the pipeline's structure are pushed, the pipeline: 1. Executes with its current configuration 2. Synthesizes CloudFormation templates for all stacks in the app @@ -34,19 +52,24 @@ AWS CDK Pipelines include a powerful "self-mutation" feature that allows the pip While powerful, this feature presents challenges: -1. **Performance Impact**: By default, CDK synthesizes all stacks in the application even when only one pipeline needs to be updated. This can be extremely slow, especially for complex applications. +1. **Performance Impact**: By default, CDK synthesizes all stacks in the application even when only one pipeline needs + to be updated. This can be extremely slow, especially for complex applications. -2. **Unnecessary Processing**: Every pipeline synthesis includes bundling operations (like frontend builds) even when those components aren't changing. +2. **Unnecessary Processing**: Every pipeline synthesis includes bundling operations (like frontend builds) even when + those components aren't changing. ### The SynthSubstituteStage and SynthSubstituteStack Solution -To address these challenges, we've implemented the `SynthSubstituteStage` and `SynthSubstituteStack` classes that act as lightweight placeholders during the synthesis process: +To address these challenges, we've implemented the `SynthSubstituteStage` and `SynthSubstituteStack` classes that act +as lightweight placeholders during the synthesis process: #### How It Works -1. During pipeline synthesis, we pass in cdk context variables to determine which specific pipeline is being synthesized. +1. During pipeline synthesis, we pass in cdk context variables to determine which specific pipeline is being + synthesized. -2. For any stage that isn't part of the current pipeline being synthesized, we replace it with a `SynthSubstituteStage` containing a minimal `SynthSubstituteStack`. +2. For any stage that isn't part of the current pipeline being synthesized, we replace it with a `SynthSubstituteStage` + containing a minimal `SynthSubstituteStack`. 3. The substitute stack synths a single SSM parameter resource, dramatically reducing synthesis time compared to full application stacks. diff --git a/backend/compact-connect-ui-app/pipeline/design/pipeline-architecture.pdf b/backend/compact-connect/docs/design/pipeline-architecture.pdf similarity index 100% rename from backend/compact-connect-ui-app/pipeline/design/pipeline-architecture.pdf rename to backend/compact-connect/docs/design/pipeline-architecture.pdf diff --git a/backend/compact-connect/pipeline/__init__.py b/backend/compact-connect/pipeline/__init__.py index 3bdcc2b1f..e4f7acdc5 100644 --- a/backend/compact-connect/pipeline/__init__.py +++ b/backend/compact-connect/pipeline/__init__.py @@ -2,7 +2,7 @@ from aws_cdk import Environment, RemovalPolicy from aws_cdk.aws_iam import Effect, PolicyDocument, PolicyStatement, Role, ServicePrincipal -from aws_cdk.aws_kms import IKey, Key +from aws_cdk.aws_kms import IKey from aws_cdk.aws_s3 import IBucket from aws_cdk.aws_sns import ITopic from aws_cdk.aws_ssm import StringParameter @@ -11,8 +11,6 @@ from cdk_nag import NagSuppressions from constructs import Construct -from common_constructs.access_logs_bucket import AccessLogsBucket -from common_constructs.alarm_topic import AlarmTopic from common_constructs.stack import Stack from pipeline.backend_pipeline import BackendPipeline from pipeline.backend_stage import BackendStage @@ -33,75 +31,6 @@ BOOTSTRAP_DEPLOY_ACTION = 'bootstrapDeploy' -class DeploymentResourcesStack(Stack): - """Stack that manages all shared resources for all pipeline stacks.""" - - def __init__( - self, - scope: Construct, - construct_id: str, - **kwargs, - ): - super().__init__(scope, construct_id, environment_name='deploy', **kwargs) - - pipeline_context_parameter_name = f'{DEPLOY_ENVIRONMENT_NAME}-compact-connect-context' - - # Fetch ssm_context if not provided locally - self.parameter = StringParameter.from_string_parameter_name( - self, - 'PipelineContext', - string_parameter_name=pipeline_context_parameter_name, - ) - value = StringParameter.value_from_lookup(self, self.parameter.parameter_name) - # When CDK runs for the first time, it synthesizes fully without actually retrieving the SSM Parameter - # value. It, instead, populates parameters and other look-ups with dummy values, synthesizes, collects all - # the look-ups together, populates them for real, then re-synthesizes with real values. - # To accommodate this pattern, we have to replace this dummy value with one that will actually - # let CDK complete its first round of synthesis, so that it can get to its second, real, synthesis. - if value != f'dummy-value-for-{pipeline_context_parameter_name}': - self.ssm_context = json.loads(value) - else: - with open(f'cdk.context.{DEPLOY_ENVIRONMENT_NAME}-example.json') as f: - self.ssm_context = json.load(f)['ssm_context'] - - self.deploy_environment_context = self.ssm_context['environments'][DEPLOY_ENVIRONMENT_NAME] - - self.pipeline_shared_encryption_key = Key( - self, - 'PipelineSharedEncryptionKey', - enable_key_rotation=True, - alias='pipeline-shared-encryption-key', - removal_policy=RemovalPolicy.RETAIN, - ) - - notifications = self.deploy_environment_context.get('notifications', {}) - self.pipeline_alarm_topic = AlarmTopic( - self, - 'AlarmTopic', - master_key=self.pipeline_shared_encryption_key, - email_subscriptions=notifications.get('email', []), - slack_subscriptions=notifications.get('slack', []), - ) - - self.pipeline_access_logs_bucket = AccessLogsBucket( - self, - 'AccessLogsBucket', - removal_policy=RemovalPolicy.RETAIN, - auto_delete_objects=False, - ) - - NagSuppressions.add_resource_suppressions_by_path( - self, - f'{self.pipeline_access_logs_bucket.node.path}/Resource', - suppressions=[ - { - 'id': 'HIPAA.Security-S3BucketLoggingEnabled', - 'reason': 'This is the access logging bucket.', - }, - ], - ) - - class BasePipelineStack(Stack): """Base stack with common functionality for all pipeline stacks (both backend and frontend).""" diff --git a/backend/compact-connect/pipeline/design/README.md b/backend/compact-connect/pipeline/design/README.md deleted file mode 100644 index b959ec06f..000000000 --- a/backend/compact-connect/pipeline/design/README.md +++ /dev/null @@ -1,96 +0,0 @@ -# CDK Pipeline Architecture Design - -[View Pipeline Architecture (PDF)](./pipeline-architecture.pdf) - -## Overview - -The CompactConnect CI/CD pipeline architecture implements an optimized deployment strategy built around AWS CDK Pipelines (see https://docs.aws.amazon.com/cdk/v2/guide/cdk_pipeline.html). It follows a multi-pipeline approach with separate backend and frontend pipelines to improve deployment speed, reliability, and security. - -## Key Components - -- **Backend Pipelines**: Deploy infrastructure resources and backend components first -- **Frontend Pipelines**: Deploy frontend applications with backend configuration values -- **Deployment Resources Stack**: Shared resources used by all pipeline stacks across all environments -- **Environments**: Test, Beta, and Production environments - -## Pipeline Flow - -1. GitHub push → Backend Pipeline -2. Backend Pipeline successful completion → Trigger Frontend Pipeline -3. Frontend Pipeline deploys web application using configuration values from Backend - -Commits pushed to the 'development' branch trigger the test pipelines. Commits pushed to the 'main' branch trigger the beta and prod pipelines. - -## Self-Mutation Feature and Optimization - -### Understanding CDK Pipeline Self-Mutation - -AWS CDK Pipelines include a powerful "self-mutation" feature that allows the pipeline to update itself. When code changes affecting the pipeline's structure are pushed, the pipeline: - -1. Executes with its current configuration -2. Synthesizes CloudFormation templates for all stacks in the app -3. Deploys a "self-mutation" step that updates the pipeline's own definition -4. Continues deployment with the updated pipeline definition - -While powerful, this feature presents challenges: - -1. **Performance Impact**: By default, CDK synthesizes all stacks in the application even when only one pipeline needs to be updated. This can be extremely slow, especially for complex applications. - -2. **Unnecessary Processing**: Every pipeline synthesis includes bundling operations (like frontend builds) even when those components aren't changing. - -### The SynthSubstituteStage and SynthSubstituteStack Solution - -To address these challenges, we've implemented the `SynthSubstituteStage` and `SynthSubstituteStack` classes that act as lightweight placeholders during the synthesis process: - -#### How It Works - -1. During pipeline synthesis, we pass in cdk context variables to determine which specific pipeline is being synthesized. - -2. For any stage that isn't part of the current pipeline being synthesized, we replace it with a `SynthSubstituteStage` containing a minimal `SynthSubstituteStack`. - -3. The substitute stack synths a single SSM parameter resource, dramatically reducing synthesis time compared to full application stacks. - -## Implementation Details - -The substitution mechanism relies on CDK context values which we pass in during the CDK synth step of the pipeline definition (see the [BackendPipeline](../backend_pipeline.py) and [FrontendPipeline](../frontend_pipeline.py) class constructors, specifically the `synth.commands` property): - -```python -commands=[ - ... other commands - # Only synthesize the specific pipeline stack needed - f'cdk synth --context pipelineStack={pipeline_stack_name} --context action=pipelineSynth', -], -``` -The following context values are used to determine which pipeline to fully synthesize: - -- `action`: Specifies the current action (e.g., `pipelineSynth`, `bootstrapDeploy`) -- `pipelineStack`: The specific pipeline stack being synthesized - -In the pipeline stack classes, the `_determine_backend_stage` and `_determine_frontend_stage` methods handle the stage substitution logic: - -```python -def _determine_backend_stage(self, construct_id, app_name, environment_name, environment_context): - # Check if we're in pipeline synthesis mode and if we're synthesizing this specific pipeline - action = self.node.try_get_context('action') - pipeline_stack_name = self.node.try_get_context('pipelineStack') - - # Use substitute stage if not synthesizing this specific pipeline or during bootstrap - if (action == PIPELINE_SYNTH_ACTION and pipeline_stack_name != self.stack_name) or action == BOOTSTRAP_DEPLOY_ACTION: - return SynthSubstituteStage( - self, - 'SubstituteBackendStage', - environment_context=environment_context, - ) - - # Otherwise, use the real stage - return BackendStage( - self, - construct_id, - app_name=app_name, - environment_name=environment_name, - environment_context=environment_context, - ) -``` - -# Bootstrapping the piplines -See this [README.md](../../README.md) for details on performing a bootstrap deployment of the pipelines. diff --git a/backend/compact-connect/pipeline/design/pipeline-architecture.pdf b/backend/compact-connect/pipeline/design/pipeline-architecture.pdf deleted file mode 100644 index c5146c2e2864567e8077cee448b9ef6524bb8bc7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 89240 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%0A=wxbf58988*?(rcE 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

lVHA>R}}g8ztm5Bg=(YkiTeF1 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 From e2df6c2e5e4d722337097878c9975fe406d3a48f Mon Sep 17 00:00:00 2001 From: Justin Frahm Date: Fri, 19 Sep 2025 10:47:46 -0600 Subject: [PATCH 03/32] Move base pipeline stack to common --- .../common_constructs/base_pipeline_stack.py | 129 ++++++++++++++++ .../deployment_resources_stack.py | 2 +- .../cdk.context.sandbox-example.json | 1 - .../pipeline/__init__.py | 102 +------------ .../pipeline/frontend_pipeline.py | 5 +- .../cdk.context.sandbox-example.json | 1 - backend/compact-connect/pipeline/__init__.py | 144 ++---------------- .../pipeline/backend_pipeline.py | 4 +- 8 files changed, 153 insertions(+), 235 deletions(-) create mode 100644 backend/common-cdk/common_constructs/base_pipeline_stack.py diff --git a/backend/common-cdk/common_constructs/base_pipeline_stack.py b/backend/common-cdk/common_constructs/base_pipeline_stack.py new file mode 100644 index 000000000..3ae09d1dc --- /dev/null +++ b/backend/common-cdk/common_constructs/base_pipeline_stack.py @@ -0,0 +1,129 @@ +import json + +from aws_cdk import Environment, RemovalPolicy +from aws_cdk.aws_iam import Effect, PolicyStatement, Role, ServicePrincipal +from aws_cdk.aws_s3 import IBucket +from aws_cdk.aws_ssm import StringParameter +from aws_cdk.pipelines import CodePipeline as CdkCodePipeline +from constructs import Construct + +from common_constructs.stack import Stack + +TEST_ENVIRONMENT_NAME = 'test' +BETA_ENVIRONMENT_NAME = 'beta' +PROD_ENVIRONMENT_NAME = 'prod' +ALLOWED_ENVIRONMENT_NAMES = [TEST_ENVIRONMENT_NAME, BETA_ENVIRONMENT_NAME, PROD_ENVIRONMENT_NAME] + +class BasePipelineStack(Stack): + """Base stack with common functionality for all pipeline stacks (both backend and frontend).""" + + def __init__( + self, + scope: Construct, + construct_id: str, + environment_name: str, + env: Environment, + removal_policy: RemovalPolicy, + pipeline_access_logs_bucket: IBucket, + **kwargs, + ): + super().__init__(scope, construct_id, environment_name='pipeline', env=env, **kwargs) + + self.env = env + self.environment_name = environment_name + self.removal_policy = removal_policy + self.access_logs_bucket = pipeline_access_logs_bucket + + pipeline_context_parameter_name = f'{self.environment_name}-compact-connect-context' + + # Fetch ssm_context if not provided locally + self.parameter = StringParameter.from_string_parameter_name( + self, + 'PipelineContext', + string_parameter_name=pipeline_context_parameter_name, + ) + value = StringParameter.value_from_lookup(self, self.parameter.parameter_name) + # When CDK runs for the first time, it synthesizes fully without actually retrieving the SSM Parameter + # value. It, instead, populates parameters and other look-ups with dummy values, synthesizes, collects all + # the look-ups together, populates them for real, then re-synthesizes with real values. + # To accommodate this pattern, we have to replace this dummy value with one that will actually + # let CDK complete its first round of synthesis, so that it can get to its second, real, synthesis. + if value != f'dummy-value-for-{pipeline_context_parameter_name}': + self.ssm_context = json.loads(value) + else: + with open(f'cdk.context.{environment_name}-example.json') as f: + self.ssm_context = json.load(f)['ssm_context'] + + self.pipeline_environment_context = self.ssm_context['environments']['pipeline'] + self.connection_arn = self.pipeline_environment_context['connection_arn'] + self.github_repo_string = self.ssm_context['github_repo_string'] + self.backup_config = self.ssm_context.get('backup_config', {}) + self.app_name = self.ssm_context['app_name'] + + def _get_predictable_role_name(self, pipeline_type: str, role_type: str) -> str: + """Generate predictable role name for bootstrap template integration. + + :param pipeline_type: 'Backend' or 'Frontend' + :param role_type: 'Synth', 'SelfMutation', 'AssetPublishing', 'Deploy' + :return: Predictable role name following pattern: CompactConnect-{env}-{pipeline}-{role}Role + """ + if self.environment_name not in ALLOWED_ENVIRONMENT_NAMES: + raise ValueError(f'Environment name must be one of {ALLOWED_ENVIRONMENT_NAMES}') + + return f'CompactConnect-{self.environment_name}-{pipeline_type}-{role_type}Role' + + def create_predictable_pipeline_role(self, pipeline_type: str) -> Role: + """Create a predictable cross-account role that will be trusted by bootstrap roles. + + :param pipeline_type: 'Backend' or 'Frontend' + :return: The cross-account role with predictable name for bootstrap trust policies + """ + # Create environment and pipeline-type specific cross-account roles + cross_account_role_name = f'CompactConnect-{self.environment_name}-{pipeline_type}-CrossAccountRole' + + return Role( + self, + f'{pipeline_type}CrossAccountRole', + role_name=cross_account_role_name, + assumed_by=ServicePrincipal('codepipeline.amazonaws.com'), + description=f'Cross-account role for {self.environment_name} {pipeline_type.lower()}' + 'pipeline bootstrap trust policies', + ) + + def _add_pipeline_cdk_assume_role_policy(self, pipeline: CdkCodePipeline): + pipeline.synth_project.add_to_role_policy( + PolicyStatement( + effect=Effect.ALLOW, + actions=['sts:AssumeRole'], + resources=[ + self.format_arn( + partition=self.partition, + service='iam', + region='', + account='*', + resource='role', + resource_name='cdk-hnb659fds-lookup-role-*', + ), + ], + ), + ) + + def _get_frontend_pipeline_name(self): + if self.environment_name not in ALLOWED_ENVIRONMENT_NAMES: + raise ValueError(f'Environment name must be one of {ALLOWED_ENVIRONMENT_NAMES}') + + return f'{self.environment_name}-compactConnect-frontendPipeline' + + def _get_frontend_pipeline_arn(self): + pipeline_name = self._get_frontend_pipeline_name() + + return self.format_arn( + partition=self.partition, + service='codepipeline', + region=self.env.region, + account=self.env.account, + resource=pipeline_name, + ) + + +DEPLOY_ENVIRONMENT_NAME = 'deploy' diff --git a/backend/common-cdk/common_constructs/deployment_resources_stack.py b/backend/common-cdk/common_constructs/deployment_resources_stack.py index dff97e532..cb1a95640 100644 --- a/backend/common-cdk/common_constructs/deployment_resources_stack.py +++ b/backend/common-cdk/common_constructs/deployment_resources_stack.py @@ -5,10 +5,10 @@ from aws_cdk.aws_ssm import StringParameter from cdk_nag import NagSuppressions from constructs import Construct -from pipeline import DEPLOY_ENVIRONMENT_NAME from common_constructs.access_logs_bucket import AccessLogsBucket from common_constructs.alarm_topic import AlarmTopic +from common_constructs.base_pipeline_stack import DEPLOY_ENVIRONMENT_NAME from common_constructs.stack import Stack diff --git a/backend/compact-connect-ui-app/cdk.context.sandbox-example.json b/backend/compact-connect-ui-app/cdk.context.sandbox-example.json index bea66dcb8..593eae5c2 100644 --- a/backend/compact-connect-ui-app/cdk.context.sandbox-example.json +++ b/backend/compact-connect-ui-app/cdk.context.sandbox-example.json @@ -16,7 +16,6 @@ "domain_name": "justin.compactconnect.org", "backup_enabled": false, "allow_local_ui": true, - "deploy_sandbox_ui": false, "security_profile": "VULNERABLE", "recaptcha_public_key": "123-KFEUsjehfuejILDVUKkRnAF9SSzb8o9uv5lY7Ih", "robots_meta": "noindex,nofollow", diff --git a/backend/compact-connect-ui-app/pipeline/__init__.py b/backend/compact-connect-ui-app/pipeline/__init__.py index edf4c6b89..9d649ba30 100644 --- a/backend/compact-connect-ui-app/pipeline/__init__.py +++ b/backend/compact-connect-ui-app/pipeline/__init__.py @@ -1,27 +1,19 @@ -import json - from aws_cdk import Environment, RemovalPolicy -from aws_cdk.aws_iam import Effect, PolicyStatement from aws_cdk.aws_kms import IKey from aws_cdk.aws_s3 import IBucket from aws_cdk.aws_sns import ITopic -from aws_cdk.aws_ssm import StringParameter -from aws_cdk.pipelines import CodePipeline as CdkCodePipeline -from common_constructs.stack import Stack +from common_constructs.base_pipeline_stack import ( + BETA_ENVIRONMENT_NAME, + PROD_ENVIRONMENT_NAME, + TEST_ENVIRONMENT_NAME, + BasePipelineStack, +) from constructs import Construct from pipeline.frontend_pipeline import FrontendPipeline from pipeline.frontend_stage import FrontendStage from pipeline.synth_substitute_stage import SynthSubstituteStage -TEST_ENVIRONMENT_NAME = 'test' -BETA_ENVIRONMENT_NAME = 'beta' -PROD_ENVIRONMENT_NAME = 'prod' - -ALLOWED_ENVIRONMENT_NAMES = [TEST_ENVIRONMENT_NAME, BETA_ENVIRONMENT_NAME, PROD_ENVIRONMENT_NAME] - -DEPLOY_ENVIRONMENT_NAME = 'deploy' - # Action constants ACTION_CONTEXT_KEY = 'action' PIPELINE_STACK_CONTEXT_KEY = 'pipelineStack' @@ -29,88 +21,6 @@ BOOTSTRAP_DEPLOY_ACTION = 'bootstrapDeploy' -class BasePipelineStack(Stack): - """Base stack with common functionality for all pipeline stacks (both backend and frontend).""" - - def __init__( - self, - scope: Construct, - construct_id: str, - environment_name: str, - env: Environment, - removal_policy: RemovalPolicy, - pipeline_access_logs_bucket: IBucket, - **kwargs, - ): - super().__init__(scope, construct_id, environment_name='pipeline', env=env, **kwargs) - - self.env = env - self.environment_name = environment_name - self.removal_policy = removal_policy - self.access_logs_bucket = pipeline_access_logs_bucket - - pipeline_context_parameter_name = f'{self.environment_name}-compact-connect-context' - - # Fetch ssm_context if not provided locally - self.parameter = StringParameter.from_string_parameter_name( - self, - 'PipelineContext', - string_parameter_name=pipeline_context_parameter_name, - ) - value = StringParameter.value_from_lookup(self, self.parameter.parameter_name) - # When CDK runs for the first time, it synthesizes fully without actually retrieving the SSM Parameter - # value. It, instead, populates parameters and other look-ups with dummy values, synthesizes, collects all - # the look-ups together, populates them for real, then re-synthesizes with real values. - # To accommodate this pattern, we have to replace this dummy value with one that will actually - # let CDK complete its first round of synthesis, so that it can get to its second, real, synthesis. - if value != f'dummy-value-for-{pipeline_context_parameter_name}': - self.ssm_context = json.loads(value) - else: - with open(f'cdk.context.{environment_name}-example.json') as f: - self.ssm_context = json.load(f)['ssm_context'] - - self.pipeline_environment_context = self.ssm_context['environments']['pipeline'] - self.connection_arn = self.pipeline_environment_context['connection_arn'] - self.github_repo_string = self.ssm_context['github_repo_string'] - self.backup_config = self.ssm_context.get('backup_config', {}) - self.app_name = self.ssm_context['app_name'] - - def _add_pipeline_cdk_assume_role_policy(self, pipeline: CdkCodePipeline): - pipeline.synth_project.add_to_role_policy( - PolicyStatement( - effect=Effect.ALLOW, - actions=['sts:AssumeRole'], - resources=[ - self.format_arn( - partition=self.partition, - service='iam', - region='', - account='*', - resource='role', - resource_name='cdk-hnb659fds-lookup-role-*', - ), - ], - ), - ) - - def _get_frontend_pipeline_name(self): - if self.environment_name not in ALLOWED_ENVIRONMENT_NAMES: - raise ValueError(f'Environment name must be one of {ALLOWED_ENVIRONMENT_NAMES}') - - return f'{self.environment_name}-compactConnect-frontendPipeline' - - def _get_frontend_pipeline_arn(self): - pipeline_name = self._get_frontend_pipeline_name() - - return self.format_arn( - partition=self.partition, - service='codepipeline', - region=self.env.region, - account=self.env.account, - resource=pipeline_name, - ) - - class BaseFrontendPipelineStack(BasePipelineStack): """ Base class for frontend pipeline stacks. diff --git a/backend/compact-connect-ui-app/pipeline/frontend_pipeline.py b/backend/compact-connect-ui-app/pipeline/frontend_pipeline.py index ac73e28ba..a991c21f4 100644 --- a/backend/compact-connect-ui-app/pipeline/frontend_pipeline.py +++ b/backend/compact-connect-ui-app/pipeline/frontend_pipeline.py @@ -2,6 +2,7 @@ import os +import common_constructs.base_pipeline_stack from aws_cdk import RemovalPolicy, Stack from aws_cdk.aws_codebuild import BuildSpec from aws_cdk.aws_codestarnotifications import NotificationRule @@ -15,8 +16,6 @@ from cdk_nag import NagSuppressions from common_constructs.bucket import Bucket -import pipeline - class FrontendPipeline(CdkCodePipeline): """ @@ -36,7 +35,7 @@ class FrontendPipeline(CdkCodePipeline): def __init__( self, - scope: pipeline.BasePipelineStack, + scope: common_constructs.base_pipeline_stack.BasePipelineStack, construct_id: str, *, pipeline_name: str, diff --git a/backend/compact-connect/cdk.context.sandbox-example.json b/backend/compact-connect/cdk.context.sandbox-example.json index bea66dcb8..593eae5c2 100644 --- a/backend/compact-connect/cdk.context.sandbox-example.json +++ b/backend/compact-connect/cdk.context.sandbox-example.json @@ -16,7 +16,6 @@ "domain_name": "justin.compactconnect.org", "backup_enabled": false, "allow_local_ui": true, - "deploy_sandbox_ui": false, "security_profile": "VULNERABLE", "recaptcha_public_key": "123-KFEUsjehfuejILDVUKkRnAF9SSzb8o9uv5lY7Ih", "robots_meta": "noindex,nofollow", diff --git a/backend/compact-connect/pipeline/__init__.py b/backend/compact-connect/pipeline/__init__.py index 0ea18692a..e962b1da0 100644 --- a/backend/compact-connect/pipeline/__init__.py +++ b/backend/compact-connect/pipeline/__init__.py @@ -1,29 +1,23 @@ -import json - from aws_cdk import Environment, RemovalPolicy from aws_cdk.aws_iam import Effect, PolicyDocument, PolicyStatement, Role, ServicePrincipal from aws_cdk.aws_kms import IKey from aws_cdk.aws_s3 import IBucket from aws_cdk.aws_sns import ITopic -from aws_cdk.aws_ssm import StringParameter from aws_cdk.pipelines import CodeBuildStep -from aws_cdk.pipelines import CodePipeline as CdkCodePipeline from cdk_nag import NagSuppressions from constructs import Construct -from common_constructs.stack import Stack +from common_constructs.base_pipeline_stack import ( + ALLOWED_ENVIRONMENT_NAMES, + BETA_ENVIRONMENT_NAME, + PROD_ENVIRONMENT_NAME, + TEST_ENVIRONMENT_NAME, + BasePipelineStack, +) from pipeline.backend_pipeline import BackendPipeline from pipeline.backend_stage import BackendStage from pipeline.synth_substitute_stage import SynthSubstituteStage -TEST_ENVIRONMENT_NAME = 'test' -BETA_ENVIRONMENT_NAME = 'beta' -PROD_ENVIRONMENT_NAME = 'prod' - -ALLOWED_ENVIRONMENT_NAMES = [TEST_ENVIRONMENT_NAME, BETA_ENVIRONMENT_NAME, PROD_ENVIRONMENT_NAME] - -DEPLOY_ENVIRONMENT_NAME = 'deploy' - # Action constants ACTION_CONTEXT_KEY = 'action' PIPELINE_STACK_CONTEXT_KEY = 'pipelineStack' @@ -31,124 +25,6 @@ BOOTSTRAP_DEPLOY_ACTION = 'bootstrapDeploy' -class BasePipelineStack(Stack): - """Base stack with common functionality for all pipeline stacks (both backend and frontend).""" - - def __init__( - self, - scope: Construct, - construct_id: str, - environment_name: str, - env: Environment, - removal_policy: RemovalPolicy, - pipeline_access_logs_bucket: IBucket, - **kwargs, - ): - super().__init__(scope, construct_id, environment_name='pipeline', env=env, **kwargs) - - self.env = env - self.environment_name = environment_name - self.removal_policy = removal_policy - self.access_logs_bucket = pipeline_access_logs_bucket - - pipeline_context_parameter_name = f'{self.environment_name}-compact-connect-context' - - # Fetch ssm_context if not provided locally - self.parameter = StringParameter.from_string_parameter_name( - self, - 'PipelineContext', - string_parameter_name=pipeline_context_parameter_name, - ) - value = StringParameter.value_from_lookup(self, self.parameter.parameter_name) - # When CDK runs for the first time, it synthesizes fully without actually retrieving the SSM Parameter - # value. It, instead, populates parameters and other look-ups with dummy values, synthesizes, collects all - # the look-ups together, populates them for real, then re-synthesizes with real values. - # To accommodate this pattern, we have to replace this dummy value with one that will actually - # let CDK complete its first round of synthesis, so that it can get to its second, real, synthesis. - if value != f'dummy-value-for-{pipeline_context_parameter_name}': - self.ssm_context = json.loads(value) - else: - with open(f'cdk.context.{environment_name}-example.json') as f: - self.ssm_context = json.load(f)['ssm_context'] - - self.pipeline_environment_context = self.ssm_context['environments']['pipeline'] - self.connection_arn = self.pipeline_environment_context['connection_arn'] - self.github_repo_string = self.ssm_context['github_repo_string'] - self.backup_config = self.ssm_context.get('backup_config', {}) - self.app_name = self.ssm_context['app_name'] - - def _get_predictable_role_name(self, pipeline_type: str, role_type: str) -> str: - """Generate predictable role name for bootstrap template integration. - - :param pipeline_type: 'Backend' or 'Frontend' - :param role_type: 'Synth', 'SelfMutation', 'AssetPublishing', 'Deploy' - :return: Predictable role name following pattern: CompactConnect-{env}-{pipeline}-{role}Role - """ - if self.environment_name not in ALLOWED_ENVIRONMENT_NAMES: - raise ValueError(f'Environment name must be one of {ALLOWED_ENVIRONMENT_NAMES}') - - return f'CompactConnect-{self.environment_name}-{pipeline_type}-{role_type}Role' - - def create_predictable_pipeline_role(self, pipeline_type: str) -> Role: - """Create a predictable cross-account role that will be trusted by bootstrap roles. - - :param pipeline_type: 'Backend' or 'Frontend' - :return: The cross-account role with predictable name for bootstrap trust policies - """ - # Create environment and pipeline-type specific cross-account roles - cross_account_role_name = f'CompactConnect-{self.environment_name}-{pipeline_type}-CrossAccountRole' - - return Role( - self, - f'{pipeline_type}CrossAccountRole', - role_name=cross_account_role_name, - assumed_by=ServicePrincipal('codepipeline.amazonaws.com'), - description=f'Cross-account role for {self.environment_name} {pipeline_type.lower()}' - 'pipeline bootstrap trust policies', - ) - - def _add_pipeline_cdk_assume_role_policy(self, pipeline: CdkCodePipeline): - pipeline.synth_project.add_to_role_policy( - PolicyStatement( - effect=Effect.ALLOW, - actions=['sts:AssumeRole'], - resources=[ - self.format_arn( - partition=self.partition, - service='iam', - region='', - account='*', - resource='role', - resource_name='cdk-hnb659fds-lookup-role-*', - ), - ], - ), - ) - - def _get_backend_pipeline_name(self): - if self.environment_name not in ALLOWED_ENVIRONMENT_NAMES: - raise ValueError(f'Environment name must be one of {ALLOWED_ENVIRONMENT_NAMES}') - - return f'{self.environment_name}-compactConnect-backendPipeline' - - def _get_frontend_pipeline_name(self): - if self.environment_name not in ALLOWED_ENVIRONMENT_NAMES: - raise ValueError(f'Environment name must be one of {ALLOWED_ENVIRONMENT_NAMES}') - - return f'{self.environment_name}-compactConnect-frontendPipeline' - - def _get_frontend_pipeline_arn(self): - pipeline_name = self._get_frontend_pipeline_name() - - return self.format_arn( - partition=self.partition, - service='codepipeline', - region=self.env.region, - account=self.env.account, - resource=pipeline_name, - ) - - class BaseBackendPipelineStack(BasePipelineStack): """ Base class for backend pipeline stacks. @@ -175,6 +51,12 @@ def __init__( **kwargs, ) + def _get_backend_pipeline_name(self): + if self.environment_name not in ALLOWED_ENVIRONMENT_NAMES: + raise ValueError(f'Environment name must be one of {ALLOWED_ENVIRONMENT_NAMES}') + + return f'{self.environment_name}-compactConnect-backendPipeline' + def _determine_backend_stage(self, construct_id, app_name, environment_name, environment_context): """ Return either a real BackendStage or a SynthSubstituteStage depending on pipeline synthesis context. diff --git a/backend/compact-connect/pipeline/backend_pipeline.py b/backend/compact-connect/pipeline/backend_pipeline.py index 1d1241384..b2bd7c949 100644 --- a/backend/compact-connect/pipeline/backend_pipeline.py +++ b/backend/compact-connect/pipeline/backend_pipeline.py @@ -14,7 +14,7 @@ from aws_cdk.pipelines import CodePipeline as CdkCodePipeline from cdk_nag import NagSuppressions -import pipeline +import common_constructs.base_pipeline_stack from common_constructs.bucket import Bucket @@ -33,7 +33,7 @@ class BackendPipeline(CdkCodePipeline): def __init__( self, - scope: pipeline.BasePipelineStack, + scope: common_constructs.base_pipeline_stack.BasePipelineStack, construct_id: str, *, pipeline_name: str, From 05e3f1a2d96be3f24b0681f5bdc5c8c80b4ff51d Mon Sep 17 00:00:00 2001 From: Justin Frahm Date: Mon, 22 Sep 2025 09:38:04 -0600 Subject: [PATCH 04/32] Update compact-connect deps --- backend/compact-connect/bin/sync_deps.sh | 2 +- .../cognito-backup/requirements-dev.txt | 32 +++++++++---------- .../python/cognito-backup/requirements.txt | 16 +++++----- .../lambdas/python/common/requirements-dev.in | 1 + .../python/common/requirements-dev.txt | 32 ++++++++++++------- .../lambdas/python/common/requirements.in | 2 +- .../lambdas/python/common/requirements.txt | 28 ++++++++-------- .../requirements-dev.txt | 20 ++++++------ .../compact-configuration/requirements.txt | 2 +- .../custom-resources/requirements-dev.txt | 21 ++++++------ .../python/custom-resources/requirements.txt | 2 +- .../python/data-events/requirements-dev.txt | 20 ++++++------ .../python/data-events/requirements.txt | 2 +- .../disaster-recovery/requirements-dev.txt | 20 ++++++------ .../python/disaster-recovery/requirements.txt | 2 +- .../provider-data-v1/requirements-dev.txt | 22 ++++++------- .../python/provider-data-v1/requirements.txt | 2 +- .../python/purchases/requirements-dev.txt | 20 ++++++------ .../lambdas/python/purchases/requirements.txt | 4 +-- .../staff-user-pre-token/requirements-dev.txt | 20 ++++++------ .../staff-user-pre-token/requirements.txt | 2 +- .../python/staff-users/requirements-dev.txt | 24 +++++++------- .../python/staff-users/requirements.txt | 2 +- backend/compact-connect/requirements-dev.txt | 28 ++++++++-------- backend/compact-connect/requirements.txt | 24 +++++++------- 25 files changed, 180 insertions(+), 170 deletions(-) diff --git a/backend/compact-connect/bin/sync_deps.sh b/backend/compact-connect/bin/sync_deps.sh index 9a9d80983..46835a8cd 100755 --- a/backend/compact-connect/bin/sync_deps.sh +++ b/backend/compact-connect/bin/sync_deps.sh @@ -1,5 +1,5 @@ ( - cd compact-connect/lambdas/nodejs + cd lambdas/nodejs yarn install ) diff --git a/backend/compact-connect/lambdas/python/cognito-backup/requirements-dev.txt b/backend/compact-connect/lambdas/python/cognito-backup/requirements-dev.txt index c94233d79..b9e90ad65 100644 --- a/backend/compact-connect/lambdas/python/cognito-backup/requirements-dev.txt +++ b/backend/compact-connect/lambdas/python/cognito-backup/requirements-dev.txt @@ -2,27 +2,27 @@ # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# pip-compile --no-emit-index-url compact-connect/lambdas/python/cognito-backup/requirements-dev.in +# pip-compile --cert=None --client-cert=None --index-url=None --no-emit-index-url --pip-args=None lambdas/python/cognito-backup/requirements-dev.in # -aws-lambda-powertools==3.19.0 - # via -r compact-connect/lambdas/python/cognito-backup/requirements-dev.in -boto3==1.40.19 +aws-lambda-powertools==3.20.0 + # via -r lambdas/python/cognito-backup/requirements-dev.in +boto3==1.40.35 # via - # -r compact-connect/lambdas/python/cognito-backup/requirements-dev.in + # -r lambdas/python/cognito-backup/requirements-dev.in # moto -botocore==1.40.19 +botocore==1.40.35 # via - # -r compact-connect/lambdas/python/cognito-backup/requirements-dev.in + # -r lambdas/python/cognito-backup/requirements-dev.in # boto3 # moto # s3transfer certifi==2025.8.3 # via requests -cffi==1.17.1 +cffi==2.0.0 # via cryptography charset-normalizer==3.4.3 # via requests -cryptography==45.0.6 +cryptography==46.0.1 # via moto idna==3.10 # via requests @@ -39,20 +39,20 @@ markupsafe==3.0.2 # via # jinja2 # werkzeug -moto[cognito-idp,s3]==5.1.11 - # via -r compact-connect/lambdas/python/cognito-backup/requirements-dev.in +moto[cognito-idp,s3]==5.1.12 + # via -r lambdas/python/cognito-backup/requirements-dev.in packaging==25.0 # via pytest pluggy==1.6.0 # via pytest py-partiql-parser==0.6.1 # via moto -pycparser==2.22 +pycparser==2.23 # via cffi pygments==2.19.2 # via pytest -pytest==8.4.1 - # via -r compact-connect/lambdas/python/cognito-backup/requirements-dev.in +pytest==8.4.2 + # via -r lambdas/python/cognito-backup/requirements-dev.in python-dateutil==2.9.0.post0 # via # botocore @@ -67,7 +67,7 @@ requests==2.32.5 # responses responses==0.25.8 # via moto -s3transfer==0.13.1 +s3transfer==0.14.0 # via boto3 six==1.17.0 # via python-dateutil @@ -80,5 +80,5 @@ urllib3==2.5.0 # responses werkzeug==3.1.3 # via moto -xmltodict==0.14.2 +xmltodict==1.0.2 # via moto diff --git a/backend/compact-connect/lambdas/python/cognito-backup/requirements.txt b/backend/compact-connect/lambdas/python/cognito-backup/requirements.txt index 98af73d6a..8d02a8f2d 100644 --- a/backend/compact-connect/lambdas/python/cognito-backup/requirements.txt +++ b/backend/compact-connect/lambdas/python/cognito-backup/requirements.txt @@ -2,15 +2,15 @@ # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# pip-compile --no-emit-index-url compact-connect/lambdas/python/cognito-backup/requirements.in +# pip-compile --cert=None --client-cert=None --index-url=None --no-emit-index-url --pip-args=None lambdas/python/cognito-backup/requirements.in # -aws-lambda-powertools==3.19.0 - # via -r compact-connect/lambdas/python/cognito-backup/requirements.in -boto3==1.40.19 - # via -r compact-connect/lambdas/python/cognito-backup/requirements.in -botocore==1.40.19 +aws-lambda-powertools==3.20.0 + # via -r lambdas/python/cognito-backup/requirements.in +boto3==1.40.35 + # via -r lambdas/python/cognito-backup/requirements.in +botocore==1.40.35 # via - # -r compact-connect/lambdas/python/cognito-backup/requirements.in + # -r lambdas/python/cognito-backup/requirements.in # boto3 # s3transfer jmespath==1.0.1 @@ -20,7 +20,7 @@ jmespath==1.0.1 # botocore python-dateutil==2.9.0.post0 # via botocore -s3transfer==0.13.1 +s3transfer==0.14.0 # via boto3 six==1.17.0 # via python-dateutil diff --git a/backend/compact-connect/lambdas/python/common/requirements-dev.in b/backend/compact-connect/lambdas/python/common/requirements-dev.in index 804353503..b737d5527 100644 --- a/backend/compact-connect/lambdas/python/common/requirements-dev.in +++ b/backend/compact-connect/lambdas/python/common/requirements-dev.in @@ -1,2 +1,3 @@ moto[dynamodb, s3]>=5.0.12, <6 +boto3-stubs[full] Faker>=28,<29 diff --git a/backend/compact-connect/lambdas/python/common/requirements-dev.txt b/backend/compact-connect/lambdas/python/common/requirements-dev.txt index 607d4ef63..d79ddd986 100644 --- a/backend/compact-connect/lambdas/python/common/requirements-dev.txt +++ b/backend/compact-connect/lambdas/python/common/requirements-dev.txt @@ -2,27 +2,33 @@ # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# pip-compile --no-emit-index-url compact-connect/lambdas/python/common/requirements-dev.in +# pip-compile --cert=None --client-cert=None --index-url=None --no-emit-index-url --pip-args=None lambdas/python/common/requirements-dev.in # -boto3==1.40.19 +boto3==1.40.35 # via moto -botocore==1.40.19 +boto3-stubs[full]==1.40.35 + # via -r lambdas/python/common/requirements-dev.in +boto3-stubs-full==1.40.34 + # via boto3-stubs +botocore==1.40.35 # via # boto3 # moto # s3transfer +botocore-stubs==1.40.33 + # via boto3-stubs certifi==2025.8.3 # via requests -cffi==1.17.1 +cffi==2.0.0 # via cryptography charset-normalizer==3.4.3 # via requests -cryptography==45.0.6 +cryptography==46.0.1 # via moto docker==7.1.0 # via moto faker==28.4.1 - # via -r compact-connect/lambdas/python/common/requirements-dev.in + # via -r lambdas/python/common/requirements-dev.in idna==3.10 # via requests jinja2==3.1.6 @@ -35,11 +41,11 @@ markupsafe==3.0.2 # via # jinja2 # werkzeug -moto[dynamodb,s3]==5.1.11 - # via -r compact-connect/lambdas/python/common/requirements-dev.in +moto[dynamodb,s3]==5.1.12 + # via -r lambdas/python/common/requirements-dev.in py-partiql-parser==0.6.1 # via moto -pycparser==2.22 +pycparser==2.23 # via cffi python-dateutil==2.9.0.post0 # via @@ -57,10 +63,14 @@ requests==2.32.5 # responses responses==0.25.8 # via moto -s3transfer==0.13.1 +s3transfer==0.14.0 # via boto3 six==1.17.0 # via python-dateutil +types-awscrt==0.27.6 + # via botocore-stubs +types-s3transfer==0.13.1 + # via boto3-stubs urllib3==2.5.0 # via # botocore @@ -69,5 +79,5 @@ urllib3==2.5.0 # responses werkzeug==3.1.3 # via moto -xmltodict==0.14.2 +xmltodict==1.0.2 # via moto diff --git a/backend/compact-connect/lambdas/python/common/requirements.in b/backend/compact-connect/lambdas/python/common/requirements.in index bd28fbdc1..0cb9f207b 100644 --- a/backend/compact-connect/lambdas/python/common/requirements.in +++ b/backend/compact-connect/lambdas/python/common/requirements.in @@ -1,6 +1,6 @@ argon2-cffi>=25.1.0, <26.0.0 aws-lambda-powertools>=3.5.0, <4 boto3>=1.34.33, <2 -cryptography>=45.0.5, <46 +cryptography>=46, <47 marshmallow>=3.21.3, <4.0.0 requests>=2.31.0, <3.0.0 diff --git a/backend/compact-connect/lambdas/python/common/requirements.txt b/backend/compact-connect/lambdas/python/common/requirements.txt index 1a11fba53..828f0ab9d 100644 --- a/backend/compact-connect/lambdas/python/common/requirements.txt +++ b/backend/compact-connect/lambdas/python/common/requirements.txt @@ -2,30 +2,30 @@ # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# pip-compile --no-emit-index-url compact-connect/lambdas/python/common/requirements.in +# pip-compile --cert=None --client-cert=None --index-url=None --no-emit-index-url --pip-args=None lambdas/python/common/requirements.in # argon2-cffi==25.1.0 - # via -r compact-connect/lambdas/python/common/requirements.in + # via -r lambdas/python/common/requirements.in argon2-cffi-bindings==25.1.0 # via argon2-cffi -aws-lambda-powertools==3.19.0 - # via -r compact-connect/lambdas/python/common/requirements.in -boto3==1.40.19 - # via -r compact-connect/lambdas/python/common/requirements.in -botocore==1.40.19 +aws-lambda-powertools==3.20.0 + # via -r lambdas/python/common/requirements.in +boto3==1.40.35 + # via -r lambdas/python/common/requirements.in +botocore==1.40.35 # via # boto3 # s3transfer certifi==2025.8.3 # via requests -cffi==1.17.1 +cffi==2.0.0 # via # argon2-cffi-bindings # cryptography charset-normalizer==3.4.3 # via requests -cryptography==45.0.6 - # via -r compact-connect/lambdas/python/common/requirements.in +cryptography==46.0.1 + # via -r lambdas/python/common/requirements.in idna==3.10 # via requests jmespath==1.0.1 @@ -34,16 +34,16 @@ jmespath==1.0.1 # boto3 # botocore marshmallow==3.26.1 - # via -r compact-connect/lambdas/python/common/requirements.in + # via -r lambdas/python/common/requirements.in packaging==25.0 # via marshmallow -pycparser==2.22 +pycparser==2.23 # via cffi python-dateutil==2.9.0.post0 # via botocore requests==2.32.5 - # via -r compact-connect/lambdas/python/common/requirements.in -s3transfer==0.13.1 + # via -r lambdas/python/common/requirements.in +s3transfer==0.14.0 # via boto3 six==1.17.0 # via python-dateutil diff --git a/backend/compact-connect/lambdas/python/compact-configuration/requirements-dev.txt b/backend/compact-connect/lambdas/python/compact-configuration/requirements-dev.txt index 81e902ad3..e30bfbb2a 100644 --- a/backend/compact-connect/lambdas/python/compact-configuration/requirements-dev.txt +++ b/backend/compact-connect/lambdas/python/compact-configuration/requirements-dev.txt @@ -2,22 +2,22 @@ # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# pip-compile --no-emit-index-url compact-connect/lambdas/python/compact-configuration/requirements-dev.in +# pip-compile --cert=None --client-cert=None --index-url=None --no-emit-index-url --pip-args=None lambdas/python/compact-configuration/requirements-dev.in # -boto3==1.40.19 +boto3==1.40.35 # via moto -botocore==1.40.19 +botocore==1.40.35 # via # boto3 # moto # s3transfer certifi==2025.8.3 # via requests -cffi==1.17.1 +cffi==2.0.0 # via cryptography charset-normalizer==3.4.3 # via requests -cryptography==45.0.6 +cryptography==46.0.1 # via moto docker==7.1.0 # via moto @@ -33,11 +33,11 @@ markupsafe==3.0.2 # via # jinja2 # werkzeug -moto[dynamodb,s3]==5.1.11 - # via -r compact-connect/lambdas/python/compact-configuration/requirements-dev.in +moto[dynamodb,s3]==5.1.12 + # via -r lambdas/python/compact-configuration/requirements-dev.in py-partiql-parser==0.6.1 # via moto -pycparser==2.22 +pycparser==2.23 # via cffi python-dateutil==2.9.0.post0 # via @@ -54,7 +54,7 @@ requests==2.32.5 # responses responses==0.25.8 # via moto -s3transfer==0.13.1 +s3transfer==0.14.0 # via boto3 six==1.17.0 # via python-dateutil @@ -66,5 +66,5 @@ urllib3==2.5.0 # responses werkzeug==3.1.3 # via moto -xmltodict==0.14.2 +xmltodict==1.0.2 # via moto diff --git a/backend/compact-connect/lambdas/python/compact-configuration/requirements.txt b/backend/compact-connect/lambdas/python/compact-configuration/requirements.txt index 87680f5d5..f56b86777 100644 --- a/backend/compact-connect/lambdas/python/compact-configuration/requirements.txt +++ b/backend/compact-connect/lambdas/python/compact-configuration/requirements.txt @@ -2,5 +2,5 @@ # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# pip-compile --no-emit-index-url compact-connect/lambdas/python/compact-configuration/requirements.in +# pip-compile --cert=None --client-cert=None --index-url=None --no-emit-index-url --pip-args=None lambdas/python/compact-configuration/requirements.in # diff --git a/backend/compact-connect/lambdas/python/custom-resources/requirements-dev.txt b/backend/compact-connect/lambdas/python/custom-resources/requirements-dev.txt index 982de0c34..82c17d527 100644 --- a/backend/compact-connect/lambdas/python/custom-resources/requirements-dev.txt +++ b/backend/compact-connect/lambdas/python/custom-resources/requirements-dev.txt @@ -2,23 +2,22 @@ # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# pip-compile --no-emit-index-url compact-connect/lambdas/python/custom-resources/requirements-dev.in +# pip-compile --cert=None --client-cert=None --index-url=None --no-emit-index-url --pip-args=None lambdas/python/custom-resources/requirements-dev.in # - -boto3==1.40.19 +boto3==1.40.35 # via moto -botocore==1.40.19 +botocore==1.40.35 # via # boto3 # moto # s3transfer certifi==2025.8.3 # via requests -cffi==1.17.1 +cffi==2.0.0 # via cryptography charset-normalizer==3.4.3 # via requests -cryptography==45.0.6 +cryptography==46.0.1 # via moto docker==7.1.0 # via moto @@ -34,11 +33,11 @@ markupsafe==3.0.2 # via # jinja2 # werkzeug -moto[dynamodb,s3]==5.1.11 - # via -r compact-connect/lambdas/python/custom-resources/requirements-dev.in +moto[dynamodb,s3]==5.1.12 + # via -r lambdas/python/custom-resources/requirements-dev.in py-partiql-parser==0.6.1 # via moto -pycparser==2.22 +pycparser==2.23 # via cffi python-dateutil==2.9.0.post0 # via @@ -55,7 +54,7 @@ requests==2.32.5 # responses responses==0.25.8 # via moto -s3transfer==0.13.1 +s3transfer==0.14.0 # via boto3 six==1.17.0 # via python-dateutil @@ -67,5 +66,5 @@ urllib3==2.5.0 # responses werkzeug==3.1.3 # via moto -xmltodict==0.14.2 +xmltodict==1.0.2 # via moto diff --git a/backend/compact-connect/lambdas/python/custom-resources/requirements.txt b/backend/compact-connect/lambdas/python/custom-resources/requirements.txt index 40f4dd194..6c98adb26 100644 --- a/backend/compact-connect/lambdas/python/custom-resources/requirements.txt +++ b/backend/compact-connect/lambdas/python/custom-resources/requirements.txt @@ -2,5 +2,5 @@ # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# pip-compile --no-emit-index-url compact-connect/lambdas/python/custom-resources/requirements.in +# pip-compile --cert=None --client-cert=None --index-url=None --no-emit-index-url --pip-args=None lambdas/python/custom-resources/requirements.in # diff --git a/backend/compact-connect/lambdas/python/data-events/requirements-dev.txt b/backend/compact-connect/lambdas/python/data-events/requirements-dev.txt index bfc33a1da..c92146177 100644 --- a/backend/compact-connect/lambdas/python/data-events/requirements-dev.txt +++ b/backend/compact-connect/lambdas/python/data-events/requirements-dev.txt @@ -2,22 +2,22 @@ # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# pip-compile --no-emit-index-url compact-connect/lambdas/python/data-events/requirements-dev.in +# pip-compile --cert=None --client-cert=None --index-url=None --no-emit-index-url --pip-args=None lambdas/python/data-events/requirements-dev.in # -boto3==1.40.19 +boto3==1.40.35 # via moto -botocore==1.40.19 +botocore==1.40.35 # via # boto3 # moto # s3transfer certifi==2025.8.3 # via requests -cffi==1.17.1 +cffi==2.0.0 # via cryptography charset-normalizer==3.4.3 # via requests -cryptography==45.0.6 +cryptography==46.0.1 # via moto docker==7.1.0 # via moto @@ -33,11 +33,11 @@ markupsafe==3.0.2 # via # jinja2 # werkzeug -moto[dynamodb,s3]==5.1.11 - # via -r compact-connect/lambdas/python/data-events/requirements-dev.in +moto[dynamodb,s3]==5.1.12 + # via -r lambdas/python/data-events/requirements-dev.in py-partiql-parser==0.6.1 # via moto -pycparser==2.22 +pycparser==2.23 # via cffi python-dateutil==2.9.0.post0 # via @@ -54,7 +54,7 @@ requests==2.32.5 # responses responses==0.25.8 # via moto -s3transfer==0.13.1 +s3transfer==0.14.0 # via boto3 six==1.17.0 # via python-dateutil @@ -66,5 +66,5 @@ urllib3==2.5.0 # responses werkzeug==3.1.3 # via moto -xmltodict==0.14.2 +xmltodict==1.0.2 # via moto diff --git a/backend/compact-connect/lambdas/python/data-events/requirements.txt b/backend/compact-connect/lambdas/python/data-events/requirements.txt index bd5f9271f..da006c453 100644 --- a/backend/compact-connect/lambdas/python/data-events/requirements.txt +++ b/backend/compact-connect/lambdas/python/data-events/requirements.txt @@ -2,5 +2,5 @@ # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# pip-compile --no-emit-index-url compact-connect/lambdas/python/data-events/requirements.in +# pip-compile --cert=None --client-cert=None --index-url=None --no-emit-index-url --pip-args=None lambdas/python/data-events/requirements.in # diff --git a/backend/compact-connect/lambdas/python/disaster-recovery/requirements-dev.txt b/backend/compact-connect/lambdas/python/disaster-recovery/requirements-dev.txt index 1c844d20c..8c2dca592 100644 --- a/backend/compact-connect/lambdas/python/disaster-recovery/requirements-dev.txt +++ b/backend/compact-connect/lambdas/python/disaster-recovery/requirements-dev.txt @@ -2,22 +2,22 @@ # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# pip-compile --no-emit-index-url compact-connect/lambdas/python/disaster-recovery/requirements-dev.in +# pip-compile --cert=None --client-cert=None --index-url=None --no-emit-index-url --pip-args=None lambdas/python/disaster-recovery/requirements-dev.in # -boto3==1.40.19 +boto3==1.40.35 # via moto -botocore==1.40.19 +botocore==1.40.35 # via # boto3 # moto # s3transfer certifi==2025.8.3 # via requests -cffi==1.17.1 +cffi==2.0.0 # via cryptography charset-normalizer==3.4.3 # via requests -cryptography==45.0.6 +cryptography==46.0.1 # via moto docker==7.1.0 # via moto @@ -33,11 +33,11 @@ markupsafe==3.0.2 # via # jinja2 # werkzeug -moto[dynamodb,s3]==5.1.11 - # via -r compact-connect/lambdas/python/disaster-recovery/requirements-dev.in +moto[dynamodb,s3]==5.1.12 + # via -r lambdas/python/disaster-recovery/requirements-dev.in py-partiql-parser==0.6.1 # via moto -pycparser==2.22 +pycparser==2.23 # via cffi python-dateutil==2.9.0.post0 # via @@ -54,7 +54,7 @@ requests==2.32.5 # responses responses==0.25.8 # via moto -s3transfer==0.13.1 +s3transfer==0.14.0 # via boto3 six==1.17.0 # via python-dateutil @@ -66,5 +66,5 @@ urllib3==2.5.0 # responses werkzeug==3.1.3 # via moto -xmltodict==0.14.2 +xmltodict==1.0.2 # via moto diff --git a/backend/compact-connect/lambdas/python/disaster-recovery/requirements.txt b/backend/compact-connect/lambdas/python/disaster-recovery/requirements.txt index 24e02f5ca..800fc58fc 100644 --- a/backend/compact-connect/lambdas/python/disaster-recovery/requirements.txt +++ b/backend/compact-connect/lambdas/python/disaster-recovery/requirements.txt @@ -2,5 +2,5 @@ # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# pip-compile --no-emit-index-url compact-connect/lambdas/python/disaster-recovery/requirements.in +# pip-compile --cert=None --client-cert=None --index-url=None --no-emit-index-url --pip-args=None lambdas/python/disaster-recovery/requirements.in # diff --git a/backend/compact-connect/lambdas/python/provider-data-v1/requirements-dev.txt b/backend/compact-connect/lambdas/python/provider-data-v1/requirements-dev.txt index d1063406e..4c01da874 100644 --- a/backend/compact-connect/lambdas/python/provider-data-v1/requirements-dev.txt +++ b/backend/compact-connect/lambdas/python/provider-data-v1/requirements-dev.txt @@ -2,27 +2,27 @@ # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# pip-compile --no-emit-index-url compact-connect/lambdas/python/provider-data-v1/requirements-dev.in +# pip-compile --cert=None --client-cert=None --index-url=None --no-emit-index-url --pip-args=None lambdas/python/provider-data-v1/requirements-dev.in # -boto3==1.40.19 +boto3==1.40.35 # via moto -botocore==1.40.19 +botocore==1.40.35 # via # boto3 # moto # s3transfer certifi==2025.8.3 # via requests -cffi==1.17.1 +cffi==2.0.0 # via cryptography charset-normalizer==3.4.3 # via requests -cryptography==45.0.6 +cryptography==46.0.1 # via moto docker==7.1.0 # via moto faker==28.4.1 - # via -r compact-connect/lambdas/python/provider-data-v1/requirements-dev.in + # via -r lambdas/python/provider-data-v1/requirements-dev.in idna==3.10 # via requests jinja2==3.1.6 @@ -35,11 +35,11 @@ markupsafe==3.0.2 # via # jinja2 # werkzeug -moto[dynamodb,s3]==5.1.11 - # via -r compact-connect/lambdas/python/provider-data-v1/requirements-dev.in +moto[dynamodb,s3]==5.1.12 + # via -r lambdas/python/provider-data-v1/requirements-dev.in py-partiql-parser==0.6.1 # via moto -pycparser==2.22 +pycparser==2.23 # via cffi python-dateutil==2.9.0.post0 # via @@ -57,7 +57,7 @@ requests==2.32.5 # responses responses==0.25.8 # via moto -s3transfer==0.13.1 +s3transfer==0.14.0 # via boto3 six==1.17.0 # via python-dateutil @@ -69,5 +69,5 @@ urllib3==2.5.0 # responses werkzeug==3.1.3 # via moto -xmltodict==0.14.2 +xmltodict==1.0.2 # via moto diff --git a/backend/compact-connect/lambdas/python/provider-data-v1/requirements.txt b/backend/compact-connect/lambdas/python/provider-data-v1/requirements.txt index 26d50642e..15e52fd4e 100644 --- a/backend/compact-connect/lambdas/python/provider-data-v1/requirements.txt +++ b/backend/compact-connect/lambdas/python/provider-data-v1/requirements.txt @@ -2,5 +2,5 @@ # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# pip-compile --no-emit-index-url compact-connect/lambdas/python/provider-data-v1/requirements.in +# pip-compile --cert=None --client-cert=None --index-url=None --no-emit-index-url --pip-args=None lambdas/python/provider-data-v1/requirements.in # diff --git a/backend/compact-connect/lambdas/python/purchases/requirements-dev.txt b/backend/compact-connect/lambdas/python/purchases/requirements-dev.txt index c122cfe73..2a1ee6b0a 100644 --- a/backend/compact-connect/lambdas/python/purchases/requirements-dev.txt +++ b/backend/compact-connect/lambdas/python/purchases/requirements-dev.txt @@ -2,22 +2,22 @@ # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# pip-compile --no-emit-index-url compact-connect/lambdas/python/purchases/requirements-dev.in +# pip-compile --cert=None --client-cert=None --index-url=None --no-emit-index-url --pip-args=None lambdas/python/purchases/requirements-dev.in # -boto3==1.40.19 +boto3==1.40.35 # via moto -botocore==1.40.19 +botocore==1.40.35 # via # boto3 # moto # s3transfer certifi==2025.8.3 # via requests -cffi==1.17.1 +cffi==2.0.0 # via cryptography charset-normalizer==3.4.3 # via requests -cryptography==45.0.6 +cryptography==46.0.1 # via moto docker==7.1.0 # via moto @@ -33,11 +33,11 @@ markupsafe==3.0.2 # via # jinja2 # werkzeug -moto[dynamodb,s3]==5.1.11 - # via -r compact-connect/lambdas/python/purchases/requirements-dev.in +moto[dynamodb,s3]==5.1.12 + # via -r lambdas/python/purchases/requirements-dev.in py-partiql-parser==0.6.1 # via moto -pycparser==2.22 +pycparser==2.23 # via cffi python-dateutil==2.9.0.post0 # via @@ -54,7 +54,7 @@ requests==2.32.5 # responses responses==0.25.8 # via moto -s3transfer==0.13.1 +s3transfer==0.14.0 # via boto3 six==1.17.0 # via python-dateutil @@ -66,5 +66,5 @@ urllib3==2.5.0 # responses werkzeug==3.1.3 # via moto -xmltodict==0.14.2 +xmltodict==1.0.2 # via moto diff --git a/backend/compact-connect/lambdas/python/purchases/requirements.txt b/backend/compact-connect/lambdas/python/purchases/requirements.txt index a7a8fd65e..3b8e3df89 100644 --- a/backend/compact-connect/lambdas/python/purchases/requirements.txt +++ b/backend/compact-connect/lambdas/python/purchases/requirements.txt @@ -2,10 +2,10 @@ # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# pip-compile --no-emit-index-url compact-connect/lambdas/python/purchases/requirements.in +# pip-compile --cert=None --client-cert=None --index-url=None --no-emit-index-url --pip-args=None lambdas/python/purchases/requirements.in # authorizenet==1.1.6 - # via -r compact-connect/lambdas/python/purchases/requirements.in + # via -r lambdas/python/purchases/requirements.in certifi==2025.8.3 # via requests charset-normalizer==3.4.3 diff --git a/backend/compact-connect/lambdas/python/staff-user-pre-token/requirements-dev.txt b/backend/compact-connect/lambdas/python/staff-user-pre-token/requirements-dev.txt index 711acbc28..5ea9fc177 100644 --- a/backend/compact-connect/lambdas/python/staff-user-pre-token/requirements-dev.txt +++ b/backend/compact-connect/lambdas/python/staff-user-pre-token/requirements-dev.txt @@ -2,22 +2,22 @@ # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# pip-compile --no-emit-index-url compact-connect/lambdas/python/staff-user-pre-token/requirements-dev.in +# pip-compile --cert=None --client-cert=None --index-url=None --no-emit-index-url --pip-args=None lambdas/python/staff-user-pre-token/requirements-dev.in # -boto3==1.40.19 +boto3==1.40.35 # via moto -botocore==1.40.19 +botocore==1.40.35 # via # boto3 # moto # s3transfer certifi==2025.8.3 # via requests -cffi==1.17.1 +cffi==2.0.0 # via cryptography charset-normalizer==3.4.3 # via requests -cryptography==45.0.6 +cryptography==46.0.1 # via moto docker==7.1.0 # via moto @@ -33,11 +33,11 @@ markupsafe==3.0.2 # via # jinja2 # werkzeug -moto[dynamodb,s3]==5.1.11 - # via -r compact-connect/lambdas/python/staff-user-pre-token/requirements-dev.in +moto[dynamodb,s3]==5.1.12 + # via -r lambdas/python/staff-user-pre-token/requirements-dev.in py-partiql-parser==0.6.1 # via moto -pycparser==2.22 +pycparser==2.23 # via cffi python-dateutil==2.9.0.post0 # via @@ -54,7 +54,7 @@ requests==2.32.5 # responses responses==0.25.8 # via moto -s3transfer==0.13.1 +s3transfer==0.14.0 # via boto3 six==1.17.0 # via python-dateutil @@ -66,5 +66,5 @@ urllib3==2.5.0 # responses werkzeug==3.1.3 # via moto -xmltodict==0.14.2 +xmltodict==1.0.2 # via moto diff --git a/backend/compact-connect/lambdas/python/staff-user-pre-token/requirements.txt b/backend/compact-connect/lambdas/python/staff-user-pre-token/requirements.txt index 346c6642c..23d37d92a 100644 --- a/backend/compact-connect/lambdas/python/staff-user-pre-token/requirements.txt +++ b/backend/compact-connect/lambdas/python/staff-user-pre-token/requirements.txt @@ -2,5 +2,5 @@ # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# pip-compile --no-emit-index-url compact-connect/lambdas/python/staff-user-pre-token/requirements.in +# pip-compile --cert=None --client-cert=None --index-url=None --no-emit-index-url --pip-args=None lambdas/python/staff-user-pre-token/requirements.in # diff --git a/backend/compact-connect/lambdas/python/staff-users/requirements-dev.txt b/backend/compact-connect/lambdas/python/staff-users/requirements-dev.txt index fb2a72460..6e72a0e88 100644 --- a/backend/compact-connect/lambdas/python/staff-users/requirements-dev.txt +++ b/backend/compact-connect/lambdas/python/staff-users/requirements-dev.txt @@ -2,29 +2,29 @@ # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# pip-compile --no-emit-index-url compact-connect/lambdas/python/staff-users/requirements-dev.in +# pip-compile --cert=None --client-cert=None --index-url=None --no-emit-index-url --pip-args=None lambdas/python/staff-users/requirements-dev.in # -boto3==1.40.19 +boto3==1.40.35 # via moto -botocore==1.40.19 +botocore==1.40.35 # via # boto3 # moto # s3transfer certifi==2025.8.3 # via requests -cffi==1.17.1 +cffi==2.0.0 # via cryptography charset-normalizer==3.4.3 # via requests -cryptography==45.0.6 +cryptography==46.0.1 # via # joserfc # moto docker==7.1.0 # via moto faker==28.4.1 - # via -r compact-connect/lambdas/python/staff-users/requirements-dev.in + # via -r lambdas/python/staff-users/requirements-dev.in idna==3.10 # via requests jinja2==3.1.6 @@ -33,17 +33,17 @@ jmespath==1.0.1 # via # boto3 # botocore -joserfc==1.3.1 +joserfc==1.3.3 # via moto markupsafe==3.0.2 # via # jinja2 # werkzeug -moto[cognitoidp,dynamodb,s3]==5.1.11 - # via -r compact-connect/lambdas/python/staff-users/requirements-dev.in +moto[cognitoidp,dynamodb,s3]==5.1.12 + # via -r lambdas/python/staff-users/requirements-dev.in py-partiql-parser==0.6.1 # via moto -pycparser==2.22 +pycparser==2.23 # via cffi python-dateutil==2.9.0.post0 # via @@ -61,7 +61,7 @@ requests==2.32.5 # responses responses==0.25.8 # via moto -s3transfer==0.13.1 +s3transfer==0.14.0 # via boto3 six==1.17.0 # via python-dateutil @@ -73,5 +73,5 @@ urllib3==2.5.0 # responses werkzeug==3.1.3 # via moto -xmltodict==0.14.2 +xmltodict==1.0.2 # via moto diff --git a/backend/compact-connect/lambdas/python/staff-users/requirements.txt b/backend/compact-connect/lambdas/python/staff-users/requirements.txt index 49d8e7e5d..3830b77fd 100644 --- a/backend/compact-connect/lambdas/python/staff-users/requirements.txt +++ b/backend/compact-connect/lambdas/python/staff-users/requirements.txt @@ -2,5 +2,5 @@ # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# pip-compile --no-emit-index-url compact-connect/lambdas/python/staff-users/requirements.in +# pip-compile --cert=None --client-cert=None --index-url=None --no-emit-index-url --pip-args=None lambdas/python/staff-users/requirements.in # diff --git a/backend/compact-connect/requirements-dev.txt b/backend/compact-connect/requirements-dev.txt index 1f4d4e0c2..82a9ca638 100644 --- a/backend/compact-connect/requirements-dev.txt +++ b/backend/compact-connect/requirements-dev.txt @@ -2,7 +2,7 @@ # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# pip-compile --no-emit-index-url compact-connect/requirements-dev.in +# pip-compile --cert=None --client-cert=None --index-url=None --no-emit-index-url --pip-args=None requirements-dev.in # boolean-py==5.0 # via license-expression @@ -16,18 +16,18 @@ certifi==2025.8.3 # via requests charset-normalizer==3.4.3 # via requests -click==8.2.1 +click==8.3.0 # via pip-tools -coverage[toml]==7.10.5 +coverage[toml]==7.10.6 # via - # -r compact-connect/requirements-dev.in + # -r requirements-dev.in # pytest-cov cyclonedx-python-lib==9.1.0 # via pip-audit defusedxml==0.7.1 # via py-serializable faker==28.4.1 - # via -r compact-connect/requirements-dev.in + # via -r requirements-dev.in filelock==3.19.1 # via cachecontrol idna==3.10 @@ -53,11 +53,11 @@ packaging==25.0 pip-api==0.0.34 # via pip-audit pip-audit==2.9.0 - # via -r compact-connect/requirements-dev.in + # via -r requirements-dev.in pip-requirements-parser==32.0.1 # via pip-audit pip-tools==7.5.0 - # via -r compact-connect/requirements-dev.in + # via -r requirements-dev.in platformdirs==4.4.0 # via pip-audit pluggy==1.6.0 @@ -70,18 +70,18 @@ pygments==2.19.2 # via # pytest # rich -pyparsing==3.2.3 +pyparsing==3.2.4 # via pip-requirements-parser pyproject-hooks==1.2.0 # via # build # pip-tools -pytest==8.4.1 +pytest==8.4.2 # via - # -r compact-connect/requirements-dev.in + # -r requirements-dev.in # pytest-cov -pytest-cov==6.2.1 - # via -r compact-connect/requirements-dev.in +pytest-cov==7.0.0 + # via -r requirements-dev.in python-dateutil==2.9.0.post0 # via faker requests==2.32.5 @@ -90,8 +90,8 @@ requests==2.32.5 # pip-audit rich==14.1.0 # via pip-audit -ruff==0.12.11 - # via -r compact-connect/requirements-dev.in +ruff==0.13.1 + # via -r requirements-dev.in six==1.17.0 # via python-dateutil sortedcontainers==2.4.0 diff --git a/backend/compact-connect/requirements.txt b/backend/compact-connect/requirements.txt index 9fd7b5812..97e6c37ff 100644 --- a/backend/compact-connect/requirements.txt +++ b/backend/compact-connect/requirements.txt @@ -2,7 +2,7 @@ # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# pip-compile --no-emit-index-url compact-connect/requirements.in +# pip-compile --cert=None --client-cert=None --index-url=None --no-emit-index-url --pip-args=None requirements.in # attrs==25.3.0 # via @@ -12,28 +12,28 @@ aws-cdk-asset-awscli-v1==2.2.242 # via aws-cdk-lib aws-cdk-asset-node-proxy-agent-v6==2.1.0 # via aws-cdk-lib -aws-cdk-aws-lambda-python-alpha==2.213.0a0 - # via -r compact-connect/requirements.in -aws-cdk-cloud-assembly-schema==48.6.0 +aws-cdk-aws-lambda-python-alpha==2.215.0a0 + # via -r requirements.in +aws-cdk-cloud-assembly-schema==48.10.0 # via aws-cdk-lib -aws-cdk-lib==2.213.0 +aws-cdk-lib==2.215.0 # via - # -r compact-connect/requirements.in + # -r requirements.in # aws-cdk-aws-lambda-python-alpha # cdk-nag -cattrs==25.1.1 +cattrs==25.2.0 # via jsii -cdk-nag==2.37.11 - # via -r compact-connect/requirements.in +cdk-nag==2.37.31 + # via -r requirements.in constructs==10.4.2 # via - # -r compact-connect/requirements.in + # -r requirements.in # aws-cdk-aws-lambda-python-alpha # aws-cdk-lib # cdk-nag importlib-resources==6.5.2 # via jsii -jsii==1.113.0 +jsii==1.114.1 # via # aws-cdk-asset-awscli-v1 # aws-cdk-asset-node-proxy-agent-v6 @@ -55,7 +55,7 @@ publication==0.0.3 python-dateutil==2.9.0.post0 # via jsii pyyaml==6.0.2 - # via -r compact-connect/requirements.in + # via -r requirements.in six==1.17.0 # via python-dateutil typeguard==2.13.3 From 29eccc044bfab2505a133ad9a4d28515d63f5106 Mon Sep 17 00:00:00 2001 From: Justin Frahm Date: Wed, 24 Sep 2025 13:17:41 -0600 Subject: [PATCH 05/32] Move new frontend tests to frontend app, linting --- .github/workflows/check-common-cdk.yml | 2 +- .../tests/app/test_pipeline.py | 71 +++++++++++++++++++ backend/compact-connect/app.py | 1 + .../common_constructs/cc_api.py | 4 +- .../common_constructs/cognito_user_backup.py | 6 +- .../common_constructs/user_pool.py | 3 +- backend/compact-connect/pipeline/__init__.py | 4 +- .../pipeline/backend_pipeline.py | 3 +- .../compact-connect/pipeline/backend_stage.py | 2 +- .../stacks/api_lambda_stack/__init__.py | 2 +- .../stacks/api_lambda_stack/provider_users.py | 2 +- .../stacks/api_stack/__init__.py | 4 +- .../compact-connect/stacks/api_stack/api.py | 2 +- .../stacks/api_stack/v1_api/api.py | 2 +- .../stacks/api_stack/v1_api/api_model.py | 2 +- .../stacks/api_stack/v1_api/attestations.py | 2 +- .../api_stack/v1_api/bulk_upload_url.py | 2 +- .../v1_api/compact_configuration_api.py | 2 +- .../stacks/api_stack/v1_api/credentials.py | 2 +- .../stacks/api_stack/v1_api/post_licenses.py | 2 +- .../api_stack/v1_api/provider_management.py | 2 +- .../stacks/api_stack/v1_api/provider_users.py | 2 +- .../api_stack/v1_api/public_lookup_api.py | 2 +- .../stacks/api_stack/v1_api/purchases.py | 2 +- .../disaster_recovery_stack/__init__.py | 2 +- .../restore_dynamo_db_table_step_function.py | 3 +- .../sync_table_step_function.py | 2 +- .../stacks/event_listener_stack/__init__.py | 2 +- .../compact-connect/stacks/ingest_stack.py | 2 +- .../stacks/managed_login_stack.py | 2 +- .../stacks/notification_stack.py | 2 +- .../stacks/persistent_stack/__init__.py | 8 +-- .../persistent_stack/bulk_uploads_bucket.py | 6 +- .../compact_configuration_upload.py | 2 +- .../persistent_stack/provider_users_bucket.py | 4 +- .../stacks/persistent_stack/ssn_table.py | 2 +- .../transaction_reports_bucket.py | 3 +- .../user_email_notifications.py | 2 +- .../stacks/provider_users/__init__.py | 4 +- .../compact-connect/stacks/reporting_stack.py | 2 +- .../stacks/state_api_stack/__init__.py | 4 +- .../state_api_stack/v1_api/api_model.py | 2 +- .../state_api_stack/v1_api/bulk_upload_url.py | 2 +- .../state_api_stack/v1_api/post_licenses.py | 2 +- .../v1_api/provider_management.py | 2 +- .../stacks/state_auth/__init__.py | 2 +- .../transaction_monitoring_stack/__init__.py | 2 +- ...transaction_history_processing_workflow.py | 2 +- backend/compact-connect/tests/app/base.py | 2 +- .../tests/app/test_pipeline.py | 23 ------ .../test_cognito_user_backup.py | 2 +- 51 files changed, 133 insertions(+), 88 deletions(-) diff --git a/.github/workflows/check-common-cdk.yml b/.github/workflows/check-common-cdk.yml index 80d381f9b..576b5203a 100644 --- a/.github/workflows/check-common-cdk.yml +++ b/.github/workflows/check-common-cdk.yml @@ -53,4 +53,4 @@ jobs: - name: Test backend # Start at 14%, because that's what our 'unit' coverage is, while we convert this to more stand-alone # We will raise this up to 90 over time, to support these constructs more like a library - run: "cd backend/common-cdk; pytest tests --cov=common_constructs --cov-fail-under 14" + run: "cd backend/common-cdk; pytest tests --cov=common_constructs --cov-fail-under 11" diff --git a/backend/compact-connect-ui-app/tests/app/test_pipeline.py b/backend/compact-connect-ui-app/tests/app/test_pipeline.py index 08b20dfa0..74796477e 100644 --- a/backend/compact-connect-ui-app/tests/app/test_pipeline.py +++ b/backend/compact-connect-ui-app/tests/app/test_pipeline.py @@ -1,6 +1,8 @@ import json from unittest import TestCase +from aws_cdk.assertions import Match, Template + from tests.app.base import TstAppABC @@ -39,3 +41,72 @@ def test_synth_pipeline(self): self.app.prod_frontend_pipeline_stack.prod_frontend_stage.frontend_deployment_stack, ): self._inspect_frontend_deployment_stack(frontend_deployment_stack) + + def test_predictable_pipeline_role_names_created(self): + """Test that pipelines create roles with predictable names for bootstrap trust policies.""" + # Test frontend pipeline role naming - roles are environment-specific + frontend_test_cases = [ + (self.app.test_frontend_pipeline_stack, 'CompactConnect-test-Frontend-CrossAccountRole'), + (self.app.beta_frontend_pipeline_stack, 'CompactConnect-beta-Frontend-CrossAccountRole'), + (self.app.prod_frontend_pipeline_stack, 'CompactConnect-prod-Frontend-CrossAccountRole'), + ] + + for frontend_stack, expected_role_name in frontend_test_cases: + with self.subTest(role=expected_role_name): + frontend_template = Template.from_stack(frontend_stack) + + # Look for the predictable frontend pipeline role + frontend_roles = frontend_template.find_resources( + 'AWS::IAM::Role', props={'Properties': {'RoleName': expected_role_name}} + ) + self.assertEqual( + len(frontend_roles), + 1, + f'Should have exactly one frontend pipeline role with name {expected_role_name}', + ) + + def test_pipeline_role_trust_policies(self): + """Test that pipeline roles have correct trust policies for CodePipeline service.""" + # Test that all pipeline roles trust the CodePipeline service + # Note: Roles are environment-specific to avoid naming conflicts + test_cases = [ + (self.app.test_frontend_pipeline_stack, 'CompactConnect-test-Frontend-CrossAccountRole'), + (self.app.beta_frontend_pipeline_stack, 'CompactConnect-beta-Frontend-CrossAccountRole'), + (self.app.prod_frontend_pipeline_stack, 'CompactConnect-prod-Frontend-CrossAccountRole'), + ] + + for stack, expected_role_name in test_cases: + with self.subTest(role=expected_role_name): + template = Template.from_stack(stack) + + # Verify pipeline role trusts CodePipeline service + template.has_resource_properties( + 'AWS::IAM::Role', + { + 'RoleName': expected_role_name, + 'AssumeRolePolicyDocument': { + 'Statement': Match.array_with( + [ + { + 'Effect': 'Allow', + 'Principal': {'Service': 'codepipeline.amazonaws.com'}, + 'Action': 'sts:AssumeRole', + } + ] + ) + }, + }, + ) + + def test_pipeline_uses_predictable_roles_for_actions(self): + """Test that pipelines are configured to use predictable roles for all actions.""" + # Test that pipelines use the role parameter and use_pipeline_role_for_actions=True + frontend_pipeline_stack = self.app.test_frontend_pipeline_stack + frontend_template = Template.from_stack(frontend_pipeline_stack) + + # The pipeline should reference the predictable cross-account role + # This validates that our role parameter is being used + frontend_template.has_resource_properties( + 'AWS::CodePipeline::Pipeline', + {'RoleArn': {'Fn::GetAtt': [Match.string_like_regexp('.*FrontendCrossAccountRole.*'), 'Arn']}}, + ) diff --git a/backend/compact-connect/app.py b/backend/compact-connect/app.py index c8036ca5e..7d43f4ff1 100644 --- a/backend/compact-connect/app.py +++ b/backend/compact-connect/app.py @@ -9,6 +9,7 @@ from common_constructs.deployment_resources_stack import DeploymentResourcesStack from common_constructs.stack import StandardTags + from pipeline import ( ACTION_CONTEXT_KEY, PIPELINE_STACK_CONTEXT_KEY, diff --git a/backend/compact-connect/common_constructs/cc_api.py b/backend/compact-connect/common_constructs/cc_api.py index 49d1d1fe6..2291ff22d 100644 --- a/backend/compact-connect/common_constructs/cc_api.py +++ b/backend/compact-connect/common_constructs/cc_api.py @@ -26,11 +26,11 @@ from aws_cdk.aws_route53 import ARecord, IHostedZone, RecordTarget from aws_cdk.aws_route53_targets import ApiGateway from cdk_nag import NagSuppressions -from constructs import IConstruct - from common_constructs.security_profile import SecurityProfile from common_constructs.stack import AppStack from common_constructs.webacl import WebACL, WebACLScope +from constructs import IConstruct + from stacks import persistent_stack as ps MD_FORMAT = r'^[01]{1}[0-9]{1}-[0-3]{1}[0-9]{1}$' diff --git a/backend/compact-connect/common_constructs/cognito_user_backup.py b/backend/compact-connect/common_constructs/cognito_user_backup.py index b821bd273..374b3b92c 100644 --- a/backend/compact-connect/common_constructs/cognito_user_backup.py +++ b/backend/compact-connect/common_constructs/cognito_user_backup.py @@ -21,13 +21,13 @@ from aws_cdk.aws_s3 import BucketEncryption, LifecycleRule from aws_cdk.aws_sns import ITopic from cdk_nag import NagSuppressions +from common_constructs.access_logs_bucket import AccessLogsBucket +from common_constructs.bucket import Bucket +from common_constructs.stack import Stack from constructs import Construct -from common_constructs.access_logs_bucket import AccessLogsBucket from common_constructs.backup_plan import CCBackupPlan -from common_constructs.bucket import Bucket from common_constructs.python_function import PythonFunction -from common_constructs.stack import Stack from stacks.backup_infrastructure_stack import BackupInfrastructureStack diff --git a/backend/compact-connect/common_constructs/user_pool.py b/backend/compact-connect/common_constructs/user_pool.py index 446f781ef..1e50ef970 100644 --- a/backend/compact-connect/common_constructs/user_pool.py +++ b/backend/compact-connect/common_constructs/user_pool.py @@ -30,9 +30,8 @@ from aws_cdk.aws_cognito import UserPool as CdkUserPool from aws_cdk.aws_kms import IKey from cdk_nag import NagSuppressions -from constructs import Construct - from common_constructs.security_profile import SecurityProfile +from constructs import Construct class UserPool(CdkUserPool): diff --git a/backend/compact-connect/pipeline/__init__.py b/backend/compact-connect/pipeline/__init__.py index e962b1da0..a6a04aa8e 100644 --- a/backend/compact-connect/pipeline/__init__.py +++ b/backend/compact-connect/pipeline/__init__.py @@ -5,8 +5,6 @@ from aws_cdk.aws_sns import ITopic from aws_cdk.pipelines import CodeBuildStep from cdk_nag import NagSuppressions -from constructs import Construct - from common_constructs.base_pipeline_stack import ( ALLOWED_ENVIRONMENT_NAMES, BETA_ENVIRONMENT_NAME, @@ -14,6 +12,8 @@ TEST_ENVIRONMENT_NAME, BasePipelineStack, ) +from constructs import Construct + from pipeline.backend_pipeline import BackendPipeline from pipeline.backend_stage import BackendStage from pipeline.synth_substitute_stage import SynthSubstituteStage diff --git a/backend/compact-connect/pipeline/backend_pipeline.py b/backend/compact-connect/pipeline/backend_pipeline.py index b2bd7c949..781a787db 100644 --- a/backend/compact-connect/pipeline/backend_pipeline.py +++ b/backend/compact-connect/pipeline/backend_pipeline.py @@ -2,6 +2,7 @@ import os +import common_constructs.base_pipeline_stack from aws_cdk import RemovalPolicy, Stack from aws_cdk.aws_codebuild import BuildSpec from aws_cdk.aws_codestarnotifications import NotificationRule @@ -13,8 +14,6 @@ from aws_cdk.pipelines import CodeBuildOptions, CodePipelineSource, ShellStep from aws_cdk.pipelines import CodePipeline as CdkCodePipeline from cdk_nag import NagSuppressions - -import common_constructs.base_pipeline_stack from common_constructs.bucket import Bucket diff --git a/backend/compact-connect/pipeline/backend_stage.py b/backend/compact-connect/pipeline/backend_stage.py index ca8782998..3dd131188 100644 --- a/backend/compact-connect/pipeline/backend_stage.py +++ b/backend/compact-connect/pipeline/backend_stage.py @@ -1,7 +1,7 @@ from aws_cdk import Environment, Stage +from common_constructs.stack import StandardTags from constructs import Construct -from common_constructs.stack import StandardTags from stacks.api_lambda_stack import ApiLambdaStack from stacks.api_stack import ApiStack from stacks.disaster_recovery_stack import DisasterRecoveryStack diff --git a/backend/compact-connect/stacks/api_lambda_stack/__init__.py b/backend/compact-connect/stacks/api_lambda_stack/__init__.py index ddd92358c..bf68bad61 100644 --- a/backend/compact-connect/stacks/api_lambda_stack/__init__.py +++ b/backend/compact-connect/stacks/api_lambda_stack/__init__.py @@ -1,8 +1,8 @@ from __future__ import annotations +from common_constructs.stack import AppStack from constructs import Construct -from common_constructs.stack import AppStack from stacks import persistent_stack as ps from stacks.provider_users import ProviderUsersStack diff --git a/backend/compact-connect/stacks/api_lambda_stack/provider_users.py b/backend/compact-connect/stacks/api_lambda_stack/provider_users.py index 809321727..025ff4f35 100644 --- a/backend/compact-connect/stacks/api_lambda_stack/provider_users.py +++ b/backend/compact-connect/stacks/api_lambda_stack/provider_users.py @@ -7,9 +7,9 @@ from aws_cdk.aws_cloudwatch_actions import SnsAction from aws_cdk.aws_secretsmanager import Secret from cdk_nag import NagSuppressions +from common_constructs.stack import Stack from common_constructs.python_function import PythonFunction -from common_constructs.stack import Stack from stacks import persistent_stack as ps from stacks.provider_users import ProviderUsersStack diff --git a/backend/compact-connect/stacks/api_stack/__init__.py b/backend/compact-connect/stacks/api_stack/__init__.py index fdb5d4ead..4d2578047 100644 --- a/backend/compact-connect/stacks/api_stack/__init__.py +++ b/backend/compact-connect/stacks/api_stack/__init__.py @@ -1,9 +1,9 @@ from __future__ import annotations -from constructs import Construct - from common_constructs.security_profile import SecurityProfile from common_constructs.stack import AppStack +from constructs import Construct + from stacks import persistent_stack as ps from stacks.api_lambda_stack import ApiLambdaStack from stacks.provider_users import ProviderUsersStack diff --git a/backend/compact-connect/stacks/api_stack/api.py b/backend/compact-connect/stacks/api_stack/api.py index 351993d16..1301f3441 100644 --- a/backend/compact-connect/stacks/api_stack/api.py +++ b/backend/compact-connect/stacks/api_stack/api.py @@ -4,10 +4,10 @@ from functools import cached_property from aws_cdk import ArnFormat +from common_constructs.stack import Stack from constructs import Construct from common_constructs.cc_api import CCApi -from common_constructs.stack import Stack from stacks import persistent_stack as ps from stacks.api_lambda_stack import ApiLambdaStack from stacks.provider_users import ProviderUsersStack diff --git a/backend/compact-connect/stacks/api_stack/v1_api/api.py b/backend/compact-connect/stacks/api_stack/v1_api/api.py index 4331277c9..262d7369e 100644 --- a/backend/compact-connect/stacks/api_stack/v1_api/api.py +++ b/backend/compact-connect/stacks/api_stack/v1_api/api.py @@ -4,10 +4,10 @@ from aws_cdk.aws_apigateway import AuthorizationType, IResource, MethodOptions from cdk_nag import NagSuppressions +from common_constructs.stack import Stack from common_constructs.python_function import PythonFunction from common_constructs.ssm_parameter_utility import SSMParameterUtility -from common_constructs.stack import Stack from stacks import persistent_stack as ps from stacks.api_lambda_stack import ApiLambdaStack diff --git a/backend/compact-connect/stacks/api_stack/v1_api/api_model.py b/backend/compact-connect/stacks/api_stack/v1_api/api_model.py index e03b7198b..a5f53c9b8 100644 --- a/backend/compact-connect/stacks/api_stack/v1_api/api_model.py +++ b/backend/compact-connect/stacks/api_stack/v1_api/api_model.py @@ -4,10 +4,10 @@ from __future__ import annotations from aws_cdk.aws_apigateway import JsonSchema, JsonSchemaType, Model +from common_constructs.stack import AppStack # Importing module level to allow lazy loading for typing from common_constructs import cc_api -from common_constructs.stack import AppStack class ApiModel: diff --git a/backend/compact-connect/stacks/api_stack/v1_api/attestations.py b/backend/compact-connect/stacks/api_stack/v1_api/attestations.py index 73e090906..dde18f992 100644 --- a/backend/compact-connect/stacks/api_stack/v1_api/attestations.py +++ b/backend/compact-connect/stacks/api_stack/v1_api/attestations.py @@ -5,10 +5,10 @@ from aws_cdk import Duration from aws_cdk.aws_apigateway import LambdaIntegration, MethodResponse, Resource from cdk_nag import NagSuppressions +from common_constructs.stack import Stack from common_constructs.cc_api import CCApi from common_constructs.python_function import PythonFunction -from common_constructs.stack import Stack from stacks import persistent_stack as ps from .api_model import ApiModel diff --git a/backend/compact-connect/stacks/api_stack/v1_api/bulk_upload_url.py b/backend/compact-connect/stacks/api_stack/v1_api/bulk_upload_url.py index d795928f4..09dc242b1 100644 --- a/backend/compact-connect/stacks/api_stack/v1_api/bulk_upload_url.py +++ b/backend/compact-connect/stacks/api_stack/v1_api/bulk_upload_url.py @@ -6,10 +6,10 @@ from aws_cdk.aws_apigateway import AuthorizationType, LambdaIntegration, MethodOptions, MethodResponse, Resource from aws_cdk.aws_iam import IRole from aws_cdk.aws_s3 import IBucket +from common_constructs.stack import Stack from common_constructs.cc_api import CCApi from common_constructs.python_function import PythonFunction -from common_constructs.stack import Stack from .api_model import ApiModel diff --git a/backend/compact-connect/stacks/api_stack/v1_api/compact_configuration_api.py b/backend/compact-connect/stacks/api_stack/v1_api/compact_configuration_api.py index 4e2604ede..667f9b086 100644 --- a/backend/compact-connect/stacks/api_stack/v1_api/compact_configuration_api.py +++ b/backend/compact-connect/stacks/api_stack/v1_api/compact_configuration_api.py @@ -5,10 +5,10 @@ from aws_cdk import Duration from aws_cdk.aws_apigateway import LambdaIntegration, MethodOptions, MethodResponse, Resource from cdk_nag import NagSuppressions +from common_constructs.stack import Stack from common_constructs.cc_api import CCApi from common_constructs.python_function import PythonFunction -from common_constructs.stack import Stack from stacks import persistent_stack as ps from .api_model import ApiModel diff --git a/backend/compact-connect/stacks/api_stack/v1_api/credentials.py b/backend/compact-connect/stacks/api_stack/v1_api/credentials.py index 41bdd820d..e101ea81d 100644 --- a/backend/compact-connect/stacks/api_stack/v1_api/credentials.py +++ b/backend/compact-connect/stacks/api_stack/v1_api/credentials.py @@ -6,10 +6,10 @@ from aws_cdk.aws_apigateway import LambdaIntegration, MethodOptions, MethodResponse, Resource from aws_cdk.aws_iam import Effect, PolicyStatement from cdk_nag import NagSuppressions +from common_constructs.stack import Stack from common_constructs.cc_api import CCApi from common_constructs.python_function import PythonFunction -from common_constructs.stack import Stack from stacks import persistent_stack as ps from .api_model import ApiModel diff --git a/backend/compact-connect/stacks/api_stack/v1_api/post_licenses.py b/backend/compact-connect/stacks/api_stack/v1_api/post_licenses.py index e40a9c80a..a73656b11 100644 --- a/backend/compact-connect/stacks/api_stack/v1_api/post_licenses.py +++ b/backend/compact-connect/stacks/api_stack/v1_api/post_licenses.py @@ -7,10 +7,10 @@ from aws_cdk.aws_dynamodb import ITable from aws_cdk.aws_iam import IRole from aws_cdk.aws_sqs import IQueue +from common_constructs.stack import Stack from common_constructs.cc_api import CCApi from common_constructs.python_function import PythonFunction -from common_constructs.stack import Stack from stacks import persistent_stack as ps from .api_model import ApiModel diff --git a/backend/compact-connect/stacks/api_stack/v1_api/provider_management.py b/backend/compact-connect/stacks/api_stack/v1_api/provider_management.py index 8d6f31c76..f119a4447 100644 --- a/backend/compact-connect/stacks/api_stack/v1_api/provider_management.py +++ b/backend/compact-connect/stacks/api_stack/v1_api/provider_management.py @@ -17,11 +17,11 @@ from aws_cdk.aws_iam import Policy, PolicyStatement from aws_cdk.aws_kms import IKey from cdk_nag import NagSuppressions +from common_constructs.stack import Stack from common_constructs.cc_api import CCApi from common_constructs.nodejs_function import NodejsFunction from common_constructs.python_function import PythonFunction -from common_constructs.stack import Stack from stacks import persistent_stack as ps from stacks.persistent_stack import ProviderTable, ProviderUsersBucket, RateLimitingTable, SSNTable, StaffUsers diff --git a/backend/compact-connect/stacks/api_stack/v1_api/provider_users.py b/backend/compact-connect/stacks/api_stack/v1_api/provider_users.py index 289d8ae46..57e5df608 100644 --- a/backend/compact-connect/stacks/api_stack/v1_api/provider_users.py +++ b/backend/compact-connect/stacks/api_stack/v1_api/provider_users.py @@ -3,10 +3,10 @@ from aws_cdk import Duration from aws_cdk.aws_apigateway import LambdaIntegration, MethodResponse, Resource from cdk_nag import NagSuppressions +from common_constructs.stack import Stack from common_constructs.cc_api import CCApi from common_constructs.python_function import PythonFunction -from common_constructs.stack import Stack from stacks.api_lambda_stack import ApiLambdaStack from .api_model import ApiModel diff --git a/backend/compact-connect/stacks/api_stack/v1_api/public_lookup_api.py b/backend/compact-connect/stacks/api_stack/v1_api/public_lookup_api.py index 2697859fa..feb74d125 100644 --- a/backend/compact-connect/stacks/api_stack/v1_api/public_lookup_api.py +++ b/backend/compact-connect/stacks/api_stack/v1_api/public_lookup_api.py @@ -5,10 +5,10 @@ from aws_cdk import Duration from aws_cdk.aws_apigateway import LambdaIntegration, MethodResponse, Resource from cdk_nag import NagSuppressions +from common_constructs.stack import Stack from common_constructs.cc_api import CCApi from common_constructs.python_function import PythonFunction -from common_constructs.stack import Stack from stacks import persistent_stack as ps from .api_model import ApiModel diff --git a/backend/compact-connect/stacks/api_stack/v1_api/purchases.py b/backend/compact-connect/stacks/api_stack/v1_api/purchases.py index d5225c261..f0aea11e7 100644 --- a/backend/compact-connect/stacks/api_stack/v1_api/purchases.py +++ b/backend/compact-connect/stacks/api_stack/v1_api/purchases.py @@ -8,10 +8,10 @@ from aws_cdk.aws_iam import Effect, PolicyStatement from aws_cdk.aws_kms import IKey from cdk_nag import NagSuppressions +from common_constructs.stack import Stack from common_constructs.cc_api import CCApi from common_constructs.python_function import PythonFunction -from common_constructs.stack import Stack from stacks.persistent_stack import CompactConfigurationTable, ProviderTable from .api_model import ApiModel diff --git a/backend/compact-connect/stacks/disaster_recovery_stack/__init__.py b/backend/compact-connect/stacks/disaster_recovery_stack/__init__.py index 9cdc369df..901e57bf8 100644 --- a/backend/compact-connect/stacks/disaster_recovery_stack/__init__.py +++ b/backend/compact-connect/stacks/disaster_recovery_stack/__init__.py @@ -2,9 +2,9 @@ from aws_cdk.aws_dynamodb import Table from aws_cdk.aws_iam import PolicyStatement, ServicePrincipal from aws_cdk.aws_kms import Key +from common_constructs.stack import AppStack from constructs import Construct -from common_constructs.stack import AppStack from stacks import persistent_stack as ps from stacks.disaster_recovery_stack.restore_dynamo_db_table_step_function import ( RestoreDynamoDbTableStepFunctionConstruct, diff --git a/backend/compact-connect/stacks/disaster_recovery_stack/restore_dynamo_db_table_step_function.py b/backend/compact-connect/stacks/disaster_recovery_stack/restore_dynamo_db_table_step_function.py index 5609a9696..eb108772a 100644 --- a/backend/compact-connect/stacks/disaster_recovery_stack/restore_dynamo_db_table_step_function.py +++ b/backend/compact-connect/stacks/disaster_recovery_stack/restore_dynamo_db_table_step_function.py @@ -20,9 +20,8 @@ WaitTime, ) from cdk_nag import NagSuppressions -from constructs import Construct - from common_constructs.stack import Stack +from constructs import Construct class RestoreDynamoDbTableStepFunctionConstruct(Construct): diff --git a/backend/compact-connect/stacks/disaster_recovery_stack/sync_table_step_function.py b/backend/compact-connect/stacks/disaster_recovery_stack/sync_table_step_function.py index 8c6e0af77..c10cfd058 100644 --- a/backend/compact-connect/stacks/disaster_recovery_stack/sync_table_step_function.py +++ b/backend/compact-connect/stacks/disaster_recovery_stack/sync_table_step_function.py @@ -19,10 +19,10 @@ ) from aws_cdk.aws_stepfunctions_tasks import LambdaInvoke from cdk_nag import NagSuppressions +from common_constructs.stack import Stack from constructs import Construct from common_constructs.python_function import PythonFunction -from common_constructs.stack import Stack class SyncTableDataStepFunctionConstruct(Construct): diff --git a/backend/compact-connect/stacks/event_listener_stack/__init__.py b/backend/compact-connect/stacks/event_listener_stack/__init__.py index 5af836006..1fb112adb 100644 --- a/backend/compact-connect/stacks/event_listener_stack/__init__.py +++ b/backend/compact-connect/stacks/event_listener_stack/__init__.py @@ -5,12 +5,12 @@ from aws_cdk import Duration from aws_cdk.aws_events import EventBus from cdk_nag import NagSuppressions +from common_constructs.stack import AppStack from constructs import Construct from common_constructs.python_function import PythonFunction from common_constructs.queue_event_listener import QueueEventListener from common_constructs.ssm_parameter_utility import SSMParameterUtility -from common_constructs.stack import AppStack from stacks import persistent_stack as ps diff --git a/backend/compact-connect/stacks/ingest_stack.py b/backend/compact-connect/stacks/ingest_stack.py index e51ea3abd..cb8e0c4ef 100644 --- a/backend/compact-connect/stacks/ingest_stack.py +++ b/backend/compact-connect/stacks/ingest_stack.py @@ -8,12 +8,12 @@ from aws_cdk.aws_events import EventBus, EventPattern, Rule from aws_cdk.aws_events_targets import SqsQueue from cdk_nag import NagSuppressions +from common_constructs.stack import AppStack, Stack from constructs import Construct from common_constructs.python_function import PythonFunction from common_constructs.queued_lambda_processor import QueuedLambdaProcessor from common_constructs.ssm_parameter_utility import SSMParameterUtility -from common_constructs.stack import AppStack, Stack from stacks import persistent_stack as ps diff --git a/backend/compact-connect/stacks/managed_login_stack.py b/backend/compact-connect/stacks/managed_login_stack.py index 8672fc14b..621d55dee 100644 --- a/backend/compact-connect/stacks/managed_login_stack.py +++ b/backend/compact-connect/stacks/managed_login_stack.py @@ -1,9 +1,9 @@ import json from aws_cdk.aws_cognito import CfnManagedLoginBranding +from common_constructs.stack import AppStack from constructs import Construct -from common_constructs.stack import AppStack from stacks.persistent_stack import PersistentStack from stacks.provider_users import ProviderUsersStack diff --git a/backend/compact-connect/stacks/notification_stack.py b/backend/compact-connect/stacks/notification_stack.py index 457bde652..0e61513ae 100644 --- a/backend/compact-connect/stacks/notification_stack.py +++ b/backend/compact-connect/stacks/notification_stack.py @@ -8,13 +8,13 @@ from aws_cdk.aws_events import EventBus, EventPattern, IEventBus, Rule from aws_cdk.aws_events_targets import SqsQueue from cdk_nag import NagSuppressions +from common_constructs.stack import AppStack from constructs import Construct from common_constructs.python_function import PythonFunction from common_constructs.queue_event_listener import QueueEventListener from common_constructs.queued_lambda_processor import QueuedLambdaProcessor from common_constructs.ssm_parameter_utility import SSMParameterUtility -from common_constructs.stack import AppStack from stacks import persistent_stack as ps diff --git a/backend/compact-connect/stacks/persistent_stack/__init__.py b/backend/compact-connect/stacks/persistent_stack/__init__.py index 9e5dc8104..e6563dac2 100644 --- a/backend/compact-connect/stacks/persistent_stack/__init__.py +++ b/backend/compact-connect/stacks/persistent_stack/__init__.py @@ -8,16 +8,16 @@ from aws_cdk.aws_lambda_python_alpha import PythonLayerVersion from aws_cdk.aws_logs import QueryDefinition, QueryString from cdk_nag import NagSuppressions -from constructs import Construct - from common_constructs.access_logs_bucket import AccessLogsBucket from common_constructs.alarm_topic import AlarmTopic from common_constructs.frontend_app_config_utility import PersistentStackFrontendAppConfigUtility +from common_constructs.security_profile import SecurityProfile +from common_constructs.stack import AppStack +from constructs import Construct + from common_constructs.nodejs_function import NodejsFunction from common_constructs.python_function import COMMON_PYTHON_LAMBDA_LAYER_SSM_PARAMETER_NAME -from common_constructs.security_profile import SecurityProfile from common_constructs.ssm_parameter_utility import SSMParameterUtility -from common_constructs.stack import AppStack from stacks.backup_infrastructure_stack import BackupInfrastructureStack from stacks.persistent_stack.bulk_uploads_bucket import BulkUploadsBucket from stacks.persistent_stack.compact_configuration_table import CompactConfigurationTable diff --git a/backend/compact-connect/stacks/persistent_stack/bulk_uploads_bucket.py b/backend/compact-connect/stacks/persistent_stack/bulk_uploads_bucket.py index a1ad7565d..5f39f145e 100644 --- a/backend/compact-connect/stacks/persistent_stack/bulk_uploads_bucket.py +++ b/backend/compact-connect/stacks/persistent_stack/bulk_uploads_bucket.py @@ -13,13 +13,13 @@ from aws_cdk.aws_s3_notifications import LambdaDestination from aws_cdk.aws_sqs import IQueue from cdk_nag import NagSuppressions +from common_constructs.access_logs_bucket import AccessLogsBucket +from common_constructs.bucket import Bucket +from common_constructs.stack import Stack from constructs import Construct import stacks.persistent_stack as ps -from common_constructs.access_logs_bucket import AccessLogsBucket -from common_constructs.bucket import Bucket from common_constructs.python_function import PythonFunction -from common_constructs.stack import Stack class BulkUploadsBucket(Bucket): diff --git a/backend/compact-connect/stacks/persistent_stack/compact_configuration_upload.py b/backend/compact-connect/stacks/persistent_stack/compact_configuration_upload.py index 1df685d80..8816e5ca6 100644 --- a/backend/compact-connect/stacks/persistent_stack/compact_configuration_upload.py +++ b/backend/compact-connect/stacks/persistent_stack/compact_configuration_upload.py @@ -7,10 +7,10 @@ from aws_cdk.aws_logs import RetentionDays from aws_cdk.custom_resources import Provider from cdk_nag import NagSuppressions +from common_constructs.stack import Stack from constructs import Construct from common_constructs.python_function import PythonFunction -from common_constructs.stack import Stack from .compact_configuration_table import CompactConfigurationTable diff --git a/backend/compact-connect/stacks/persistent_stack/provider_users_bucket.py b/backend/compact-connect/stacks/persistent_stack/provider_users_bucket.py index 8f52df3e4..415b35856 100644 --- a/backend/compact-connect/stacks/persistent_stack/provider_users_bucket.py +++ b/backend/compact-connect/stacks/persistent_stack/provider_users_bucket.py @@ -12,12 +12,12 @@ from aws_cdk.aws_s3 import BucketEncryption, CorsRule, EventType, HttpMethods from aws_cdk.aws_s3_notifications import LambdaDestination from cdk_nag import NagSuppressions +from common_constructs.access_logs_bucket import AccessLogsBucket +from common_constructs.bucket import Bucket from constructs import Construct import stacks.persistent_stack as ps -from common_constructs.access_logs_bucket import AccessLogsBucket from common_constructs.backup_plan import CCBackupPlan -from common_constructs.bucket import Bucket from common_constructs.python_function import PythonFunction from stacks.backup_infrastructure_stack import BackupInfrastructureStack diff --git a/backend/compact-connect/stacks/persistent_stack/ssn_table.py b/backend/compact-connect/stacks/persistent_stack/ssn_table.py index b90d64b31..6e2e2bceb 100644 --- a/backend/compact-connect/stacks/persistent_stack/ssn_table.py +++ b/backend/compact-connect/stacks/persistent_stack/ssn_table.py @@ -16,12 +16,12 @@ from aws_cdk.aws_kms import Key from aws_cdk.aws_sns import ITopic from cdk_nag import NagSuppressions +from common_constructs.stack import Stack from constructs import Construct from common_constructs.backup_plan import CCBackupPlan from common_constructs.python_function import PythonFunction from common_constructs.queued_lambda_processor import QueuedLambdaProcessor -from common_constructs.stack import Stack from stacks.backup_infrastructure_stack import BackupInfrastructureStack diff --git a/backend/compact-connect/stacks/persistent_stack/transaction_reports_bucket.py b/backend/compact-connect/stacks/persistent_stack/transaction_reports_bucket.py index aa1c857a0..51d025b88 100644 --- a/backend/compact-connect/stacks/persistent_stack/transaction_reports_bucket.py +++ b/backend/compact-connect/stacks/persistent_stack/transaction_reports_bucket.py @@ -3,10 +3,9 @@ from aws_cdk.aws_kms import IKey from aws_cdk.aws_s3 import BucketEncryption from cdk_nag import NagSuppressions -from constructs import Construct - from common_constructs.access_logs_bucket import AccessLogsBucket from common_constructs.bucket import Bucket +from constructs import Construct class TransactionReportsBucket(Bucket): diff --git a/backend/compact-connect/stacks/persistent_stack/user_email_notifications.py b/backend/compact-connect/stacks/persistent_stack/user_email_notifications.py index 846ea56b6..02a3066c3 100644 --- a/backend/compact-connect/stacks/persistent_stack/user_email_notifications.py +++ b/backend/compact-connect/stacks/persistent_stack/user_email_notifications.py @@ -9,10 +9,10 @@ from aws_cdk.aws_sns import Subscription, SubscriptionProtocol, Topic from aws_cdk.custom_resources import Provider from cdk_nag import NagSuppressions +from common_constructs.stack import Stack from constructs import Construct from common_constructs.python_function import PythonFunction -from common_constructs.stack import Stack class UserEmailNotifications(Construct): diff --git a/backend/compact-connect/stacks/provider_users/__init__.py b/backend/compact-connect/stacks/provider_users/__init__.py index f6c661c22..180993679 100644 --- a/backend/compact-connect/stacks/provider_users/__init__.py +++ b/backend/compact-connect/stacks/provider_users/__init__.py @@ -1,10 +1,10 @@ from aws_cdk import RemovalPolicy from aws_cdk.aws_cognito import SignInAliases, UserPoolEmail from aws_cdk.aws_logs import QueryDefinition, QueryString -from constructs import Construct - from common_constructs.security_profile import SecurityProfile from common_constructs.stack import AppStack +from constructs import Construct + from stacks.persistent_stack import PersistentStack from stacks.provider_users.provider_users import ProviderUsers diff --git a/backend/compact-connect/stacks/reporting_stack.py b/backend/compact-connect/stacks/reporting_stack.py index c4a88fbe9..7e81ee32b 100644 --- a/backend/compact-connect/stacks/reporting_stack.py +++ b/backend/compact-connect/stacks/reporting_stack.py @@ -10,11 +10,11 @@ from aws_cdk.aws_events_targets import LambdaFunction from aws_cdk.aws_logs import QueryDefinition, QueryString from cdk_nag import NagSuppressions +from common_constructs.stack import AppStack from constructs import Construct from common_constructs.nodejs_function import NodejsFunction from common_constructs.python_function import PythonFunction -from common_constructs.stack import AppStack from stacks import persistent_stack as ps diff --git a/backend/compact-connect/stacks/state_api_stack/__init__.py b/backend/compact-connect/stacks/state_api_stack/__init__.py index 12192c225..35a2a2c18 100644 --- a/backend/compact-connect/stacks/state_api_stack/__init__.py +++ b/backend/compact-connect/stacks/state_api_stack/__init__.py @@ -1,9 +1,9 @@ from __future__ import annotations -from constructs import Construct - from common_constructs.security_profile import SecurityProfile from common_constructs.stack import AppStack +from constructs import Construct + from stacks import persistent_stack as ps from stacks.state_auth import StateAuthStack diff --git a/backend/compact-connect/stacks/state_api_stack/v1_api/api_model.py b/backend/compact-connect/stacks/state_api_stack/v1_api/api_model.py index b3153e8b1..d0ca9992f 100644 --- a/backend/compact-connect/stacks/state_api_stack/v1_api/api_model.py +++ b/backend/compact-connect/stacks/state_api_stack/v1_api/api_model.py @@ -4,10 +4,10 @@ from __future__ import annotations from aws_cdk.aws_apigateway import JsonSchema, JsonSchemaType, Model +from common_constructs.stack import AppStack # Importing module level to allow lazy loading for typing from common_constructs import cc_api -from common_constructs.stack import AppStack class ApiModel: diff --git a/backend/compact-connect/stacks/state_api_stack/v1_api/bulk_upload_url.py b/backend/compact-connect/stacks/state_api_stack/v1_api/bulk_upload_url.py index 47c11171a..8499d2c8a 100644 --- a/backend/compact-connect/stacks/state_api_stack/v1_api/bulk_upload_url.py +++ b/backend/compact-connect/stacks/state_api_stack/v1_api/bulk_upload_url.py @@ -5,10 +5,10 @@ from aws_cdk import Duration from aws_cdk.aws_apigateway import AuthorizationType, LambdaIntegration, MethodOptions, MethodResponse, Resource from aws_cdk.aws_iam import IRole +from common_constructs.stack import Stack from common_constructs.cc_api import CCApi from common_constructs.python_function import PythonFunction -from common_constructs.stack import Stack from stacks import persistent_stack as ps from .api_model import ApiModel diff --git a/backend/compact-connect/stacks/state_api_stack/v1_api/post_licenses.py b/backend/compact-connect/stacks/state_api_stack/v1_api/post_licenses.py index 4940c4c43..8e9bf0840 100644 --- a/backend/compact-connect/stacks/state_api_stack/v1_api/post_licenses.py +++ b/backend/compact-connect/stacks/state_api_stack/v1_api/post_licenses.py @@ -5,10 +5,10 @@ from aws_cdk import Duration from aws_cdk.aws_apigateway import LambdaIntegration, MethodOptions, MethodResponse, Resource from aws_cdk.aws_iam import IRole +from common_constructs.stack import Stack from common_constructs.cc_api import CCApi from common_constructs.python_function import PythonFunction -from common_constructs.stack import Stack from stacks import persistent_stack as ps from .api_model import ApiModel diff --git a/backend/compact-connect/stacks/state_api_stack/v1_api/provider_management.py b/backend/compact-connect/stacks/state_api_stack/v1_api/provider_management.py index 15d79b091..e40456f45 100644 --- a/backend/compact-connect/stacks/state_api_stack/v1_api/provider_management.py +++ b/backend/compact-connect/stacks/state_api_stack/v1_api/provider_management.py @@ -7,10 +7,10 @@ from aws_cdk.aws_dynamodb import ITable from aws_cdk.aws_kms import IKey from cdk_nag import NagSuppressions +from common_constructs.stack import Stack from common_constructs.cc_api import CCApi from common_constructs.python_function import PythonFunction -from common_constructs.stack import Stack from stacks import persistent_stack as ps from stacks.persistent_stack import ProviderTable diff --git a/backend/compact-connect/stacks/state_auth/__init__.py b/backend/compact-connect/stacks/state_auth/__init__.py index c7b64bf40..65e8a5235 100644 --- a/backend/compact-connect/stacks/state_auth/__init__.py +++ b/backend/compact-connect/stacks/state_auth/__init__.py @@ -1,7 +1,7 @@ from aws_cdk import RemovalPolicy +from common_constructs.stack import AppStack from constructs import Construct -from common_constructs.stack import AppStack from stacks.persistent_stack import PersistentStack from stacks.state_auth.state_auth_users import StateAuthUsers diff --git a/backend/compact-connect/stacks/transaction_monitoring_stack/__init__.py b/backend/compact-connect/stacks/transaction_monitoring_stack/__init__.py index 018056e71..7acd12028 100644 --- a/backend/compact-connect/stacks/transaction_monitoring_stack/__init__.py +++ b/backend/compact-connect/stacks/transaction_monitoring_stack/__init__.py @@ -2,9 +2,9 @@ import json +from common_constructs.stack import AppStack from constructs import Construct -from common_constructs.stack import AppStack from stacks import persistent_stack as ps from .transaction_history_processing_workflow import TransactionHistoryProcessingWorkflow diff --git a/backend/compact-connect/stacks/transaction_monitoring_stack/transaction_history_processing_workflow.py b/backend/compact-connect/stacks/transaction_monitoring_stack/transaction_history_processing_workflow.py index 695355539..117f15071 100644 --- a/backend/compact-connect/stacks/transaction_monitoring_stack/transaction_history_processing_workflow.py +++ b/backend/compact-connect/stacks/transaction_monitoring_stack/transaction_history_processing_workflow.py @@ -25,10 +25,10 @@ ) from aws_cdk.aws_stepfunctions_tasks import LambdaInvoke from cdk_nag import NagSuppressions +from common_constructs.stack import Stack from constructs import Construct from common_constructs.python_function import PythonFunction -from common_constructs.stack import Stack from stacks import persistent_stack as ps diff --git a/backend/compact-connect/tests/app/base.py b/backend/compact-connect/tests/app/base.py index 8d59e2e5b..bcd7467fe 100644 --- a/backend/compact-connect/tests/app/base.py +++ b/backend/compact-connect/tests/app/base.py @@ -13,10 +13,10 @@ from aws_cdk.aws_kms import CfnKey from aws_cdk.aws_lambda import CfnEventSourceMapping from aws_cdk.aws_sqs import CfnQueue +from common_constructs.stack import Stack from app import CompactConnectApp from common_constructs.backup_plan import CCBackupPlan -from common_constructs.stack import Stack from pipeline import BackendStage from stacks.api_stack import ApiStack from stacks.persistent_stack import PersistentStack diff --git a/backend/compact-connect/tests/app/test_pipeline.py b/backend/compact-connect/tests/app/test_pipeline.py index 329c47e4a..5685bc391 100644 --- a/backend/compact-connect/tests/app/test_pipeline.py +++ b/backend/compact-connect/tests/app/test_pipeline.py @@ -363,26 +363,6 @@ def test_predictable_pipeline_role_names_created(self): f'Should have exactly one backend pipeline role with name {expected_role_name}', ) - # Test frontend pipeline role naming - roles are environment-specific - frontend_test_cases = [ - (self.app.test_frontend_pipeline_stack, 'CompactConnect-test-Frontend-CrossAccountRole'), - (self.app.beta_frontend_pipeline_stack, 'CompactConnect-beta-Frontend-CrossAccountRole'), - (self.app.prod_frontend_pipeline_stack, 'CompactConnect-prod-Frontend-CrossAccountRole'), - ] - - for frontend_stack, expected_role_name in frontend_test_cases: - with self.subTest(role=expected_role_name): - frontend_template = Template.from_stack(frontend_stack) - - # Look for the predictable frontend pipeline role - frontend_roles = frontend_template.find_resources( - 'AWS::IAM::Role', props={'Properties': {'RoleName': expected_role_name}} - ) - self.assertEqual( - len(frontend_roles), - 1, - f'Should have exactly one frontend pipeline role with name {expected_role_name}', - ) def test_pipeline_role_trust_policies(self): """Test that pipeline roles have correct trust policies for CodePipeline service.""" @@ -392,9 +372,6 @@ def test_pipeline_role_trust_policies(self): (self.app.test_backend_pipeline_stack, 'CompactConnect-test-Backend-CrossAccountRole'), (self.app.beta_backend_pipeline_stack, 'CompactConnect-beta-Backend-CrossAccountRole'), (self.app.prod_backend_pipeline_stack, 'CompactConnect-prod-Backend-CrossAccountRole'), - (self.app.test_frontend_pipeline_stack, 'CompactConnect-test-Frontend-CrossAccountRole'), - (self.app.beta_frontend_pipeline_stack, 'CompactConnect-beta-Frontend-CrossAccountRole'), - (self.app.prod_frontend_pipeline_stack, 'CompactConnect-prod-Frontend-CrossAccountRole'), ] for stack, expected_role_name in test_cases: diff --git a/backend/compact-connect/tests/common_constructs/test_cognito_user_backup.py b/backend/compact-connect/tests/common_constructs/test_cognito_user_backup.py index d2935d494..7beb210f0 100644 --- a/backend/compact-connect/tests/common_constructs/test_cognito_user_backup.py +++ b/backend/compact-connect/tests/common_constructs/test_cognito_user_backup.py @@ -18,8 +18,8 @@ from aws_cdk.aws_lambda import CfnFunction from aws_cdk.aws_s3 import CfnBucket from aws_cdk.aws_sns import Topic - from common_constructs.access_logs_bucket import AccessLogsBucket + from common_constructs.cognito_user_backup import CognitoUserBackup from stacks.backup_infrastructure_stack import BackupInfrastructureStack From 20435f5d50280f78ce2ff3ba5097f7b78639df58 Mon Sep 17 00:00:00 2001 From: Justin Frahm Date: Wed, 24 Sep 2025 14:10:41 -0600 Subject: [PATCH 06/32] Update pipelines to v2 --- backend/compact-connect-ui-app/pipeline/frontend_pipeline.py | 2 ++ backend/compact-connect/pipeline/backend_pipeline.py | 2 ++ 2 files changed, 4 insertions(+) diff --git a/backend/compact-connect-ui-app/pipeline/frontend_pipeline.py b/backend/compact-connect-ui-app/pipeline/frontend_pipeline.py index a991c21f4..1af11d671 100644 --- a/backend/compact-connect-ui-app/pipeline/frontend_pipeline.py +++ b/backend/compact-connect-ui-app/pipeline/frontend_pipeline.py @@ -5,6 +5,7 @@ import common_constructs.base_pipeline_stack from aws_cdk import RemovalPolicy, Stack from aws_cdk.aws_codebuild import BuildSpec +from aws_cdk.aws_codepipeline import PipelineType from aws_cdk.aws_codestarnotifications import NotificationRule from aws_cdk.aws_iam import ServicePrincipal from aws_cdk.aws_kms import IKey @@ -80,6 +81,7 @@ def __init__( scope, construct_id, pipeline_name=pipeline_name, + pipeline_type=PipelineType.V2, artifact_bucket=artifact_bucket, role=pipeline_role, use_pipeline_role_for_actions=True, diff --git a/backend/compact-connect/pipeline/backend_pipeline.py b/backend/compact-connect/pipeline/backend_pipeline.py index 781a787db..62b529588 100644 --- a/backend/compact-connect/pipeline/backend_pipeline.py +++ b/backend/compact-connect/pipeline/backend_pipeline.py @@ -5,6 +5,7 @@ import common_constructs.base_pipeline_stack from aws_cdk import RemovalPolicy, Stack from aws_cdk.aws_codebuild import BuildSpec +from aws_cdk.aws_codepipeline import PipelineType from aws_cdk.aws_codestarnotifications import NotificationRule from aws_cdk.aws_iam import ServicePrincipal from aws_cdk.aws_kms import IKey @@ -77,6 +78,7 @@ def __init__( scope, construct_id, pipeline_name=pipeline_name, + pipeline_type=PipelineType.V2, artifact_bucket=artifact_bucket, role=pipeline_role, use_pipeline_role_for_actions=True, From 600ac6392a2aa35e6494860266d9a11c083ae901 Mon Sep 17 00:00:00 2001 From: Justin Frahm Date: Sat, 27 Sep 2025 13:34:53 -0600 Subject: [PATCH 07/32] Remove unsupported commit hook example --- backend/bin/pre-commit | 123 ----------------------------------------- 1 file changed, 123 deletions(-) delete mode 100755 backend/bin/pre-commit diff --git a/backend/bin/pre-commit b/backend/bin/pre-commit deleted file mode 100755 index e0a6859f5..000000000 --- a/backend/bin/pre-commit +++ /dev/null @@ -1,123 +0,0 @@ -#!/usr/bin/env bash -# -# An example hook script to verify what is about to be committed. -# Called by "git commit" with no arguments. The hook should -# exit with non-zero status after issuing an appropriate message if -# it wants to stop the commit. -# -# To enable this hook, move this file to ".git/hooks/pre-commit". - -# TODO: Investigate a reasonable pre-commit testing approach with multi-service - -if git rev-parse --verify HEAD >/dev/null 2>&1 -then - against=HEAD -else - # Initial commit: diff against an empty tree object - against=$(git hash-object -t tree /dev/null) -fi - -# If you want to allow non-ASCII filenames set this variable to true. -allownonascii=$(git config --type=bool hooks.allownonascii) - -# Redirect output to stderr. -exec 1>&2 - - -# Cross platform projects tend to avoid non-ASCII filenames; prevent -# them from being added to the repository. We exploit the fact that the -# printable range starts at the space character and ends with tilde. -if [ "$allownonascii" != "true" ] && - # Note that the use of brackets around a tr range is ok here, (it's - # even required, for portability to Solaris 10's /usr/bin/tr), since - # the square bracket bytes happen to fall in the designated range. - test $(git diff --cached --name-only --diff-filter=A -z $against | - LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0 -then - cat <<\EOF -Error: Attempt to add a non-ASCII file name. - -This can cause problems if you want to work with people on other platforms. - -To be portable it is advisable to rename the file. - -If you know what you are doing you can disable this check using: - - git config hooks.allownonascii true -EOF - exit 1 -fi - -# If there are whitespace errors, print the offending file names and fail. -git diff-index --check --cached $against -- || exit 1 - -# Check individual files (not deleted) in the index -files=$(git diff-index --cached --name-only --diff-filter=d "$against") -result=0 # track exit code -if [ -n "$files" ]; then - for f in $files; do - # Only report known text files... - if [ -z "$(git diff-index --cached --stat=1 "$against" "$f" | grep -is '| Bin')" ]; then - # Only match regular files (e.g. no symlinks) - # See: https://stackoverflow.com/a/8347325 - if [ "$(git ls-files --stage "$f" | awk '{print $1}' | head -c2)" = "10" ]; then - # Checking if the last line is just a newline - # Using staged version of file instead of working dir - if [ -n "$(git cat-file blob "$(git ls-files --stage "$f" | awk '{print $2}')" | tail -c1)" ]; then - # Report error - if [ "$result" -lt "1" ]; then - echo "Error: The following files have no trailing newline:" 1>&2 - fi - echo -en "\t" 1>&2 - echo "$f" 1>&2 - result=1 - fi - fi - fi - done -fi -[ "$result" -lt "1" ] || exit "$result" - - -# Backend checks -dir=$(pwd) -cd backend - -# Lint all python files -files=$(git diff-index \ - --cached \ - --name-only \ - $against \ - --relative \ - --no-renames \ - --diff-filter=dr \ - -- '*.py') -if [ -n "$files" ]; then - ruff check $files || exit "$?" -fi - - -# Check dependencies for known vulnerabilities -pip-audit || exit "$?" - -# Lint all typescript files - -files=$(git diff-index \ - --cached \ - --name-only \ - $against \ - --relative \ - --no-renames \ - --diff-filter=dr \ - -- '*.ts') -if [ -n "$files" ]; then - ( - cd compact-connect/lambdas/nodejs/ - yarn lint:ingest || exit "$?" - ) || exit "$?" -fi - -# Run the back-end tests -bin/run_tests.sh -no || exit "$?" - -cd "$dir" From 179420311a6d5af99bdc840b101e2057e2c7c6e2 Mon Sep 17 00:00:00 2001 From: Justin Frahm Date: Sat, 27 Sep 2025 15:11:01 -0600 Subject: [PATCH 08/32] Use pipeline role in synth steps --- backend/compact-connect-ui-app/pipeline/frontend_pipeline.py | 5 +++-- backend/compact-connect/pipeline/backend_pipeline.py | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/backend/compact-connect-ui-app/pipeline/frontend_pipeline.py b/backend/compact-connect-ui-app/pipeline/frontend_pipeline.py index 1af11d671..c060dabfb 100644 --- a/backend/compact-connect-ui-app/pipeline/frontend_pipeline.py +++ b/backend/compact-connect-ui-app/pipeline/frontend_pipeline.py @@ -12,7 +12,7 @@ from aws_cdk.aws_s3 import BucketEncryption, IBucket from aws_cdk.aws_sns import ITopic from aws_cdk.aws_ssm import IParameter -from aws_cdk.pipelines import CodeBuildOptions, CodePipelineSource, ShellStep +from aws_cdk.pipelines import CodeBuildOptions, CodeBuildStep, CodePipelineSource from aws_cdk.pipelines import CodePipeline as CdkCodePipeline from cdk_nag import NagSuppressions from common_constructs.bucket import Bucket @@ -85,7 +85,7 @@ def __init__( artifact_bucket=artifact_bucket, role=pipeline_role, use_pipeline_role_for_actions=True, - synth=ShellStep( + synth=CodeBuildStep( 'Synth', input=CodePipelineSource.connection( repo_string=github_repo_string, @@ -111,6 +111,7 @@ def __init__( # Only synthesize the specific pipeline stack needed f'cdk synth --context pipelineStack={pipeline_stack_name} --context action=pipelineSynth', ], + role=pipeline_role, ), synth_code_build_defaults=CodeBuildOptions( partial_build_spec=BuildSpec.from_object( diff --git a/backend/compact-connect/pipeline/backend_pipeline.py b/backend/compact-connect/pipeline/backend_pipeline.py index 62b529588..d1ae15925 100644 --- a/backend/compact-connect/pipeline/backend_pipeline.py +++ b/backend/compact-connect/pipeline/backend_pipeline.py @@ -12,7 +12,7 @@ from aws_cdk.aws_s3 import BucketEncryption, IBucket from aws_cdk.aws_sns import ITopic from aws_cdk.aws_ssm import IParameter -from aws_cdk.pipelines import CodeBuildOptions, CodePipelineSource, ShellStep +from aws_cdk.pipelines import CodeBuildOptions, CodeBuildStep, CodePipelineSource from aws_cdk.pipelines import CodePipeline as CdkCodePipeline from cdk_nag import NagSuppressions from common_constructs.bucket import Bucket @@ -82,7 +82,7 @@ def __init__( artifact_bucket=artifact_bucket, role=pipeline_role, use_pipeline_role_for_actions=True, - synth=ShellStep( + synth=CodeBuildStep( 'Synth', input=CodePipelineSource.connection( repo_string=github_repo_string, @@ -105,6 +105,7 @@ def __init__( # Only synthesize the specific pipeline stack needed f'cdk synth --context pipelineStack={pipeline_stack_name} --context action=pipelineSynth', ], + role=pipeline_role, ), synth_code_build_defaults=CodeBuildOptions( partial_build_spec=BuildSpec.from_object( From 31d208cb16ca302bd9141882ee566aa8976a3cdc Mon Sep 17 00:00:00 2001 From: Justin Frahm Date: Mon, 29 Sep 2025 22:45:03 -0600 Subject: [PATCH 09/32] Use cross-account role for CodeBuild steps --- .../common_constructs/base_pipeline_stack.py | 7 +- .../pipeline/frontend_pipeline.py | 109 ++++++++++++++++- .../tests/app/test_pipeline.py | 5 +- .../pipeline/backend_pipeline.py | 110 +++++++++++++++++- .../tests/app/test_pipeline.py | 5 +- 5 files changed, 226 insertions(+), 10 deletions(-) diff --git a/backend/common-cdk/common_constructs/base_pipeline_stack.py b/backend/common-cdk/common_constructs/base_pipeline_stack.py index 3ae09d1dc..116e54520 100644 --- a/backend/common-cdk/common_constructs/base_pipeline_stack.py +++ b/backend/common-cdk/common_constructs/base_pipeline_stack.py @@ -1,7 +1,7 @@ import json from aws_cdk import Environment, RemovalPolicy -from aws_cdk.aws_iam import Effect, PolicyStatement, Role, ServicePrincipal +from aws_cdk.aws_iam import CompositePrincipal, Effect, PolicyStatement, Role, ServicePrincipal from aws_cdk.aws_s3 import IBucket from aws_cdk.aws_ssm import StringParameter from aws_cdk.pipelines import CodePipeline as CdkCodePipeline @@ -85,7 +85,10 @@ def create_predictable_pipeline_role(self, pipeline_type: str) -> Role: self, f'{pipeline_type}CrossAccountRole', role_name=cross_account_role_name, - assumed_by=ServicePrincipal('codepipeline.amazonaws.com'), + assumed_by=CompositePrincipal( + ServicePrincipal('codepipeline.amazonaws.com'), + ServicePrincipal('codebuild.amazonaws.com'), + ), description=f'Cross-account role for {self.environment_name} {pipeline_type.lower()}' 'pipeline bootstrap trust policies', ) diff --git a/backend/compact-connect-ui-app/pipeline/frontend_pipeline.py b/backend/compact-connect-ui-app/pipeline/frontend_pipeline.py index c060dabfb..7778a3d2f 100644 --- a/backend/compact-connect-ui-app/pipeline/frontend_pipeline.py +++ b/backend/compact-connect-ui-app/pipeline/frontend_pipeline.py @@ -3,11 +3,11 @@ import os import common_constructs.base_pipeline_stack -from aws_cdk import RemovalPolicy, Stack -from aws_cdk.aws_codebuild import BuildSpec +from aws_cdk import ArnFormat, Fn, RemovalPolicy, Stack +from aws_cdk.aws_codebuild import BuildSpec, CfnProject from aws_cdk.aws_codepipeline import PipelineType from aws_cdk.aws_codestarnotifications import NotificationRule -from aws_cdk.aws_iam import ServicePrincipal +from aws_cdk.aws_iam import Effect, PolicyStatement, Role, ServicePrincipal from aws_cdk.aws_kms import IKey from aws_cdk.aws_s3 import BucketEncryption, IBucket from aws_cdk.aws_sns import ITopic @@ -174,6 +174,7 @@ def build_pipeline(self) -> None: ) self._add_alarms() + self._add_codebuild_pipeline_role_override() def _add_alarms(self): NotificationRule( @@ -192,3 +193,105 @@ def _add_alarms(self): # Grant CodeStar permission to use the key that encrypts the alarm topic code_star_principal = ServicePrincipal('codestar-notifications.amazonaws.com') self._encryption_key.grant_encrypt_decrypt(code_star_principal) + + def _add_codebuild_pipeline_role_override(self): + """ + CodePipeline does not support automatically using the pipeline role for the CodeBuild steps it generates. + To allow the Assets step to assume roles into the environment accounts, we need to force it to use the + CodePipeline role for the Assets step. + + This is done by overriding the CodeBuild role with the pipeline role for the Assets step. + """ + assets_node = self.node.try_find_child('Assets') + # The pipeline won't _always_ build an Assets step (like for the substitution stack), so we need to handle + # it not existing + if assets_node is not None: + # Override the role used + stack = Stack.of(self) + pipeline_role: Role = self.pipeline.role + file_asset_node: CfnProject = assets_node.node.try_find_child('FileAsset').node.default_child + file_asset_node.add_property_override('ServiceRole', pipeline_role.role_arn) + + # Add the permissions this role will need for the Assets step + # Note: many of the permissions needed for this step are already granted by virtue of being + # passed into the Synth CodeBuildStep, which automatically configures it with permissions. + # We don't duplicate those here. + pipeline_role.add_to_principal_policy( + PolicyStatement( + effect=Effect.ALLOW, + actions=[ + 'logs:CreateLogGroup', + 'logs:CreateLogStream', + 'logs:PutLogEvents', + ], + resources=[ + stack.format_arn( + partition=stack.partition, + service='logs', + region=stack.region, + account=stack.account, + resource='log-group', + resource_name=Fn.join( + '', + [ + '/aws/codebuild/', + Fn.ref(stack.get_logical_id(file_asset_node)), + ':*' + ] + ) , + arn_format=ArnFormat.COLON_RESOURCE_NAME, + ), + ], + ) + ) + pipeline_role.add_to_principal_policy( + PolicyStatement( + effect=Effect.ALLOW, + actions=[ + 'sts:AssumeRole', + ], + resources=[ + stack.format_arn( + partition=stack.partition, + service='iam', + region='', + account='*', + resource='role', + resource_name=f'cdk-hnb659fds-file-publishing-role-*-{stack.region}', + ), + ], + ) + + ) + pipeline_role.add_to_principal_policy( + PolicyStatement( + effect=Effect.ALLOW, + actions=[ + "codebuild:BatchPutCodeCoverages", + "codebuild:BatchPutTestCases", + "codebuild:CreateReport", + "codebuild:CreateReportGroup", + "codebuild:UpdateReport" + ], + resources=[ + Fn.join( + '', + [ + stack.format_arn( + partition=stack.partition, + service='codebuild', + region=stack.region, + account=stack.account, + resource='report-group', + resource_name='', + ), + Fn.ref(stack.get_logical_id(file_asset_node)), + '-*', + ] + ), + ] + ) + ) + + # Now, remove the unused role and default policy + assets_node.node.try_remove_child('FileRole') diff --git a/backend/compact-connect-ui-app/tests/app/test_pipeline.py b/backend/compact-connect-ui-app/tests/app/test_pipeline.py index 74796477e..824cd317f 100644 --- a/backend/compact-connect-ui-app/tests/app/test_pipeline.py +++ b/backend/compact-connect-ui-app/tests/app/test_pipeline.py @@ -89,7 +89,10 @@ def test_pipeline_role_trust_policies(self): [ { 'Effect': 'Allow', - 'Principal': {'Service': 'codepipeline.amazonaws.com'}, + 'Principal': {'Service': [ + 'codebuild.amazonaws.com', + 'codepipeline.amazonaws.com', + ]}, 'Action': 'sts:AssumeRole', } ] diff --git a/backend/compact-connect/pipeline/backend_pipeline.py b/backend/compact-connect/pipeline/backend_pipeline.py index d1ae15925..f517d5c4f 100644 --- a/backend/compact-connect/pipeline/backend_pipeline.py +++ b/backend/compact-connect/pipeline/backend_pipeline.py @@ -3,11 +3,11 @@ import os import common_constructs.base_pipeline_stack -from aws_cdk import RemovalPolicy, Stack -from aws_cdk.aws_codebuild import BuildSpec +from aws_cdk import ArnFormat, Fn, RemovalPolicy, Stack +from aws_cdk.aws_codebuild import BuildSpec, CfnProject from aws_cdk.aws_codepipeline import PipelineType from aws_cdk.aws_codestarnotifications import NotificationRule -from aws_cdk.aws_iam import ServicePrincipal +from aws_cdk.aws_iam import Effect, PolicyStatement, Role, ServicePrincipal from aws_cdk.aws_kms import IKey from aws_cdk.aws_s3 import BucketEncryption, IBucket from aws_cdk.aws_sns import ITopic @@ -73,6 +73,7 @@ def __init__( # Create predictable pipeline role before initializing the pipeline pipeline_role = scope.create_predictable_pipeline_role('Backend') + artifact_bucket.grant_read(pipeline_role) super().__init__( scope, @@ -168,6 +169,7 @@ def build_pipeline(self) -> None: ) self._add_alarms() + self._add_codebuild_pipeline_role_override() def _add_alarms(self): NotificationRule( @@ -186,3 +188,105 @@ def _add_alarms(self): # Grant CodeStar permission to use the key that encrypts the alarm topic code_star_principal = ServicePrincipal('codestar-notifications.amazonaws.com') self._encryption_key.grant_encrypt_decrypt(code_star_principal) + + def _add_codebuild_pipeline_role_override(self): + """ + CodePipeline does not support automatically using the pipeline role for the CodeBuild steps it generates. + To allow the Assets step to assume roles into the environment accounts, we need to force it to use the + CodePipeline role for the Assets step. + + This is done by overriding the CodeBuild role with the pipeline role for the Assets step. + """ + assets_node = self.node.try_find_child('Assets') + # The pipeline won't _always_ build an Assets step (like for the substitution stack), so we need to handle + # it not existing + if assets_node is not None: + # Override the role used + stack = Stack.of(self) + pipeline_role: Role = self.pipeline.role + file_asset_node: CfnProject = assets_node.node.try_find_child('FileAsset').node.default_child + file_asset_node.add_property_override('ServiceRole', pipeline_role.role_arn) + + # Add the permissions this role will need for the Assets step + # Note: many of the permissions needed for this step are already granted by virtue of being + # passed into the Synth CodeBuildStep, which automatically configures it with permissions. + # We don't duplicate those here. + pipeline_role.add_to_principal_policy( + PolicyStatement( + effect=Effect.ALLOW, + actions=[ + 'logs:CreateLogGroup', + 'logs:CreateLogStream', + 'logs:PutLogEvents', + ], + resources=[ + stack.format_arn( + partition=stack.partition, + service='logs', + region=stack.region, + account=stack.account, + resource='log-group', + resource_name=Fn.join( + '', + [ + '/aws/codebuild/', + Fn.ref(stack.get_logical_id(file_asset_node)), + ':*' + ] + ) , + arn_format=ArnFormat.COLON_RESOURCE_NAME, + ), + ], + ) + ) + pipeline_role.add_to_principal_policy( + PolicyStatement( + effect=Effect.ALLOW, + actions=[ + 'sts:AssumeRole', + ], + resources=[ + stack.format_arn( + partition=stack.partition, + service='iam', + region='', + account='*', + resource='role', + resource_name=f'cdk-hnb659fds-file-publishing-role-*-{stack.region}', + ), + ], + ) + + ) + pipeline_role.add_to_principal_policy( + PolicyStatement( + effect=Effect.ALLOW, + actions=[ + "codebuild:BatchPutCodeCoverages", + "codebuild:BatchPutTestCases", + "codebuild:CreateReport", + "codebuild:CreateReportGroup", + "codebuild:UpdateReport" + ], + resources=[ + Fn.join( + '', + [ + stack.format_arn( + partition=stack.partition, + service='codebuild', + region=stack.region, + account=stack.account, + resource='report-group', + resource_name='', + ), + Fn.ref(stack.get_logical_id(file_asset_node)), + '-*', + ] + ), + ] + ) + ) + + # Now, remove the unused role and default policy + assets_node.node.try_remove_child('FileRole') diff --git a/backend/compact-connect/tests/app/test_pipeline.py b/backend/compact-connect/tests/app/test_pipeline.py index 8fffc1ac6..5a927272d 100644 --- a/backend/compact-connect/tests/app/test_pipeline.py +++ b/backend/compact-connect/tests/app/test_pipeline.py @@ -388,7 +388,10 @@ def test_pipeline_role_trust_policies(self): [ { 'Effect': 'Allow', - 'Principal': {'Service': 'codepipeline.amazonaws.com'}, + 'Principal': {'Service': [ + 'codebuild.amazonaws.com', + 'codepipeline.amazonaws.com', + ]}, 'Action': 'sts:AssumeRole', } ] From b8ad3df0c94f5639b4392ff4daa29f22fbe75e90 Mon Sep 17 00:00:00 2001 From: Justin Frahm Date: Tue, 30 Sep 2025 13:36:37 -0600 Subject: [PATCH 10/32] Remove duplicate app_client files --- .../app_clients/README.md | 272 ------------ .../app_clients/bin/create_app_client.py | 341 --------------- .../app_clients/bin/manage_signature_keys.py | 390 ------------------ .../README.md | 1 - 4 files changed, 1004 deletions(-) delete mode 100644 backend/compact-connect-ui-app/app_clients/README.md delete mode 100755 backend/compact-connect-ui-app/app_clients/bin/create_app_client.py delete mode 100755 backend/compact-connect-ui-app/app_clients/bin/manage_signature_keys.py delete mode 100644 backend/compact-connect-ui-app/app_clients/it_staff_onboarding_instructions/README.md diff --git a/backend/compact-connect-ui-app/app_clients/README.md b/backend/compact-connect-ui-app/app_clients/README.md deleted file mode 100644 index a80c8e51a..000000000 --- a/backend/compact-connect-ui-app/app_clients/README.md +++ /dev/null @@ -1,272 +0,0 @@ -# App Client Management for Staff Users - -## Overview - -This document is a guide for technical staff for managing Cognito app clients for machine-to-machine authentication in -the State API. All app clients must be documented in the external 'Compact Connect App Client Registry' Google Sheet -(If you do not have access to said registry, contact a maintainer of the project and request access). - -## Creating a New App Client - -### 1. Prerequisites - -Before creating a new app client, ensure you have: -- Jurisdiction requirements documented (compact and state) -- Contact information for the consuming team -- Approval to grant the app client with the requested scopes -- AWS credentials configured with permissions to create app clients for the State Auth user pool in the needed AWS - accounts -- Python 3.10+ installed with boto3 dependency (`pip install boto3`) - -### 2. Update Registry - -Add the new app client information to the external Google Sheet registry for tracking and disaster recovery purposes -(ie a deployment error or AWS region outage causes app client data to be lost so it must be recreated). - -#### **Scope Configuration** - -Scopes are the permissions that the app client will have. There are two tiers of scopes: - -##### **Compact-Level Scopes:** - -These are the scopes that are scoped to a specific compact. Granting these scopes will allow the app client to perform -actions across all jurisdictions within that compact. Generally, the only scope that should be granted at the compact -level is the `{compact}/readGeneral` scope if needed. - -The following scopes are available at the compact level: -``` -{compact}/admin -{compact}/readGeneral -{compact}/readSSN -{compact}/write -``` - -##### **Jurisdiction-Level Scopes:** - -These are the scopes that are scoped to a specific jurisdiction/compact combination. Granting these scopes will allow -the app client to perform actions within a specific jurisdiction/compact combination. You should only grant these -scopes if the consuming team has a specific need for a jurisdiction/compact combination. - -The following scopes are available at the jurisdiction level: -```text -{jurisdiction}/{compact}.admin -{jurisdiction}/{compact}.write -{jurisdiction}/{compact}.readPrivate -{jurisdiction}/{compact}.readSSN -``` - -Currently, the most common scope needed by app clients is `{jurisdiction}/{compact}.write`, which allows uploading -license data for a jurisdiction/compact combination. Scopes that expose PII (e.g., `.readSSN`, `.readPrivate`) should -be granted sparingly and will require valid request signatures once a signing public key is configured for the -jurisdiction. - -### 3. Create App Client Using Interactive Python Script - -**Use the provided Python script in the bin directory for streamlined app client creation:** - - -```bash -python3 bin/create_app_client.py -e -u -``` - -**Interactive Process:** -The script will prompt you for: -- App client name (e.g., "example-ky-app-client-v1") -- Compact (aslp, octp, coun) -- State postal abbreviation (e.g., "ky", "la") -- Additional scopes (optional) - -**Automatic Scope Generation:** -The script automatically creates these standard scopes: -- `{compact}/readGeneral` - General read access for the compact -- `{state}/{compact}.write` - Write access for the specific state/compact combination - -### 4. **Send Credentials to Consuming Team** - -**When using the Python script (recommended):** -The script will output two separate sections: - -**A. Credentials JSON (for one-time link service):** -```json -{ - "clientId": "6g34example89j", - "clientSecret": "1234example567890" -} -``` -**Important:** These credentials should be securely transmitted to the consuming team via an encrypted channel -(i.e., a one-time use link). Copy this JSON and use it with your one-time secret link generator. Once you have sent -the credentials over to the IT staff, ensure you remove all remnants of the credentials from your device. - -**B. Email Template:** -The script will also generate an email template with contextual information (compact name, state, auth URL, license -upload URL) that you can copy/paste into your email client. This template includes a placeholder for the one-time -link that you'll generate separately. - - -#### Email Instructions for consuming team - -As part of the email message sent to the consuming team, be sure to include the onboarding instructions document from -the `it_staff_onboarding_instructions/` directory. - -## Managing API Signing Public Keys - -### Overview - -Signature-based authentication provides an additional layer of security for API access to sensitive licensure data. Each -compact/state combination can have multiple SIGNATURE public keys configured to support key rotation and zero-downtime -deployments. - -### Authorization Requirements - -**⚠️ CRITICAL SECURITY NOTICE:** Due to the sensitivity of the data protected by SIGNATURE authentication (including -partial Social Security Numbers, personal addresses, and professional license details), configuration of new SIGNATURE -public keys in production environments **MUST** include explicit authorization from the state board executive director. - - -### Creating SIGNATURE Public Keys - -Once a state configures a public key, they will be able to access the SIGNATURE-required API endpoints. API endpoints with -_optional_ SIGNATURE support will also begin to enforce SIGNATURE signatures for that combination of compact and state. **This -means that, once a compact/state has a public key configured, they will be denied access to SIGNATURE-Optional endpoints, -such as the `POST license` endpoint, unless they have also implemented SIGNATURE signatures there as well.** Be sure that -the representative is advised that they should begin signing those requests _before_ CompactConnect has a configured -public key. - -#### 1. Prerequisites - -Before creating a new SIGNATURE public key, ensure you have: -- **Production Authorization**: Explicit approval from the state board executive director for production environments -- Validated the identity of the individual providing the public key to you -- Jurisdiction and compact information confirmed -- Contact information for the state IT representative -- The public key file (`.pub` format) from the state IT representative -- AWS credentials configured with permissions to write to the compact configuration table -- Python 3.10+ installed with boto3 dependency (`pip install boto3`) - -#### 2. Key ID Naming Convention - -The state IT department should provide an identifier; however, you can recommend a descriptive key ID that includes: -- Environment indicator (if applicable) -- Version or date suffix - -Examples: -- `prod-key-001` -- `beta-key-2024-01` - -#### 3. Create SIGNATURE Public Key Using Interactive Python Script - -**Use the provided Python script in the bin directory for streamlined SIGNATURE key management:** - -```bash -python3 bin/manage_signature_keys.py create -t -``` - -**Interactive Process:** -The script will prompt you for: -- Compact (aslp, octp, coun) -- State postal abbreviation (e.g., "ky", "la") -- Key ID (e.g., "client-org-prod-key-001") - -**File Reading:** -The script will: -- Notify you that it will read the public key from `.pub` -- Validate the PEM format of the public key -- Check for existing keys with the same ID -- Write the key to the compact configuration database - -#### 4. Database Schema - -SIGNATURE keys are stored in the compact configuration table with the following schema: -- **Primary Key (pk)**: `{compact}#SIGNATURE_KEYS#{state}` -- **Sort Key (sk)**: `{compact}#JURISDICTION#{jurisdiction}#{key_id}` -- **Additional Fields**: - - `publicKey`: PEM-encoded public key content - - `compact`: Compact abbreviation - - `jurisdiction`: Jurisdiction abbreviation - - `keyId`: Key identifier - - `createdAt`: Creation timestamp - -### Deleting SIGNATURE Public Keys - -#### 1. Prerequisites - -Before deleting a SIGNATURE public key, ensure you have: -- Confirmation that the key is no longer in use by the state IT department -- Confirmation of the key id to be deleted -- Understanding of the impact on API access for the compact/state combination - -#### 2. Delete SIGNATURE Public Key Using Interactive Python Script - -```bash -python3 bin/manage_signature_keys.py delete -t -``` - -**Interactive Process:** -The script will: -- Prompt for compact and state -- List all existing keys for the compact/state combination -- Allow you to select the specific key ID to delete -- Require typing "DELETE" to confirm the deletion -- Remove the key from the compact configuration database - -### Key Rotation Best Practices - -#### 1. Planning - -- Coordinate with the State IT representative well in advance -- Plan for zero-downtime deployment - -#### 2. Implementation - -- Create new keys before removing old ones -- Allow both keys to be active during the transition period -- Monitor API access and authentication success rates -- Remove old keys only after confirming new keys are working correctly - -#### 3. Documentation - -- Document key rotation dates and reasons -- Maintain audit trail of all key management activities - -### Security Considerations - -#### 1. Key Storage - -- Public keys are stored in DynamoDB with appropriate access controls -- Private keys should never be stored in CompactConnect systems -- State IT departments are responsible for secure private key management - -#### 2. Access Control - -- Only authorized technical staff should have access to key management resources -- All key management activities should be logged and audited -- Production key creation requires executive director approval - -## Rotating App Client Credentials - -Unfortunately, AWS Cognito does not support rotating app client credentials for an existing app client. The only way -to rotate credentials is to create a new app client with a new clientId and clientSecret and then delete the old one. -The following process should be performed if credentials are accidentally exposed or in the event of a security breach -where the old credentials are compromised. - -### 1. Pre-rotation Tasks - -- Contact consuming team to schedule rotation -- Follow "Creating a New App Client" steps above using either the Python script (recommended) or AWS CLI, you will -increment clientName version suffix by 1 (e.g. "example-ky-app-client-v1" -> "example-ky-app-client-v2") -- Follow “Creating a New App Client” using the Python script (recommended) or AWS CLI. Increment the client name’s - version suffix by 1 (e.g., “example-ky-app-client-v1” -> “example-ky-app-client-v2”). -- Update the external Google Sheet registry with new client information - -### 2. Migration - -- Provide new client id and client secret to consuming team -- Consuming team will need to confirm that the new credentials are deployed in their systems, the old app client is -not in use, and their systems are working as expected. - -### 3. Cleanup - -- Delete old app client from Cognito using the following cli command: -``` -aws cognito-idp delete-user-pool-client --user-pool-id '' --client-id '' -``` diff --git a/backend/compact-connect-ui-app/app_clients/bin/create_app_client.py b/backend/compact-connect-ui-app/app_clients/bin/create_app_client.py deleted file mode 100755 index bb99339b2..000000000 --- a/backend/compact-connect-ui-app/app_clients/bin/create_app_client.py +++ /dev/null @@ -1,341 +0,0 @@ -#!/usr/bin/env python3 -# ruff: noqa: T201 we use print statements for scripts run locally - -""" -Script to create AWS Cognito app clients interactively. - -This script prompts users for the necessary information to create app clients -in different environments (test, beta, prod) and automatically generates -the standard scopes based on compact and state inputs. -""" - -import argparse -import json -import re -import sys -from pathlib import Path - -import boto3 -from botocore.exceptions import ClientError, NoCredentialsError - - -def load_cdk_config(): - """Load configuration from cdk.json file.""" - # Find cdk.json file - look in parent directories - current_dir = Path(__file__).parent - cdk_json_path = None - - # Look up the directory tree for cdk.json - for parent in [current_dir] + list(current_dir.parents): - potential_path = parent / 'cdk.json' - if potential_path.exists(): - cdk_json_path = potential_path - break - - if not cdk_json_path: - raise FileNotFoundError('Could not find cdk.json file in current directory or parent directories') - - with open(cdk_json_path) as f: - cdk_config = json.load(f) - - context = cdk_config.get('context', {}) - - return { - 'compacts': context.get('compacts', []), - 'active_compact_member_jurisdictions': context.get('active_compact_member_jurisdictions', {}), - } - - -# Load configuration from cdk.json -CDK_CONFIG = load_cdk_config() -VALID_COMPACTS = CDK_CONFIG['compacts'] -ACTIVE_COMPACT_JURISDICTIONS = CDK_CONFIG['active_compact_member_jurisdictions'] - - -# Valid scope patterns for validation -VALID_SCOPE_PATTERNS = [ - r'^[a-z]+/readGeneral$', - r'^[a-z]+/readSSN$', - r'^[a-z]+/write$', - r'^[a-z]+/admin$', - r'^[a-z]{2}/[a-z]+\.write$', - r'^[a-z]{2}/[a-z]+\.readPrivate$', - r'^[a-z]{2}/[a-z]+\.readSSN$', - r'^[a-z]{2}/[a-z]+\.admin$', -] - - -def validate_compact(compact): - """Validate compact input.""" - compact = compact.lower().strip() - if compact not in VALID_COMPACTS: - raise ValueError(f'Invalid compact: {compact}. Valid compacts are: {", ".join(VALID_COMPACTS)}') - return compact - - -def validate_state(state, compact): - """Validate state postal abbreviation for the given compact.""" - state = state.lower().strip() - - # Get valid states for this compact - valid_states = ACTIVE_COMPACT_JURISDICTIONS.get(compact, []) - - if state not in valid_states: - raise ValueError( - f'Invalid state: {state}. Valid states for {compact.upper()} compact are: {", ".join(sorted(valid_states))}' - ) - return state - - -def validate_scope(scope): - """Validate a single scope against known patterns.""" - scope = scope.strip() - for pattern in VALID_SCOPE_PATTERNS: - if re.match(pattern, scope): - return True - return False - - -def validate_additional_scopes(scopes_input): - """Validate additional scopes input.""" - if not scopes_input.strip(): - return [] - - scopes = [scope.strip() for scope in scopes_input.split(',')] - invalid_scopes = [] - - for scope in scopes: - if not validate_scope(scope): - invalid_scopes.append(scope) - - if invalid_scopes: - print(f'\nInvalid scopes detected: {", ".join(invalid_scopes)}') - print('Valid scope patterns:') - print(' Compact-level: {compact}/readGeneral, {compact}/readSSN, {compact}/write, {compact}/admin') - print( - 'Jurisdiction-level: {state}/{compact}.write, {state}/{compact}.readPrivate, {state}/{compact}.readSSN, ' - '{state}/{compact}.admin' - ) - raise ValueError('Invalid scopes provided') - - return scopes - - -def get_user_input(): - """Get user input for app client configuration.""" - print('=== App Client Configuration ===\n') - - # Get client name - client_name = input("Enter the app client name (e.g., 'example-ky-app-client-v1'): ").strip() - if not client_name: - raise ValueError('Client name is required') - - # Get compact - while True: - try: - print(f'\nValid compacts: {", ".join(VALID_COMPACTS)}') - compact = input('Enter the compact: ').strip() - compact = validate_compact(compact) - break - except ValueError as e: - print(f'Error: {e}') - - # Get state - while True: - try: - valid_states = ACTIVE_COMPACT_JURISDICTIONS.get(compact, []) - print(f'\nValid states for {compact.upper()} compact: {", ".join(sorted(valid_states))}') - state = input("Enter the state postal abbreviation (e.g., 'ky', 'la'): ").strip() - state = validate_state(state, compact) - break - except ValueError as e: - print(f'Error: {e}') - - # Get additional scopes (optional) - print('\nThe following scope will be automatically included:') - print(f' - {state}/{compact}.write') - - additional_scopes = [] - while True: - try: - scopes_input = input('\nEnter any additional scopes (comma-separated, or press Enter for none): ').strip() - additional_scopes = validate_additional_scopes(scopes_input) - break - except ValueError as e: - print(f'Error: {e}') - continue - - # Generate final scope list - scopes = [f'{state}/{compact}.write'] - scopes.extend(additional_scopes) - - # Remove duplicates - deduped_scopes = list(set(scopes)) - - print('\nFinal configuration:') - print(f' Client Name: {client_name}') - print(f' Compact: {compact}') - print(f' State: {state}') - print(f' Scopes: {", ".join(deduped_scopes)}') - - confirm = input('\nProceed with this configuration? (y/N): ').strip().lower() - if confirm != 'y': - print('Configuration cancelled.') - sys.exit(0) - - return {'clientName': client_name, 'compact': compact, 'state': state, 'scopes': deduped_scopes} - - -def create_app_client(user_pool_id, config): - """Create the app client using boto3 Cognito client.""" - client_name = config['clientName'] - scopes = config['scopes'] - - print(f'\nCreating app client: {client_name}') - print(f'With scopes: {", ".join(scopes)}') - - try: - # Create boto3 Cognito IDP client - cognito_client = boto3.client('cognito-idp', region_name='us-east-1') - - # Create the user pool client - return cognito_client.create_user_pool_client( - UserPoolId=user_pool_id, - ClientName=client_name, - PreventUserExistenceErrors='ENABLED', - GenerateSecret=True, - TokenValidityUnits={'AccessToken': 'minutes'}, - AccessTokenValidity=15, - AllowedOAuthFlowsUserPoolClient=True, - AllowedOAuthFlows=['client_credentials'], - AllowedOAuthScopes=scopes, - ) - - except NoCredentialsError: - print('Error: AWS credentials not found. Please configure your AWS credentials.') - print("You can use 'aws configure' or set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment variables.") - sys.exit(1) - except ClientError as e: - error_code = e.response['Error']['Code'] - error_message = e.response['Error']['Message'] - print(f'Error creating app client: {error_code} - {error_message}') - sys.exit(1) - - -def print_credentials(client_id, client_secret): - """Print only the sensitive credentials in JSON format for secure copy/paste.""" - credentials = { - 'clientId': client_id, - 'clientSecret': client_secret, - } - - print('\n' + '=' * 60) - print('APP CLIENT CREDENTIALS (FOR ONE-TIME LINK SERVICE)') - print('=' * 60) - print(json.dumps(credentials, indent=2)) - print('=' * 60) - print('Please copy the JSON above and use it with your one-time secret link generator.') - print('Do not leave these credentials in terminal history or logs.') - print('=' * 60) - - -def print_email_template(environment, compact, state): - """Print an email template with contextual information for the consuming team.""" - # Get environment-specific URLs - auth_urls = { - 'beta': 'https://compact-connect-state-auth-beta.auth.us-east-1.amazoncognito.com/oauth2/token', - 'prod': 'https://compact-connect-state-auth.auth.us-east-1.amazoncognito.com/oauth2/token', - 'test': 'https://compact-connect-state-auth-test.auth.us-east-1.amazoncognito.com/oauth2/token', - } - - api_base_urls = { - 'beta': 'https://state-api.beta.compactconnect.org', - 'prod': 'https://state-api.compactconnect.org', - 'test': 'https://state-api.test.compactconnect.org', - } - - # Compact name mapping - compact_names = { - 'aslp': 'Audiology and Speech Language Pathology', - 'octp': 'Occupational Therapy', - 'coun': 'Counseling', - } - - compact_name = compact_names.get(compact, compact.upper()) - auth_url = auth_urls.get(environment) - license_upload_url = f'{api_base_urls.get(environment)}/v1/compacts/{compact}/jurisdictions/{state}/licenses' - - email_template = f""" -Thank you for integrating with Compact Connect! You have been designated as the IT professional who is able to handle -credentials for secure machine-to-machine authentication between your state and CompactConnect. - -Details for these credentials are: -Compact: {compact_name} -State: {state.upper()} -Auth URL: {auth_url} -License Upload URL: {license_upload_url} - -Follow this link to your API credentials as soon as you are ready to securely store them. They will only be viewable -once: - - -For more information on CompactConnect and how to integrate your state IT system with ours, see the documentation -here: -https://github.com/csg-org/CompactConnect/blob/development/backend/compact-connect/docs/it_staff_onboarding_instructions.md -""" - - print('\n' + '=' * 60) - print('EMAIL TEMPLATE (COPY/PASTE INTO EMAIL CLIENT)') - print('=' * 60) - print(email_template.strip()) - print('=' * 60) - - -def main(): - parser = argparse.ArgumentParser(description='Create AWS Cognito app client interactively') - parser.add_argument( - '-e', '--environment', required=True, choices=['test', 'beta', 'prod'], help='Environment (test, beta, or prod)' - ) - parser.add_argument('-u', '--user-pool-id', required=True, help='AWS Cognito User Pool ID') - - args = parser.parse_args() - - try: - print(f'Creating app client for {args.environment} environment...') - print(f'User Pool ID: {args.user_pool_id}\n') - - # Get configuration from user input - config = get_user_input() - - # Create the app client - response = create_app_client(args.user_pool_id, config) - - # Extract credentials from response - user_pool_client = response.get('UserPoolClient', {}) - client_id = user_pool_client.get('ClientId') - client_secret = user_pool_client.get('ClientSecret') - client_name = user_pool_client.get('ClientName') - - if not client_id or not client_secret: - print('Error: Could not extract client ID or secret from AWS response') - sys.exit(1) - - print('\n✅ App client created successfully!') - print(f'Client Name: {client_name}') - print(f'Client ID: {client_id}') - - # Print credentials for secure copy/paste - print_credentials(client_id, client_secret) - - # Print email template - print_email_template(args.environment, config['compact'], config['state']) - - print('\n📝 Remember to add this app client to your external registry!') - - except Exception as e: # noqa: BLE001 - print(f'Error: {e}') - sys.exit(1) - - -if __name__ == '__main__': - main() diff --git a/backend/compact-connect-ui-app/app_clients/bin/manage_signature_keys.py b/backend/compact-connect-ui-app/app_clients/bin/manage_signature_keys.py deleted file mode 100755 index 50b4696cf..000000000 --- a/backend/compact-connect-ui-app/app_clients/bin/manage_signature_keys.py +++ /dev/null @@ -1,390 +0,0 @@ -#!/usr/bin/env python3 -# ruff: noqa: T201 we use print statements for scripts run locally - -""" -Script to manage SIGNATURE public keys in the compact configuration database. - -This script allows users to create and delete SIGNATURE public keys for different -compact/jurisdiction combinations. It follows the same interactive style as -the create_app_client.py script. -""" - -import argparse -import json -import sys -from datetime import UTC, datetime -from pathlib import Path - -import boto3 -from botocore.exceptions import ClientError, NoCredentialsError - - -def load_cdk_config(): - """Load configuration from cdk.json file.""" - # Find cdk.json file - look in parent directories - current_dir = Path(__file__).parent - cdk_json_path = None - - # Look up the directory tree for cdk.json - for parent in [current_dir] + list(current_dir.parents): - potential_path = parent / 'cdk.json' - if potential_path.exists(): - cdk_json_path = potential_path - break - - if not cdk_json_path: - raise FileNotFoundError('Could not find cdk.json file in current directory or parent directories') - - with open(cdk_json_path) as f: - cdk_config = json.load(f) - - context = cdk_config.get('context', {}) - - return { - 'compacts': context.get('compacts', []), - 'active_compact_member_jurisdictions': context.get('active_compact_member_jurisdictions', {}), - } - - -# Load configuration from cdk.json -CDK_CONFIG = load_cdk_config() -VALID_COMPACTS = CDK_CONFIG['compacts'] -ACTIVE_COMPACT_JURISDICTIONS = CDK_CONFIG['active_compact_member_jurisdictions'] - - -def validate_compact(compact): - """Validate compact input.""" - compact = compact.lower().strip() - if compact not in VALID_COMPACTS: - raise ValueError(f'Invalid compact: {compact}. Valid compacts are: {", ".join(VALID_COMPACTS)}') - return compact - - -def validate_state(state, compact): - """Validate state postal abbreviation for the given compact.""" - state = state.lower().strip() - - # Get valid states for this compact - valid_states = ACTIVE_COMPACT_JURISDICTIONS.get(compact, []) - - if state not in valid_states: - raise ValueError( - f'Invalid state: {state}. Valid states for {compact.upper()} compact are: {", ".join(sorted(valid_states))}' - ) - return state - - -def validate_key_id(key_id): - """Validate key ID input.""" - key_id = key_id.strip() - if not key_id: - raise ValueError('Key ID cannot be empty') - if len(key_id) > 100: # Reasonable limit for key ID - raise ValueError('Key ID is too long (max 100 characters)') - if not key_id.replace('-', '').replace('_', '').isalnum(): - raise ValueError('Key ID can only contain alphanumeric characters, hyphens, and underscores') - return key_id - - -def get_user_input_for_create(): - """Get user input for creating a SIGNATURE public key.""" - print('=== SIGNATURE Public Key Creation ===\n') - - # Get compact - while True: - try: - print(f'Valid compacts: {", ".join(VALID_COMPACTS)}') - compact = input('Enter the compact: ').strip() - compact = validate_compact(compact) - break - except ValueError as e: - print(f'Error: {e}') - - # Get state - while True: - try: - valid_states = ACTIVE_COMPACT_JURISDICTIONS.get(compact, []) - print(f'\nValid states for {compact.upper()} compact: {", ".join(sorted(valid_states))}') - state = input("Enter the state postal abbreviation (e.g., 'ky', 'la'): ").strip() - state = validate_state(state, compact) - break - except ValueError as e: - print(f'Error: {e}') - - # Get key ID - while True: - try: - key_id = input('\nEnter the key ID (e.g., "client-key-001"): ').strip() - key_id = validate_key_id(key_id) - break - except ValueError as e: - print(f'Error: {e}') - - print('\nConfiguration:') - print(f' Compact: {compact}') - print(f' State: {state}') - print(f' Key ID: {key_id}') - print(f' Public key file: {key_id}.pub') - - confirm = input('\nProceed with this configuration? (y/N): ').strip().lower() - if confirm != 'y': - print('Configuration cancelled.') - sys.exit(0) - - return {'compact': compact, 'state': state, 'key_id': key_id} - - -def read_public_key_file(key_id): - """Read the public key from the specified file.""" - file_path = Path(f'{key_id}.pub') - - if not file_path.exists(): - raise FileNotFoundError( - f'Public key file "{file_path}" not found. Please ensure the file exists in the current directory.' - ) - - try: - with open(file_path) as f: - public_key_content = f.read().strip() - - if not public_key_content: - raise ValueError('Public key file is empty') - - # Basic validation that this looks like a PEM public key - if not public_key_content.startswith('-----BEGIN PUBLIC KEY-----'): - raise ValueError( - 'Public key file does not appear to be in PEM format (should start with "-----BEGIN PUBLIC KEY-----")' - ) - - if not public_key_content.endswith('-----END PUBLIC KEY-----'): - raise ValueError( - 'Public key file does not appear to be in PEM format (should end with "-----END PUBLIC KEY-----")' - ) - - return public_key_content - - except (FileNotFoundError, ValueError) as e: - raise ValueError(f'Error reading public key file: {e}') from e - - -def create_signature_key(table_name, config): - """Create the SIGNATURE public key in DynamoDB.""" - compact = config['compact'] - state = config['state'] - key_id = config['key_id'] - - print(f'\nCreating SIGNATURE public key: {key_id}') - print(f'For compact: {compact}, state: {state}') - - try: - # Create boto3 DynamoDB client - dynamodb = boto3.resource('dynamodb') - table = dynamodb.Table(table_name) - - # Check if key already exists - pk = f'{compact}#SIGNATURE_KEYS#{state}' - sk = f'{compact}#JURISDICTION#{state}#{key_id}' - - response = table.get_item(Key={'pk': pk, 'sk': sk}) - - if 'Item' in response: - print(f'\n⚠️ Warning: A key with ID "{key_id}" already exists for {compact}/{state}') - overwrite = input('Do you want to overwrite it? (y/N): ').strip().lower() - if overwrite != 'y': - print('Operation cancelled.') - sys.exit(0) - - # Read the public key file - print(f'\nReading public key from {key_id}.pub...') - public_key_pem = read_public_key_file(key_id) - - # Create the item - item = { - 'pk': pk, - 'sk': sk, - 'publicKey': public_key_pem, - 'compact': compact, - 'jurisdiction': state, - 'keyId': key_id, - 'createdAt': datetime.now(tz=UTC).isoformat(), - } - - # Write to DynamoDB - table.put_item(Item=item) - - print('\n✅ SIGNATURE public key created successfully!') - print(f'Key ID: {key_id}') - print(f'Compact: {compact}') - print(f'State: {state}') - - except NoCredentialsError: - print('Error: AWS credentials not found. Please configure your AWS credentials.') - print("You can use 'aws configure' or set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment variables.") - raise - except ClientError as e: - error_code = e.response['Error']['Code'] - error_message = e.response['Error']['Message'] - print(f'Error creating SIGNATURE key: {error_code} - {error_message}') - raise - - -def get_user_input_for_delete(): - """Get user input for deleting a SIGNATURE public key.""" - print('=== SIGNATURE Public Key Deletion ===\n') - - # Get compact - while True: - try: - print(f'Valid compacts: {", ".join(VALID_COMPACTS)}') - compact = input('Enter the compact: ').strip() - compact = validate_compact(compact) - break - except ValueError as e: - print(f'Error: {e}') - - # Get state - while True: - try: - valid_states = ACTIVE_COMPACT_JURISDICTIONS.get(compact, []) - print(f'\nValid states for {compact.upper()} compact: {", ".join(sorted(valid_states))}') - state = input("Enter the state postal abbreviation (e.g., 'ky', 'la'): ").strip() - state = validate_state(state, compact) - break - except ValueError as e: - print(f'Error: {e}') - - return {'compact': compact, 'state': state} - - -def list_existing_keys(table_name, config): - """List existing SIGNATURE keys for the given compact/state combination.""" - compact = config['compact'] - state = config['state'] - - try: - # Create boto3 DynamoDB client - dynamodb = boto3.resource('dynamodb') - table = dynamodb.Table(table_name) - - # Query for existing keys - pk = f'{compact}#SIGNATURE_KEYS#{state}' - sk_prefix = f'{compact}#JURISDICTION#{state}#' - - response = table.query( - KeyConditionExpression='pk = :pk AND begins_with(sk, :sk_prefix)', - ExpressionAttributeValues={':pk': pk, ':sk_prefix': sk_prefix}, - ) - - items = response.get('Items', []) - - if not items: - print(f'\nNo SIGNATURE keys found for {compact}/{state}') - return [] - - print(f'\nExisting SIGNATURE keys for {compact}/{state}:') - for i, item in enumerate(items, 1): - key_id = item['sk'].split('#')[-1] - created_at = item.get('createdAt', 'Unknown') - print(f' {i}. Key ID: {key_id} (Created: {created_at})') - - return items - - except NoCredentialsError: - print('Error: AWS credentials not found. Please configure your AWS credentials.') - print("You can use 'aws configure' or set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment variables.") - raise - except ClientError as e: - error_code = e.response['Error']['Code'] - error_message = e.response['Error']['Message'] - print(f'Error listing SIGNATURE keys: {error_code} - {error_message}') - raise - - -def delete_signature_key(table_name, config, key_id): - """Delete the specified SIGNATURE public key from DynamoDB.""" - compact = config['compact'] - state = config['state'] - - print(f'\nDeleting SIGNATURE public key: {key_id}') - print(f'For compact: {compact}, state: {state}') - - try: - # Create boto3 DynamoDB client - dynamodb = boto3.resource('dynamodb') - table = dynamodb.Table(table_name) - - # Delete the item - pk = f'{compact}#SIGNATURE_KEYS#{state}' - sk = f'{compact}#JURISDICTION#{state}#{key_id}' - - table.delete_item(Key={'pk': pk, 'sk': sk}) - - print('\n✅ SIGNATURE public key deleted successfully!') - print(f'Key ID: {key_id}') - print(f'Compact: {compact}') - print(f'State: {state}') - - except NoCredentialsError: - print('Error: AWS credentials not found. Please configure your AWS credentials.') - print("You can use 'aws configure' or set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment variables.") - raise - except ClientError as e: - error_code = e.response['Error']['Code'] - error_message = e.response['Error']['Message'] - print(f'Error deleting SIGNATURE key: {error_code} - {error_message}') - raise - - -def main(): - parser = argparse.ArgumentParser(description='Manage SIGNATURE public keys in the compact configuration database') - parser.add_argument('action', choices=['create', 'delete'], help='Action to perform (create or delete)') - parser.add_argument('-t', '--table-name', required=True, help='DynamoDB table name for compact configuration') - - args = parser.parse_args() - - print(f'Managing SIGNATURE keys for {args.table_name} table...\n') - - if args.action == 'create': - # Create flow - config = get_user_input_for_create() - - create_signature_key(args.table_name, config) - - elif args.action == 'delete': - # Delete flow - config = get_user_input_for_delete() - - # List existing keys - existing_keys = list_existing_keys(args.table_name, config) - - if not existing_keys: - print('\nNo keys to delete.') - sys.exit(0) - - # Get key ID to delete - while True: - key_id = input('\nEnter the exact key ID to delete: ').strip() - key_id = validate_key_id(key_id) - - # Check if key exists - key_exists = any(item['sk'].split('#')[-1] == key_id for item in existing_keys) - if not key_exists: - print(f'Error: Key ID "{key_id}" not found in the list above') - continue - - break - - # Final confirmation - print(f'\n⚠️ You are about to delete SIGNATURE key "{key_id}" for {config["compact"]}/{config["state"]}') - print('This action cannot be undone.') - - confirm = input('\nAre you sure you want to delete this key? Type "DELETE" to confirm: ').strip() - if confirm != 'DELETE': - print('Deletion cancelled.') - sys.exit(0) - - delete_signature_key(args.table_name, config, key_id) - - -if __name__ == '__main__': - main() diff --git a/backend/compact-connect-ui-app/app_clients/it_staff_onboarding_instructions/README.md b/backend/compact-connect-ui-app/app_clients/it_staff_onboarding_instructions/README.md deleted file mode 100644 index f28dc34c4..000000000 --- a/backend/compact-connect-ui-app/app_clients/it_staff_onboarding_instructions/README.md +++ /dev/null @@ -1 +0,0 @@ -[Moved Here](../../docs/it_staff_onboarding_instructions.md) From 322c09b83d20b35b07bdb94a236c3fc66c2d2bfd Mon Sep 17 00:00:00 2001 From: Justin Frahm Date: Tue, 30 Sep 2025 13:36:37 -0600 Subject: [PATCH 11/32] Remove duplicate app_client files --- .../common_constructs/base_pipeline_stack.py | 14 +- .../deployment_resources_stack.py | 12 +- backend/compact-connect-ui-app/app.py | 2 + .../app_clients/README.md | 272 ------------ .../app_clients/bin/create_app_client.py | 341 --------------- .../app_clients/bin/manage_signature_keys.py | 390 ------------------ .../README.md | 1 - .../pipeline/frontend_pipeline.py | 3 +- backend/compact-connect/app.py | 2 + .../pipeline/backend_pipeline.py | 3 +- 10 files changed, 28 insertions(+), 1012 deletions(-) delete mode 100644 backend/compact-connect-ui-app/app_clients/README.md delete mode 100755 backend/compact-connect-ui-app/app_clients/bin/create_app_client.py delete mode 100755 backend/compact-connect-ui-app/app_clients/bin/manage_signature_keys.py delete mode 100644 backend/compact-connect-ui-app/app_clients/it_staff_onboarding_instructions/README.md diff --git a/backend/common-cdk/common_constructs/base_pipeline_stack.py b/backend/common-cdk/common_constructs/base_pipeline_stack.py index 116e54520..fc7c5856d 100644 --- a/backend/common-cdk/common_constructs/base_pipeline_stack.py +++ b/backend/common-cdk/common_constructs/base_pipeline_stack.py @@ -1,4 +1,5 @@ import json +from enum import StrEnum from aws_cdk import Environment, RemovalPolicy from aws_cdk.aws_iam import CompositePrincipal, Effect, PolicyStatement, Role, ServicePrincipal @@ -12,8 +13,16 @@ TEST_ENVIRONMENT_NAME = 'test' BETA_ENVIRONMENT_NAME = 'beta' PROD_ENVIRONMENT_NAME = 'prod' +DEPLOY_ENVIRONMENT_NAME = 'deploy' + ALLOWED_ENVIRONMENT_NAMES = [TEST_ENVIRONMENT_NAME, BETA_ENVIRONMENT_NAME, PROD_ENVIRONMENT_NAME] + +class CCPipelineType(StrEnum): + BACKEND = 'Backend' + FRONTEND = 'Frontend' + + class BasePipelineStack(Stack): """Base stack with common functionality for all pipeline stacks (both backend and frontend).""" @@ -72,7 +81,7 @@ def _get_predictable_role_name(self, pipeline_type: str, role_type: str) -> str: return f'CompactConnect-{self.environment_name}-{pipeline_type}-{role_type}Role' - def create_predictable_pipeline_role(self, pipeline_type: str) -> Role: + def create_predictable_pipeline_role(self, pipeline_type: CCPipelineType) -> Role: """Create a predictable cross-account role that will be trusted by bootstrap roles. :param pipeline_type: 'Backend' or 'Frontend' @@ -127,6 +136,3 @@ def _get_frontend_pipeline_arn(self): account=self.env.account, resource=pipeline_name, ) - - -DEPLOY_ENVIRONMENT_NAME = 'deploy' diff --git a/backend/common-cdk/common_constructs/deployment_resources_stack.py b/backend/common-cdk/common_constructs/deployment_resources_stack.py index cb1a95640..cb7f07a0d 100644 --- a/backend/common-cdk/common_constructs/deployment_resources_stack.py +++ b/backend/common-cdk/common_constructs/deployment_resources_stack.py @@ -8,7 +8,7 @@ from common_constructs.access_logs_bucket import AccessLogsBucket from common_constructs.alarm_topic import AlarmTopic -from common_constructs.base_pipeline_stack import DEPLOY_ENVIRONMENT_NAME +from common_constructs.base_pipeline_stack import DEPLOY_ENVIRONMENT_NAME, CCPipelineType from common_constructs.stack import Stack @@ -19,6 +19,7 @@ def __init__( self, scope: Construct, construct_id: str, + pipeline_type: CCPipelineType, **kwargs, ): super().__init__(scope, construct_id, environment_name='deploy', **kwargs) @@ -53,7 +54,14 @@ def __init__( removal_policy=RemovalPolicy.RETAIN, ) - notifications = self.deploy_environment_context.get('notifications', {}) + match pipeline_type: + case CCPipelineType.BACKEND: + notifications = self.deploy_environment_context.get('back_end_notifications', {}) + case CCPipelineType.FRONTEND: + notifications = self.deploy_environment_context.get('front_end_notifications', {}) + case _: + raise ValueError(f'Invalid pipeline type: {pipeline_type}') + self.pipeline_alarm_topic = AlarmTopic( self, 'AlarmTopic', diff --git a/backend/compact-connect-ui-app/app.py b/backend/compact-connect-ui-app/app.py index dd18b8210..522444d97 100644 --- a/backend/compact-connect-ui-app/app.py +++ b/backend/compact-connect-ui-app/app.py @@ -7,6 +7,7 @@ # Make the `common_constructs` namespace package under `common-cdk` available to Python sys.path.insert(0, os.path.abspath(os.path.join('..', 'common-cdk'))) +from common_constructs.base_pipeline_stack import CCPipelineType from common_constructs.deployment_resources_stack import DeploymentResourcesStack from common_constructs.stack import StandardTags @@ -134,6 +135,7 @@ def add_deployment_resources_stack(self): self.deployment_resources_stack = DeploymentResourcesStack( self, DEPLOYMENT_RESOURCES_STACK, + pipeline_type=CCPipelineType.FRONTEND, env=self.environment, standard_tags=StandardTags(**self.tags, environment='deploy'), ) diff --git a/backend/compact-connect-ui-app/app_clients/README.md b/backend/compact-connect-ui-app/app_clients/README.md deleted file mode 100644 index a80c8e51a..000000000 --- a/backend/compact-connect-ui-app/app_clients/README.md +++ /dev/null @@ -1,272 +0,0 @@ -# App Client Management for Staff Users - -## Overview - -This document is a guide for technical staff for managing Cognito app clients for machine-to-machine authentication in -the State API. All app clients must be documented in the external 'Compact Connect App Client Registry' Google Sheet -(If you do not have access to said registry, contact a maintainer of the project and request access). - -## Creating a New App Client - -### 1. Prerequisites - -Before creating a new app client, ensure you have: -- Jurisdiction requirements documented (compact and state) -- Contact information for the consuming team -- Approval to grant the app client with the requested scopes -- AWS credentials configured with permissions to create app clients for the State Auth user pool in the needed AWS - accounts -- Python 3.10+ installed with boto3 dependency (`pip install boto3`) - -### 2. Update Registry - -Add the new app client information to the external Google Sheet registry for tracking and disaster recovery purposes -(ie a deployment error or AWS region outage causes app client data to be lost so it must be recreated). - -#### **Scope Configuration** - -Scopes are the permissions that the app client will have. There are two tiers of scopes: - -##### **Compact-Level Scopes:** - -These are the scopes that are scoped to a specific compact. Granting these scopes will allow the app client to perform -actions across all jurisdictions within that compact. Generally, the only scope that should be granted at the compact -level is the `{compact}/readGeneral` scope if needed. - -The following scopes are available at the compact level: -``` -{compact}/admin -{compact}/readGeneral -{compact}/readSSN -{compact}/write -``` - -##### **Jurisdiction-Level Scopes:** - -These are the scopes that are scoped to a specific jurisdiction/compact combination. Granting these scopes will allow -the app client to perform actions within a specific jurisdiction/compact combination. You should only grant these -scopes if the consuming team has a specific need for a jurisdiction/compact combination. - -The following scopes are available at the jurisdiction level: -```text -{jurisdiction}/{compact}.admin -{jurisdiction}/{compact}.write -{jurisdiction}/{compact}.readPrivate -{jurisdiction}/{compact}.readSSN -``` - -Currently, the most common scope needed by app clients is `{jurisdiction}/{compact}.write`, which allows uploading -license data for a jurisdiction/compact combination. Scopes that expose PII (e.g., `.readSSN`, `.readPrivate`) should -be granted sparingly and will require valid request signatures once a signing public key is configured for the -jurisdiction. - -### 3. Create App Client Using Interactive Python Script - -**Use the provided Python script in the bin directory for streamlined app client creation:** - - -```bash -python3 bin/create_app_client.py -e -u -``` - -**Interactive Process:** -The script will prompt you for: -- App client name (e.g., "example-ky-app-client-v1") -- Compact (aslp, octp, coun) -- State postal abbreviation (e.g., "ky", "la") -- Additional scopes (optional) - -**Automatic Scope Generation:** -The script automatically creates these standard scopes: -- `{compact}/readGeneral` - General read access for the compact -- `{state}/{compact}.write` - Write access for the specific state/compact combination - -### 4. **Send Credentials to Consuming Team** - -**When using the Python script (recommended):** -The script will output two separate sections: - -**A. Credentials JSON (for one-time link service):** -```json -{ - "clientId": "6g34example89j", - "clientSecret": "1234example567890" -} -``` -**Important:** These credentials should be securely transmitted to the consuming team via an encrypted channel -(i.e., a one-time use link). Copy this JSON and use it with your one-time secret link generator. Once you have sent -the credentials over to the IT staff, ensure you remove all remnants of the credentials from your device. - -**B. Email Template:** -The script will also generate an email template with contextual information (compact name, state, auth URL, license -upload URL) that you can copy/paste into your email client. This template includes a placeholder for the one-time -link that you'll generate separately. - - -#### Email Instructions for consuming team - -As part of the email message sent to the consuming team, be sure to include the onboarding instructions document from -the `it_staff_onboarding_instructions/` directory. - -## Managing API Signing Public Keys - -### Overview - -Signature-based authentication provides an additional layer of security for API access to sensitive licensure data. Each -compact/state combination can have multiple SIGNATURE public keys configured to support key rotation and zero-downtime -deployments. - -### Authorization Requirements - -**⚠️ CRITICAL SECURITY NOTICE:** Due to the sensitivity of the data protected by SIGNATURE authentication (including -partial Social Security Numbers, personal addresses, and professional license details), configuration of new SIGNATURE -public keys in production environments **MUST** include explicit authorization from the state board executive director. - - -### Creating SIGNATURE Public Keys - -Once a state configures a public key, they will be able to access the SIGNATURE-required API endpoints. API endpoints with -_optional_ SIGNATURE support will also begin to enforce SIGNATURE signatures for that combination of compact and state. **This -means that, once a compact/state has a public key configured, they will be denied access to SIGNATURE-Optional endpoints, -such as the `POST license` endpoint, unless they have also implemented SIGNATURE signatures there as well.** Be sure that -the representative is advised that they should begin signing those requests _before_ CompactConnect has a configured -public key. - -#### 1. Prerequisites - -Before creating a new SIGNATURE public key, ensure you have: -- **Production Authorization**: Explicit approval from the state board executive director for production environments -- Validated the identity of the individual providing the public key to you -- Jurisdiction and compact information confirmed -- Contact information for the state IT representative -- The public key file (`.pub` format) from the state IT representative -- AWS credentials configured with permissions to write to the compact configuration table -- Python 3.10+ installed with boto3 dependency (`pip install boto3`) - -#### 2. Key ID Naming Convention - -The state IT department should provide an identifier; however, you can recommend a descriptive key ID that includes: -- Environment indicator (if applicable) -- Version or date suffix - -Examples: -- `prod-key-001` -- `beta-key-2024-01` - -#### 3. Create SIGNATURE Public Key Using Interactive Python Script - -**Use the provided Python script in the bin directory for streamlined SIGNATURE key management:** - -```bash -python3 bin/manage_signature_keys.py create -t -``` - -**Interactive Process:** -The script will prompt you for: -- Compact (aslp, octp, coun) -- State postal abbreviation (e.g., "ky", "la") -- Key ID (e.g., "client-org-prod-key-001") - -**File Reading:** -The script will: -- Notify you that it will read the public key from `.pub` -- Validate the PEM format of the public key -- Check for existing keys with the same ID -- Write the key to the compact configuration database - -#### 4. Database Schema - -SIGNATURE keys are stored in the compact configuration table with the following schema: -- **Primary Key (pk)**: `{compact}#SIGNATURE_KEYS#{state}` -- **Sort Key (sk)**: `{compact}#JURISDICTION#{jurisdiction}#{key_id}` -- **Additional Fields**: - - `publicKey`: PEM-encoded public key content - - `compact`: Compact abbreviation - - `jurisdiction`: Jurisdiction abbreviation - - `keyId`: Key identifier - - `createdAt`: Creation timestamp - -### Deleting SIGNATURE Public Keys - -#### 1. Prerequisites - -Before deleting a SIGNATURE public key, ensure you have: -- Confirmation that the key is no longer in use by the state IT department -- Confirmation of the key id to be deleted -- Understanding of the impact on API access for the compact/state combination - -#### 2. Delete SIGNATURE Public Key Using Interactive Python Script - -```bash -python3 bin/manage_signature_keys.py delete -t -``` - -**Interactive Process:** -The script will: -- Prompt for compact and state -- List all existing keys for the compact/state combination -- Allow you to select the specific key ID to delete -- Require typing "DELETE" to confirm the deletion -- Remove the key from the compact configuration database - -### Key Rotation Best Practices - -#### 1. Planning - -- Coordinate with the State IT representative well in advance -- Plan for zero-downtime deployment - -#### 2. Implementation - -- Create new keys before removing old ones -- Allow both keys to be active during the transition period -- Monitor API access and authentication success rates -- Remove old keys only after confirming new keys are working correctly - -#### 3. Documentation - -- Document key rotation dates and reasons -- Maintain audit trail of all key management activities - -### Security Considerations - -#### 1. Key Storage - -- Public keys are stored in DynamoDB with appropriate access controls -- Private keys should never be stored in CompactConnect systems -- State IT departments are responsible for secure private key management - -#### 2. Access Control - -- Only authorized technical staff should have access to key management resources -- All key management activities should be logged and audited -- Production key creation requires executive director approval - -## Rotating App Client Credentials - -Unfortunately, AWS Cognito does not support rotating app client credentials for an existing app client. The only way -to rotate credentials is to create a new app client with a new clientId and clientSecret and then delete the old one. -The following process should be performed if credentials are accidentally exposed or in the event of a security breach -where the old credentials are compromised. - -### 1. Pre-rotation Tasks - -- Contact consuming team to schedule rotation -- Follow "Creating a New App Client" steps above using either the Python script (recommended) or AWS CLI, you will -increment clientName version suffix by 1 (e.g. "example-ky-app-client-v1" -> "example-ky-app-client-v2") -- Follow “Creating a New App Client” using the Python script (recommended) or AWS CLI. Increment the client name’s - version suffix by 1 (e.g., “example-ky-app-client-v1” -> “example-ky-app-client-v2”). -- Update the external Google Sheet registry with new client information - -### 2. Migration - -- Provide new client id and client secret to consuming team -- Consuming team will need to confirm that the new credentials are deployed in their systems, the old app client is -not in use, and their systems are working as expected. - -### 3. Cleanup - -- Delete old app client from Cognito using the following cli command: -``` -aws cognito-idp delete-user-pool-client --user-pool-id '' --client-id '' -``` diff --git a/backend/compact-connect-ui-app/app_clients/bin/create_app_client.py b/backend/compact-connect-ui-app/app_clients/bin/create_app_client.py deleted file mode 100755 index bb99339b2..000000000 --- a/backend/compact-connect-ui-app/app_clients/bin/create_app_client.py +++ /dev/null @@ -1,341 +0,0 @@ -#!/usr/bin/env python3 -# ruff: noqa: T201 we use print statements for scripts run locally - -""" -Script to create AWS Cognito app clients interactively. - -This script prompts users for the necessary information to create app clients -in different environments (test, beta, prod) and automatically generates -the standard scopes based on compact and state inputs. -""" - -import argparse -import json -import re -import sys -from pathlib import Path - -import boto3 -from botocore.exceptions import ClientError, NoCredentialsError - - -def load_cdk_config(): - """Load configuration from cdk.json file.""" - # Find cdk.json file - look in parent directories - current_dir = Path(__file__).parent - cdk_json_path = None - - # Look up the directory tree for cdk.json - for parent in [current_dir] + list(current_dir.parents): - potential_path = parent / 'cdk.json' - if potential_path.exists(): - cdk_json_path = potential_path - break - - if not cdk_json_path: - raise FileNotFoundError('Could not find cdk.json file in current directory or parent directories') - - with open(cdk_json_path) as f: - cdk_config = json.load(f) - - context = cdk_config.get('context', {}) - - return { - 'compacts': context.get('compacts', []), - 'active_compact_member_jurisdictions': context.get('active_compact_member_jurisdictions', {}), - } - - -# Load configuration from cdk.json -CDK_CONFIG = load_cdk_config() -VALID_COMPACTS = CDK_CONFIG['compacts'] -ACTIVE_COMPACT_JURISDICTIONS = CDK_CONFIG['active_compact_member_jurisdictions'] - - -# Valid scope patterns for validation -VALID_SCOPE_PATTERNS = [ - r'^[a-z]+/readGeneral$', - r'^[a-z]+/readSSN$', - r'^[a-z]+/write$', - r'^[a-z]+/admin$', - r'^[a-z]{2}/[a-z]+\.write$', - r'^[a-z]{2}/[a-z]+\.readPrivate$', - r'^[a-z]{2}/[a-z]+\.readSSN$', - r'^[a-z]{2}/[a-z]+\.admin$', -] - - -def validate_compact(compact): - """Validate compact input.""" - compact = compact.lower().strip() - if compact not in VALID_COMPACTS: - raise ValueError(f'Invalid compact: {compact}. Valid compacts are: {", ".join(VALID_COMPACTS)}') - return compact - - -def validate_state(state, compact): - """Validate state postal abbreviation for the given compact.""" - state = state.lower().strip() - - # Get valid states for this compact - valid_states = ACTIVE_COMPACT_JURISDICTIONS.get(compact, []) - - if state not in valid_states: - raise ValueError( - f'Invalid state: {state}. Valid states for {compact.upper()} compact are: {", ".join(sorted(valid_states))}' - ) - return state - - -def validate_scope(scope): - """Validate a single scope against known patterns.""" - scope = scope.strip() - for pattern in VALID_SCOPE_PATTERNS: - if re.match(pattern, scope): - return True - return False - - -def validate_additional_scopes(scopes_input): - """Validate additional scopes input.""" - if not scopes_input.strip(): - return [] - - scopes = [scope.strip() for scope in scopes_input.split(',')] - invalid_scopes = [] - - for scope in scopes: - if not validate_scope(scope): - invalid_scopes.append(scope) - - if invalid_scopes: - print(f'\nInvalid scopes detected: {", ".join(invalid_scopes)}') - print('Valid scope patterns:') - print(' Compact-level: {compact}/readGeneral, {compact}/readSSN, {compact}/write, {compact}/admin') - print( - 'Jurisdiction-level: {state}/{compact}.write, {state}/{compact}.readPrivate, {state}/{compact}.readSSN, ' - '{state}/{compact}.admin' - ) - raise ValueError('Invalid scopes provided') - - return scopes - - -def get_user_input(): - """Get user input for app client configuration.""" - print('=== App Client Configuration ===\n') - - # Get client name - client_name = input("Enter the app client name (e.g., 'example-ky-app-client-v1'): ").strip() - if not client_name: - raise ValueError('Client name is required') - - # Get compact - while True: - try: - print(f'\nValid compacts: {", ".join(VALID_COMPACTS)}') - compact = input('Enter the compact: ').strip() - compact = validate_compact(compact) - break - except ValueError as e: - print(f'Error: {e}') - - # Get state - while True: - try: - valid_states = ACTIVE_COMPACT_JURISDICTIONS.get(compact, []) - print(f'\nValid states for {compact.upper()} compact: {", ".join(sorted(valid_states))}') - state = input("Enter the state postal abbreviation (e.g., 'ky', 'la'): ").strip() - state = validate_state(state, compact) - break - except ValueError as e: - print(f'Error: {e}') - - # Get additional scopes (optional) - print('\nThe following scope will be automatically included:') - print(f' - {state}/{compact}.write') - - additional_scopes = [] - while True: - try: - scopes_input = input('\nEnter any additional scopes (comma-separated, or press Enter for none): ').strip() - additional_scopes = validate_additional_scopes(scopes_input) - break - except ValueError as e: - print(f'Error: {e}') - continue - - # Generate final scope list - scopes = [f'{state}/{compact}.write'] - scopes.extend(additional_scopes) - - # Remove duplicates - deduped_scopes = list(set(scopes)) - - print('\nFinal configuration:') - print(f' Client Name: {client_name}') - print(f' Compact: {compact}') - print(f' State: {state}') - print(f' Scopes: {", ".join(deduped_scopes)}') - - confirm = input('\nProceed with this configuration? (y/N): ').strip().lower() - if confirm != 'y': - print('Configuration cancelled.') - sys.exit(0) - - return {'clientName': client_name, 'compact': compact, 'state': state, 'scopes': deduped_scopes} - - -def create_app_client(user_pool_id, config): - """Create the app client using boto3 Cognito client.""" - client_name = config['clientName'] - scopes = config['scopes'] - - print(f'\nCreating app client: {client_name}') - print(f'With scopes: {", ".join(scopes)}') - - try: - # Create boto3 Cognito IDP client - cognito_client = boto3.client('cognito-idp', region_name='us-east-1') - - # Create the user pool client - return cognito_client.create_user_pool_client( - UserPoolId=user_pool_id, - ClientName=client_name, - PreventUserExistenceErrors='ENABLED', - GenerateSecret=True, - TokenValidityUnits={'AccessToken': 'minutes'}, - AccessTokenValidity=15, - AllowedOAuthFlowsUserPoolClient=True, - AllowedOAuthFlows=['client_credentials'], - AllowedOAuthScopes=scopes, - ) - - except NoCredentialsError: - print('Error: AWS credentials not found. Please configure your AWS credentials.') - print("You can use 'aws configure' or set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment variables.") - sys.exit(1) - except ClientError as e: - error_code = e.response['Error']['Code'] - error_message = e.response['Error']['Message'] - print(f'Error creating app client: {error_code} - {error_message}') - sys.exit(1) - - -def print_credentials(client_id, client_secret): - """Print only the sensitive credentials in JSON format for secure copy/paste.""" - credentials = { - 'clientId': client_id, - 'clientSecret': client_secret, - } - - print('\n' + '=' * 60) - print('APP CLIENT CREDENTIALS (FOR ONE-TIME LINK SERVICE)') - print('=' * 60) - print(json.dumps(credentials, indent=2)) - print('=' * 60) - print('Please copy the JSON above and use it with your one-time secret link generator.') - print('Do not leave these credentials in terminal history or logs.') - print('=' * 60) - - -def print_email_template(environment, compact, state): - """Print an email template with contextual information for the consuming team.""" - # Get environment-specific URLs - auth_urls = { - 'beta': 'https://compact-connect-state-auth-beta.auth.us-east-1.amazoncognito.com/oauth2/token', - 'prod': 'https://compact-connect-state-auth.auth.us-east-1.amazoncognito.com/oauth2/token', - 'test': 'https://compact-connect-state-auth-test.auth.us-east-1.amazoncognito.com/oauth2/token', - } - - api_base_urls = { - 'beta': 'https://state-api.beta.compactconnect.org', - 'prod': 'https://state-api.compactconnect.org', - 'test': 'https://state-api.test.compactconnect.org', - } - - # Compact name mapping - compact_names = { - 'aslp': 'Audiology and Speech Language Pathology', - 'octp': 'Occupational Therapy', - 'coun': 'Counseling', - } - - compact_name = compact_names.get(compact, compact.upper()) - auth_url = auth_urls.get(environment) - license_upload_url = f'{api_base_urls.get(environment)}/v1/compacts/{compact}/jurisdictions/{state}/licenses' - - email_template = f""" -Thank you for integrating with Compact Connect! You have been designated as the IT professional who is able to handle -credentials for secure machine-to-machine authentication between your state and CompactConnect. - -Details for these credentials are: -Compact: {compact_name} -State: {state.upper()} -Auth URL: {auth_url} -License Upload URL: {license_upload_url} - -Follow this link to your API credentials as soon as you are ready to securely store them. They will only be viewable -once: - - -For more information on CompactConnect and how to integrate your state IT system with ours, see the documentation -here: -https://github.com/csg-org/CompactConnect/blob/development/backend/compact-connect/docs/it_staff_onboarding_instructions.md -""" - - print('\n' + '=' * 60) - print('EMAIL TEMPLATE (COPY/PASTE INTO EMAIL CLIENT)') - print('=' * 60) - print(email_template.strip()) - print('=' * 60) - - -def main(): - parser = argparse.ArgumentParser(description='Create AWS Cognito app client interactively') - parser.add_argument( - '-e', '--environment', required=True, choices=['test', 'beta', 'prod'], help='Environment (test, beta, or prod)' - ) - parser.add_argument('-u', '--user-pool-id', required=True, help='AWS Cognito User Pool ID') - - args = parser.parse_args() - - try: - print(f'Creating app client for {args.environment} environment...') - print(f'User Pool ID: {args.user_pool_id}\n') - - # Get configuration from user input - config = get_user_input() - - # Create the app client - response = create_app_client(args.user_pool_id, config) - - # Extract credentials from response - user_pool_client = response.get('UserPoolClient', {}) - client_id = user_pool_client.get('ClientId') - client_secret = user_pool_client.get('ClientSecret') - client_name = user_pool_client.get('ClientName') - - if not client_id or not client_secret: - print('Error: Could not extract client ID or secret from AWS response') - sys.exit(1) - - print('\n✅ App client created successfully!') - print(f'Client Name: {client_name}') - print(f'Client ID: {client_id}') - - # Print credentials for secure copy/paste - print_credentials(client_id, client_secret) - - # Print email template - print_email_template(args.environment, config['compact'], config['state']) - - print('\n📝 Remember to add this app client to your external registry!') - - except Exception as e: # noqa: BLE001 - print(f'Error: {e}') - sys.exit(1) - - -if __name__ == '__main__': - main() diff --git a/backend/compact-connect-ui-app/app_clients/bin/manage_signature_keys.py b/backend/compact-connect-ui-app/app_clients/bin/manage_signature_keys.py deleted file mode 100755 index 50b4696cf..000000000 --- a/backend/compact-connect-ui-app/app_clients/bin/manage_signature_keys.py +++ /dev/null @@ -1,390 +0,0 @@ -#!/usr/bin/env python3 -# ruff: noqa: T201 we use print statements for scripts run locally - -""" -Script to manage SIGNATURE public keys in the compact configuration database. - -This script allows users to create and delete SIGNATURE public keys for different -compact/jurisdiction combinations. It follows the same interactive style as -the create_app_client.py script. -""" - -import argparse -import json -import sys -from datetime import UTC, datetime -from pathlib import Path - -import boto3 -from botocore.exceptions import ClientError, NoCredentialsError - - -def load_cdk_config(): - """Load configuration from cdk.json file.""" - # Find cdk.json file - look in parent directories - current_dir = Path(__file__).parent - cdk_json_path = None - - # Look up the directory tree for cdk.json - for parent in [current_dir] + list(current_dir.parents): - potential_path = parent / 'cdk.json' - if potential_path.exists(): - cdk_json_path = potential_path - break - - if not cdk_json_path: - raise FileNotFoundError('Could not find cdk.json file in current directory or parent directories') - - with open(cdk_json_path) as f: - cdk_config = json.load(f) - - context = cdk_config.get('context', {}) - - return { - 'compacts': context.get('compacts', []), - 'active_compact_member_jurisdictions': context.get('active_compact_member_jurisdictions', {}), - } - - -# Load configuration from cdk.json -CDK_CONFIG = load_cdk_config() -VALID_COMPACTS = CDK_CONFIG['compacts'] -ACTIVE_COMPACT_JURISDICTIONS = CDK_CONFIG['active_compact_member_jurisdictions'] - - -def validate_compact(compact): - """Validate compact input.""" - compact = compact.lower().strip() - if compact not in VALID_COMPACTS: - raise ValueError(f'Invalid compact: {compact}. Valid compacts are: {", ".join(VALID_COMPACTS)}') - return compact - - -def validate_state(state, compact): - """Validate state postal abbreviation for the given compact.""" - state = state.lower().strip() - - # Get valid states for this compact - valid_states = ACTIVE_COMPACT_JURISDICTIONS.get(compact, []) - - if state not in valid_states: - raise ValueError( - f'Invalid state: {state}. Valid states for {compact.upper()} compact are: {", ".join(sorted(valid_states))}' - ) - return state - - -def validate_key_id(key_id): - """Validate key ID input.""" - key_id = key_id.strip() - if not key_id: - raise ValueError('Key ID cannot be empty') - if len(key_id) > 100: # Reasonable limit for key ID - raise ValueError('Key ID is too long (max 100 characters)') - if not key_id.replace('-', '').replace('_', '').isalnum(): - raise ValueError('Key ID can only contain alphanumeric characters, hyphens, and underscores') - return key_id - - -def get_user_input_for_create(): - """Get user input for creating a SIGNATURE public key.""" - print('=== SIGNATURE Public Key Creation ===\n') - - # Get compact - while True: - try: - print(f'Valid compacts: {", ".join(VALID_COMPACTS)}') - compact = input('Enter the compact: ').strip() - compact = validate_compact(compact) - break - except ValueError as e: - print(f'Error: {e}') - - # Get state - while True: - try: - valid_states = ACTIVE_COMPACT_JURISDICTIONS.get(compact, []) - print(f'\nValid states for {compact.upper()} compact: {", ".join(sorted(valid_states))}') - state = input("Enter the state postal abbreviation (e.g., 'ky', 'la'): ").strip() - state = validate_state(state, compact) - break - except ValueError as e: - print(f'Error: {e}') - - # Get key ID - while True: - try: - key_id = input('\nEnter the key ID (e.g., "client-key-001"): ').strip() - key_id = validate_key_id(key_id) - break - except ValueError as e: - print(f'Error: {e}') - - print('\nConfiguration:') - print(f' Compact: {compact}') - print(f' State: {state}') - print(f' Key ID: {key_id}') - print(f' Public key file: {key_id}.pub') - - confirm = input('\nProceed with this configuration? (y/N): ').strip().lower() - if confirm != 'y': - print('Configuration cancelled.') - sys.exit(0) - - return {'compact': compact, 'state': state, 'key_id': key_id} - - -def read_public_key_file(key_id): - """Read the public key from the specified file.""" - file_path = Path(f'{key_id}.pub') - - if not file_path.exists(): - raise FileNotFoundError( - f'Public key file "{file_path}" not found. Please ensure the file exists in the current directory.' - ) - - try: - with open(file_path) as f: - public_key_content = f.read().strip() - - if not public_key_content: - raise ValueError('Public key file is empty') - - # Basic validation that this looks like a PEM public key - if not public_key_content.startswith('-----BEGIN PUBLIC KEY-----'): - raise ValueError( - 'Public key file does not appear to be in PEM format (should start with "-----BEGIN PUBLIC KEY-----")' - ) - - if not public_key_content.endswith('-----END PUBLIC KEY-----'): - raise ValueError( - 'Public key file does not appear to be in PEM format (should end with "-----END PUBLIC KEY-----")' - ) - - return public_key_content - - except (FileNotFoundError, ValueError) as e: - raise ValueError(f'Error reading public key file: {e}') from e - - -def create_signature_key(table_name, config): - """Create the SIGNATURE public key in DynamoDB.""" - compact = config['compact'] - state = config['state'] - key_id = config['key_id'] - - print(f'\nCreating SIGNATURE public key: {key_id}') - print(f'For compact: {compact}, state: {state}') - - try: - # Create boto3 DynamoDB client - dynamodb = boto3.resource('dynamodb') - table = dynamodb.Table(table_name) - - # Check if key already exists - pk = f'{compact}#SIGNATURE_KEYS#{state}' - sk = f'{compact}#JURISDICTION#{state}#{key_id}' - - response = table.get_item(Key={'pk': pk, 'sk': sk}) - - if 'Item' in response: - print(f'\n⚠️ Warning: A key with ID "{key_id}" already exists for {compact}/{state}') - overwrite = input('Do you want to overwrite it? (y/N): ').strip().lower() - if overwrite != 'y': - print('Operation cancelled.') - sys.exit(0) - - # Read the public key file - print(f'\nReading public key from {key_id}.pub...') - public_key_pem = read_public_key_file(key_id) - - # Create the item - item = { - 'pk': pk, - 'sk': sk, - 'publicKey': public_key_pem, - 'compact': compact, - 'jurisdiction': state, - 'keyId': key_id, - 'createdAt': datetime.now(tz=UTC).isoformat(), - } - - # Write to DynamoDB - table.put_item(Item=item) - - print('\n✅ SIGNATURE public key created successfully!') - print(f'Key ID: {key_id}') - print(f'Compact: {compact}') - print(f'State: {state}') - - except NoCredentialsError: - print('Error: AWS credentials not found. Please configure your AWS credentials.') - print("You can use 'aws configure' or set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment variables.") - raise - except ClientError as e: - error_code = e.response['Error']['Code'] - error_message = e.response['Error']['Message'] - print(f'Error creating SIGNATURE key: {error_code} - {error_message}') - raise - - -def get_user_input_for_delete(): - """Get user input for deleting a SIGNATURE public key.""" - print('=== SIGNATURE Public Key Deletion ===\n') - - # Get compact - while True: - try: - print(f'Valid compacts: {", ".join(VALID_COMPACTS)}') - compact = input('Enter the compact: ').strip() - compact = validate_compact(compact) - break - except ValueError as e: - print(f'Error: {e}') - - # Get state - while True: - try: - valid_states = ACTIVE_COMPACT_JURISDICTIONS.get(compact, []) - print(f'\nValid states for {compact.upper()} compact: {", ".join(sorted(valid_states))}') - state = input("Enter the state postal abbreviation (e.g., 'ky', 'la'): ").strip() - state = validate_state(state, compact) - break - except ValueError as e: - print(f'Error: {e}') - - return {'compact': compact, 'state': state} - - -def list_existing_keys(table_name, config): - """List existing SIGNATURE keys for the given compact/state combination.""" - compact = config['compact'] - state = config['state'] - - try: - # Create boto3 DynamoDB client - dynamodb = boto3.resource('dynamodb') - table = dynamodb.Table(table_name) - - # Query for existing keys - pk = f'{compact}#SIGNATURE_KEYS#{state}' - sk_prefix = f'{compact}#JURISDICTION#{state}#' - - response = table.query( - KeyConditionExpression='pk = :pk AND begins_with(sk, :sk_prefix)', - ExpressionAttributeValues={':pk': pk, ':sk_prefix': sk_prefix}, - ) - - items = response.get('Items', []) - - if not items: - print(f'\nNo SIGNATURE keys found for {compact}/{state}') - return [] - - print(f'\nExisting SIGNATURE keys for {compact}/{state}:') - for i, item in enumerate(items, 1): - key_id = item['sk'].split('#')[-1] - created_at = item.get('createdAt', 'Unknown') - print(f' {i}. Key ID: {key_id} (Created: {created_at})') - - return items - - except NoCredentialsError: - print('Error: AWS credentials not found. Please configure your AWS credentials.') - print("You can use 'aws configure' or set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment variables.") - raise - except ClientError as e: - error_code = e.response['Error']['Code'] - error_message = e.response['Error']['Message'] - print(f'Error listing SIGNATURE keys: {error_code} - {error_message}') - raise - - -def delete_signature_key(table_name, config, key_id): - """Delete the specified SIGNATURE public key from DynamoDB.""" - compact = config['compact'] - state = config['state'] - - print(f'\nDeleting SIGNATURE public key: {key_id}') - print(f'For compact: {compact}, state: {state}') - - try: - # Create boto3 DynamoDB client - dynamodb = boto3.resource('dynamodb') - table = dynamodb.Table(table_name) - - # Delete the item - pk = f'{compact}#SIGNATURE_KEYS#{state}' - sk = f'{compact}#JURISDICTION#{state}#{key_id}' - - table.delete_item(Key={'pk': pk, 'sk': sk}) - - print('\n✅ SIGNATURE public key deleted successfully!') - print(f'Key ID: {key_id}') - print(f'Compact: {compact}') - print(f'State: {state}') - - except NoCredentialsError: - print('Error: AWS credentials not found. Please configure your AWS credentials.') - print("You can use 'aws configure' or set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment variables.") - raise - except ClientError as e: - error_code = e.response['Error']['Code'] - error_message = e.response['Error']['Message'] - print(f'Error deleting SIGNATURE key: {error_code} - {error_message}') - raise - - -def main(): - parser = argparse.ArgumentParser(description='Manage SIGNATURE public keys in the compact configuration database') - parser.add_argument('action', choices=['create', 'delete'], help='Action to perform (create or delete)') - parser.add_argument('-t', '--table-name', required=True, help='DynamoDB table name for compact configuration') - - args = parser.parse_args() - - print(f'Managing SIGNATURE keys for {args.table_name} table...\n') - - if args.action == 'create': - # Create flow - config = get_user_input_for_create() - - create_signature_key(args.table_name, config) - - elif args.action == 'delete': - # Delete flow - config = get_user_input_for_delete() - - # List existing keys - existing_keys = list_existing_keys(args.table_name, config) - - if not existing_keys: - print('\nNo keys to delete.') - sys.exit(0) - - # Get key ID to delete - while True: - key_id = input('\nEnter the exact key ID to delete: ').strip() - key_id = validate_key_id(key_id) - - # Check if key exists - key_exists = any(item['sk'].split('#')[-1] == key_id for item in existing_keys) - if not key_exists: - print(f'Error: Key ID "{key_id}" not found in the list above') - continue - - break - - # Final confirmation - print(f'\n⚠️ You are about to delete SIGNATURE key "{key_id}" for {config["compact"]}/{config["state"]}') - print('This action cannot be undone.') - - confirm = input('\nAre you sure you want to delete this key? Type "DELETE" to confirm: ').strip() - if confirm != 'DELETE': - print('Deletion cancelled.') - sys.exit(0) - - delete_signature_key(args.table_name, config, key_id) - - -if __name__ == '__main__': - main() diff --git a/backend/compact-connect-ui-app/app_clients/it_staff_onboarding_instructions/README.md b/backend/compact-connect-ui-app/app_clients/it_staff_onboarding_instructions/README.md deleted file mode 100644 index f28dc34c4..000000000 --- a/backend/compact-connect-ui-app/app_clients/it_staff_onboarding_instructions/README.md +++ /dev/null @@ -1 +0,0 @@ -[Moved Here](../../docs/it_staff_onboarding_instructions.md) diff --git a/backend/compact-connect-ui-app/pipeline/frontend_pipeline.py b/backend/compact-connect-ui-app/pipeline/frontend_pipeline.py index 7778a3d2f..e87917a0c 100644 --- a/backend/compact-connect-ui-app/pipeline/frontend_pipeline.py +++ b/backend/compact-connect-ui-app/pipeline/frontend_pipeline.py @@ -15,6 +15,7 @@ from aws_cdk.pipelines import CodeBuildOptions, CodeBuildStep, CodePipelineSource from aws_cdk.pipelines import CodePipeline as CdkCodePipeline from cdk_nag import NagSuppressions +from common_constructs.base_pipeline_stack import CCPipelineType from common_constructs.bucket import Bucket @@ -75,7 +76,7 @@ def __init__( ) # Create predictable pipeline role before initializing the pipeline - pipeline_role = scope.create_predictable_pipeline_role('Frontend') + pipeline_role = scope.create_predictable_pipeline_role(CCPipelineType.FRONTEND) super().__init__( scope, diff --git a/backend/compact-connect/app.py b/backend/compact-connect/app.py index 7d43f4ff1..2d48aa457 100644 --- a/backend/compact-connect/app.py +++ b/backend/compact-connect/app.py @@ -7,6 +7,7 @@ # Make the `common_constructs` namespace package under `common-cdk` available to Python sys.path.insert(0, os.path.abspath(os.path.join('..', 'common-cdk'))) +from common_constructs.base_pipeline_stack import CCPipelineType from common_constructs.deployment_resources_stack import DeploymentResourcesStack from common_constructs.stack import StandardTags @@ -135,6 +136,7 @@ def add_deployment_resources_stack(self): self.deployment_resources_stack = DeploymentResourcesStack( self, DEPLOYMENT_RESOURCES_STACK, + pipeline_type=CCPipelineType.BACKEND, env=self.environment, standard_tags=StandardTags(**self.tags, environment='deploy'), ) diff --git a/backend/compact-connect/pipeline/backend_pipeline.py b/backend/compact-connect/pipeline/backend_pipeline.py index f517d5c4f..229d8bcca 100644 --- a/backend/compact-connect/pipeline/backend_pipeline.py +++ b/backend/compact-connect/pipeline/backend_pipeline.py @@ -15,6 +15,7 @@ from aws_cdk.pipelines import CodeBuildOptions, CodeBuildStep, CodePipelineSource from aws_cdk.pipelines import CodePipeline as CdkCodePipeline from cdk_nag import NagSuppressions +from common_constructs.base_pipeline_stack import CCPipelineType from common_constructs.bucket import Bucket @@ -72,7 +73,7 @@ def __init__( ) # Create predictable pipeline role before initializing the pipeline - pipeline_role = scope.create_predictable_pipeline_role('Backend') + pipeline_role = scope.create_predictable_pipeline_role(CCPipelineType.BACKEND) artifact_bucket.grant_read(pipeline_role) super().__init__( From f4d5f0fe916043eff14af61a649a43fa586ee64e Mon Sep 17 00:00:00 2001 From: Justin Frahm Date: Wed, 17 Sep 2025 12:43:14 -0600 Subject: [PATCH 12/32] WIP: refactor backend to be multi-service --- .github/workflows/check-common-cdk.yml | 56 + .../check-compact-connect-ui-app.yml | 108 + .github/workflows/check-compact-connect.yml | 108 + .github/workflows/check-lambda-js.yml | 66 - ...eck-python.yml => check-multi-account.yml} | 30 +- backend/bin/compile_requirements.sh | 31 - backend/bin/pre-commit | 2 + backend/bin/sync_deps.sh | 34 - .../common-cdk/common_constructs/README.md | 9 + .../common_constructs/access_logs_bucket.py | 0 .../common_constructs/alarm_topic.py | 0 .../common_constructs/bucket.py | 0 .../frontend_app_config_utility.py | 0 .../common_constructs/security_profile.py | 0 .../service_principal_name.py | 0 .../slack_channel_configuration.py | 0 .../common_constructs/stack.py | 0 .../common_constructs/webacl.py | 0 backend/common-cdk/requirements-dev.in | 6 + backend/common-cdk/requirements.in | 4 + backend/common-cdk/ruff.toml | 112 + .../tests}/__init__.py | 0 .../tests}/test_alarm_topic.py | 1 + .../{ => compact-connect-ui-app}/.coveragerc | 1 + backend/compact-connect-ui-app/README.md | 318 + backend/compact-connect-ui-app/app.py | 195 + .../app_clients/README.md | 272 + .../app_clients/bin/create_app_client.py | 341 + .../app_clients/bin/manage_signature_keys.py | 390 + .../README.md | 1 + .../bin/compile_requirements.sh | 5 + .../bin/run_python_tests.py | 16 +- .../bin/run_tests.sh | 2 +- .../compact-connect-ui-app/bin/sync_deps.sh | 8 + .../cdk.context.beta-example.json | 64 + .../cdk.context.deploy-example.json | 24 + .../cdk.context.prod-example.json | 64 + .../cdk.context.sandbox-example.json | 32 + .../cdk.context.test-example.json | 65 + backend/compact-connect-ui-app/cdk.json | 103 + .../lambdas/nodejs/.gitignore | 6 + .../lambdas/nodejs/.mocharc.js | 0 .../lambdas/nodejs/Gruntfile.js | 64 + .../lambdas/nodejs/README.md | 39 + .../nodejs/cloudfront-csp/.editorconfig | 0 .../lambdas/nodejs/cloudfront-csp/.gitignore | 0 .../lambdas/nodejs/cloudfront-csp/README.md | 0 .../lambdas/nodejs/cloudfront-csp/index.js | 0 .../cloudfront-csp/test/config/index.js | 0 .../nodejs/cloudfront-csp/test/index.test.js | 0 .../lambdas/nodejs/eslint.config.mjs | 82 + .../lambdas/nodejs/jest.config.mjs | 20 + .../lambdas/nodejs/package.json | 62 + .../lambdas/nodejs/tsconfig.json | 32 + .../lambdas/nodejs/yarn.lock | 6245 +++++++++++++++++ .../pipeline/__init__.py | 399 ++ .../design/.!35012!pipeline-architecture.pdf | Bin 0 -> 353 bytes .../pipeline/design/README.md | 96 + .../pipeline/design/pipeline-architecture.pdf | Bin 0 -> 89240 bytes .../pipeline/frontend_pipeline.py | 0 .../pipeline/frontend_stage.py | 1 + .../pipeline/synth_substitute_stack.py | 30 + .../pipeline/synth_substitute_stage.py | 38 + .../requirements-dev.in | 6 + .../requirements-dev.txt | 108 + .../compact-connect-ui-app/requirements.in | 6 + .../compact-connect-ui-app/requirements.txt | 74 + backend/compact-connect-ui-app/ruff.toml | 112 + .../compact-connect-ui-app/stacks/__init__.py | 0 .../frontend_deployment_stack/__init__.py | 0 .../frontend_deployment_stack/deployment.py | 0 .../frontend_deployment_stack/distribution.py | 0 .../compact-connect-ui-app/tests/__init__.py | 5 + .../tests/app/__init__.py | 0 .../compact-connect-ui-app/tests/app/base.py | 149 + .../tests/app/test_pipeline.py | 41 + ...end-FrontendDeploymentStack-UI_BUCKET.json | 0 ...ontendDeploymentStack-UI_DISTRIBUTION.json | 0 ...Stack-UI_DISTRIBUTION_LAMBDA_FUNCTION.json | 0 ...end-FrontendDeploymentStack-UI_BUCKET.json | 0 ...ontendDeploymentStack-UI_DISTRIBUTION.json | 0 ...Stack-UI_DISTRIBUTION_LAMBDA_FUNCTION.json | 0 ...end-FrontendDeploymentStack-UI_BUCKET.json | 0 ...ontendDeploymentStack-UI_DISTRIBUTION.json | 0 ...Stack-UI_DISTRIBUTION_LAMBDA_FUNCTION.json | 0 .../.!35287!military_affiliation.pdf | Bin 0 -> 9 bytes .../test_files/military_affiliation.pdf | Bin 0 -> 24457 bytes .../resources/test_ui_directory/index.html | 0 backend/compact-connect/.coveragerc | 1 + backend/compact-connect/app.py | 76 +- .../bin/compile_requirements.sh | 25 + .../compact-connect/bin/run_python_tests.py | 180 + backend/compact-connect/bin/run_tests.sh | 129 + backend/compact-connect/bin/sync_deps.sh | 28 + .../common_constructs/README.md | 8 + .../common_constructs/cc_api.py | 2 +- .../common_constructs/cognito_user_backup.py | 2 +- .../common_constructs/resource_scope_mixin.py | 1 + .../lambdas/nodejs/cloudfront-csp/yarn.lock | 2971 -------- .../lambdas/nodejs/eslint.config.mjs | 8 - .../lambdas/nodejs/package.json | 2 +- .../python/compact-configuration/.coveragerc | 10 - .../python/custom-resources/.coveragerc | 10 - .../lambdas/python/data-events/.coveragerc | 10 - .../python/provider-data-v1/.coveragerc | 10 - .../lambdas/python/purchases/.coveragerc | 10 - .../python/staff-user-pre-token/.coveragerc | 10 - .../lambdas/python/staff-users/.coveragerc | 10 - backend/compact-connect/pipeline/__init__.py | 222 +- .../pipeline/backend_pipeline.py | 2 + .../compact-connect/pipeline/backend_stage.py | 3 +- backend/compact-connect/ruff.toml | 112 + .../stacks/api_lambda_stack/__init__.py | 2 +- .../stacks/api_lambda_stack/provider_users.py | 2 +- .../stacks/api_stack/__init__.py | 4 +- .../compact-connect/stacks/api_stack/api.py | 4 +- .../stacks/api_stack/v1_api/api.py | 2 +- .../stacks/api_stack/v1_api/attestations.py | 2 +- .../api_stack/v1_api/bulk_upload_url.py | 1 + .../v1_api/compact_configuration_api.py | 2 +- .../stacks/api_stack/v1_api/credentials.py | 2 +- .../stacks/api_stack/v1_api/post_licenses.py | 2 +- .../api_stack/v1_api/provider_management.py | 2 +- .../stacks/api_stack/v1_api/provider_users.py | 2 +- .../api_stack/v1_api/public_lookup_api.py | 2 +- .../stacks/api_stack/v1_api/purchases.py | 2 +- .../stacks/api_stack/v1_api/staff_users.py | 2 +- .../disaster_recovery_stack/__init__.py | 2 +- .../restore_dynamo_db_table_step_function.py | 3 +- .../sync_table_step_function.py | 3 +- .../stacks/event_listener_stack/__init__.py | 4 +- .../compact-connect/stacks/ingest_stack.py | 4 +- .../stacks/managed_login_stack.py | 2 +- .../stacks/notification_stack.py | 4 +- .../stacks/persistent_stack/__init__.py | 4 +- .../persistent_stack/bulk_uploads_bucket.py | 6 +- .../compact_configuration_table.py | 2 +- .../compact_configuration_upload.py | 3 +- .../persistent_stack/data_event_table.py | 4 +- .../stacks/persistent_stack/provider_table.py | 2 +- .../persistent_stack/provider_users_bucket.py | 6 +- .../stacks/persistent_stack/ssn_table.py | 4 +- .../stacks/persistent_stack/staff_users.py | 4 +- .../transaction_history_table.py | 2 +- .../transaction_reports_bucket.py | 3 +- .../user_email_notifications.py | 3 +- .../stacks/persistent_stack/users_table.py | 2 +- .../stacks/provider_users/__init__.py | 4 +- .../stacks/provider_users/provider_users.py | 4 +- .../compact-connect/stacks/reporting_stack.py | 4 +- .../stacks/state_api_stack/__init__.py | 4 +- .../stacks/state_api_stack/api.py | 2 +- .../state_api_stack/v1_api/bulk_upload_url.py | 2 +- .../state_api_stack/v1_api/post_licenses.py | 2 +- .../v1_api/provider_management.py | 2 +- .../stacks/state_auth/__init__.py | 2 +- .../stacks/state_auth/state_auth_users.py | 2 +- .../transaction_monitoring_stack/__init__.py | 2 +- ...transaction_history_processing_workflow.py | 4 +- backend/compact-connect/tests/__init__.py | 5 + backend/compact-connect/tests/app/base.py | 36 +- .../tests/app/test_api/__init__.py | 2 +- .../tests/app/test_cognito_backup.py | 2 +- .../tests/app/test_pipeline.py | 39 +- .../compact-connect/tests/app/test_sandbox.py | 1 - .../test_cognito_user_backup.py | 1 + .../test_queue_event_listener.py | 1 + .../test_queued_lambda_processor.py | 1 + .../common => multi-account}/.coveragerc | 9 +- .../backups/requirements-dev.txt | 20 +- .../multi-account/backups/requirements.txt | 6 +- .../multi-account/bin/compile_requirements.sh | 9 + backend/multi-account/bin/run_python_tests.py | 171 + backend/multi-account/bin/run_tests.sh | 115 + backend/multi-account/bin/sync_deps.sh | 7 + .../control-tower/requirements-dev.in | 5 + .../control-tower/requirements-dev.txt | 98 +- .../control-tower/requirements.txt | 6 +- .../log-aggregation/requirements-dev.txt | 6 +- .../log-aggregation/requirements.txt | 6 +- backend/multi-account/ruff.toml | 112 + backend/social-work-app/README.md | 3 + 182 files changed, 11048 insertions(+), 3672 deletions(-) create mode 100644 .github/workflows/check-common-cdk.yml create mode 100644 .github/workflows/check-compact-connect-ui-app.yml create mode 100644 .github/workflows/check-compact-connect.yml delete mode 100644 .github/workflows/check-lambda-js.yml rename .github/workflows/{check-python.yml => check-multi-account.yml} (62%) delete mode 100755 backend/bin/compile_requirements.sh delete mode 100755 backend/bin/sync_deps.sh create mode 100644 backend/common-cdk/common_constructs/README.md rename backend/{compact-connect => common-cdk}/common_constructs/access_logs_bucket.py (100%) rename backend/{compact-connect => common-cdk}/common_constructs/alarm_topic.py (100%) rename backend/{compact-connect => common-cdk}/common_constructs/bucket.py (100%) rename backend/{compact-connect => common-cdk}/common_constructs/frontend_app_config_utility.py (100%) rename backend/{compact-connect => common-cdk}/common_constructs/security_profile.py (100%) rename backend/{compact-connect => common-cdk}/common_constructs/service_principal_name.py (100%) rename backend/{compact-connect => common-cdk}/common_constructs/slack_channel_configuration.py (100%) rename backend/{compact-connect => common-cdk}/common_constructs/stack.py (100%) rename backend/{compact-connect => common-cdk}/common_constructs/webacl.py (100%) create mode 100644 backend/common-cdk/requirements-dev.in create mode 100644 backend/common-cdk/requirements.in create mode 100644 backend/common-cdk/ruff.toml rename backend/{compact-connect/common_constructs => common-cdk/tests}/__init__.py (100%) rename backend/{compact-connect/tests/common_constructs => common-cdk/tests}/test_alarm_topic.py (99%) rename backend/{ => compact-connect-ui-app}/.coveragerc (93%) create mode 100644 backend/compact-connect-ui-app/README.md create mode 100644 backend/compact-connect-ui-app/app.py create mode 100644 backend/compact-connect-ui-app/app_clients/README.md create mode 100755 backend/compact-connect-ui-app/app_clients/bin/create_app_client.py create mode 100755 backend/compact-connect-ui-app/app_clients/bin/manage_signature_keys.py create mode 100644 backend/compact-connect-ui-app/app_clients/it_staff_onboarding_instructions/README.md create mode 100755 backend/compact-connect-ui-app/bin/compile_requirements.sh rename backend/{ => compact-connect-ui-app}/bin/run_python_tests.py (88%) rename backend/{ => compact-connect-ui-app}/bin/run_tests.sh (98%) create mode 100755 backend/compact-connect-ui-app/bin/sync_deps.sh create mode 100644 backend/compact-connect-ui-app/cdk.context.beta-example.json create mode 100644 backend/compact-connect-ui-app/cdk.context.deploy-example.json create mode 100644 backend/compact-connect-ui-app/cdk.context.prod-example.json create mode 100644 backend/compact-connect-ui-app/cdk.context.sandbox-example.json create mode 100644 backend/compact-connect-ui-app/cdk.context.test-example.json create mode 100644 backend/compact-connect-ui-app/cdk.json create mode 100644 backend/compact-connect-ui-app/lambdas/nodejs/.gitignore rename backend/{compact-connect => compact-connect-ui-app}/lambdas/nodejs/.mocharc.js (100%) create mode 100644 backend/compact-connect-ui-app/lambdas/nodejs/Gruntfile.js create mode 100644 backend/compact-connect-ui-app/lambdas/nodejs/README.md rename backend/{compact-connect => compact-connect-ui-app}/lambdas/nodejs/cloudfront-csp/.editorconfig (100%) rename backend/{compact-connect => compact-connect-ui-app}/lambdas/nodejs/cloudfront-csp/.gitignore (100%) rename backend/{compact-connect => compact-connect-ui-app}/lambdas/nodejs/cloudfront-csp/README.md (100%) rename backend/{compact-connect => compact-connect-ui-app}/lambdas/nodejs/cloudfront-csp/index.js (100%) rename backend/{compact-connect => compact-connect-ui-app}/lambdas/nodejs/cloudfront-csp/test/config/index.js (100%) rename backend/{compact-connect => compact-connect-ui-app}/lambdas/nodejs/cloudfront-csp/test/index.test.js (100%) create mode 100644 backend/compact-connect-ui-app/lambdas/nodejs/eslint.config.mjs create mode 100644 backend/compact-connect-ui-app/lambdas/nodejs/jest.config.mjs create mode 100644 backend/compact-connect-ui-app/lambdas/nodejs/package.json create mode 100644 backend/compact-connect-ui-app/lambdas/nodejs/tsconfig.json create mode 100644 backend/compact-connect-ui-app/lambdas/nodejs/yarn.lock create mode 100644 backend/compact-connect-ui-app/pipeline/__init__.py create mode 100644 backend/compact-connect-ui-app/pipeline/design/.!35012!pipeline-architecture.pdf create mode 100644 backend/compact-connect-ui-app/pipeline/design/README.md create mode 100644 backend/compact-connect-ui-app/pipeline/design/pipeline-architecture.pdf rename backend/{compact-connect => compact-connect-ui-app}/pipeline/frontend_pipeline.py (100%) rename backend/{compact-connect => compact-connect-ui-app}/pipeline/frontend_stage.py (99%) create mode 100644 backend/compact-connect-ui-app/pipeline/synth_substitute_stack.py create mode 100644 backend/compact-connect-ui-app/pipeline/synth_substitute_stage.py create mode 100644 backend/compact-connect-ui-app/requirements-dev.in create mode 100644 backend/compact-connect-ui-app/requirements-dev.txt create mode 100644 backend/compact-connect-ui-app/requirements.in create mode 100644 backend/compact-connect-ui-app/requirements.txt create mode 100644 backend/compact-connect-ui-app/ruff.toml create mode 100644 backend/compact-connect-ui-app/stacks/__init__.py rename backend/{compact-connect => compact-connect-ui-app}/stacks/frontend_deployment_stack/__init__.py (100%) rename backend/{compact-connect => compact-connect-ui-app}/stacks/frontend_deployment_stack/deployment.py (100%) rename backend/{compact-connect => compact-connect-ui-app}/stacks/frontend_deployment_stack/distribution.py (100%) create mode 100644 backend/compact-connect-ui-app/tests/__init__.py create mode 100644 backend/compact-connect-ui-app/tests/app/__init__.py create mode 100644 backend/compact-connect-ui-app/tests/app/base.py create mode 100644 backend/compact-connect-ui-app/tests/app/test_pipeline.py rename backend/{compact-connect => compact-connect-ui-app}/tests/resources/snapshots/BetaFrontend-FrontendDeploymentStack-UI_BUCKET.json (100%) rename backend/{compact-connect => compact-connect-ui-app}/tests/resources/snapshots/BetaFrontend-FrontendDeploymentStack-UI_DISTRIBUTION.json (100%) rename backend/{compact-connect => compact-connect-ui-app}/tests/resources/snapshots/BetaFrontend-FrontendDeploymentStack-UI_DISTRIBUTION_LAMBDA_FUNCTION.json (100%) rename backend/{compact-connect => compact-connect-ui-app}/tests/resources/snapshots/ProdFrontend-FrontendDeploymentStack-UI_BUCKET.json (100%) rename backend/{compact-connect => compact-connect-ui-app}/tests/resources/snapshots/ProdFrontend-FrontendDeploymentStack-UI_DISTRIBUTION.json (100%) rename backend/{compact-connect => compact-connect-ui-app}/tests/resources/snapshots/ProdFrontend-FrontendDeploymentStack-UI_DISTRIBUTION_LAMBDA_FUNCTION.json (100%) rename backend/{compact-connect => compact-connect-ui-app}/tests/resources/snapshots/TestFrontend-FrontendDeploymentStack-UI_BUCKET.json (100%) rename backend/{compact-connect => compact-connect-ui-app}/tests/resources/snapshots/TestFrontend-FrontendDeploymentStack-UI_DISTRIBUTION.json (100%) rename backend/{compact-connect => compact-connect-ui-app}/tests/resources/snapshots/TestFrontend-FrontendDeploymentStack-UI_DISTRIBUTION_LAMBDA_FUNCTION.json (100%) create mode 100644 backend/compact-connect-ui-app/tests/resources/test_files/.!35287!military_affiliation.pdf create mode 100644 backend/compact-connect-ui-app/tests/resources/test_files/military_affiliation.pdf rename backend/{compact-connect => compact-connect-ui-app}/tests/resources/test_ui_directory/index.html (100%) create mode 100755 backend/compact-connect/bin/compile_requirements.sh create mode 100755 backend/compact-connect/bin/run_python_tests.py create mode 100755 backend/compact-connect/bin/run_tests.sh create mode 100755 backend/compact-connect/bin/sync_deps.sh create mode 100644 backend/compact-connect/common_constructs/README.md delete mode 100644 backend/compact-connect/lambdas/nodejs/cloudfront-csp/yarn.lock delete mode 100644 backend/compact-connect/lambdas/python/compact-configuration/.coveragerc delete mode 100644 backend/compact-connect/lambdas/python/custom-resources/.coveragerc delete mode 100644 backend/compact-connect/lambdas/python/data-events/.coveragerc delete mode 100644 backend/compact-connect/lambdas/python/provider-data-v1/.coveragerc delete mode 100644 backend/compact-connect/lambdas/python/purchases/.coveragerc delete mode 100644 backend/compact-connect/lambdas/python/staff-user-pre-token/.coveragerc delete mode 100644 backend/compact-connect/lambdas/python/staff-users/.coveragerc create mode 100644 backend/compact-connect/ruff.toml rename backend/{compact-connect/lambdas/python/common => multi-account}/.coveragerc (51%) create mode 100755 backend/multi-account/bin/compile_requirements.sh create mode 100755 backend/multi-account/bin/run_python_tests.py create mode 100755 backend/multi-account/bin/run_tests.sh create mode 100755 backend/multi-account/bin/sync_deps.sh create mode 100644 backend/multi-account/ruff.toml create mode 100644 backend/social-work-app/README.md diff --git a/.github/workflows/check-common-cdk.yml b/.github/workflows/check-common-cdk.yml new file mode 100644 index 000000000..80d381f9b --- /dev/null +++ b/.github/workflows/check-common-cdk.yml @@ -0,0 +1,56 @@ +name: Check-Common-CDK + +on: + pull_request: + paths: + - backend/common-cdk/** + +env: + AWS_REGION : "us-east-1" + +# Permission can be added at job level or workflow level +permissions: + id-token: write # This is required for requesting the JWT + contents: read # This is required for actions/checkout + +jobs: + LintPython: + runs-on: ubuntu-latest + steps: + - uses: actions/setup-python@v5 + with: + python-version: '3.12' + + # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it + - uses: actions/checkout@v2 + + - name: Install dev dependencies + run: "pip install -r backend/common-cdk/requirements-dev.in" + + - name: Lint Code + run: "cd backend/common-cdk; ruff check $(git ls-files '*.py')" + + - name: Check Dependencies + run: "pip-audit" + + TestApp: + runs-on: ubuntu-latest + steps: + # Checks-out the repository under $GITHUB_WORKSPACE + - uses: actions/checkout@v2 + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Install dev dependencies + run: "pip install -r backend/common-cdk/requirements-dev.in" + + - name: Install all Python dependencies + run: "pip install -r backend/common-cdk/requirements.in" + + - name: Test backend + # Start at 14%, because that's what our 'unit' coverage is, while we convert this to more stand-alone + # We will raise this up to 90 over time, to support these constructs more like a library + run: "cd backend/common-cdk; pytest tests --cov=common_constructs --cov-fail-under 14" diff --git a/.github/workflows/check-compact-connect-ui-app.yml b/.github/workflows/check-compact-connect-ui-app.yml new file mode 100644 index 000000000..915c2c2ad --- /dev/null +++ b/.github/workflows/check-compact-connect-ui-app.yml @@ -0,0 +1,108 @@ +name: Check-CompactConnect-UI-Infra + +on: + pull_request: + paths: + - backend/compact-connect-ui-app/** + +env: + AWS_REGION : "us-east-1" + +# Permission can be added at job level or workflow level +permissions: + id-token: write # This is required for requesting the JWT + contents: read # This is required for actions/checkout + +jobs: + LintPython: + runs-on: ubuntu-latest + steps: + - uses: actions/setup-python@v5 + with: + python-version: '3.12' + + # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it + - uses: actions/checkout@v2 + + - name: Install dev dependencies + run: "pip install -r backend/compact-connect-ui-app/requirements-dev.txt" + + - name: Lint Code + run: "cd backend/compact-connect-ui-app; ruff check $(git ls-files '*.py')" + + - name: Check Dependencies + run: "pip-audit" + + LintNode: + runs-on: ubuntu-latest + + steps: + # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it + - uses: actions/checkout@v2 + + - run: echo "💡 The ${{ github.repository }} repository has been cloned to the runner." + - run: echo "🖥️ The workflow is now ready to test your code on the runner." + + # Setup Node + - name: Setup Node + uses: actions/setup-node@v1 + with: + node-version: '22.1.0' + + # Use any cached yarn dependencies (saves build time) + - uses: actions/cache@v4 + with: + path: '**/node_modules' + key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }} + + # Install Yarn Dependencies + - name: Install Node.js dependencies + run: yarn install + working-directory: ./backend/compact-connect-ui-app/lambdas/nodejs + + # Run Linter Checks + - name: Run linter + run: yarn run lint + working-directory: ./backend/compact-connect-ui-app/lambdas/nodejs + + # Audit dependencies for vulnerabilities + - name: Audit dependencies (Cloudfront CSP Lambda) + run: yarn run audit:dependencies + working-directory: ./backend/compact-connect-ui-app/lambdas/nodejs + + TestApp: + runs-on: ubuntu-latest + steps: + # Checks-out the repository under $GITHUB_WORKSPACE + - uses: actions/checkout@v2 + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + + # Setup Node + - name: Setup Node + uses: actions/setup-node@v1 + with: + node-version: '22.1.0' + + # Use any cached yarn dependencies (saves build time) + - uses: actions/cache@v4 + with: + path: '**/node_modules' + key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }} + + # Install Yarn Dependencies + - name: Install Node.js dependencies + run: yarn install + working-directory: ./backend/compact-connect-ui-app/lambdas/nodejs + + - name: Install dev dependencies + run: "pip install -r backend/compact-connect-ui-app/requirements-dev.txt" + + - name: Install all Python dependencies + run: "cd backend/compact-connect-ui-app; bin/sync_deps.sh" + + - name: Test backend + run: "cd backend/compact-connect-ui-app; bin/run_tests.sh -l all -no" diff --git a/.github/workflows/check-compact-connect.yml b/.github/workflows/check-compact-connect.yml new file mode 100644 index 000000000..267fbf577 --- /dev/null +++ b/.github/workflows/check-compact-connect.yml @@ -0,0 +1,108 @@ +name: Check-CompactConnect + +on: + pull_request: + paths: + - backend/compact-connect/** + +env: + AWS_REGION : "us-east-1" + +# Permission can be added at job level or workflow level +permissions: + id-token: write # This is required for requesting the JWT + contents: read # This is required for actions/checkout + +jobs: + LintPython: + runs-on: ubuntu-latest + steps: + - uses: actions/setup-python@v5 + with: + python-version: '3.12' + + # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it + - uses: actions/checkout@v2 + + - name: Install dev dependencies + run: "pip install -r backend/compact-connect/requirements-dev.txt" + + - name: Lint Code + run: "cd backend/compact-connect; ruff check $(git ls-files '*.py')" + + - name: Check Dependencies + run: "pip-audit" + + LintNode: + runs-on: ubuntu-latest + + steps: + # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it + - uses: actions/checkout@v2 + + - run: echo "💡 The ${{ github.repository }} repository has been cloned to the runner." + - run: echo "🖥️ The workflow is now ready to test your code on the runner." + + # Setup Node + - name: Setup Node + uses: actions/setup-node@v1 + with: + node-version: '22.1.0' + + # Use any cached yarn dependencies (saves build time) + - uses: actions/cache@v4 + with: + path: '**/node_modules' + key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }} + + # Install Yarn Dependencies + - name: Install Node.js dependencies + run: yarn install + working-directory: ./backend/compact-connect/lambdas/nodejs + + # Run Linter Checks + - name: Run linter + run: yarn run lint + working-directory: ./backend/compact-connect/lambdas/nodejs + + # Audit dependencies for vulnerabilities + - name: Audit dependencies + run: yarn run audit:dependencies + working-directory: ./backend/compact-connect/lambdas/nodejs + + TestApp: + runs-on: ubuntu-latest + steps: + # Checks-out the repository under $GITHUB_WORKSPACE + - uses: actions/checkout@v2 + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + + # Setup Node + - name: Setup Node + uses: actions/setup-node@v1 + with: + node-version: '22.1.0' + + # Use any cached yarn dependencies (saves build time) + - uses: actions/cache@v4 + with: + path: '**/node_modules' + key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }} + + # Install Yarn Dependencies + - name: Install Node.js dependencies + run: yarn install + working-directory: ./backend/compact-connect/lambdas/nodejs + + - name: Install dev dependencies + run: "pip install -r backend/compact-connect/requirements-dev.txt" + + - name: Install all Python dependencies + run: "cd backend/compact-connect; bin/sync_deps.sh" + + - name: Test backend + run: "cd backend/compact-connect; bin/run_tests.sh -l all -no" diff --git a/.github/workflows/check-lambda-js.yml b/.github/workflows/check-lambda-js.yml deleted file mode 100644 index d32e93710..000000000 --- a/.github/workflows/check-lambda-js.yml +++ /dev/null @@ -1,66 +0,0 @@ -# Compact Connect - Lambda JavaScript Checks - -name: Check-Lambda-JS - -# Controls when the action will run. -on: - # Triggers the workflow on pull requests to trunk branches involving changes to lambda javascript - pull_request: - branches: - - main - - development - - ia-web-development - - ia-development - paths: - - backend/compact-connect/lambdas/nodejs/** - - # Allows you to run this workflow manually from the Actions tab - workflow_dispatch: - -# A workflow run is made up of one or more jobs that can run sequentially or in parallel -jobs: - CheckLambdas: - runs-on: ubuntu-latest - - steps: - - run: echo "🎉 The job was automatically triggered by a ${{ github.event_name }} event." - - run: echo "🐧 This job is now running on a ${{ runner.os }} server hosted by GitHub!" - - run: echo "🔎 The name of your branch is ${{ github.ref }} and your repository is ${{ github.repository }}." - - # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - - uses: actions/checkout@v2 - - - run: echo "💡 The ${{ github.repository }} repository has been cloned to the runner." - - run: echo "🖥️ The workflow is now ready to test your code on the runner." - - # Setup Node - - name: Setup Node - uses: actions/setup-node@v1 - with: - node-version: '22.1.0' - - # Use any cached yarn dependencies (saves build time) - - uses: actions/cache@v4 - with: - path: '**/node_modules' - key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }} - - # Install Yarn Dependencies - - name: Install JS dependencies (Cloudfront CSP Lambda) - run: yarn install - working-directory: ./backend/compact-connect/lambdas/nodejs - - # Run Linter Checks - - name: Run linter (Cloudfront CSP Lambda) - run: yarn run lint - working-directory: ./backend/compact-connect/lambdas/nodejs - - # Run Unit Tests - - name: Run unit tests (Cloudfront CSP Lambda) - run: yarn test - working-directory: ./backend/compact-connect/lambdas/nodejs - - # Audit dependencies for vulnerabilities - - name: Audit dependencies (Cloudfront CSP Lambda) - run: yarn run audit:dependencies - working-directory: ./backend/compact-connect/lambdas/nodejs diff --git a/.github/workflows/check-python.yml b/.github/workflows/check-multi-account.yml similarity index 62% rename from .github/workflows/check-python.yml rename to .github/workflows/check-multi-account.yml index c3606048a..ca7cf7f9b 100644 --- a/.github/workflows/check-python.yml +++ b/.github/workflows/check-multi-account.yml @@ -1,9 +1,9 @@ -name: Check-Python +name: Check-Multi-Account on: pull_request: paths: - - backend/** + - backend/multi-account/** env: AWS_REGION : "us-east-1" @@ -25,30 +25,30 @@ jobs: - uses: actions/checkout@v2 - name: Install dev dependencies - run: "pip install -r backend/compact-connect/requirements-dev.txt" + run: "pip install -r backend/multi-account/control-tower/requirements-dev.txt" - name: Lint Code - run: "cd backend; ruff check $(git ls-files '*.py')" + run: "cd backend/multi-account; ruff check $(git ls-files '*.py')" - TestPython: + - name: Check Dependencies + run: "pip-audit" + + TestApps: runs-on: ubuntu-latest steps: + # Checks-out the repository under $GITHUB_WORKSPACE + - uses: actions/checkout@v2 + - name: Setup Python uses: actions/setup-python@v5 with: python-version: '3.12' - # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - - uses: actions/checkout@v2 - - name: Install dev dependencies - run: "pip install -r backend/compact-connect/requirements-dev.txt" + run: "pip install -r backend/multi-account/control-tower/requirements-dev.txt" - - name: Install all dependencies - run: "cd backend; bin/sync_deps.sh" + - name: Install all Python dependencies + run: "cd backend/multi-account; bin/sync_deps.sh" - name: Test backend - run: "cd backend; bin/run_tests.sh -l python -no" - - - name: Check Dependencies - run: "pip-audit" + run: "cd backend/multi-account; bin/run_tests.sh -l all -no" diff --git a/backend/bin/compile_requirements.sh b/backend/bin/compile_requirements.sh deleted file mode 100755 index c64ad58c6..000000000 --- a/backend/bin/compile_requirements.sh +++ /dev/null @@ -1,31 +0,0 @@ -set -e - -pip-compile --no-emit-index-url --upgrade --no-strip-extras multi-account/backups/requirements-dev.in -pip-compile --no-emit-index-url --upgrade --no-strip-extras multi-account/backups/requirements.in -pip-compile --no-emit-index-url --upgrade --no-strip-extras multi-account/control-tower/requirements-dev.in -pip-compile --no-emit-index-url --upgrade --no-strip-extras multi-account/control-tower/requirements.in -pip-compile --no-emit-index-url --upgrade --no-strip-extras multi-account/log-aggregation/requirements-dev.in -pip-compile --no-emit-index-url --upgrade --no-strip-extras multi-account/log-aggregation/requirements.in -pip-compile --no-emit-index-url --upgrade --no-strip-extras compact-connect/requirements-dev.in -pip-compile --no-emit-index-url --upgrade --no-strip-extras compact-connect/requirements.in -pip-compile --no-emit-index-url --upgrade --no-strip-extras compact-connect/lambdas/python/cognito-backup/requirements-dev.in -pip-compile --no-emit-index-url --upgrade --no-strip-extras compact-connect/lambdas/python/cognito-backup/requirements.in -pip-compile --no-emit-index-url --upgrade --no-strip-extras compact-connect/lambdas/python/compact-configuration/requirements-dev.in -pip-compile --no-emit-index-url --upgrade --no-strip-extras compact-connect/lambdas/python/compact-configuration/requirements.in -pip-compile --no-emit-index-url --upgrade --no-strip-extras compact-connect/lambdas/python/common/requirements-dev.in -pip-compile --no-emit-index-url --upgrade --no-strip-extras compact-connect/lambdas/python/common/requirements.in -pip-compile --no-emit-index-url --upgrade --no-strip-extras compact-connect/lambdas/python/custom-resources/requirements-dev.in -pip-compile --no-emit-index-url --upgrade --no-strip-extras compact-connect/lambdas/python/custom-resources/requirements.in -pip-compile --no-emit-index-url --upgrade --no-strip-extras compact-connect/lambdas/python/data-events/requirements-dev.in -pip-compile --no-emit-index-url --upgrade --no-strip-extras compact-connect/lambdas/python/data-events/requirements.in -pip-compile --no-emit-index-url --upgrade --no-strip-extras compact-connect/lambdas/python/disaster-recovery/requirements-dev.in -pip-compile --no-emit-index-url --upgrade --no-strip-extras compact-connect/lambdas/python/disaster-recovery/requirements.in -pip-compile --no-emit-index-url --upgrade --no-strip-extras compact-connect/lambdas/python/provider-data-v1/requirements-dev.in -pip-compile --no-emit-index-url --upgrade --no-strip-extras compact-connect/lambdas/python/provider-data-v1/requirements.in -pip-compile --no-emit-index-url --upgrade --no-strip-extras compact-connect/lambdas/python/purchases/requirements-dev.in -pip-compile --no-emit-index-url --upgrade --no-strip-extras compact-connect/lambdas/python/purchases/requirements.in -pip-compile --no-emit-index-url --upgrade --no-strip-extras compact-connect/lambdas/python/staff-user-pre-token/requirements-dev.in -pip-compile --no-emit-index-url --upgrade --no-strip-extras compact-connect/lambdas/python/staff-user-pre-token/requirements.in -pip-compile --no-emit-index-url --upgrade --no-strip-extras compact-connect/lambdas/python/staff-users/requirements-dev.in -pip-compile --no-emit-index-url --upgrade --no-strip-extras compact-connect/lambdas/python/staff-users/requirements.in -bin/sync_deps.sh diff --git a/backend/bin/pre-commit b/backend/bin/pre-commit index 038b6fbb1..e0a6859f5 100755 --- a/backend/bin/pre-commit +++ b/backend/bin/pre-commit @@ -7,6 +7,8 @@ # # To enable this hook, move this file to ".git/hooks/pre-commit". +# TODO: Investigate a reasonable pre-commit testing approach with multi-service + if git rev-parse --verify HEAD >/dev/null 2>&1 then against=HEAD diff --git a/backend/bin/sync_deps.sh b/backend/bin/sync_deps.sh deleted file mode 100755 index c987ad9af..000000000 --- a/backend/bin/sync_deps.sh +++ /dev/null @@ -1,34 +0,0 @@ -( - cd compact-connect/lambdas/nodejs - yarn install -) - -pip-sync \ - multi-account/backups/requirements-dev.txt \ - multi-account/backups/requirements.txt \ - multi-account/control-tower/requirements-dev.txt \ - multi-account/control-tower/requirements.txt \ - multi-account/log-aggregation/requirements-dev.txt \ - multi-account/log-aggregation/requirements.txt \ - compact-connect/requirements-dev.txt \ - compact-connect/requirements.txt \ - compact-connect/lambdas/python/cognito-backup/requirements-dev.txt \ - compact-connect/lambdas/python/cognito-backup/requirements.txt \ - compact-connect/lambdas/python/compact-configuration/requirements-dev.txt \ - compact-connect/lambdas/python/compact-configuration/requirements.txt \ - compact-connect/lambdas/python/common/requirements-dev.txt \ - compact-connect/lambdas/python/common/requirements.txt \ - compact-connect/lambdas/python/custom-resources/requirements-dev.txt \ - compact-connect/lambdas/python/custom-resources/requirements.txt \ - compact-connect/lambdas/python/data-events/requirements-dev.txt \ - compact-connect/lambdas/python/data-events/requirements.txt \ - compact-connect/lambdas/python/disaster-recovery/requirements-dev.txt \ - compact-connect/lambdas/python/disaster-recovery/requirements.txt \ - compact-connect/lambdas/python/provider-data-v1/requirements-dev.txt \ - compact-connect/lambdas/python/provider-data-v1/requirements.txt \ - compact-connect/lambdas/python/purchases/requirements-dev.txt \ - compact-connect/lambdas/python/purchases/requirements.txt \ - compact-connect/lambdas/python/staff-user-pre-token/requirements-dev.txt \ - compact-connect/lambdas/python/staff-user-pre-token/requirements.txt \ - compact-connect/lambdas/python/staff-users/requirements-dev.txt \ - compact-connect/lambdas/python/staff-users/requirements.txt diff --git a/backend/common-cdk/common_constructs/README.md b/backend/common-cdk/common_constructs/README.md new file mode 100644 index 000000000..ca7bb143d --- /dev/null +++ b/backend/common-cdk/common_constructs/README.md @@ -0,0 +1,9 @@ +# Common Constructs + +This is the root node of a +[namespace package](https://docs.python.org/3/reference/import.html#reference-namespace-package), which houses common +CDK constructs that are used across different apps in the CompactConnect project. Modules in this package will be +merged in Python with similarly-named namespace packages in each app's specific folder. + +> **Note: Do not add an `__init__.py` file to any of the `common_constructs` packages, or they will break the +> import behavior. diff --git a/backend/compact-connect/common_constructs/access_logs_bucket.py b/backend/common-cdk/common_constructs/access_logs_bucket.py similarity index 100% rename from backend/compact-connect/common_constructs/access_logs_bucket.py rename to backend/common-cdk/common_constructs/access_logs_bucket.py diff --git a/backend/compact-connect/common_constructs/alarm_topic.py b/backend/common-cdk/common_constructs/alarm_topic.py similarity index 100% rename from backend/compact-connect/common_constructs/alarm_topic.py rename to backend/common-cdk/common_constructs/alarm_topic.py diff --git a/backend/compact-connect/common_constructs/bucket.py b/backend/common-cdk/common_constructs/bucket.py similarity index 100% rename from backend/compact-connect/common_constructs/bucket.py rename to backend/common-cdk/common_constructs/bucket.py diff --git a/backend/compact-connect/common_constructs/frontend_app_config_utility.py b/backend/common-cdk/common_constructs/frontend_app_config_utility.py similarity index 100% rename from backend/compact-connect/common_constructs/frontend_app_config_utility.py rename to backend/common-cdk/common_constructs/frontend_app_config_utility.py diff --git a/backend/compact-connect/common_constructs/security_profile.py b/backend/common-cdk/common_constructs/security_profile.py similarity index 100% rename from backend/compact-connect/common_constructs/security_profile.py rename to backend/common-cdk/common_constructs/security_profile.py diff --git a/backend/compact-connect/common_constructs/service_principal_name.py b/backend/common-cdk/common_constructs/service_principal_name.py similarity index 100% rename from backend/compact-connect/common_constructs/service_principal_name.py rename to backend/common-cdk/common_constructs/service_principal_name.py diff --git a/backend/compact-connect/common_constructs/slack_channel_configuration.py b/backend/common-cdk/common_constructs/slack_channel_configuration.py similarity index 100% rename from backend/compact-connect/common_constructs/slack_channel_configuration.py rename to backend/common-cdk/common_constructs/slack_channel_configuration.py diff --git a/backend/compact-connect/common_constructs/stack.py b/backend/common-cdk/common_constructs/stack.py similarity index 100% rename from backend/compact-connect/common_constructs/stack.py rename to backend/common-cdk/common_constructs/stack.py diff --git a/backend/compact-connect/common_constructs/webacl.py b/backend/common-cdk/common_constructs/webacl.py similarity index 100% rename from backend/compact-connect/common_constructs/webacl.py rename to backend/common-cdk/common_constructs/webacl.py diff --git a/backend/common-cdk/requirements-dev.in b/backend/common-cdk/requirements-dev.in new file mode 100644 index 000000000..34e2caeca --- /dev/null +++ b/backend/common-cdk/requirements-dev.in @@ -0,0 +1,6 @@ +pytest>=6.2.5 +pytest-cov +coverage +ruff +pip-tools +pip-audit diff --git a/backend/common-cdk/requirements.in b/backend/common-cdk/requirements.in new file mode 100644 index 000000000..fc655459b --- /dev/null +++ b/backend/common-cdk/requirements.in @@ -0,0 +1,4 @@ +aws-cdk-lib>=2.142.0 +aws-cdk-aws-lambda-python-alpha>=2.142.0a0 +constructs>=10.0.0,<11.0.0 +cdk-nag>=2.28.10, <3 diff --git a/backend/common-cdk/ruff.toml b/backend/common-cdk/ruff.toml new file mode 100644 index 000000000..66682be80 --- /dev/null +++ b/backend/common-cdk/ruff.toml @@ -0,0 +1,112 @@ +# Exclude a variety of commonly ignored directories. +exclude = [ + ".bzr", + ".direnv", + ".eggs", + ".git", + ".git-rewrite", + ".hg", + ".ipynb_checkpoints", + ".mypy_cache", + ".nox", + ".pants.d", + ".pyenv", + ".pytest_cache", + ".pytype", + ".ruff_cache", + ".svn", + ".tox", + ".venv", + ".vscode", + "__pypackages__", + "_build", + "buck-out", + "build", + "dist", + "node_modules", + "site-packages", + "venv" +] + +line-length = 120 +indent-width = 4 + +# Assume Python 3.12 +target-version = "py312" + +[lint] +select = [ + "A", # flake8-builtins + "ARG", # flake8-unused-arguments + "B", # flake8-bugbear + "BLE", # flake8-blind-except + "DTZ", # flake8-datetimez + "E", # pycodestyle + "F", # Pyflakes + "FIX", # flake8-fixme + "FA", # flake8-future-annotations + "G", # flake8-logging-format + "I", # isort + "N", # pep8-naming + "PIE", # flake8-pie + "SLF", # flake8-self + "RET", # flake8-return + "RSE", # flake8-raise + "S", # flake8-bandit + "T", # flake8-print + "UP", # pyupgrade + "W", # warning +] +ignore = [ + "S311", # suspicious-non-cryptographic-random-usage + "N818", # error-suffix-on-exception-name + # the following are ignored by recommendation of ruff documentation + # due to conflicts with the ruff formatter see https://docs.astral.sh/ruff/formatter/#conflicting-lint-rules + "W191", # indentation contains tabs + "E111", # indentation-with-invalid-multiple + "E114", # indentation-with-invalid-multiple-comment + "E117", # over-indented + "D206", # indent-with-spaces + "D300", # triple-single-quotes + "Q000", # bad-quotes-inline-string + "Q001", # bad-quotes-multiline-string + "Q002", # bad-quotes-docstring + "Q003", # avoidable-escaped-quote + "COM812", # missing-trailing-comma + "COM819", # prohibited-trailing-comma + "ISC001", # single-line-implicit-string-concatenation + "ISC002", # multi-line-implicit-string-concatenation +] + +# Allow fix for all enabled rules (when `--fix`) is provided. +fixable = ["ALL"] +unfixable = [] + +# Allow unused variables when underscore-prefixed. +dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" + +[format] +quote-style = "single" + +# Like Black, indent with spaces, rather than tabs. +indent-style = "space" + +# Like Black, respect magic trailing commas. +skip-magic-trailing-comma = false + +# Like Black, automatically detect the appropriate line ending. +line-ending = "auto" + +# Enable auto-formatting of code examples in docstrings. Markdown, +# reStructuredText code/literal blocks and doctests are all supported. +# +# This is currently disabled by default, but it is planned for this +# to be opt-out in the future. +docstring-code-format = false + +# Set the line length limit used when formatting code snippets in +# docstrings. +# +# This only has an effect when the `docstring-code-format` setting is +# enabled. +docstring-code-line-length = "dynamic" diff --git a/backend/compact-connect/common_constructs/__init__.py b/backend/common-cdk/tests/__init__.py similarity index 100% rename from backend/compact-connect/common_constructs/__init__.py rename to backend/common-cdk/tests/__init__.py diff --git a/backend/compact-connect/tests/common_constructs/test_alarm_topic.py b/backend/common-cdk/tests/test_alarm_topic.py similarity index 99% rename from backend/compact-connect/tests/common_constructs/test_alarm_topic.py rename to backend/common-cdk/tests/test_alarm_topic.py index 08ab26ce0..c7375210f 100644 --- a/backend/compact-connect/tests/common_constructs/test_alarm_topic.py +++ b/backend/common-cdk/tests/test_alarm_topic.py @@ -5,6 +5,7 @@ from aws_cdk.aws_chatbot import CfnSlackChannelConfiguration from aws_cdk.aws_kms import Key from aws_cdk.aws_sns import CfnSubscription, CfnTopic + from common_constructs.alarm_topic import AlarmTopic diff --git a/backend/.coveragerc b/backend/compact-connect-ui-app/.coveragerc similarity index 93% rename from backend/.coveragerc rename to backend/compact-connect-ui-app/.coveragerc index 9b9fe13dc..6e6498b9e 100644 --- a/backend/.coveragerc +++ b/backend/compact-connect-ui-app/.coveragerc @@ -5,6 +5,7 @@ include = data_file = .coverage omit = + */bin/* */cdk.out/* */smoke-test/* */tests/* diff --git a/backend/compact-connect-ui-app/README.md b/backend/compact-connect-ui-app/README.md new file mode 100644 index 000000000..c577ba4f0 --- /dev/null +++ b/backend/compact-connect-ui-app/README.md @@ -0,0 +1,318 @@ +# Compact Connect - Backend developer documentation + +## Looking for technical user documentation? +[Find it here](./docs/README.md) + +## Introduction + +This is an [AWS-CDK](https://aws.amazon.com/cdk/) based project for the backend components of the licensure compact system. + +## Table of Contents +- **[Prerequisites](#prerequisites)** +- **[Environment Config](#environment-config)** +- **[Installing Dependencies](#installing-dependencies)** +- **[Local Development](#local-development)** +- **[Tests](#tests)** +- **[Deployment](#deployment)** +- **[Google reCAPTCHA Setup](#google-recaptcha-setup)** +- **[Decommissioning](#decommissioning)** +- **[More Info](#more-info)** + +## Prerequisites +[Back to top](#compact-connect---backend-developer-documentation) + +To deploy this app, you will need: +1) Access to an AWS account +2) Python>=3.12 installed on your machine, preferably through a virtual environment management tool like [pyenv](https://github.com/pyenv/pyenv), + for clean management of virtual environments across multiple Python versions. +3) Otherwise, follow the [Prerequisites section](https://cdkworkshop.com/15-prerequisites.html) of the CDK workshop to + prepare your system to work with AWS-CDK, including a NodeJS install. +4) Follow the steps in the [Installing Dependencies](#installing-dependencies) section. + +## Environment Config +[Back to top](#compact-connect---backend-developer-documentation) + +The `cdk.json` file tells the CDK Toolkit how to execute your app, including configuration specific to any given target +deployment. You can add local configuration that will be merged into the `cdk.json['context']` values with a +`cdk.context.json` file that you will not check in. + +This project is set up like a standard Python project. To use it, create and activate a python virtual environment +using the tools of your choice (`pyenv` and `venv` are common). + +Once the virtualenv is activated, you can install the required dependencies. + +## Installing Dependencies +[Back to top](#compact-connect---backend-developer-documentation) + +Python requirements are pinned in [`requirements.txt`](requirements.txt). Install them using `pip`: + +``` +$ pip install -r requirements.txt +``` + +Node.js requirements (for some selected Lambda runtimes) are defined in [`package.json`](./lambdas/nodejs). Install them using `yarn`. + +```shell +$ cd lambdas/nodejs +$ yarn install +``` + +At this point you can now synthesize the CloudFormation template(s) for this code. + +``` +$ cdk synth +``` + +For development work there are additional requirements in `requirements-dev.txt` to install with +`pip install -r requirements-dev.txt`. + +To add additional dependencies, for example other CDK libraries, just add them to the `requirements.in` file and rerun +`pip-compile requirements.in`, then `pip install -r requirements.txt` command. + +## Local Development +[Back to top](#compact-connect---backend-developer-documentation) + +Local development can be done by editing the python code and `cdk.json`. For development purposes, this is simply a +Python project that can be exercised with local tests. Be sure to install the development requirements: + +``` +pip install -r requirements-dev.txt +``` + +Note that this project is a cloud-native app that has many small modular runtimes and integrations. Simulating that +distributed environment locally is not feasible, so the project relies heavily on test-driven development and solid +unit/functional tests to be incorporated in the development workflow. If you want to deploy this app to see how it runs +in the cloud, you can do so by configuring context for your own sandbox AWS account with context variables in +`cdk.context.json` and running the appropriate `cdk deploy` command. + +Once the deployment completes, you may want to run a local frontend. To do so, you must [populate a `.env` +file](../../webroot/README.md#environment-variables) with data on certain AWS resources (for example, AWS Cognito auth +domains and client IDs). A quick way to do that is to run `bin/fetch_aws_resources.py --as-env` from the +`backend/compact-connect` directory and copy/paste the output into `webroot/.env`. To see more data on your deployment +in human-readable format (for example, DynamoDB table names), run `bin/fetch_aws_resources.py` without any additional +flags. + +## Tests +[Back to top](#compact-connect---backend-developer-documentation) + +Being a cloud project whose infrastructure is written in Python, establishing tests, using the python `unittest` +library is critical to maintaining reliability and velocity. Be sure that any updates you add are covered +by tests, so we don't introduce bugs or cost time identifying testable bugs after deployment. Note that all +unit/functional tests bundled with this app should be designed to execute with zero requirements for environmental +setup (including environment variables) beyond simply installing the dependencies in `requirements*.txt` files. CDK +tests are defined under the [tests](./tests) directory. Runtime code tests should be similarly bundled within the +lambda folders. Code that is common across all lambdas should be tested in the `common-python` directory, to reduce +duplication and ensure consistency across the app. + +To execute the tests, simply run `bin/sync_deps.sh` then `bin/run_tests.sh` from the `backend` directory. + +## Documentation +[Back to top](#compact-connect---backend-developer-documentation) + +Keeping documentation current is an important part of feature development in this project. If the feature involves a non-trivial amount of architecture or other technical design, be sure that the design and considerations are captured in the [design documentation](./docs/design). If any updates are made to the API, be sure to follow these steps to keep the documentation current: +1) Export a fresh api specification (OAS 3.0) is exported from API Gateway and used to update [the Open API Specification JSON file](./docs/api-specification/latest-oas30.json). +2) Run `bin/trim_oas30.py` to organize and trim the API to include only supported API endpoints (and update the script itself, if needed). +3) If you exported the api specification from somewhere other than the CSG Test environment, be sure to set the `servers[0].url` entry back to the correct base URL for the CSG Test environment. +4) Use `bin/update_postman_collection.py` to update the [Postman Collection and Environment](./docs/postman), based on your new api spec, as appropriate. + +## Deployment +[Back to top](#compact-connect---backend-developer-documentation) + +### AWS Service Quota Increases +Before deploying to any environment (sandbox, test, beta, or production), you'll need to request service quota increases for the following AWS services: + +#### 1. Resource Servers Per User Pool (Amazon Cognito) +The Staff Users pool in CompactConnect uses resource servers for every jurisdiction (50+ states/territories). It also has resource servers for each compact to implement granular permission scopes. As detailed in [User Architecture documentation](./docs/design/README.md#user-architecture), resource server scopes are defined at both the jurisdiction level (ie for state administrators) and the compact level (ie for compact administrators), allowing for fine-grained access control tailored to each entity's specific needs. + +**Required Steps:** +1. Visit the [AWS Service Quotas console](https://console.aws.amazon.com/servicequotas/home) in each AWS account you'll be deploying to +2. Search for "Amazon Cognito User Pools" +3. Find "Resource servers per user pool" (default value is 25) +4. Request an increase to at least 100 resource servers per user pool +5. Wait for AWS to approve the increase before attempting deployment + +This increase gives sufficient capacity for all jurisdictions (50+ states/territories) plus all compacts, with room for future expansion. + +#### 2. Concurrent Executions (AWS Lambda) +CompactConnect uses numerous Lambda functions to power its backend services. By default, new AWS accounts have a very low concurrent execution limit. + +**Required Steps:** +1. Visit the [AWS Service Quotas console](https://console.aws.amazon.com/servicequotas/home) in each AWS account you'll be deploying to +2. Search for "AWS Lambda" +3. Find "Concurrent executions" (default value is 10 for new accounts) +4. Request an increase to at least 1,000 concurrent executions +5. Wait for AWS to approve the increase before attempting deployment + +This increase ensures that your Lambda functions can scale appropriately during periods of high traffic without throttling. + +### First deploy to a Sandbox environment +The very first deploy to a new environment (like your personal sandbox account) requires a few steps to fully set up +its environment: +1) *Optional:* Create a new Route53 HostedZone in your AWS sandbox account for the DNS domain name you want to use for + your app. See [About Route53 Hosted Zones](#about-route53-hosted-zones) for more. Note: Without this step, you will + not be able to log in to the UI hosted in CloudFront. The Oauth2 authentication process requires a predictable + callback url to be pre-configured, which the domain name provides. You can still run a local UI against this app, + so long as you leave the `allow_local_ui` context value set to `true` and remove the `domain_name` param in your environment's context. +2) *Optional if testing SES email notifications with custom domain:* By default, AWS does not allow sending emails to unverified email + addresses. If you need to test SES email notifications and do not want to request AWS to remove your account from + the SES sandbox, you will need to set up a verified SES email identity for each address you want to send emails to. + See [Creating an email address identity](https://docs.aws.amazon.com/ses/latest/dg/creating-identities.html#verify-email-addresses-procedure). Alternatively, you can request AWS to remove your account + from the SES sandbox, which will allow you to send emails to addresses that are not verified. See [SES Sandbox](https://docs.aws.amazon.com/ses/latest/dg/request-production-access.html). + If you do not specify the `domain_name` field in your environment context, cognito will use its default email configuration. + See [Default User Pool Email Settings](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-email.html#user-pool-email-default) +3) Copy [cdk.context.sandbox-example.json](./cdk.context.sandbox-example.json) to `cdk.context.json`. +4) At the top level of the JSON structure update the `"environment_name"` field to your own name. +5) Update the environment entry under `ssm_context.environments` to your own name and your own AWS sandbox account id (which you can find by following these [these instructions](https://docs.aws.amazon.com/accounts/latest/reference/manage-acct-identifiers.html#FindAccountId)), + and domain name, if you set one up. **If you opted not to create a HostedZone, remove the `domain_name` field.** + The key under `environments` must match the value you put under `environment_name`. +6) Configure your aws cli to authenticate against your own account. There are several ways to do this based on the + type of authentication you use to login to your account. See the [AWS CLI Configuration Guide](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-quickstart.html). +7) Complete the [Google reCAPTCHA Setup](#google-recaptcha-setup) steps for your sandbox environment. +8) Run `cdk bootstrap` to add some base CDK support infrastructure to your AWS account. +9) Run `cdk deploy 'Sandbox/*'` to get the initial backend stack resources deployed. +10) *Optional:* If you have a domain name configured for your sandbox environment, once the backend stacks have successfully deployed, you can deploy the frontend UI by setting the 'deploy_sandbox_ui' context field to `true` and run `cdk deploy 'SandboxUI/*'`. If you do not have a domain name configured, you can still run the UI from local host (see the README.md under the webroot folder for more information about running the app on localhost). If you do have a domain name configured, the application will be accessible at the 'app' subdomain of the configured domain name (e.g., app.[configured_domain.com]). + +### Subsequent sandbox deploys: +For any future deploys, everything is set up so a simple `cdk deploy 'Sandbox/*'` should update all your infrastructure +to reflect the changes in your code. Full deployment steps are: +1) Make sure your python environment is active. +2) Run `bin/sync_deps.sh` from `backend/` to ensure you have the latest requirements installed. +3) Configure your aws cli to authenticate against your own account. +4) Run `cdk deploy 'Sandbox/*'` to deploy the app to your AWS account. + +### Verifying SES configuration for Cognito User Notifications +If your account is in the SES sandbox, the simplest way to verify that SES is integrated with your cognito user pool is +to first go the AWS SES console and create an SES verified email identity for the email address you want to send a test +message to, See [Creating an email address identity](https://docs.aws.amazon.com/ses/latest/dg/creating-identities.html#verify-email-addresses-procedure). + +Once you have verified your email address, go to the AWS Cognito console and find your user pool. From there, you have +the option to create a new user using your verified email address, and select the option to send an email invite. Once +you create the user, you should receive an email notification from Cognito, and you can verify that +the FROM address is using your custom domain. The DMARC authentication will reject any emails from your domain that are +not properly configured using SPF and DKIM, so if you get the email notification from Cognito, you've verified that the +authentication is working as expected. + +### First deploy to the production environment +The production environment requires a few steps to fully set up before deploys can be automated. Refer to the +[README.md](../multi-account/README.md) for details on setting up a full multi-account architecture environment. Once +that is done, perform the following steps to deploy the CI/CD pipelines into the appropriate AWS account: +- Complete the [Google reCAPTCHA Setup](#google-recaptcha-setup) steps for each environment you will be deploying to (test, beta, prod). Use the appropriate domain name for the environment (ie `app.test.compactconnect.org` for test environment, `app.beta.compactconnect.org` for beta environment, `app.compactconnect.org` for production). For the production environment, make sure to complete the billing setup steps as well. +- Have someone who has suitable permissions in the GitHub organization that hosts this code navigate to the AWS Console + for the Deploy account, go to the + [AWS CodeStar Connections](https://us-east-1.console.aws.amazon.com/codesuite/settings/connections) page and create a + connection that grants AWS permission to receive GitHub events. Note the ARN of the resulting connection for + the next step. +- Create a new Route53 hosted zone for the domain name you plan to use for the app in each of the production, beta, and test AWS + accounts. See [About Route53 hosted zones](#about-route53-hosted-zones) below for more detail. +- Request AWS to remove your account from the SES sandbox and wait for them to complete this request. + See [SES Sandbox](https://docs.aws.amazon.com/ses/latest/dg/request-production-access.html). +- With the [aws-cli](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html), set up your local machine to authenticate against the Deploy account as an administrator. +- For every environment, copy the appropriate example context file (`cdk.context.deploy-example.json` for the `Deploy` account, `cdk.context.test-example.json` for the `Test` account, `cdk.context.beta-example.json` for the `Beta` account, or `cdk.context.prod-example.json` for the `Prod` account) to `cdk.context.json` and update the values to match your respective accounts and other identifiers, including the code star connection you just created to match the identifiers for your actual accounts and resources. You will then need to run the `bin/put_ssm_context.sh ` script to push relevant content from your `cdk.context.json` script into an SSM Parameter in your Deploy account. Replace `` with the target environment. For example, to set up for the test environment: `bin/put_ssm_context.sh test`. + For example, to set up for the test environment: `bin/put_ssm_context.sh test`. +- Optional: If a Slack integration is desired for operational support, have someone with permission to install Slack + apps in your workspace and Admin access to each of the Test, Beta, Prod, and Deploy accounts log into each AWS account + and go to the Chatbot service. Select 'Slack' under the **Configure a chat client** box and click **Configure + client**, then follow the Slack authorization prompts. This will authorize AWS to integrate with the channels you + identify in your `cdk.context.json` file. For each Slack channel you want to integrate, be sure to invite your new AWS app to those channels. Update the + `notifications.slack` sections of the `cdk.context.json` file with the details for your Slack workspace and channels. + If you opt not to integrate with Slack, remove the `slack` fields from the file. +- Set cli-environment variables `CDK_DEFAULT_ACCOUNT` and `CDK_DEFAULT_REGION` to your deployment account id and `us-east-1`, respectively. +- For each environment (test, beta, prod), you need to deploy both the backend and frontend pipeline stacks: + +1. **Deploy the Backend Pipeline Stacks first (note: you will need to approve the permission change requests for each stack deployment in the terminal)**: + `cdk deploy --context action=bootstrapDeploy TestBackendPipelineStack BetaBackendPipelineStack ProdBackendPipelineStack` + +2. **Then deploy the Frontend Pipeline Stacks (approve the permission change requests for each stack deployment)**: + `cdk deploy --context action=bootstrapDeploy TestFrontendPipelineStack BetaFrontendPipelineStack ProdFrontendPipelineStack` + +**Important**: When a pipeline stack is deployed, it will automatically trigger a deployment to its environment from the configured branch in your GitHub repo. The first time you deploy the backend pipeline, it should pass all the steps except the final trigger of the frontend pipeline, since the frontend pipeline will not exist until you deploy it. From there on, the pipelines should integrate as designed. + +### Subsequent production deploys + +Once the pipelines are established with the above steps, deployments will be automatically handled: + +- Pushes to the `development` branch will trigger the test backend pipeline, which will then trigger the test frontend pipeline +- Pushes to the `main` branch will trigger both the beta and production backend pipelines, which will then trigger their respective frontend pipelines. + +## Google reCAPTCHA Setup +[Back to top](#compact-connect---backend-developer-documentation) + +The practitioner registration endpoint uses Google reCAPTCHA to prevent abuse. Follow these steps to set up reCAPTCHA for your environment: + +1. Visit https://www.google.com/recaptcha/ +2. Go to "v3 Admin Console" + - If needed, enter your Google account credentials +3. Create a site + - Recaptcha type is v3 (score based) + - Domain will be the frontend browser domain for the environment ('localhost' for local development) + - Google Cloud Platform may require a project name + - Submit +4. Open the Settings for the new site + - The Site Key (Public) will need to be set in the cdk.context.json for the appropriate environment under the field named `recaptcha_public_key` for deployments, or in your local `.env` file of the webroot folder (if running the app locally) + - The Secret Key (Private) will need to be manually stored in the AWS account in secrets manager, using the following secret name: + `compact-connect/env/{value of 'environment_name' in cdk.context.json}/recaptcha/token` + The value of the secret key should be in the following format: + ``` + { + "token": "" + } + ``` + You can run the following aws cli command to create the secret (make sure you are logged in to the same AWS account you want to store the secret in, under the us-east-1 region): + ``` + aws secretsmanager create-secret --name compact-connect/env/{value of 'environment_name' in cdk.context.json}/recaptcha/token --secret-string '{"token": ""}' + ``` + +For Production environments, additional billing setup is required: +1. In the Settings for a reCAPTCHA site, click "View in Cloud Console" +2. From the main nav, go to Billing +3. If you have an existing billing account, you may link it. Otherwise, you can create a New Billing account, where you will add payment information +4. More info on Google Recaptcha billing: https://cloud.google.com/recaptcha/docs/billing-information + +### Useful commands + +* `cdk ls` list all stacks in the app +* `cdk synth` emits the synthesized CloudFormation template +* `cdk deploy` deploy this stack to your default AWS account/region +* `cdk diff` compare deployed stack with current state +* `cdk docs` open CDK documentation + +## Decommissioning +[Back to top](#compact-connect---backend-developer-documentation) + +You can tear down resources associated with any of the CloudFormation stacks for this application with +`cdk destroy `. Most persistent resources with data remain in the Persistent stack, so you can freely +destroy the others without losing users or data. If you wish to destroy the Persistent stack as well, be aware that +some resources may be left behind as CloudFormation is designed to err on the side of orphaning resources over data +loss. You can identify any resources that weren't destroyed by watching the stack deletion from the AWS CloudFormation +Console, then looking at the resources after its delete is complete, to look for any with a `Delete Skipped` status. + +## About Route53 hosted zones + +A Hosted Zone in Route53 represents a collection of DNS records for a particular domain and its subdomains, managed +together. See the [Route53 FAQs for more](https://aws.amazon.com/route53/faqs/). When creating a hosted zone, you have +to also configure the domain name registrar (be it AWS or some other vendor) to point to the name servers associated +with your hosted zone, before the records in the zone will have any effect. When deploying this app, creating a hosted +zone in the AWS account for the UI and API domains is part of the environment setup. If you use the common approach +of having your test environments be a subdomain of your production environments (i.e. `compcatconnect.org` for prod +and `test.compcatconnect.org` for test), you need to delegate nameserver authority from your production hosted zone +(`compactconnect.org` in this example) to your test account's hosted zone (`test.compactconnect.org`). To do this, you +need to create your production hosted zone (`compactconnect.org`) in your production account first, then create your +test hosted zone (`test.compactconnect.org`) in your test account second, then delegate name server authority to your +test subdomain. To do this, find the NS record associated with your test hosted zone and copy its value, which should +look something like: +```text +ns-1.awsdns-19.co.uk. +ns-2.awsdns-18.com. +ns-5.awsdns-15.net. +ns-6.awsdns-16.org. +``` + +Copy those name server values and, back in your production hosted zone, create a new NS record that matches the test +one, with the same value (i.e. Record Name: `test.compcatconnect.org`, Type: `NS`, Value: ``). Once that +is done, your test hosted zone is ready for use by the app. You will need to perform this action for your beta environment as well, should you choose to deploy one. + +## More Info +[Back to top](#compact-connect---backend-developer-documentation) + +- [cdk-workshop](https://cdkworkshop.com/): If you are new to CDK, I highly recommend you go through the CDK Workshop for a quick + introduction to the technology and its concepts before getting too deep into any particular project. diff --git a/backend/compact-connect-ui-app/app.py b/backend/compact-connect-ui-app/app.py new file mode 100644 index 000000000..be006f967 --- /dev/null +++ b/backend/compact-connect-ui-app/app.py @@ -0,0 +1,195 @@ +#!/usr/bin/env python3 +import os +import sys + +from aws_cdk import App, Environment + +# Make the `common_constructs` namespace package under `common-cdk` available to Python +sys.path.insert(0, os.path.abspath(os.path.join('..', 'common-cdk'))) + +from common_constructs.stack import StandardTags +from pipeline import ( + ACTION_CONTEXT_KEY, + PIPELINE_STACK_CONTEXT_KEY, + PIPELINE_SYNTH_ACTION, + BetaFrontendPipelineStack, + DeploymentResourcesStack, + ProdFrontendPipelineStack, + TestFrontendPipelineStack, +) +from pipeline.frontend_stage import FrontendStage + +# Pipeline stack name constants for DRY code +TEST_FRONTEND_PIPELINE_STACK = 'TestFrontendPipelineStack' +BETA_FRONTEND_PIPELINE_STACK = 'BetaFrontendPipelineStack' +PROD_FRONTEND_PIPELINE_STACK = 'ProdFrontendPipelineStack' +DEPLOYMENT_RESOURCES_STACK = 'DeploymentResourcesStack' + +# CDK path +CDK_PATH = 'backend/compact-connect-ui-app' + + +class CompactConnectApp(App): + """ + CompactConnect CDK Application + + This application implements a CDK Pipeline deployment architecture with + performance optimizations for faster synthesis and deployment workflows. + + Pipeline Execution Flow: + ---------------------- + - GitHub push → Frontend Pipeline + - GitHub push → Backend Pipeline → Frontend Pipeline + + Stack Structure: + --------------- + - Frontend Pipeline Stacks: TestFrontendPipelineStack, BetaFrontendPipelineStack, ProdFrontendPipelineStack + - DeploymentResourcesStack: Shared resources needed by all pipeline stacks + + Each pipeline type is in its own dedicated stack to avoid self-mutation conflicts. + + Environment Deployments: + ------------------------------- + see README.md for instructions on how to deploy to a sandbox or pipeline environment. + """ + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.sandbox_environment = self.node.try_get_context('sandbox') + + # Toggle for developers to deploy to a sandbox account without the pipeline + if self.sandbox_environment: + self._setup_sandbox_environment() + else: + self._setup_pipeline_environment() + + def _setup_sandbox_environment(self): + """Set up sandbox environment stacks""" + # ssm_context must be provided locally for a sandbox deploy + # TODO: review what context is still needed in this reduced app + ssm_context = self.node.get_context('ssm_context') + environment_name = self.node.get_context('environment_name') + environment_context = ssm_context['environments'][environment_name] + + # TODO: review this toggle flow for what can be simplified + # TODO: update pipeline docs + # NOTE: for first-time sandbox deployments, ensure you deploy the backend stage successfully first + # by running `cdk deploy 'Sandbox/*'`, then if you have a domain name configured and want to deploy the UI for + # your sandbox environment, set the 'deploy_sandbox_ui' field to true and deploy this stack by running + # `cdk deploy 'SandboxUI/*'. This ensures the user pool values are configured to be bundled with the UI build + # artifact. + if environment_context.get('deploy_sandbox_ui', False): + if not environment_context.get('domain_name'): + raise ValueError( + 'Cannot deploy sandbox UI if domain name is not configured for your environment. ' + 'You may still run the app from localhost. See README.md in the webroot folder for ' + 'more information about running the app from localhost.' + ) + + self.sandbox_frontend_stage = FrontendStage( + self, + 'SandboxUI', + environment_name=environment_name, + environment_context=environment_context, + ) + + def _setup_pipeline_environment(self): + """ + Set up pipeline environment stacks based on action and pipeline stack context + + This method implements the conditional stack creation pattern that is key to optimizing + synthesis performance in the CI/CD pipelines. It follows these rules: + + 1. For bootstrapDeploy: Creates all stacks to ensure permissions are correctly set when deploying resources + 2. For pipelineSynth: Creates only the specific stack requested to minimize synthesis time + + This approach dramatically reduces synthesis time in the pipeline while maintaining + all necessary permissions and relationships between stacks during bootstrap deployments. + """ + self.tags = self.node.get_context('tags') + self.action = self.node.try_get_context(ACTION_CONTEXT_KEY) + self.pipeline_stack_name = self.node.try_get_context(PIPELINE_STACK_CONTEXT_KEY) + + # Validate when in pipeline synth mode + if self.action == PIPELINE_SYNTH_ACTION and not self.pipeline_stack_name: + raise ValueError( + f"When action is '{PIPELINE_SYNTH_ACTION}', '{PIPELINE_STACK_CONTEXT_KEY}' context must be specified." + ) + + self.environment = Environment( + account=os.environ['CDK_DEFAULT_ACCOUNT'], + region=os.environ['CDK_DEFAULT_REGION'], + ) + + self.add_all_pipeline_stacks() + + def add_all_pipeline_stacks(self): + """ + add all pipeline stacks for deployment + + This is needed so that permissions set by the DeploymentResourcesStack are properly added for the pipeline + stack resources in every environment. + """ + # This stack must be declared first, as all other pipeline stacks depend on it. + # TODO: decide how we will handle these common deployment resources after breaking up into multiple apps + self.add_deployment_resources_stack() + + self.add_test_frontend_pipeline_stack() + self.add_beta_frontend_pipeline_stack() + self.add_prod_frontend_pipeline_stack() + + def add_deployment_resources_stack(self): + """add the deployment resources stack""" + self.deployment_resources_stack = DeploymentResourcesStack( + self, + DEPLOYMENT_RESOURCES_STACK, + env=self.environment, + standard_tags=StandardTags(**self.tags, environment='deploy'), + ) + + def add_test_frontend_pipeline_stack(self): + """add and return the Test Frontend Pipeline Stack""" + self.test_frontend_pipeline_stack = TestFrontendPipelineStack( + self, + TEST_FRONTEND_PIPELINE_STACK, + pipeline_shared_encryption_key=self.deployment_resources_stack.pipeline_shared_encryption_key, + pipeline_alarm_topic=self.deployment_resources_stack.pipeline_alarm_topic, + pipeline_access_logs_bucket=self.deployment_resources_stack.pipeline_access_logs_bucket, + env=self.environment, + standard_tags=StandardTags(**self.tags, environment='pipeline'), + cdk_path=CDK_PATH, + ) + return self.test_frontend_pipeline_stack + + def add_beta_frontend_pipeline_stack(self): + """add and return the Beta Frontend Pipeline Stack""" + self.beta_frontend_pipeline_stack = BetaFrontendPipelineStack( + self, + BETA_FRONTEND_PIPELINE_STACK, + pipeline_shared_encryption_key=self.deployment_resources_stack.pipeline_shared_encryption_key, + pipeline_alarm_topic=self.deployment_resources_stack.pipeline_alarm_topic, + pipeline_access_logs_bucket=self.deployment_resources_stack.pipeline_access_logs_bucket, + env=self.environment, + standard_tags=StandardTags(**self.tags, environment='pipeline'), + cdk_path=CDK_PATH, + ) + return self.beta_frontend_pipeline_stack + + def add_prod_frontend_pipeline_stack(self): + """add and return the Production Frontend Pipeline Stack""" + self.prod_frontend_pipeline_stack = ProdFrontendPipelineStack( + self, + PROD_FRONTEND_PIPELINE_STACK, + pipeline_shared_encryption_key=self.deployment_resources_stack.pipeline_shared_encryption_key, + pipeline_alarm_topic=self.deployment_resources_stack.pipeline_alarm_topic, + pipeline_access_logs_bucket=self.deployment_resources_stack.pipeline_access_logs_bucket, + env=self.environment, + standard_tags=StandardTags(**self.tags, environment='pipeline'), + cdk_path=CDK_PATH, + ) + return self.prod_frontend_pipeline_stack + + +if __name__ == '__main__': + app = CompactConnectApp() + app.synth() diff --git a/backend/compact-connect-ui-app/app_clients/README.md b/backend/compact-connect-ui-app/app_clients/README.md new file mode 100644 index 000000000..a80c8e51a --- /dev/null +++ b/backend/compact-connect-ui-app/app_clients/README.md @@ -0,0 +1,272 @@ +# App Client Management for Staff Users + +## Overview + +This document is a guide for technical staff for managing Cognito app clients for machine-to-machine authentication in +the State API. All app clients must be documented in the external 'Compact Connect App Client Registry' Google Sheet +(If you do not have access to said registry, contact a maintainer of the project and request access). + +## Creating a New App Client + +### 1. Prerequisites + +Before creating a new app client, ensure you have: +- Jurisdiction requirements documented (compact and state) +- Contact information for the consuming team +- Approval to grant the app client with the requested scopes +- AWS credentials configured with permissions to create app clients for the State Auth user pool in the needed AWS + accounts +- Python 3.10+ installed with boto3 dependency (`pip install boto3`) + +### 2. Update Registry + +Add the new app client information to the external Google Sheet registry for tracking and disaster recovery purposes +(ie a deployment error or AWS region outage causes app client data to be lost so it must be recreated). + +#### **Scope Configuration** + +Scopes are the permissions that the app client will have. There are two tiers of scopes: + +##### **Compact-Level Scopes:** + +These are the scopes that are scoped to a specific compact. Granting these scopes will allow the app client to perform +actions across all jurisdictions within that compact. Generally, the only scope that should be granted at the compact +level is the `{compact}/readGeneral` scope if needed. + +The following scopes are available at the compact level: +``` +{compact}/admin +{compact}/readGeneral +{compact}/readSSN +{compact}/write +``` + +##### **Jurisdiction-Level Scopes:** + +These are the scopes that are scoped to a specific jurisdiction/compact combination. Granting these scopes will allow +the app client to perform actions within a specific jurisdiction/compact combination. You should only grant these +scopes if the consuming team has a specific need for a jurisdiction/compact combination. + +The following scopes are available at the jurisdiction level: +```text +{jurisdiction}/{compact}.admin +{jurisdiction}/{compact}.write +{jurisdiction}/{compact}.readPrivate +{jurisdiction}/{compact}.readSSN +``` + +Currently, the most common scope needed by app clients is `{jurisdiction}/{compact}.write`, which allows uploading +license data for a jurisdiction/compact combination. Scopes that expose PII (e.g., `.readSSN`, `.readPrivate`) should +be granted sparingly and will require valid request signatures once a signing public key is configured for the +jurisdiction. + +### 3. Create App Client Using Interactive Python Script + +**Use the provided Python script in the bin directory for streamlined app client creation:** + + +```bash +python3 bin/create_app_client.py -e -u +``` + +**Interactive Process:** +The script will prompt you for: +- App client name (e.g., "example-ky-app-client-v1") +- Compact (aslp, octp, coun) +- State postal abbreviation (e.g., "ky", "la") +- Additional scopes (optional) + +**Automatic Scope Generation:** +The script automatically creates these standard scopes: +- `{compact}/readGeneral` - General read access for the compact +- `{state}/{compact}.write` - Write access for the specific state/compact combination + +### 4. **Send Credentials to Consuming Team** + +**When using the Python script (recommended):** +The script will output two separate sections: + +**A. Credentials JSON (for one-time link service):** +```json +{ + "clientId": "6g34example89j", + "clientSecret": "1234example567890" +} +``` +**Important:** These credentials should be securely transmitted to the consuming team via an encrypted channel +(i.e., a one-time use link). Copy this JSON and use it with your one-time secret link generator. Once you have sent +the credentials over to the IT staff, ensure you remove all remnants of the credentials from your device. + +**B. Email Template:** +The script will also generate an email template with contextual information (compact name, state, auth URL, license +upload URL) that you can copy/paste into your email client. This template includes a placeholder for the one-time +link that you'll generate separately. + + +#### Email Instructions for consuming team + +As part of the email message sent to the consuming team, be sure to include the onboarding instructions document from +the `it_staff_onboarding_instructions/` directory. + +## Managing API Signing Public Keys + +### Overview + +Signature-based authentication provides an additional layer of security for API access to sensitive licensure data. Each +compact/state combination can have multiple SIGNATURE public keys configured to support key rotation and zero-downtime +deployments. + +### Authorization Requirements + +**⚠️ CRITICAL SECURITY NOTICE:** Due to the sensitivity of the data protected by SIGNATURE authentication (including +partial Social Security Numbers, personal addresses, and professional license details), configuration of new SIGNATURE +public keys in production environments **MUST** include explicit authorization from the state board executive director. + + +### Creating SIGNATURE Public Keys + +Once a state configures a public key, they will be able to access the SIGNATURE-required API endpoints. API endpoints with +_optional_ SIGNATURE support will also begin to enforce SIGNATURE signatures for that combination of compact and state. **This +means that, once a compact/state has a public key configured, they will be denied access to SIGNATURE-Optional endpoints, +such as the `POST license` endpoint, unless they have also implemented SIGNATURE signatures there as well.** Be sure that +the representative is advised that they should begin signing those requests _before_ CompactConnect has a configured +public key. + +#### 1. Prerequisites + +Before creating a new SIGNATURE public key, ensure you have: +- **Production Authorization**: Explicit approval from the state board executive director for production environments +- Validated the identity of the individual providing the public key to you +- Jurisdiction and compact information confirmed +- Contact information for the state IT representative +- The public key file (`.pub` format) from the state IT representative +- AWS credentials configured with permissions to write to the compact configuration table +- Python 3.10+ installed with boto3 dependency (`pip install boto3`) + +#### 2. Key ID Naming Convention + +The state IT department should provide an identifier; however, you can recommend a descriptive key ID that includes: +- Environment indicator (if applicable) +- Version or date suffix + +Examples: +- `prod-key-001` +- `beta-key-2024-01` + +#### 3. Create SIGNATURE Public Key Using Interactive Python Script + +**Use the provided Python script in the bin directory for streamlined SIGNATURE key management:** + +```bash +python3 bin/manage_signature_keys.py create -t +``` + +**Interactive Process:** +The script will prompt you for: +- Compact (aslp, octp, coun) +- State postal abbreviation (e.g., "ky", "la") +- Key ID (e.g., "client-org-prod-key-001") + +**File Reading:** +The script will: +- Notify you that it will read the public key from `.pub` +- Validate the PEM format of the public key +- Check for existing keys with the same ID +- Write the key to the compact configuration database + +#### 4. Database Schema + +SIGNATURE keys are stored in the compact configuration table with the following schema: +- **Primary Key (pk)**: `{compact}#SIGNATURE_KEYS#{state}` +- **Sort Key (sk)**: `{compact}#JURISDICTION#{jurisdiction}#{key_id}` +- **Additional Fields**: + - `publicKey`: PEM-encoded public key content + - `compact`: Compact abbreviation + - `jurisdiction`: Jurisdiction abbreviation + - `keyId`: Key identifier + - `createdAt`: Creation timestamp + +### Deleting SIGNATURE Public Keys + +#### 1. Prerequisites + +Before deleting a SIGNATURE public key, ensure you have: +- Confirmation that the key is no longer in use by the state IT department +- Confirmation of the key id to be deleted +- Understanding of the impact on API access for the compact/state combination + +#### 2. Delete SIGNATURE Public Key Using Interactive Python Script + +```bash +python3 bin/manage_signature_keys.py delete -t +``` + +**Interactive Process:** +The script will: +- Prompt for compact and state +- List all existing keys for the compact/state combination +- Allow you to select the specific key ID to delete +- Require typing "DELETE" to confirm the deletion +- Remove the key from the compact configuration database + +### Key Rotation Best Practices + +#### 1. Planning + +- Coordinate with the State IT representative well in advance +- Plan for zero-downtime deployment + +#### 2. Implementation + +- Create new keys before removing old ones +- Allow both keys to be active during the transition period +- Monitor API access and authentication success rates +- Remove old keys only after confirming new keys are working correctly + +#### 3. Documentation + +- Document key rotation dates and reasons +- Maintain audit trail of all key management activities + +### Security Considerations + +#### 1. Key Storage + +- Public keys are stored in DynamoDB with appropriate access controls +- Private keys should never be stored in CompactConnect systems +- State IT departments are responsible for secure private key management + +#### 2. Access Control + +- Only authorized technical staff should have access to key management resources +- All key management activities should be logged and audited +- Production key creation requires executive director approval + +## Rotating App Client Credentials + +Unfortunately, AWS Cognito does not support rotating app client credentials for an existing app client. The only way +to rotate credentials is to create a new app client with a new clientId and clientSecret and then delete the old one. +The following process should be performed if credentials are accidentally exposed or in the event of a security breach +where the old credentials are compromised. + +### 1. Pre-rotation Tasks + +- Contact consuming team to schedule rotation +- Follow "Creating a New App Client" steps above using either the Python script (recommended) or AWS CLI, you will +increment clientName version suffix by 1 (e.g. "example-ky-app-client-v1" -> "example-ky-app-client-v2") +- Follow “Creating a New App Client” using the Python script (recommended) or AWS CLI. Increment the client name’s + version suffix by 1 (e.g., “example-ky-app-client-v1” -> “example-ky-app-client-v2”). +- Update the external Google Sheet registry with new client information + +### 2. Migration + +- Provide new client id and client secret to consuming team +- Consuming team will need to confirm that the new credentials are deployed in their systems, the old app client is +not in use, and their systems are working as expected. + +### 3. Cleanup + +- Delete old app client from Cognito using the following cli command: +``` +aws cognito-idp delete-user-pool-client --user-pool-id '' --client-id '' +``` diff --git a/backend/compact-connect-ui-app/app_clients/bin/create_app_client.py b/backend/compact-connect-ui-app/app_clients/bin/create_app_client.py new file mode 100755 index 000000000..bb99339b2 --- /dev/null +++ b/backend/compact-connect-ui-app/app_clients/bin/create_app_client.py @@ -0,0 +1,341 @@ +#!/usr/bin/env python3 +# ruff: noqa: T201 we use print statements for scripts run locally + +""" +Script to create AWS Cognito app clients interactively. + +This script prompts users for the necessary information to create app clients +in different environments (test, beta, prod) and automatically generates +the standard scopes based on compact and state inputs. +""" + +import argparse +import json +import re +import sys +from pathlib import Path + +import boto3 +from botocore.exceptions import ClientError, NoCredentialsError + + +def load_cdk_config(): + """Load configuration from cdk.json file.""" + # Find cdk.json file - look in parent directories + current_dir = Path(__file__).parent + cdk_json_path = None + + # Look up the directory tree for cdk.json + for parent in [current_dir] + list(current_dir.parents): + potential_path = parent / 'cdk.json' + if potential_path.exists(): + cdk_json_path = potential_path + break + + if not cdk_json_path: + raise FileNotFoundError('Could not find cdk.json file in current directory or parent directories') + + with open(cdk_json_path) as f: + cdk_config = json.load(f) + + context = cdk_config.get('context', {}) + + return { + 'compacts': context.get('compacts', []), + 'active_compact_member_jurisdictions': context.get('active_compact_member_jurisdictions', {}), + } + + +# Load configuration from cdk.json +CDK_CONFIG = load_cdk_config() +VALID_COMPACTS = CDK_CONFIG['compacts'] +ACTIVE_COMPACT_JURISDICTIONS = CDK_CONFIG['active_compact_member_jurisdictions'] + + +# Valid scope patterns for validation +VALID_SCOPE_PATTERNS = [ + r'^[a-z]+/readGeneral$', + r'^[a-z]+/readSSN$', + r'^[a-z]+/write$', + r'^[a-z]+/admin$', + r'^[a-z]{2}/[a-z]+\.write$', + r'^[a-z]{2}/[a-z]+\.readPrivate$', + r'^[a-z]{2}/[a-z]+\.readSSN$', + r'^[a-z]{2}/[a-z]+\.admin$', +] + + +def validate_compact(compact): + """Validate compact input.""" + compact = compact.lower().strip() + if compact not in VALID_COMPACTS: + raise ValueError(f'Invalid compact: {compact}. Valid compacts are: {", ".join(VALID_COMPACTS)}') + return compact + + +def validate_state(state, compact): + """Validate state postal abbreviation for the given compact.""" + state = state.lower().strip() + + # Get valid states for this compact + valid_states = ACTIVE_COMPACT_JURISDICTIONS.get(compact, []) + + if state not in valid_states: + raise ValueError( + f'Invalid state: {state}. Valid states for {compact.upper()} compact are: {", ".join(sorted(valid_states))}' + ) + return state + + +def validate_scope(scope): + """Validate a single scope against known patterns.""" + scope = scope.strip() + for pattern in VALID_SCOPE_PATTERNS: + if re.match(pattern, scope): + return True + return False + + +def validate_additional_scopes(scopes_input): + """Validate additional scopes input.""" + if not scopes_input.strip(): + return [] + + scopes = [scope.strip() for scope in scopes_input.split(',')] + invalid_scopes = [] + + for scope in scopes: + if not validate_scope(scope): + invalid_scopes.append(scope) + + if invalid_scopes: + print(f'\nInvalid scopes detected: {", ".join(invalid_scopes)}') + print('Valid scope patterns:') + print(' Compact-level: {compact}/readGeneral, {compact}/readSSN, {compact}/write, {compact}/admin') + print( + 'Jurisdiction-level: {state}/{compact}.write, {state}/{compact}.readPrivate, {state}/{compact}.readSSN, ' + '{state}/{compact}.admin' + ) + raise ValueError('Invalid scopes provided') + + return scopes + + +def get_user_input(): + """Get user input for app client configuration.""" + print('=== App Client Configuration ===\n') + + # Get client name + client_name = input("Enter the app client name (e.g., 'example-ky-app-client-v1'): ").strip() + if not client_name: + raise ValueError('Client name is required') + + # Get compact + while True: + try: + print(f'\nValid compacts: {", ".join(VALID_COMPACTS)}') + compact = input('Enter the compact: ').strip() + compact = validate_compact(compact) + break + except ValueError as e: + print(f'Error: {e}') + + # Get state + while True: + try: + valid_states = ACTIVE_COMPACT_JURISDICTIONS.get(compact, []) + print(f'\nValid states for {compact.upper()} compact: {", ".join(sorted(valid_states))}') + state = input("Enter the state postal abbreviation (e.g., 'ky', 'la'): ").strip() + state = validate_state(state, compact) + break + except ValueError as e: + print(f'Error: {e}') + + # Get additional scopes (optional) + print('\nThe following scope will be automatically included:') + print(f' - {state}/{compact}.write') + + additional_scopes = [] + while True: + try: + scopes_input = input('\nEnter any additional scopes (comma-separated, or press Enter for none): ').strip() + additional_scopes = validate_additional_scopes(scopes_input) + break + except ValueError as e: + print(f'Error: {e}') + continue + + # Generate final scope list + scopes = [f'{state}/{compact}.write'] + scopes.extend(additional_scopes) + + # Remove duplicates + deduped_scopes = list(set(scopes)) + + print('\nFinal configuration:') + print(f' Client Name: {client_name}') + print(f' Compact: {compact}') + print(f' State: {state}') + print(f' Scopes: {", ".join(deduped_scopes)}') + + confirm = input('\nProceed with this configuration? (y/N): ').strip().lower() + if confirm != 'y': + print('Configuration cancelled.') + sys.exit(0) + + return {'clientName': client_name, 'compact': compact, 'state': state, 'scopes': deduped_scopes} + + +def create_app_client(user_pool_id, config): + """Create the app client using boto3 Cognito client.""" + client_name = config['clientName'] + scopes = config['scopes'] + + print(f'\nCreating app client: {client_name}') + print(f'With scopes: {", ".join(scopes)}') + + try: + # Create boto3 Cognito IDP client + cognito_client = boto3.client('cognito-idp', region_name='us-east-1') + + # Create the user pool client + return cognito_client.create_user_pool_client( + UserPoolId=user_pool_id, + ClientName=client_name, + PreventUserExistenceErrors='ENABLED', + GenerateSecret=True, + TokenValidityUnits={'AccessToken': 'minutes'}, + AccessTokenValidity=15, + AllowedOAuthFlowsUserPoolClient=True, + AllowedOAuthFlows=['client_credentials'], + AllowedOAuthScopes=scopes, + ) + + except NoCredentialsError: + print('Error: AWS credentials not found. Please configure your AWS credentials.') + print("You can use 'aws configure' or set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment variables.") + sys.exit(1) + except ClientError as e: + error_code = e.response['Error']['Code'] + error_message = e.response['Error']['Message'] + print(f'Error creating app client: {error_code} - {error_message}') + sys.exit(1) + + +def print_credentials(client_id, client_secret): + """Print only the sensitive credentials in JSON format for secure copy/paste.""" + credentials = { + 'clientId': client_id, + 'clientSecret': client_secret, + } + + print('\n' + '=' * 60) + print('APP CLIENT CREDENTIALS (FOR ONE-TIME LINK SERVICE)') + print('=' * 60) + print(json.dumps(credentials, indent=2)) + print('=' * 60) + print('Please copy the JSON above and use it with your one-time secret link generator.') + print('Do not leave these credentials in terminal history or logs.') + print('=' * 60) + + +def print_email_template(environment, compact, state): + """Print an email template with contextual information for the consuming team.""" + # Get environment-specific URLs + auth_urls = { + 'beta': 'https://compact-connect-state-auth-beta.auth.us-east-1.amazoncognito.com/oauth2/token', + 'prod': 'https://compact-connect-state-auth.auth.us-east-1.amazoncognito.com/oauth2/token', + 'test': 'https://compact-connect-state-auth-test.auth.us-east-1.amazoncognito.com/oauth2/token', + } + + api_base_urls = { + 'beta': 'https://state-api.beta.compactconnect.org', + 'prod': 'https://state-api.compactconnect.org', + 'test': 'https://state-api.test.compactconnect.org', + } + + # Compact name mapping + compact_names = { + 'aslp': 'Audiology and Speech Language Pathology', + 'octp': 'Occupational Therapy', + 'coun': 'Counseling', + } + + compact_name = compact_names.get(compact, compact.upper()) + auth_url = auth_urls.get(environment) + license_upload_url = f'{api_base_urls.get(environment)}/v1/compacts/{compact}/jurisdictions/{state}/licenses' + + email_template = f""" +Thank you for integrating with Compact Connect! You have been designated as the IT professional who is able to handle +credentials for secure machine-to-machine authentication between your state and CompactConnect. + +Details for these credentials are: +Compact: {compact_name} +State: {state.upper()} +Auth URL: {auth_url} +License Upload URL: {license_upload_url} + +Follow this link to your API credentials as soon as you are ready to securely store them. They will only be viewable +once: + + +For more information on CompactConnect and how to integrate your state IT system with ours, see the documentation +here: +https://github.com/csg-org/CompactConnect/blob/development/backend/compact-connect/docs/it_staff_onboarding_instructions.md +""" + + print('\n' + '=' * 60) + print('EMAIL TEMPLATE (COPY/PASTE INTO EMAIL CLIENT)') + print('=' * 60) + print(email_template.strip()) + print('=' * 60) + + +def main(): + parser = argparse.ArgumentParser(description='Create AWS Cognito app client interactively') + parser.add_argument( + '-e', '--environment', required=True, choices=['test', 'beta', 'prod'], help='Environment (test, beta, or prod)' + ) + parser.add_argument('-u', '--user-pool-id', required=True, help='AWS Cognito User Pool ID') + + args = parser.parse_args() + + try: + print(f'Creating app client for {args.environment} environment...') + print(f'User Pool ID: {args.user_pool_id}\n') + + # Get configuration from user input + config = get_user_input() + + # Create the app client + response = create_app_client(args.user_pool_id, config) + + # Extract credentials from response + user_pool_client = response.get('UserPoolClient', {}) + client_id = user_pool_client.get('ClientId') + client_secret = user_pool_client.get('ClientSecret') + client_name = user_pool_client.get('ClientName') + + if not client_id or not client_secret: + print('Error: Could not extract client ID or secret from AWS response') + sys.exit(1) + + print('\n✅ App client created successfully!') + print(f'Client Name: {client_name}') + print(f'Client ID: {client_id}') + + # Print credentials for secure copy/paste + print_credentials(client_id, client_secret) + + # Print email template + print_email_template(args.environment, config['compact'], config['state']) + + print('\n📝 Remember to add this app client to your external registry!') + + except Exception as e: # noqa: BLE001 + print(f'Error: {e}') + sys.exit(1) + + +if __name__ == '__main__': + main() diff --git a/backend/compact-connect-ui-app/app_clients/bin/manage_signature_keys.py b/backend/compact-connect-ui-app/app_clients/bin/manage_signature_keys.py new file mode 100755 index 000000000..50b4696cf --- /dev/null +++ b/backend/compact-connect-ui-app/app_clients/bin/manage_signature_keys.py @@ -0,0 +1,390 @@ +#!/usr/bin/env python3 +# ruff: noqa: T201 we use print statements for scripts run locally + +""" +Script to manage SIGNATURE public keys in the compact configuration database. + +This script allows users to create and delete SIGNATURE public keys for different +compact/jurisdiction combinations. It follows the same interactive style as +the create_app_client.py script. +""" + +import argparse +import json +import sys +from datetime import UTC, datetime +from pathlib import Path + +import boto3 +from botocore.exceptions import ClientError, NoCredentialsError + + +def load_cdk_config(): + """Load configuration from cdk.json file.""" + # Find cdk.json file - look in parent directories + current_dir = Path(__file__).parent + cdk_json_path = None + + # Look up the directory tree for cdk.json + for parent in [current_dir] + list(current_dir.parents): + potential_path = parent / 'cdk.json' + if potential_path.exists(): + cdk_json_path = potential_path + break + + if not cdk_json_path: + raise FileNotFoundError('Could not find cdk.json file in current directory or parent directories') + + with open(cdk_json_path) as f: + cdk_config = json.load(f) + + context = cdk_config.get('context', {}) + + return { + 'compacts': context.get('compacts', []), + 'active_compact_member_jurisdictions': context.get('active_compact_member_jurisdictions', {}), + } + + +# Load configuration from cdk.json +CDK_CONFIG = load_cdk_config() +VALID_COMPACTS = CDK_CONFIG['compacts'] +ACTIVE_COMPACT_JURISDICTIONS = CDK_CONFIG['active_compact_member_jurisdictions'] + + +def validate_compact(compact): + """Validate compact input.""" + compact = compact.lower().strip() + if compact not in VALID_COMPACTS: + raise ValueError(f'Invalid compact: {compact}. Valid compacts are: {", ".join(VALID_COMPACTS)}') + return compact + + +def validate_state(state, compact): + """Validate state postal abbreviation for the given compact.""" + state = state.lower().strip() + + # Get valid states for this compact + valid_states = ACTIVE_COMPACT_JURISDICTIONS.get(compact, []) + + if state not in valid_states: + raise ValueError( + f'Invalid state: {state}. Valid states for {compact.upper()} compact are: {", ".join(sorted(valid_states))}' + ) + return state + + +def validate_key_id(key_id): + """Validate key ID input.""" + key_id = key_id.strip() + if not key_id: + raise ValueError('Key ID cannot be empty') + if len(key_id) > 100: # Reasonable limit for key ID + raise ValueError('Key ID is too long (max 100 characters)') + if not key_id.replace('-', '').replace('_', '').isalnum(): + raise ValueError('Key ID can only contain alphanumeric characters, hyphens, and underscores') + return key_id + + +def get_user_input_for_create(): + """Get user input for creating a SIGNATURE public key.""" + print('=== SIGNATURE Public Key Creation ===\n') + + # Get compact + while True: + try: + print(f'Valid compacts: {", ".join(VALID_COMPACTS)}') + compact = input('Enter the compact: ').strip() + compact = validate_compact(compact) + break + except ValueError as e: + print(f'Error: {e}') + + # Get state + while True: + try: + valid_states = ACTIVE_COMPACT_JURISDICTIONS.get(compact, []) + print(f'\nValid states for {compact.upper()} compact: {", ".join(sorted(valid_states))}') + state = input("Enter the state postal abbreviation (e.g., 'ky', 'la'): ").strip() + state = validate_state(state, compact) + break + except ValueError as e: + print(f'Error: {e}') + + # Get key ID + while True: + try: + key_id = input('\nEnter the key ID (e.g., "client-key-001"): ').strip() + key_id = validate_key_id(key_id) + break + except ValueError as e: + print(f'Error: {e}') + + print('\nConfiguration:') + print(f' Compact: {compact}') + print(f' State: {state}') + print(f' Key ID: {key_id}') + print(f' Public key file: {key_id}.pub') + + confirm = input('\nProceed with this configuration? (y/N): ').strip().lower() + if confirm != 'y': + print('Configuration cancelled.') + sys.exit(0) + + return {'compact': compact, 'state': state, 'key_id': key_id} + + +def read_public_key_file(key_id): + """Read the public key from the specified file.""" + file_path = Path(f'{key_id}.pub') + + if not file_path.exists(): + raise FileNotFoundError( + f'Public key file "{file_path}" not found. Please ensure the file exists in the current directory.' + ) + + try: + with open(file_path) as f: + public_key_content = f.read().strip() + + if not public_key_content: + raise ValueError('Public key file is empty') + + # Basic validation that this looks like a PEM public key + if not public_key_content.startswith('-----BEGIN PUBLIC KEY-----'): + raise ValueError( + 'Public key file does not appear to be in PEM format (should start with "-----BEGIN PUBLIC KEY-----")' + ) + + if not public_key_content.endswith('-----END PUBLIC KEY-----'): + raise ValueError( + 'Public key file does not appear to be in PEM format (should end with "-----END PUBLIC KEY-----")' + ) + + return public_key_content + + except (FileNotFoundError, ValueError) as e: + raise ValueError(f'Error reading public key file: {e}') from e + + +def create_signature_key(table_name, config): + """Create the SIGNATURE public key in DynamoDB.""" + compact = config['compact'] + state = config['state'] + key_id = config['key_id'] + + print(f'\nCreating SIGNATURE public key: {key_id}') + print(f'For compact: {compact}, state: {state}') + + try: + # Create boto3 DynamoDB client + dynamodb = boto3.resource('dynamodb') + table = dynamodb.Table(table_name) + + # Check if key already exists + pk = f'{compact}#SIGNATURE_KEYS#{state}' + sk = f'{compact}#JURISDICTION#{state}#{key_id}' + + response = table.get_item(Key={'pk': pk, 'sk': sk}) + + if 'Item' in response: + print(f'\n⚠️ Warning: A key with ID "{key_id}" already exists for {compact}/{state}') + overwrite = input('Do you want to overwrite it? (y/N): ').strip().lower() + if overwrite != 'y': + print('Operation cancelled.') + sys.exit(0) + + # Read the public key file + print(f'\nReading public key from {key_id}.pub...') + public_key_pem = read_public_key_file(key_id) + + # Create the item + item = { + 'pk': pk, + 'sk': sk, + 'publicKey': public_key_pem, + 'compact': compact, + 'jurisdiction': state, + 'keyId': key_id, + 'createdAt': datetime.now(tz=UTC).isoformat(), + } + + # Write to DynamoDB + table.put_item(Item=item) + + print('\n✅ SIGNATURE public key created successfully!') + print(f'Key ID: {key_id}') + print(f'Compact: {compact}') + print(f'State: {state}') + + except NoCredentialsError: + print('Error: AWS credentials not found. Please configure your AWS credentials.') + print("You can use 'aws configure' or set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment variables.") + raise + except ClientError as e: + error_code = e.response['Error']['Code'] + error_message = e.response['Error']['Message'] + print(f'Error creating SIGNATURE key: {error_code} - {error_message}') + raise + + +def get_user_input_for_delete(): + """Get user input for deleting a SIGNATURE public key.""" + print('=== SIGNATURE Public Key Deletion ===\n') + + # Get compact + while True: + try: + print(f'Valid compacts: {", ".join(VALID_COMPACTS)}') + compact = input('Enter the compact: ').strip() + compact = validate_compact(compact) + break + except ValueError as e: + print(f'Error: {e}') + + # Get state + while True: + try: + valid_states = ACTIVE_COMPACT_JURISDICTIONS.get(compact, []) + print(f'\nValid states for {compact.upper()} compact: {", ".join(sorted(valid_states))}') + state = input("Enter the state postal abbreviation (e.g., 'ky', 'la'): ").strip() + state = validate_state(state, compact) + break + except ValueError as e: + print(f'Error: {e}') + + return {'compact': compact, 'state': state} + + +def list_existing_keys(table_name, config): + """List existing SIGNATURE keys for the given compact/state combination.""" + compact = config['compact'] + state = config['state'] + + try: + # Create boto3 DynamoDB client + dynamodb = boto3.resource('dynamodb') + table = dynamodb.Table(table_name) + + # Query for existing keys + pk = f'{compact}#SIGNATURE_KEYS#{state}' + sk_prefix = f'{compact}#JURISDICTION#{state}#' + + response = table.query( + KeyConditionExpression='pk = :pk AND begins_with(sk, :sk_prefix)', + ExpressionAttributeValues={':pk': pk, ':sk_prefix': sk_prefix}, + ) + + items = response.get('Items', []) + + if not items: + print(f'\nNo SIGNATURE keys found for {compact}/{state}') + return [] + + print(f'\nExisting SIGNATURE keys for {compact}/{state}:') + for i, item in enumerate(items, 1): + key_id = item['sk'].split('#')[-1] + created_at = item.get('createdAt', 'Unknown') + print(f' {i}. Key ID: {key_id} (Created: {created_at})') + + return items + + except NoCredentialsError: + print('Error: AWS credentials not found. Please configure your AWS credentials.') + print("You can use 'aws configure' or set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment variables.") + raise + except ClientError as e: + error_code = e.response['Error']['Code'] + error_message = e.response['Error']['Message'] + print(f'Error listing SIGNATURE keys: {error_code} - {error_message}') + raise + + +def delete_signature_key(table_name, config, key_id): + """Delete the specified SIGNATURE public key from DynamoDB.""" + compact = config['compact'] + state = config['state'] + + print(f'\nDeleting SIGNATURE public key: {key_id}') + print(f'For compact: {compact}, state: {state}') + + try: + # Create boto3 DynamoDB client + dynamodb = boto3.resource('dynamodb') + table = dynamodb.Table(table_name) + + # Delete the item + pk = f'{compact}#SIGNATURE_KEYS#{state}' + sk = f'{compact}#JURISDICTION#{state}#{key_id}' + + table.delete_item(Key={'pk': pk, 'sk': sk}) + + print('\n✅ SIGNATURE public key deleted successfully!') + print(f'Key ID: {key_id}') + print(f'Compact: {compact}') + print(f'State: {state}') + + except NoCredentialsError: + print('Error: AWS credentials not found. Please configure your AWS credentials.') + print("You can use 'aws configure' or set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment variables.") + raise + except ClientError as e: + error_code = e.response['Error']['Code'] + error_message = e.response['Error']['Message'] + print(f'Error deleting SIGNATURE key: {error_code} - {error_message}') + raise + + +def main(): + parser = argparse.ArgumentParser(description='Manage SIGNATURE public keys in the compact configuration database') + parser.add_argument('action', choices=['create', 'delete'], help='Action to perform (create or delete)') + parser.add_argument('-t', '--table-name', required=True, help='DynamoDB table name for compact configuration') + + args = parser.parse_args() + + print(f'Managing SIGNATURE keys for {args.table_name} table...\n') + + if args.action == 'create': + # Create flow + config = get_user_input_for_create() + + create_signature_key(args.table_name, config) + + elif args.action == 'delete': + # Delete flow + config = get_user_input_for_delete() + + # List existing keys + existing_keys = list_existing_keys(args.table_name, config) + + if not existing_keys: + print('\nNo keys to delete.') + sys.exit(0) + + # Get key ID to delete + while True: + key_id = input('\nEnter the exact key ID to delete: ').strip() + key_id = validate_key_id(key_id) + + # Check if key exists + key_exists = any(item['sk'].split('#')[-1] == key_id for item in existing_keys) + if not key_exists: + print(f'Error: Key ID "{key_id}" not found in the list above') + continue + + break + + # Final confirmation + print(f'\n⚠️ You are about to delete SIGNATURE key "{key_id}" for {config["compact"]}/{config["state"]}') + print('This action cannot be undone.') + + confirm = input('\nAre you sure you want to delete this key? Type "DELETE" to confirm: ').strip() + if confirm != 'DELETE': + print('Deletion cancelled.') + sys.exit(0) + + delete_signature_key(args.table_name, config, key_id) + + +if __name__ == '__main__': + main() diff --git a/backend/compact-connect-ui-app/app_clients/it_staff_onboarding_instructions/README.md b/backend/compact-connect-ui-app/app_clients/it_staff_onboarding_instructions/README.md new file mode 100644 index 000000000..f28dc34c4 --- /dev/null +++ b/backend/compact-connect-ui-app/app_clients/it_staff_onboarding_instructions/README.md @@ -0,0 +1 @@ +[Moved Here](../../docs/it_staff_onboarding_instructions.md) diff --git a/backend/compact-connect-ui-app/bin/compile_requirements.sh b/backend/compact-connect-ui-app/bin/compile_requirements.sh new file mode 100755 index 000000000..77379b9f5 --- /dev/null +++ b/backend/compact-connect-ui-app/bin/compile_requirements.sh @@ -0,0 +1,5 @@ +set -e + +pip-compile --no-emit-index-url --upgrade --no-strip-extras requirements-dev.in +pip-compile --no-emit-index-url --upgrade --no-strip-extras requirements.in +bin/sync_deps.sh diff --git a/backend/bin/run_python_tests.py b/backend/compact-connect-ui-app/bin/run_python_tests.py similarity index 88% rename from backend/bin/run_python_tests.py rename to backend/compact-connect-ui-app/bin/run_python_tests.py index 7fe37fbb5..1509c1497 100755 --- a/backend/bin/run_python_tests.py +++ b/backend/compact-connect-ui-app/bin/run_python_tests.py @@ -22,21 +22,7 @@ # Define the test directories to include TEST_DIRS = ( - 'compact-connect/lambdas/python/common', - 'compact-connect/lambdas/python/cognito-backup', - 'compact-connect/lambdas/python/compact-configuration', - 'compact-connect/lambdas/python/custom-resources', - 'compact-connect/lambdas/python/data-events', - 'compact-connect/lambdas/python/disaster-recovery', - 'compact-connect/lambdas/python/migration', - 'compact-connect/lambdas/python/provider-data-v1', - 'compact-connect/lambdas/python/purchases', - 'compact-connect/lambdas/python/staff-user-pre-token', - 'compact-connect/lambdas/python/staff-users', - 'compact-connect', # CDK tests - 'multi-account/control-tower', - 'multi-account/log-aggregation', - 'multi-account/backups', # Data retention backup infrastructure + '.', # CDK tests ) diff --git a/backend/bin/run_tests.sh b/backend/compact-connect-ui-app/bin/run_tests.sh similarity index 98% rename from backend/bin/run_tests.sh rename to backend/compact-connect-ui-app/bin/run_tests.sh index d0aad2fb2..ad8dcb5ee 100755 --- a/backend/bin/run_tests.sh +++ b/backend/compact-connect-ui-app/bin/run_tests.sh @@ -88,7 +88,7 @@ EXIT=1 if [[ "$LANGUAGE" == 'nodejs' || "$LANGUAGE" == 'all' ]]; then echo "Running NodeJS tests..." ( - cd compact-connect/lambdas/nodejs + cd lambdas/nodejs yarn test || exit "$?" if [[ "$REPORT" == true && "$OPEN_REPORT" == true ]]; then open 'coverage/lcov-report/index.html' diff --git a/backend/compact-connect-ui-app/bin/sync_deps.sh b/backend/compact-connect-ui-app/bin/sync_deps.sh new file mode 100755 index 000000000..f8048085e --- /dev/null +++ b/backend/compact-connect-ui-app/bin/sync_deps.sh @@ -0,0 +1,8 @@ +( + cd lambdas/nodejs + yarn install +) + +pip-sync \ + requirements-dev.txt \ + requirements.txt diff --git a/backend/compact-connect-ui-app/cdk.context.beta-example.json b/backend/compact-connect-ui-app/cdk.context.beta-example.json new file mode 100644 index 000000000..fdba61161 --- /dev/null +++ b/backend/compact-connect-ui-app/cdk.context.beta-example.json @@ -0,0 +1,64 @@ +{ + "ssm_context": { + "github_repo_string": "csg-org/CompactConnect", + "app_name": "compact-connect", + "environments": { + "pipeline": { + "account_id": "000000000000", + "region": "us-east-1", + "connection_arn": "arn:aws:codestar-connections:us-east-1:000000000000:connection/11111111-1111-1111-111111111111" + }, + "beta": { + "account_id": "222233334444", + "region": "us-east-1", + "domain_name": "beta.compactconnect.org", + "backup_enabled": false, + "recaptcha_public_key": "123-KFEUsjehfuejILDVUKkRnAF9SSzb8o9uv5lY7Ih", + "robots_meta": "noindex,nofollow", + "notifications": { + "ses_operations_support_email": "justin@example.com", + "email": [ + "justin@example.com" + ], + "slack": [ + { + "channel_name": "ops-monitoring", + "channel_id": "C0123456789", + "workspace_id": "T01234567" + } + ] + }, + "backup_policies": { + "general_data": { + "schedule": { + "week_day": "5", + "year": "*", + "month": "*", + "hour": "5", + "minute": "0" + }, + "delete_after_days": 180, + "cold_storage_after_days": 30 + }, + "frequent_updates": { + "schedule": { + "week_day": "5", + "year": "*", + "month": "*", + "hour": "5", + "minute": "0" + }, + "delete_after_days": 180, + "cold_storage_after_days": 30 + } + } + } + }, + "backup_config": { + "backup_account_id": "111122223333", + "backup_region": "us-west-2", + "general_vault_name": "CompactConnectBackupVault", + "ssn_vault_name": "CompactConnectBackupVault-SSN" + } + } +} diff --git a/backend/compact-connect-ui-app/cdk.context.deploy-example.json b/backend/compact-connect-ui-app/cdk.context.deploy-example.json new file mode 100644 index 000000000..b9f4efce5 --- /dev/null +++ b/backend/compact-connect-ui-app/cdk.context.deploy-example.json @@ -0,0 +1,24 @@ +{ + "ssm_context": { + "github_repo_string": "csg-org/CompactConnect", + "app_name": "compact-connect", + "environments": { + "deploy": { + "account_id": "000000000000", + "region": "us-east-1", + "notifications": { + "email": [ + "justin@example.com" + ], + "slack": [ + { + "channel_name": "ops-monitoring", + "channel_id": "C0123456789", + "workspace_id": "T01234567" + } + ] + } + } + } + } +} diff --git a/backend/compact-connect-ui-app/cdk.context.prod-example.json b/backend/compact-connect-ui-app/cdk.context.prod-example.json new file mode 100644 index 000000000..a6e2789b5 --- /dev/null +++ b/backend/compact-connect-ui-app/cdk.context.prod-example.json @@ -0,0 +1,64 @@ +{ + "ssm_context": { + "github_repo_string": "csg-org/CompactConnect", + "app_name": "compact-connect", + "environments": { + "pipeline": { + "account_id": "000000000000", + "region": "us-east-1", + "connection_arn": "arn:aws:codestar-connections:us-east-1:000000000000:connection/11111111-1111-1111-111111111111" + }, + "prod": { + "account_id": "000011112222", + "region": "us-east-1", + "domain_name": "compactconnect.org", + "backup_enabled": true, + "robots_meta": "index,follow", + "recaptcha_public_key": "123-KFEUsjehfuejILDVUKkRnAF9SSzb8o9uv5lY7Ih", + "notifications": { + "ses_operations_support_email": "justin@example.com", + "email": [ + "justin@example.com" + ], + "slack": [ + { + "channel_name": "ops-monitoring", + "channel_id": "C0123456789", + "workspace_id": "T01234567" + } + ] + }, + "backup_policies": { + "general_data": { + "schedule": { + "year": "*", + "month": "*", + "day": "*", + "hour": "5", + "minute": "0" + }, + "delete_after_days": 730, + "cold_storage_after_days": 30 + }, + "frequent_updates": { + "schedule": { + "year": "*", + "month": "*", + "day": "*", + "hour": "*", + "minute": "0" + }, + "delete_after_days": 730, + "cold_storage_after_days": 30 + } + } + } + }, + "backup_config": { + "backup_account_id": "111122223333", + "backup_region": "us-west-2", + "general_vault_name": "CompactConnectBackupVault", + "ssn_vault_name": "CompactConnectSSNBackupVault" + } + } +} diff --git a/backend/compact-connect-ui-app/cdk.context.sandbox-example.json b/backend/compact-connect-ui-app/cdk.context.sandbox-example.json new file mode 100644 index 000000000..bea66dcb8 --- /dev/null +++ b/backend/compact-connect-ui-app/cdk.context.sandbox-example.json @@ -0,0 +1,32 @@ +{ + "sandbox": true, + "environment_name": "justin", + "sandbox_active_compact_member_jurisdictions": { + "aslp": ["al", "ak", "ar", "co", "de", "ky", "la", "me", "md", "mn", "ms", "mo", "ne", "oh"], + "octp": ["al", "ar", "ky", "la", "ms", "ne", "oh"], + "coun": ["al", "ar", "fl", "ga", "ky", "ne", "oh", "ut"] + }, + "ssm_context": { + "github_repo_string": "csg-org/CompactConnect", + "app_name": "compact-connect", + "environments": { + "justin": { + "account_id": "111122223333", + "region": "us-east-1", + "domain_name": "justin.compactconnect.org", + "backup_enabled": false, + "allow_local_ui": true, + "deploy_sandbox_ui": false, + "security_profile": "VULNERABLE", + "recaptcha_public_key": "123-KFEUsjehfuejILDVUKkRnAF9SSzb8o9uv5lY7Ih", + "robots_meta": "noindex,nofollow", + "notifications": { + "ses_operations_support_email": "justin@example.com", + "email": [ + "justin@example.com" + ] + } + } + } + } +} diff --git a/backend/compact-connect-ui-app/cdk.context.test-example.json b/backend/compact-connect-ui-app/cdk.context.test-example.json new file mode 100644 index 000000000..56cf93bad --- /dev/null +++ b/backend/compact-connect-ui-app/cdk.context.test-example.json @@ -0,0 +1,65 @@ +{ + "ssm_context": { + "github_repo_string": "csg-org/CompactConnect", + "app_name": "compact-connect", + "environments": { + "pipeline": { + "account_id": "000000000000", + "region": "us-east-1", + "connection_arn": "arn:aws:codestar-connections:us-east-1:000000000000:connection/11111111-1111-1111-111111111111" + }, + "test": { + "account_id": "111122223333", + "region": "us-east-1", + "domain_name": "test.compactconnect.org", + "backup_enabled": true, + "allow_local_ui": true, + "recaptcha_public_key": "123-KFEUsjehfuejILDVUKkRnAF9SSzb8o9uv5lY7Ih", + "robots_meta": "noindex,nofollow", + "notifications": { + "ses_operations_support_email": "justin@example.com", + "email": [ + "justin@example.com" + ], + "slack": [ + { + "channel_name": "preprod-ops-monitoring", + "channel_id": "C1234567890", + "workspace_id": "T01234567" + } + ] + }, + "backup_policies": { + "general_data": { + "schedule": { + "week_day": "5", + "year": "*", + "month": "*", + "hour": "5", + "minute": "0" + }, + "delete_after_days": 180, + "cold_storage_after_days": 30 + }, + "frequent_updates": { + "schedule": { + "week_day": "5", + "year": "*", + "month": "*", + "hour": "5", + "minute": "0" + }, + "delete_after_days": 180, + "cold_storage_after_days": 30 + } + } + } + }, + "backup_config": { + "backup_account_id": "111122223333", + "backup_region": "us-west-2", + "general_vault_name": "CompactConnectBackupVault", + "ssn_vault_name": "CompactConnectBackupVault-SSN" + } + } +} diff --git a/backend/compact-connect-ui-app/cdk.json b/backend/compact-connect-ui-app/cdk.json new file mode 100644 index 000000000..cfedbddf2 --- /dev/null +++ b/backend/compact-connect-ui-app/cdk.json @@ -0,0 +1,103 @@ +{ + "app": "python3 app.py", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "requirements*.txt", + "source.bat", + "**/__init__.py", + "**/__pycache__", + "tests" + ] + }, + "context": { + "tags": { + "project": "compact-connect", + "service": "license-data" + }, + "jurisdictions": [ + "al", "ak", "az", "ar", "ca", "co", "ct", "de", "dc", "fl", + "ga", "hi", "id", "il", "in", "ia", "ks", "ky", "la", "me", + "md", "ma", "mi", "mn", "ms", "mo", "mt", "ne", "nv", "nh", + "nj", "nm", "ny", "nc", "nd", "oh", "ok", "or", "pa", "pr", + "ri", "sc", "sd", "tn", "tx", "ut", "vt", "va", "vi", "wa", + "wv", "wi", "wy" + ], + "license_types": { + "aslp": [ + {"name": "audiologist", "abbreviation": "aud"}, + {"name": "speech-language pathologist", "abbreviation": "slp"} + ], + "octp": [ + {"name": "occupational therapist", "abbreviation": "ot"}, + {"name": "occupational therapy assistant", "abbreviation": "ota"} + ], + "coun": [ + {"name": "licensed professional counselor", "abbreviation": "lpc"} + ] + }, + "compacts": [ + "aslp", + "octp", + "coun" + ], + "active_compact_member_jurisdictions": { + "aslp": ["al", "ak", "ar", "co", "de", "fl", "ga", "id", "in", "ia", "ks", "ky", "la", "me", "md", "mn", "ms", "mo", "mt", "ne", "nh", "nc", "oh", "ok", "ri", "sc", "tn", "ut", "vt", "va", "vi", "wa", "wv", "wi", "wy"], + "octp": ["al", "ar", "az", "co", "de", "ga", "ia", "in", "ky", "la", "me", "md", "mn", "ms", "mo", "mt", "ne", "nh", "nc", "nd", "oh", "ri", "sc", "sd", "tn", "ut", "vt", "va", "wa", "wv", "wi", "wy"], + "coun": ["al", "ar", "az", "co", "ct", "dc", "de", "fl", "ga", "ia", "in", "ks", "ky", "la", "me", "md", "mn", "ms", "mo", "mt", "ne", "nh", "nj", "nc", "nd", "oh", "ok", "ri", "sc", "sd", "tn", "ut", "vt", "va", "wa", "wv", "wi", "wy"] + }, + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-us-gov" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/core:validateSnapshotRemovalPolicy": true, + "@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": true, + "@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true, + "@aws-cdk/aws-route53-patters:useCertificate": true, + "@aws-cdk/customresources:installLatestAwsSdkDefault": false, + "@aws-cdk/aws-rds:databaseProxyUniqueResourceName": true, + "@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": true, + "@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": true, + "@aws-cdk/aws-ec2:launchTemplateDefaultUserData": true, + "@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": true, + "@aws-cdk/aws-redshift:columnId": true, + "@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": true, + "@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": true, + "@aws-cdk/aws-apigateway:requestValidatorUniqueId": true, + "@aws-cdk/aws-kms:aliasNameRef": true, + "@aws-cdk/aws-autoscaling:generateLaunchTemplateInsteadOfLaunchConfig": true, + "@aws-cdk/core:includePrefixInUniqueNameGeneration": true, + "@aws-cdk/aws-efs:denyAnonymousAccess": true, + "@aws-cdk/aws-opensearchservice:enableOpensearchMultiAzWithStandby": true, + "@aws-cdk/aws-lambda-nodejs:useLatestRuntimeVersion": true, + "@aws-cdk/aws-efs:mountTargetOrderInsensitiveLogicalId": true, + "@aws-cdk/aws-rds:auroraClusterChangeScopeOfInstanceParameterGroupWithEachParameters": true, + "@aws-cdk/aws-appsync:useArnForSourceApiAssociationIdentifier": true, + "@aws-cdk/aws-rds:preventRenderingDeprecatedCredentials": true, + "@aws-cdk/aws-codepipeline-actions:useNewDefaultBranchForCodeCommitSource": true, + "@aws-cdk/aws-cloudwatch-actions:changeLambdaPermissionLogicalIdForLambdaAction": true, + "@aws-cdk/aws-codepipeline:crossAccountKeysDefaultValueToFalse": true, + "@aws-cdk/aws-codepipeline:defaultPipelineTypeToV2": true, + "@aws-cdk/aws-kms:reduceCrossAccountRegionPolicyScope": true, + "@aws-cdk/aws-eks:nodegroupNameAttribute": true, + "@aws-cdk/aws-ec2:ebsDefaultGp3Volume": true + } +} diff --git a/backend/compact-connect-ui-app/lambdas/nodejs/.gitignore b/backend/compact-connect-ui-app/lambdas/nodejs/.gitignore new file mode 100644 index 000000000..22f3e812b --- /dev/null +++ b/backend/compact-connect-ui-app/lambdas/nodejs/.gitignore @@ -0,0 +1,6 @@ +lib/**/*.mjs +lib/**/*.js +lib/**/*.js.map +bin/**/*.mjs +bin/**/*.js +bin/**/*.js.map diff --git a/backend/compact-connect/lambdas/nodejs/.mocharc.js b/backend/compact-connect-ui-app/lambdas/nodejs/.mocharc.js similarity index 100% rename from backend/compact-connect/lambdas/nodejs/.mocharc.js rename to backend/compact-connect-ui-app/lambdas/nodejs/.mocharc.js diff --git a/backend/compact-connect-ui-app/lambdas/nodejs/Gruntfile.js b/backend/compact-connect-ui-app/lambdas/nodejs/Gruntfile.js new file mode 100644 index 000000000..92efc316e --- /dev/null +++ b/backend/compact-connect-ui-app/lambdas/nodejs/Gruntfile.js @@ -0,0 +1,64 @@ +// +// Gruntfile.js +// CompactConnect +// +// Created by InspiringApps on 7/22/2024. +// + +module.exports = function (grunt) { + require('jit-grunt')(grunt); + + grunt.initConfig({ + //==================== + //= Directory config = + //==================== + lambdaFiles: [ + 'cloudfront-csp/**/**.js', + '!**/node_modules/**/*.js', + '!**/dist/**/*.js', + '!Gruntfile.js', + ], + changedFiles: ['<%= lambdaFiles %>'], + + //=========== + //= ES Lint = + //=========== + eslint: { + initial: { + src: ['<%= lambdaFiles %>'], + }, + watch: { + src: '<%= changedFiles %>', + } + }, + + //================ + //= Watch config = + //================ + watch: { + lambdaFiles: { + files: ['<%= lambdaFiles %>'], + tasks: ['eslint:watch'], + options: { spawn: false } + }, + } + + }); + + // Update the `changedFiles` value with just the files that changed. + // Tasks that operate on <%= changedFiles %> will then operate faster. + var changedFiles = Object.create(null); + var onChange = grunt.util._.debounce(function() { + grunt.config('changedFiles', Object.keys(changedFiles)); + changedFiles = Object.create(null); + }, 200); + + grunt.event.on('watch', function(action, filepath) { + changedFiles[filepath] = action; + onChange(); + }); + + // Task definition + grunt.registerTask('default', ['eslint:initial', 'watch']); + grunt.registerTask('check', ['eslint:initial']); +}; diff --git a/backend/compact-connect-ui-app/lambdas/nodejs/README.md b/backend/compact-connect-ui-app/lambdas/nodejs/README.md new file mode 100644 index 000000000..2d27942eb --- /dev/null +++ b/backend/compact-connect-ui-app/lambdas/nodejs/README.md @@ -0,0 +1,39 @@ +# NodeJS Lambdas + +This folder contains all lambda runtimes that are written with NodeJS/TypeScript. Because these lambdas are each bundled through CDK with ESBuild, we can pull common code and tests together, leaving only the entrypoints in a lambda-specific folder, leaving ESBuild to pull in only needed lib code. + + +## Prerequisites +* **[Node](https://github.com/creationix/nvm#installation) `22.X`** +* **[Yarn](https://yarnpkg.com/en/) `1.22.22`** + * `npm install --global yarn@1.22.22` + +_[back to top](#ingest-event-reporter-lambda)_ + +--- +## Installing dependencies +- `yarn install` + +## Bundling the runtime +- `yarn build` + +_[back to top](#ingest-event-reporter-lambda)_ + +--- +## Local development +- **Linting** + - `yarn run lint` + - Lints all code in all the Lambda function +- **Running an individual Lambda** + - The easiest way to execute the Lambda is to run the tests ([see below](#tests)) + - Commenting out certain tests to limit the execution scope & repetition is trivial + +_[back to top](#ingest-event-reporter-lambda)_ + +--- +## Testing +This project uses `jest` and `aws-sdk-client-mock` for approachable unit testing. The code in this folder can be tested by running: +- `yarn install` +- `yarn test` + +or by using the utility scripts located at `backend/bin`. diff --git a/backend/compact-connect/lambdas/nodejs/cloudfront-csp/.editorconfig b/backend/compact-connect-ui-app/lambdas/nodejs/cloudfront-csp/.editorconfig similarity index 100% rename from backend/compact-connect/lambdas/nodejs/cloudfront-csp/.editorconfig rename to backend/compact-connect-ui-app/lambdas/nodejs/cloudfront-csp/.editorconfig diff --git a/backend/compact-connect/lambdas/nodejs/cloudfront-csp/.gitignore b/backend/compact-connect-ui-app/lambdas/nodejs/cloudfront-csp/.gitignore similarity index 100% rename from backend/compact-connect/lambdas/nodejs/cloudfront-csp/.gitignore rename to backend/compact-connect-ui-app/lambdas/nodejs/cloudfront-csp/.gitignore diff --git a/backend/compact-connect/lambdas/nodejs/cloudfront-csp/README.md b/backend/compact-connect-ui-app/lambdas/nodejs/cloudfront-csp/README.md similarity index 100% rename from backend/compact-connect/lambdas/nodejs/cloudfront-csp/README.md rename to backend/compact-connect-ui-app/lambdas/nodejs/cloudfront-csp/README.md diff --git a/backend/compact-connect/lambdas/nodejs/cloudfront-csp/index.js b/backend/compact-connect-ui-app/lambdas/nodejs/cloudfront-csp/index.js similarity index 100% rename from backend/compact-connect/lambdas/nodejs/cloudfront-csp/index.js rename to backend/compact-connect-ui-app/lambdas/nodejs/cloudfront-csp/index.js diff --git a/backend/compact-connect/lambdas/nodejs/cloudfront-csp/test/config/index.js b/backend/compact-connect-ui-app/lambdas/nodejs/cloudfront-csp/test/config/index.js similarity index 100% rename from backend/compact-connect/lambdas/nodejs/cloudfront-csp/test/config/index.js rename to backend/compact-connect-ui-app/lambdas/nodejs/cloudfront-csp/test/config/index.js diff --git a/backend/compact-connect/lambdas/nodejs/cloudfront-csp/test/index.test.js b/backend/compact-connect-ui-app/lambdas/nodejs/cloudfront-csp/test/index.test.js similarity index 100% rename from backend/compact-connect/lambdas/nodejs/cloudfront-csp/test/index.test.js rename to backend/compact-connect-ui-app/lambdas/nodejs/cloudfront-csp/test/index.test.js diff --git a/backend/compact-connect-ui-app/lambdas/nodejs/eslint.config.mjs b/backend/compact-connect-ui-app/lambdas/nodejs/eslint.config.mjs new file mode 100644 index 000000000..bea30f936 --- /dev/null +++ b/backend/compact-connect-ui-app/lambdas/nodejs/eslint.config.mjs @@ -0,0 +1,82 @@ +import typescriptParser from '@typescript-eslint/parser'; +import typescriptPlugin from '@typescript-eslint/eslint-plugin'; + +const OFF = 0; +const WARNING = 1; +const ERROR = 2; + +export default [ + { + ignores: ['cdk.out/**/*'], + }, + { + files: ['**/*.ts', '**/*.js'], + languageOptions: { + ecmaVersion: 2022, + sourceType: 'module', + parser: typescriptParser, + globals: { + es2022: true, + node: true, + }, + }, + plugins: { + '@typescript-eslint': typescriptPlugin, + }, + rules: { + indent: [ERROR, 4], + 'linebreak-style': [ERROR, 'unix'], + quotes: [ERROR, 'single', { allowTemplateLiterals: true }], + semi: [ERROR, 'always'], + 'max-len': [ERROR, { + code: 120, + ignoreComments: true, + ignoreUrls: true, + ignoreTemplateLiterals: true, + ignoreRegExpLiterals: true, + ignoreStrings: true, + }], + 'no-multi-spaces': [ERROR, { ignoreEOLComments: true }], + 'arrow-parens': [ERROR, 'always'], + 'comma-dangle': [ERROR, { + functions: 'never', + imports: 'never', + exports: 'ignore', + arrays: 'ignore', + objects: 'ignore', + }], + 'array-bracket-spacing': OFF, + 'object-curly-spacing': [ERROR, 'always', { + objectsInObjects: false, + arraysInObjects: false, + }], + 'no-param-reassign': [ERROR, { props: false }], + 'max-classes-per-file': [WARNING, 8], + 'lines-between-class-members': [ERROR, 'always', { exceptAfterSingleLine: true }], + 'implicit-arrow-linebreak': OFF, + 'class-methods-use-this': OFF, + '@typescript-eslint/no-explicit-any': OFF, + 'padding-line-between-statements': [ + ERROR, + { blankLine: 'always', prev: ['const', 'let', 'var'], next: '*' }, + { blankLine: 'any', prev: ['const', 'let', 'var'], next: ['const', 'let', 'var'] }, + ], + } + }, + { + files: ['**/*.js'], + rules: { + '@typescript-eslint/no-var-requires': OFF, + '@typescript-eslint/camelcase': OFF, + }, + }, + { + files: ['**/__tests__/*.{j,t}s?(x)'], + rules: { + 'no-unused-expressions': OFF, + 'quote-props': OFF, + 'import/no-extraneous-dependencies': OFF, + '@typescript-eslint/no-var-requires': OFF, + }, + } +]; diff --git a/backend/compact-connect-ui-app/lambdas/nodejs/jest.config.mjs b/backend/compact-connect-ui-app/lambdas/nodejs/jest.config.mjs new file mode 100644 index 000000000..023b7de50 --- /dev/null +++ b/backend/compact-connect-ui-app/lambdas/nodejs/jest.config.mjs @@ -0,0 +1,20 @@ +export default { + preset: 'ts-jest', + transform: {}, // Disables all transformations to commonJS + testEnvironment: 'node', + testMatch: ['**/tests/**/*.test.[jt]s?(x)'], + moduleFileExtensions: ['ts', 'js'], + verbose: true, + testPathIgnorePatterns: [ + '/node_modules/', + ], + passWithNoTests: true, // Allow Jest to pass when no tests are found + coverageThreshold: { + global: { + branches: 90, + functions: 90, + lines: 90, + statements: 90, + } + } +}; diff --git a/backend/compact-connect-ui-app/lambdas/nodejs/package.json b/backend/compact-connect-ui-app/lambdas/nodejs/package.json new file mode 100644 index 000000000..10867f024 --- /dev/null +++ b/backend/compact-connect-ui-app/lambdas/nodejs/package.json @@ -0,0 +1,62 @@ +{ + "name": "compact-connect", + "version": "1.0.0", + "type": "commonjs", + "description": "NodeJS lambdas for Compact Connect", + "scripts": { + "build": "tsc", + "watch": "tsc -w", + "test:ingest": "NODE_OPTIONS=\"$NODE_OPTIONS --experimental-vm-modules\" jest --coverage", + "lint:ingest": "eslint '**/*.ts' --no-error-on-unmatched-pattern", + "lint:ingest:fix": "eslint --fix '**/*.ts' --no-error-on-unmatched-pattern", + "test:csp": "mocha cloudfront-csp/test", + "lint:csp": "grunt check", + "lint": "eslint '**/*.ts' --no-error-on-unmatched-pattern && grunt check", + "test": "NODE_OPTIONS=\"$NODE_OPTIONS --experimental-vm-modules\" jest --coverage && mocha cloudfront-csp/test", + "audit:dependencies": "yarn audit --groups dependencies --level moderate", + "grunt": "grunt" + }, + "author": "Inspiring Apps", + "license": "UNLICENSED", + "devDependencies": { + "@types/aws-lambda": "8.10.145", + "@types/jest": "^29.5.12", + "@types/node": "22.5.4", + "@types/nodemailer": "^6.4.14", + "@types/react": "^18.3.12", + "@typescript-eslint/eslint-plugin": "^8.12.2", + "@typescript-eslint/parser": "^8.12.2", + "aws-sdk-client-mock": "^4.1.0", + "aws-sdk-client-mock-jest": "^4.1.0", + "esbuild": "0.24.0", + "eslint": "^9.13.0", + "jest": "^29.7.0", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "ts-jest": "^29.2.5", + "ts-node": "^10.9.2", + "tslib": "^2.8.0", + "tsx": "^4.19.2", + "typescript": "5.6.3", + "chai": "^4.1.2", + "chai-match-pattern": "^1.1.0", + "chalk": "^4.1.2", + "grunt": "^1.6.1", + "grunt-contrib-watch": "^1.1.0", + "grunt-eslint": "^25.0.0", + "jit-grunt": "^0.10.0", + "lambda-local": "^2.2.0", + "mocha": "^11.0.0" + }, + "dependencies": { + "@aws-lambda-powertools/logger": "^2.10.0", + "@aws-sdk/client-dynamodb": "^3.682.0", + "@aws-sdk/client-s3": "^3.682.0", + "@aws-sdk/client-ses": "^3.682.0", + "@aws-sdk/util-dynamodb": "^3.682.0", + "@usewaypoint/email-builder": "^0.0.6", + "aws-lambda": "1.0.7", + "nodemailer": "^6.9.12", + "zod": "^3.23.8" + } +} diff --git a/backend/compact-connect-ui-app/lambdas/nodejs/tsconfig.json b/backend/compact-connect-ui-app/lambdas/nodejs/tsconfig.json new file mode 100644 index 000000000..bf3fc50fc --- /dev/null +++ b/backend/compact-connect-ui-app/lambdas/nodejs/tsconfig.json @@ -0,0 +1,32 @@ +{ + "compilerOptions": { + "target": "esnext", + "module": "esnext", + "strict": true, + "jsx": "preserve", + "importHelpers": true, + "moduleResolution": "node", + "experimentalDecorators": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "sourceMap": true, + "baseUrl": ".", + "noImplicitAny": false, + "lib": [ + "esnext", + "scripthost" + ], + "typeRoots": [ + "./node_modules/@types" + ] + }, + "include": [ + "**/handler.ts", + "lib/**/*.ts", + "lib/**/*.tsx" + ], + "exclude": [ + "cdk.out", + "node_modules" + ] +} diff --git a/backend/compact-connect-ui-app/lambdas/nodejs/yarn.lock b/backend/compact-connect-ui-app/lambdas/nodejs/yarn.lock new file mode 100644 index 000000000..d902386c0 --- /dev/null +++ b/backend/compact-connect-ui-app/lambdas/nodejs/yarn.lock @@ -0,0 +1,6245 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@ampproject/remapping@^2.2.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.3.0.tgz#ed441b6fa600072520ce18b43d2c8cc8caecc7f4" + integrity sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw== + dependencies: + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.24" + +"@aws-crypto/crc32@5.2.0": + version "5.2.0" + resolved "https://registry.yarnpkg.com/@aws-crypto/crc32/-/crc32-5.2.0.tgz#cfcc22570949c98c6689cfcbd2d693d36cdae2e1" + integrity sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg== + dependencies: + "@aws-crypto/util" "^5.2.0" + "@aws-sdk/types" "^3.222.0" + tslib "^2.6.2" + +"@aws-crypto/crc32c@5.2.0": + version "5.2.0" + resolved "https://registry.yarnpkg.com/@aws-crypto/crc32c/-/crc32c-5.2.0.tgz#4e34aab7f419307821509a98b9b08e84e0c1917e" + integrity sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag== + dependencies: + "@aws-crypto/util" "^5.2.0" + "@aws-sdk/types" "^3.222.0" + tslib "^2.6.2" + +"@aws-crypto/sha1-browser@5.2.0": + version "5.2.0" + resolved "https://registry.yarnpkg.com/@aws-crypto/sha1-browser/-/sha1-browser-5.2.0.tgz#b0ee2d2821d3861f017e965ef3b4cb38e3b6a0f4" + integrity sha512-OH6lveCFfcDjX4dbAvCFSYUjJZjDr/3XJ3xHtjn3Oj5b9RjojQo8npoLeA/bNwkOkrSQ0wgrHzXk4tDRxGKJeg== + dependencies: + "@aws-crypto/supports-web-crypto" "^5.2.0" + "@aws-crypto/util" "^5.2.0" + "@aws-sdk/types" "^3.222.0" + "@aws-sdk/util-locate-window" "^3.0.0" + "@smithy/util-utf8" "^2.0.0" + tslib "^2.6.2" + +"@aws-crypto/sha256-browser@5.2.0": + version "5.2.0" + resolved "https://registry.yarnpkg.com/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz#153895ef1dba6f9fce38af550e0ef58988eb649e" + integrity sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw== + dependencies: + "@aws-crypto/sha256-js" "^5.2.0" + "@aws-crypto/supports-web-crypto" "^5.2.0" + "@aws-crypto/util" "^5.2.0" + "@aws-sdk/types" "^3.222.0" + "@aws-sdk/util-locate-window" "^3.0.0" + "@smithy/util-utf8" "^2.0.0" + tslib "^2.6.2" + +"@aws-crypto/sha256-js@5.2.0", "@aws-crypto/sha256-js@^5.2.0": + version "5.2.0" + resolved "https://registry.yarnpkg.com/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz#c4fdb773fdbed9a664fc1a95724e206cf3860042" + integrity sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA== + dependencies: + "@aws-crypto/util" "^5.2.0" + "@aws-sdk/types" "^3.222.0" + tslib "^2.6.2" + +"@aws-crypto/supports-web-crypto@^5.2.0": + version "5.2.0" + resolved "https://registry.yarnpkg.com/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz#a1e399af29269be08e695109aa15da0a07b5b5fb" + integrity sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg== + dependencies: + tslib "^2.6.2" + +"@aws-crypto/util@5.2.0", "@aws-crypto/util@^5.2.0": + version "5.2.0" + resolved "https://registry.yarnpkg.com/@aws-crypto/util/-/util-5.2.0.tgz#71284c9cffe7927ddadac793c14f14886d3876da" + integrity sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ== + dependencies: + "@aws-sdk/types" "^3.222.0" + "@smithy/util-utf8" "^2.0.0" + tslib "^2.6.2" + +"@aws-lambda-powertools/commons@^2.11.0": + version "2.11.0" + resolved "https://registry.yarnpkg.com/@aws-lambda-powertools/commons/-/commons-2.11.0.tgz#7fb54a8e775769b260455c9e7c6f414506212620" + integrity sha512-gtiBeUSyg4VcL4qra5G4AwuOl7CNF6g0oDasAS5KxkEi8IhTku6S7vJTpQ82+12oviwKBD+w27yEcGQmSw5xdg== + +"@aws-lambda-powertools/logger@^2.10.0": + version "2.11.0" + resolved "https://registry.yarnpkg.com/@aws-lambda-powertools/logger/-/logger-2.11.0.tgz#bd3426adfa761700d1420ba7b959eef929733769" + integrity sha512-40wST/S644ngwadp7Kk70Rgvh35yQ0R3HqFKxAtlBxQNAGsqxQtjJeBUg/KQDKUhE1IZH0ahXyEN3B7rSxPn0Q== + dependencies: + "@aws-lambda-powertools/commons" "^2.11.0" + lodash.merge "^4.6.2" + +"@aws-sdk/client-dynamodb@^3.682.0": + version "3.705.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/client-dynamodb/-/client-dynamodb-3.705.0.tgz#e25cd3a3bc6884549dfee5003e3b5622ea8cf047" + integrity sha512-uHmjzK4/r6KiXSMofSRmLdGb0N+X42yoTZN9YrQK2PxPMdLjh7JCGv4thlLcZP1NBHPfFxsEh61kqf8+1SfzgQ== + dependencies: + "@aws-crypto/sha256-browser" "5.2.0" + "@aws-crypto/sha256-js" "5.2.0" + "@aws-sdk/client-sso-oidc" "3.699.0" + "@aws-sdk/client-sts" "3.699.0" + "@aws-sdk/core" "3.696.0" + "@aws-sdk/credential-provider-node" "3.699.0" + "@aws-sdk/middleware-endpoint-discovery" "3.696.0" + "@aws-sdk/middleware-host-header" "3.696.0" + "@aws-sdk/middleware-logger" "3.696.0" + "@aws-sdk/middleware-recursion-detection" "3.696.0" + "@aws-sdk/middleware-user-agent" "3.696.0" + "@aws-sdk/region-config-resolver" "3.696.0" + "@aws-sdk/types" "3.696.0" + "@aws-sdk/util-endpoints" "3.696.0" + "@aws-sdk/util-user-agent-browser" "3.696.0" + "@aws-sdk/util-user-agent-node" "3.696.0" + "@smithy/config-resolver" "^3.0.12" + "@smithy/core" "^2.5.3" + "@smithy/fetch-http-handler" "^4.1.1" + "@smithy/hash-node" "^3.0.10" + "@smithy/invalid-dependency" "^3.0.10" + "@smithy/middleware-content-length" "^3.0.12" + "@smithy/middleware-endpoint" "^3.2.3" + "@smithy/middleware-retry" "^3.0.27" + "@smithy/middleware-serde" "^3.0.10" + "@smithy/middleware-stack" "^3.0.10" + "@smithy/node-config-provider" "^3.1.11" + "@smithy/node-http-handler" "^3.3.1" + "@smithy/protocol-http" "^4.1.7" + "@smithy/smithy-client" "^3.4.4" + "@smithy/types" "^3.7.1" + "@smithy/url-parser" "^3.0.10" + "@smithy/util-base64" "^3.0.0" + "@smithy/util-body-length-browser" "^3.0.0" + "@smithy/util-body-length-node" "^3.0.0" + "@smithy/util-defaults-mode-browser" "^3.0.27" + "@smithy/util-defaults-mode-node" "^3.0.27" + "@smithy/util-endpoints" "^2.1.6" + "@smithy/util-middleware" "^3.0.10" + "@smithy/util-retry" "^3.0.10" + "@smithy/util-utf8" "^3.0.0" + "@smithy/util-waiter" "^3.1.9" + "@types/uuid" "^9.0.1" + tslib "^2.6.2" + uuid "^9.0.1" + +"@aws-sdk/client-s3@^3.682.0": + version "3.705.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/client-s3/-/client-s3-3.705.0.tgz#7a4a4784bd5b3ca3187ff876b771eaf0cbde1c42" + integrity sha512-Fm0Cbc4zr0yG0DnNycz7ywlL5tQFdLSb7xCIPfzrxJb3YQiTXWxH5eu61SSsP/Z6RBNRolmRPvst/iNgX0fWvA== + dependencies: + "@aws-crypto/sha1-browser" "5.2.0" + "@aws-crypto/sha256-browser" "5.2.0" + "@aws-crypto/sha256-js" "5.2.0" + "@aws-sdk/client-sso-oidc" "3.699.0" + "@aws-sdk/client-sts" "3.699.0" + "@aws-sdk/core" "3.696.0" + "@aws-sdk/credential-provider-node" "3.699.0" + "@aws-sdk/middleware-bucket-endpoint" "3.696.0" + "@aws-sdk/middleware-expect-continue" "3.696.0" + "@aws-sdk/middleware-flexible-checksums" "3.701.0" + "@aws-sdk/middleware-host-header" "3.696.0" + "@aws-sdk/middleware-location-constraint" "3.696.0" + "@aws-sdk/middleware-logger" "3.696.0" + "@aws-sdk/middleware-recursion-detection" "3.696.0" + "@aws-sdk/middleware-sdk-s3" "3.696.0" + "@aws-sdk/middleware-ssec" "3.696.0" + "@aws-sdk/middleware-user-agent" "3.696.0" + "@aws-sdk/region-config-resolver" "3.696.0" + "@aws-sdk/signature-v4-multi-region" "3.696.0" + "@aws-sdk/types" "3.696.0" + "@aws-sdk/util-endpoints" "3.696.0" + "@aws-sdk/util-user-agent-browser" "3.696.0" + "@aws-sdk/util-user-agent-node" "3.696.0" + "@aws-sdk/xml-builder" "3.696.0" + "@smithy/config-resolver" "^3.0.12" + "@smithy/core" "^2.5.3" + "@smithy/eventstream-serde-browser" "^3.0.13" + "@smithy/eventstream-serde-config-resolver" "^3.0.10" + "@smithy/eventstream-serde-node" "^3.0.12" + "@smithy/fetch-http-handler" "^4.1.1" + "@smithy/hash-blob-browser" "^3.1.9" + "@smithy/hash-node" "^3.0.10" + "@smithy/hash-stream-node" "^3.1.9" + "@smithy/invalid-dependency" "^3.0.10" + "@smithy/md5-js" "^3.0.10" + "@smithy/middleware-content-length" "^3.0.12" + "@smithy/middleware-endpoint" "^3.2.3" + "@smithy/middleware-retry" "^3.0.27" + "@smithy/middleware-serde" "^3.0.10" + "@smithy/middleware-stack" "^3.0.10" + "@smithy/node-config-provider" "^3.1.11" + "@smithy/node-http-handler" "^3.3.1" + "@smithy/protocol-http" "^4.1.7" + "@smithy/smithy-client" "^3.4.4" + "@smithy/types" "^3.7.1" + "@smithy/url-parser" "^3.0.10" + "@smithy/util-base64" "^3.0.0" + "@smithy/util-body-length-browser" "^3.0.0" + "@smithy/util-body-length-node" "^3.0.0" + "@smithy/util-defaults-mode-browser" "^3.0.27" + "@smithy/util-defaults-mode-node" "^3.0.27" + "@smithy/util-endpoints" "^2.1.6" + "@smithy/util-middleware" "^3.0.10" + "@smithy/util-retry" "^3.0.10" + "@smithy/util-stream" "^3.3.1" + "@smithy/util-utf8" "^3.0.0" + "@smithy/util-waiter" "^3.1.9" + tslib "^2.6.2" + +"@aws-sdk/client-ses@^3.682.0": + version "3.699.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/client-ses/-/client-ses-3.699.0.tgz#b48d9c8865877b7baa4b1a90cb3e4c2d59c0c5e0" + integrity sha512-prpkr2jnhD2KsinQMBdX2wvSpNxFm9d02EUR4L78yxjg2oppXmu/cBjWdlVrSkqqE2EYfcHo0JV2WmRZZC1VyQ== + dependencies: + "@aws-crypto/sha256-browser" "5.2.0" + "@aws-crypto/sha256-js" "5.2.0" + "@aws-sdk/client-sso-oidc" "3.699.0" + "@aws-sdk/client-sts" "3.699.0" + "@aws-sdk/core" "3.696.0" + "@aws-sdk/credential-provider-node" "3.699.0" + "@aws-sdk/middleware-host-header" "3.696.0" + "@aws-sdk/middleware-logger" "3.696.0" + "@aws-sdk/middleware-recursion-detection" "3.696.0" + "@aws-sdk/middleware-user-agent" "3.696.0" + "@aws-sdk/region-config-resolver" "3.696.0" + "@aws-sdk/types" "3.696.0" + "@aws-sdk/util-endpoints" "3.696.0" + "@aws-sdk/util-user-agent-browser" "3.696.0" + "@aws-sdk/util-user-agent-node" "3.696.0" + "@smithy/config-resolver" "^3.0.12" + "@smithy/core" "^2.5.3" + "@smithy/fetch-http-handler" "^4.1.1" + "@smithy/hash-node" "^3.0.10" + "@smithy/invalid-dependency" "^3.0.10" + "@smithy/middleware-content-length" "^3.0.12" + "@smithy/middleware-endpoint" "^3.2.3" + "@smithy/middleware-retry" "^3.0.27" + "@smithy/middleware-serde" "^3.0.10" + "@smithy/middleware-stack" "^3.0.10" + "@smithy/node-config-provider" "^3.1.11" + "@smithy/node-http-handler" "^3.3.1" + "@smithy/protocol-http" "^4.1.7" + "@smithy/smithy-client" "^3.4.4" + "@smithy/types" "^3.7.1" + "@smithy/url-parser" "^3.0.10" + "@smithy/util-base64" "^3.0.0" + "@smithy/util-body-length-browser" "^3.0.0" + "@smithy/util-body-length-node" "^3.0.0" + "@smithy/util-defaults-mode-browser" "^3.0.27" + "@smithy/util-defaults-mode-node" "^3.0.27" + "@smithy/util-endpoints" "^2.1.6" + "@smithy/util-middleware" "^3.0.10" + "@smithy/util-retry" "^3.0.10" + "@smithy/util-utf8" "^3.0.0" + "@smithy/util-waiter" "^3.1.9" + tslib "^2.6.2" + +"@aws-sdk/client-sso-oidc@3.699.0": + version "3.699.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.699.0.tgz#a35665e681abd518b56330bc7dab63041fbdaf83" + integrity sha512-u8a1GorY5D1l+4FQAf4XBUC1T10/t7neuwT21r0ymrtMFSK2a9QqVHKMoLkvavAwyhJnARSBM9/UQC797PFOFw== + dependencies: + "@aws-crypto/sha256-browser" "5.2.0" + "@aws-crypto/sha256-js" "5.2.0" + "@aws-sdk/core" "3.696.0" + "@aws-sdk/credential-provider-node" "3.699.0" + "@aws-sdk/middleware-host-header" "3.696.0" + "@aws-sdk/middleware-logger" "3.696.0" + "@aws-sdk/middleware-recursion-detection" "3.696.0" + "@aws-sdk/middleware-user-agent" "3.696.0" + "@aws-sdk/region-config-resolver" "3.696.0" + "@aws-sdk/types" "3.696.0" + "@aws-sdk/util-endpoints" "3.696.0" + "@aws-sdk/util-user-agent-browser" "3.696.0" + "@aws-sdk/util-user-agent-node" "3.696.0" + "@smithy/config-resolver" "^3.0.12" + "@smithy/core" "^2.5.3" + "@smithy/fetch-http-handler" "^4.1.1" + "@smithy/hash-node" "^3.0.10" + "@smithy/invalid-dependency" "^3.0.10" + "@smithy/middleware-content-length" "^3.0.12" + "@smithy/middleware-endpoint" "^3.2.3" + "@smithy/middleware-retry" "^3.0.27" + "@smithy/middleware-serde" "^3.0.10" + "@smithy/middleware-stack" "^3.0.10" + "@smithy/node-config-provider" "^3.1.11" + "@smithy/node-http-handler" "^3.3.1" + "@smithy/protocol-http" "^4.1.7" + "@smithy/smithy-client" "^3.4.4" + "@smithy/types" "^3.7.1" + "@smithy/url-parser" "^3.0.10" + "@smithy/util-base64" "^3.0.0" + "@smithy/util-body-length-browser" "^3.0.0" + "@smithy/util-body-length-node" "^3.0.0" + "@smithy/util-defaults-mode-browser" "^3.0.27" + "@smithy/util-defaults-mode-node" "^3.0.27" + "@smithy/util-endpoints" "^2.1.6" + "@smithy/util-middleware" "^3.0.10" + "@smithy/util-retry" "^3.0.10" + "@smithy/util-utf8" "^3.0.0" + tslib "^2.6.2" + +"@aws-sdk/client-sso@3.696.0": + version "3.696.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/client-sso/-/client-sso-3.696.0.tgz#a9251e88cdfc91fb14191f760f68baa835e88f1c" + integrity sha512-q5TTkd08JS0DOkHfUL853tuArf7NrPeqoS5UOvqJho8ibV9Ak/a/HO4kNvy9Nj3cib/toHYHsQIEtecUPSUUrQ== + dependencies: + "@aws-crypto/sha256-browser" "5.2.0" + "@aws-crypto/sha256-js" "5.2.0" + "@aws-sdk/core" "3.696.0" + "@aws-sdk/middleware-host-header" "3.696.0" + "@aws-sdk/middleware-logger" "3.696.0" + "@aws-sdk/middleware-recursion-detection" "3.696.0" + "@aws-sdk/middleware-user-agent" "3.696.0" + "@aws-sdk/region-config-resolver" "3.696.0" + "@aws-sdk/types" "3.696.0" + "@aws-sdk/util-endpoints" "3.696.0" + "@aws-sdk/util-user-agent-browser" "3.696.0" + "@aws-sdk/util-user-agent-node" "3.696.0" + "@smithy/config-resolver" "^3.0.12" + "@smithy/core" "^2.5.3" + "@smithy/fetch-http-handler" "^4.1.1" + "@smithy/hash-node" "^3.0.10" + "@smithy/invalid-dependency" "^3.0.10" + "@smithy/middleware-content-length" "^3.0.12" + "@smithy/middleware-endpoint" "^3.2.3" + "@smithy/middleware-retry" "^3.0.27" + "@smithy/middleware-serde" "^3.0.10" + "@smithy/middleware-stack" "^3.0.10" + "@smithy/node-config-provider" "^3.1.11" + "@smithy/node-http-handler" "^3.3.1" + "@smithy/protocol-http" "^4.1.7" + "@smithy/smithy-client" "^3.4.4" + "@smithy/types" "^3.7.1" + "@smithy/url-parser" "^3.0.10" + "@smithy/util-base64" "^3.0.0" + "@smithy/util-body-length-browser" "^3.0.0" + "@smithy/util-body-length-node" "^3.0.0" + "@smithy/util-defaults-mode-browser" "^3.0.27" + "@smithy/util-defaults-mode-node" "^3.0.27" + "@smithy/util-endpoints" "^2.1.6" + "@smithy/util-middleware" "^3.0.10" + "@smithy/util-retry" "^3.0.10" + "@smithy/util-utf8" "^3.0.0" + tslib "^2.6.2" + +"@aws-sdk/client-sts@3.699.0": + version "3.699.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/client-sts/-/client-sts-3.699.0.tgz#9419be6bbf3809008128117afea8b9129b5a959d" + integrity sha512-++lsn4x2YXsZPIzFVwv3fSUVM55ZT0WRFmPeNilYIhZClxHLmVAWKH4I55cY9ry60/aTKYjzOXkWwyBKGsGvQg== + dependencies: + "@aws-crypto/sha256-browser" "5.2.0" + "@aws-crypto/sha256-js" "5.2.0" + "@aws-sdk/client-sso-oidc" "3.699.0" + "@aws-sdk/core" "3.696.0" + "@aws-sdk/credential-provider-node" "3.699.0" + "@aws-sdk/middleware-host-header" "3.696.0" + "@aws-sdk/middleware-logger" "3.696.0" + "@aws-sdk/middleware-recursion-detection" "3.696.0" + "@aws-sdk/middleware-user-agent" "3.696.0" + "@aws-sdk/region-config-resolver" "3.696.0" + "@aws-sdk/types" "3.696.0" + "@aws-sdk/util-endpoints" "3.696.0" + "@aws-sdk/util-user-agent-browser" "3.696.0" + "@aws-sdk/util-user-agent-node" "3.696.0" + "@smithy/config-resolver" "^3.0.12" + "@smithy/core" "^2.5.3" + "@smithy/fetch-http-handler" "^4.1.1" + "@smithy/hash-node" "^3.0.10" + "@smithy/invalid-dependency" "^3.0.10" + "@smithy/middleware-content-length" "^3.0.12" + "@smithy/middleware-endpoint" "^3.2.3" + "@smithy/middleware-retry" "^3.0.27" + "@smithy/middleware-serde" "^3.0.10" + "@smithy/middleware-stack" "^3.0.10" + "@smithy/node-config-provider" "^3.1.11" + "@smithy/node-http-handler" "^3.3.1" + "@smithy/protocol-http" "^4.1.7" + "@smithy/smithy-client" "^3.4.4" + "@smithy/types" "^3.7.1" + "@smithy/url-parser" "^3.0.10" + "@smithy/util-base64" "^3.0.0" + "@smithy/util-body-length-browser" "^3.0.0" + "@smithy/util-body-length-node" "^3.0.0" + "@smithy/util-defaults-mode-browser" "^3.0.27" + "@smithy/util-defaults-mode-node" "^3.0.27" + "@smithy/util-endpoints" "^2.1.6" + "@smithy/util-middleware" "^3.0.10" + "@smithy/util-retry" "^3.0.10" + "@smithy/util-utf8" "^3.0.0" + tslib "^2.6.2" + +"@aws-sdk/core@3.696.0": + version "3.696.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/core/-/core-3.696.0.tgz#bdf306bdc019f485738d91d8838eec877861dd26" + integrity sha512-3c9III1k03DgvRZWg8vhVmfIXPG6hAciN9MzQTzqGngzWAELZF/WONRTRQuDFixVtarQatmLHYVw/atGeA2Byw== + dependencies: + "@aws-sdk/types" "3.696.0" + "@smithy/core" "^2.5.3" + "@smithy/node-config-provider" "^3.1.11" + "@smithy/property-provider" "^3.1.9" + "@smithy/protocol-http" "^4.1.7" + "@smithy/signature-v4" "^4.2.2" + "@smithy/smithy-client" "^3.4.4" + "@smithy/types" "^3.7.1" + "@smithy/util-middleware" "^3.0.10" + fast-xml-parser "4.4.1" + tslib "^2.6.2" + +"@aws-sdk/credential-provider-env@3.696.0": + version "3.696.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-env/-/credential-provider-env-3.696.0.tgz#afad9e61cd03da404bb03e5bce83c49736b85271" + integrity sha512-T9iMFnJL7YTlESLpVFT3fg1Lkb1lD+oiaIC8KMpepb01gDUBIpj9+Y+pA/cgRWW0yRxmkDXNazAE2qQTVFGJzA== + dependencies: + "@aws-sdk/core" "3.696.0" + "@aws-sdk/types" "3.696.0" + "@smithy/property-provider" "^3.1.9" + "@smithy/types" "^3.7.1" + tslib "^2.6.2" + +"@aws-sdk/credential-provider-http@3.696.0": + version "3.696.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-http/-/credential-provider-http-3.696.0.tgz#535756f9f427fbe851a8c1db7b0e3aaaf7790ba2" + integrity sha512-GV6EbvPi2eq1+WgY/o2RFA3P7HGmnkIzCNmhwtALFlqMroLYWKE7PSeHw66Uh1dFQeVESn0/+hiUNhu1mB0emA== + dependencies: + "@aws-sdk/core" "3.696.0" + "@aws-sdk/types" "3.696.0" + "@smithy/fetch-http-handler" "^4.1.1" + "@smithy/node-http-handler" "^3.3.1" + "@smithy/property-provider" "^3.1.9" + "@smithy/protocol-http" "^4.1.7" + "@smithy/smithy-client" "^3.4.4" + "@smithy/types" "^3.7.1" + "@smithy/util-stream" "^3.3.1" + tslib "^2.6.2" + +"@aws-sdk/credential-provider-ini@3.699.0": + version "3.699.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.699.0.tgz#7919a454b05c5446d04a0d3270807046a029ee30" + integrity sha512-dXmCqjJnKmG37Q+nLjPVu22mNkrGHY8hYoOt3Jo9R2zr5MYV7s/NHsCHr+7E+BZ+tfZYLRPeB1wkpTeHiEcdRw== + dependencies: + "@aws-sdk/core" "3.696.0" + "@aws-sdk/credential-provider-env" "3.696.0" + "@aws-sdk/credential-provider-http" "3.696.0" + "@aws-sdk/credential-provider-process" "3.696.0" + "@aws-sdk/credential-provider-sso" "3.699.0" + "@aws-sdk/credential-provider-web-identity" "3.696.0" + "@aws-sdk/types" "3.696.0" + "@smithy/credential-provider-imds" "^3.2.6" + "@smithy/property-provider" "^3.1.9" + "@smithy/shared-ini-file-loader" "^3.1.10" + "@smithy/types" "^3.7.1" + tslib "^2.6.2" + +"@aws-sdk/credential-provider-node@3.699.0": + version "3.699.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-node/-/credential-provider-node-3.699.0.tgz#6a1e32a49a7fa71d10c85a927267d1782444def1" + integrity sha512-MmEmNDo1bBtTgRmdNfdQksXu4uXe66s0p1hi1YPrn1h59Q605eq/xiWbGL6/3KdkViH6eGUuABeV2ODld86ylg== + dependencies: + "@aws-sdk/credential-provider-env" "3.696.0" + "@aws-sdk/credential-provider-http" "3.696.0" + "@aws-sdk/credential-provider-ini" "3.699.0" + "@aws-sdk/credential-provider-process" "3.696.0" + "@aws-sdk/credential-provider-sso" "3.699.0" + "@aws-sdk/credential-provider-web-identity" "3.696.0" + "@aws-sdk/types" "3.696.0" + "@smithy/credential-provider-imds" "^3.2.6" + "@smithy/property-provider" "^3.1.9" + "@smithy/shared-ini-file-loader" "^3.1.10" + "@smithy/types" "^3.7.1" + tslib "^2.6.2" + +"@aws-sdk/credential-provider-process@3.696.0": + version "3.696.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-process/-/credential-provider-process-3.696.0.tgz#45da7b948aa40987b413c7c0d4a8125bf1433651" + integrity sha512-mL1RcFDe9sfmyU5K1nuFkO8UiJXXxLX4JO1gVaDIOvPqwStpUAwi3A1BoeZhWZZNQsiKI810RnYGo0E0WB/hUA== + dependencies: + "@aws-sdk/core" "3.696.0" + "@aws-sdk/types" "3.696.0" + "@smithy/property-provider" "^3.1.9" + "@smithy/shared-ini-file-loader" "^3.1.10" + "@smithy/types" "^3.7.1" + tslib "^2.6.2" + +"@aws-sdk/credential-provider-sso@3.699.0": + version "3.699.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.699.0.tgz#515e2ecd407bace3141b8b192505631de415667e" + integrity sha512-Ekp2cZG4pl9D8+uKWm4qO1xcm8/MeiI8f+dnlZm8aQzizeC+aXYy9GyoclSf6daK8KfRPiRfM7ZHBBL5dAfdMA== + dependencies: + "@aws-sdk/client-sso" "3.696.0" + "@aws-sdk/core" "3.696.0" + "@aws-sdk/token-providers" "3.699.0" + "@aws-sdk/types" "3.696.0" + "@smithy/property-provider" "^3.1.9" + "@smithy/shared-ini-file-loader" "^3.1.10" + "@smithy/types" "^3.7.1" + tslib "^2.6.2" + +"@aws-sdk/credential-provider-web-identity@3.696.0": + version "3.696.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.696.0.tgz#3f97c00bd3bc7cfd988e098af67ff7c8392ce188" + integrity sha512-XJ/CVlWChM0VCoc259vWguFUjJDn/QwDqHwbx+K9cg3v6yrqXfK5ai+p/6lx0nQpnk4JzPVeYYxWRpaTsGC9rg== + dependencies: + "@aws-sdk/core" "3.696.0" + "@aws-sdk/types" "3.696.0" + "@smithy/property-provider" "^3.1.9" + "@smithy/types" "^3.7.1" + tslib "^2.6.2" + +"@aws-sdk/endpoint-cache@3.693.0": + version "3.693.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/endpoint-cache/-/endpoint-cache-3.693.0.tgz#4b3f0bbc16dc2907e1b977e3d8ddfc7ba008fd12" + integrity sha512-/zK0ZZncBf5FbTfo8rJMcQIXXk4Ibhe5zEMiwFNivVPR2uNC0+oqfwXz7vjxwY0t6BPE3Bs4h9uFEz4xuGCY6w== + dependencies: + mnemonist "0.38.3" + tslib "^2.6.2" + +"@aws-sdk/middleware-bucket-endpoint@3.696.0": + version "3.696.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.696.0.tgz#5c4e6c75855e94a8d7160812b63cb911373a4811" + integrity sha512-V07jishKHUS5heRNGFpCWCSTjRJyQLynS/ncUeE8ZYtG66StOOQWftTwDfFOSoXlIqrXgb4oT9atryzXq7Z4LQ== + dependencies: + "@aws-sdk/types" "3.696.0" + "@aws-sdk/util-arn-parser" "3.693.0" + "@smithy/node-config-provider" "^3.1.11" + "@smithy/protocol-http" "^4.1.7" + "@smithy/types" "^3.7.1" + "@smithy/util-config-provider" "^3.0.0" + tslib "^2.6.2" + +"@aws-sdk/middleware-endpoint-discovery@3.696.0": + version "3.696.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-endpoint-discovery/-/middleware-endpoint-discovery-3.696.0.tgz#c1a4cf63888fbd6903c3a767355829397e5ae9f3" + integrity sha512-KZvgR3lB9zdLuuO+SxeQQVDn8R46Brlolsbv7JGyR6id0BNy6pqitHdcrZCyp9jaMjrSFcPROceeLy70Cu3pZg== + dependencies: + "@aws-sdk/endpoint-cache" "3.693.0" + "@aws-sdk/types" "3.696.0" + "@smithy/node-config-provider" "^3.1.11" + "@smithy/protocol-http" "^4.1.7" + "@smithy/types" "^3.7.1" + tslib "^2.6.2" + +"@aws-sdk/middleware-expect-continue@3.696.0": + version "3.696.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.696.0.tgz#9c8e56d45bc99899f0ed054ea67d0f9703b76356" + integrity sha512-vpVukqY3U2pb+ULeX0shs6L0aadNep6kKzjme/MyulPjtUDJpD3AekHsXRrCCGLmOqSKqRgQn5zhV9pQhHsb6Q== + dependencies: + "@aws-sdk/types" "3.696.0" + "@smithy/protocol-http" "^4.1.7" + "@smithy/types" "^3.7.1" + tslib "^2.6.2" + +"@aws-sdk/middleware-flexible-checksums@3.701.0": + version "3.701.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.701.0.tgz#1793c77302f37aeab61205a0dbd89b45081bcc4a" + integrity sha512-adNaPCyTT+CiVM0ufDiO1Fe7nlRmJdI9Hcgj0M9S6zR7Dw70Ra5z8Lslkd7syAccYvZaqxLklGjPQH/7GNxwTA== + dependencies: + "@aws-crypto/crc32" "5.2.0" + "@aws-crypto/crc32c" "5.2.0" + "@aws-crypto/util" "5.2.0" + "@aws-sdk/core" "3.696.0" + "@aws-sdk/types" "3.696.0" + "@smithy/is-array-buffer" "^3.0.0" + "@smithy/node-config-provider" "^3.1.11" + "@smithy/protocol-http" "^4.1.7" + "@smithy/types" "^3.7.1" + "@smithy/util-middleware" "^3.0.10" + "@smithy/util-stream" "^3.3.1" + "@smithy/util-utf8" "^3.0.0" + tslib "^2.6.2" + +"@aws-sdk/middleware-host-header@3.696.0": + version "3.696.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-host-header/-/middleware-host-header-3.696.0.tgz#20aae0efeb973ca1a6db1b1014acbcdd06ad472e" + integrity sha512-zELJp9Ta2zkX7ELggMN9qMCgekqZhFC5V2rOr4hJDEb/Tte7gpfKSObAnw/3AYiVqt36sjHKfdkoTsuwGdEoDg== + dependencies: + "@aws-sdk/types" "3.696.0" + "@smithy/protocol-http" "^4.1.7" + "@smithy/types" "^3.7.1" + tslib "^2.6.2" + +"@aws-sdk/middleware-location-constraint@3.696.0": + version "3.696.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.696.0.tgz#21edf9fab1b29cb5f819c3be57e1286a86ae8be2" + integrity sha512-FgH12OB0q+DtTrP2aiDBddDKwL4BPOrm7w3VV9BJrSdkqQCNBPz8S1lb0y5eVH4tBG+2j7gKPlOv1wde4jF/iw== + dependencies: + "@aws-sdk/types" "3.696.0" + "@smithy/types" "^3.7.1" + tslib "^2.6.2" + +"@aws-sdk/middleware-logger@3.696.0": + version "3.696.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-logger/-/middleware-logger-3.696.0.tgz#79d68b7e5ba181511ade769b11165bfb7527181e" + integrity sha512-KhkHt+8AjCxcR/5Zp3++YPJPpFQzxpr+jmONiT/Jw2yqnSngZ0Yspm5wGoRx2hS1HJbyZNuaOWEGuJoxLeBKfA== + dependencies: + "@aws-sdk/types" "3.696.0" + "@smithy/types" "^3.7.1" + tslib "^2.6.2" + +"@aws-sdk/middleware-recursion-detection@3.696.0": + version "3.696.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.696.0.tgz#aa437d645d74cb785905162266741125c18f182a" + integrity sha512-si/maV3Z0hH7qa99f9ru2xpS5HlfSVcasRlNUXKSDm611i7jFMWwGNLUOXFAOLhXotPX5G3Z6BLwL34oDeBMug== + dependencies: + "@aws-sdk/types" "3.696.0" + "@smithy/protocol-http" "^4.1.7" + "@smithy/types" "^3.7.1" + tslib "^2.6.2" + +"@aws-sdk/middleware-sdk-s3@3.696.0": + version "3.696.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.696.0.tgz#63199a2df26e097122c07edf2e178f6d407b0ba7" + integrity sha512-M7fEiAiN7DBMHflzOFzh1I2MNSlLpbiH2ubs87bdRc2wZsDPSbs4l3v6h3WLhxoQK0bq6vcfroudrLBgvCuX3Q== + dependencies: + "@aws-sdk/core" "3.696.0" + "@aws-sdk/types" "3.696.0" + "@aws-sdk/util-arn-parser" "3.693.0" + "@smithy/core" "^2.5.3" + "@smithy/node-config-provider" "^3.1.11" + "@smithy/protocol-http" "^4.1.7" + "@smithy/signature-v4" "^4.2.2" + "@smithy/smithy-client" "^3.4.4" + "@smithy/types" "^3.7.1" + "@smithy/util-config-provider" "^3.0.0" + "@smithy/util-middleware" "^3.0.10" + "@smithy/util-stream" "^3.3.1" + "@smithy/util-utf8" "^3.0.0" + tslib "^2.6.2" + +"@aws-sdk/middleware-ssec@3.696.0": + version "3.696.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-ssec/-/middleware-ssec-3.696.0.tgz#b809692109f02722b90a74ca3593d4b6906ddc18" + integrity sha512-w/d6O7AOZ7Pg3w2d3BxnX5RmGNWb5X4RNxF19rJqcgu/xqxxE/QwZTNd5a7eTsqLXAUIfbbR8hh0czVfC1pJLA== + dependencies: + "@aws-sdk/types" "3.696.0" + "@smithy/types" "^3.7.1" + tslib "^2.6.2" + +"@aws-sdk/middleware-user-agent@3.696.0": + version "3.696.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.696.0.tgz#626c89300f6b3af5aefc1cb6d9ac19eebf8bc97d" + integrity sha512-Lvyj8CTyxrHI6GHd2YVZKIRI5Fmnugt3cpJo0VrKKEgK5zMySwEZ1n4dqPK6czYRWKd5+WnYHYAuU+Wdk6Jsjw== + dependencies: + "@aws-sdk/core" "3.696.0" + "@aws-sdk/types" "3.696.0" + "@aws-sdk/util-endpoints" "3.696.0" + "@smithy/core" "^2.5.3" + "@smithy/protocol-http" "^4.1.7" + "@smithy/types" "^3.7.1" + tslib "^2.6.2" + +"@aws-sdk/region-config-resolver@3.696.0": + version "3.696.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/region-config-resolver/-/region-config-resolver-3.696.0.tgz#146c428702c09db75df5234b5d40ce49d147d0cf" + integrity sha512-7EuH142lBXjI8yH6dVS/CZeiK/WZsmb/8zP6bQbVYpMrppSTgB3MzZZdxVZGzL5r8zPQOU10wLC4kIMy0qdBVQ== + dependencies: + "@aws-sdk/types" "3.696.0" + "@smithy/node-config-provider" "^3.1.11" + "@smithy/types" "^3.7.1" + "@smithy/util-config-provider" "^3.0.0" + "@smithy/util-middleware" "^3.0.10" + tslib "^2.6.2" + +"@aws-sdk/signature-v4-multi-region@3.696.0": + version "3.696.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.696.0.tgz#3a110c24a659818df665857e4e894e40eb59762b" + integrity sha512-ijPkoLjXuPtgxAYlDoYls8UaG/VKigROn9ebbvPL/orEY5umedd3iZTcS9T+uAf4Ur3GELLxMQiERZpfDKaz3g== + dependencies: + "@aws-sdk/middleware-sdk-s3" "3.696.0" + "@aws-sdk/types" "3.696.0" + "@smithy/protocol-http" "^4.1.7" + "@smithy/signature-v4" "^4.2.2" + "@smithy/types" "^3.7.1" + tslib "^2.6.2" + +"@aws-sdk/token-providers@3.699.0": + version "3.699.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/token-providers/-/token-providers-3.699.0.tgz#354990dd52d651c1f7a64c4c0894c868cdc81de2" + integrity sha512-kuiEW9DWs7fNos/SM+y58HCPhcIzm1nEZLhe2/7/6+TvAYLuEWURYsbK48gzsxXlaJ2k/jGY3nIsA7RptbMOwA== + dependencies: + "@aws-sdk/types" "3.696.0" + "@smithy/property-provider" "^3.1.9" + "@smithy/shared-ini-file-loader" "^3.1.10" + "@smithy/types" "^3.7.1" + tslib "^2.6.2" + +"@aws-sdk/types@3.696.0", "@aws-sdk/types@^3.222.0": + version "3.696.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/types/-/types-3.696.0.tgz#559c3df74dc389b6f40ba6ec6daffeab155330cd" + integrity sha512-9rTvUJIAj5d3//U5FDPWGJ1nFJLuWb30vugGOrWk7aNZ6y9tuA3PI7Cc9dP8WEXKVyK1vuuk8rSFP2iqXnlgrw== + dependencies: + "@smithy/types" "^3.7.1" + tslib "^2.6.2" + +"@aws-sdk/util-arn-parser@3.693.0": + version "3.693.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-arn-parser/-/util-arn-parser-3.693.0.tgz#8dae27eb822ab4f88be28bb3c0fc11f1f13d3948" + integrity sha512-WC8x6ca+NRrtpAH64rWu+ryDZI3HuLwlEr8EU6/dbC/pt+r/zC0PBoC15VEygUaBA+isppCikQpGyEDu0Yj7gQ== + dependencies: + tslib "^2.6.2" + +"@aws-sdk/util-dynamodb@^3.682.0": + version "3.705.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-dynamodb/-/util-dynamodb-3.705.0.tgz#4ee2c1f56e2c6c0ef4b4170a24408b48e4d395a9" + integrity sha512-cETrLaH8HGnHy2ohse02OR1/ux6IeT9dA1kngfYZyv0RhC1nt0fm6xJCTt8KPucPTTgXgGWzLaCB/AYu9uxH7A== + dependencies: + tslib "^2.6.2" + +"@aws-sdk/util-endpoints@3.696.0": + version "3.696.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-endpoints/-/util-endpoints-3.696.0.tgz#79e18714419a423a64094381b849214499f00577" + integrity sha512-T5s0IlBVX+gkb9g/I6CLt4yAZVzMSiGnbUqWihWsHvQR1WOoIcndQy/Oz/IJXT9T2ipoy7a80gzV6a5mglrioA== + dependencies: + "@aws-sdk/types" "3.696.0" + "@smithy/types" "^3.7.1" + "@smithy/util-endpoints" "^2.1.6" + tslib "^2.6.2" + +"@aws-sdk/util-locate-window@^3.0.0": + version "3.693.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-locate-window/-/util-locate-window-3.693.0.tgz#1160f6d055cf074ca198eb8ecf89b6311537ad6c" + integrity sha512-ttrag6haJLWABhLqtg1Uf+4LgHWIMOVSYL+VYZmAp2v4PUGOwWmWQH0Zk8RM7YuQcLfH/EoR72/Yxz6A4FKcuw== + dependencies: + tslib "^2.6.2" + +"@aws-sdk/util-user-agent-browser@3.696.0": + version "3.696.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.696.0.tgz#2034765c81313d5e50783662332d35ec041755a0" + integrity sha512-Z5rVNDdmPOe6ELoM5AhF/ja5tSjbe6ctSctDPb0JdDf4dT0v2MfwhJKzXju2RzX8Es/77Glh7MlaXLE0kCB9+Q== + dependencies: + "@aws-sdk/types" "3.696.0" + "@smithy/types" "^3.7.1" + bowser "^2.11.0" + tslib "^2.6.2" + +"@aws-sdk/util-user-agent-node@3.696.0": + version "3.696.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.696.0.tgz#3267119e2be02185f3b4e0beb0cc495d392260b4" + integrity sha512-KhKqcfyXIB0SCCt+qsu4eJjsfiOrNzK5dCV7RAW2YIpp+msxGUUX0NdRE9rkzjiv+3EMktgJm3eEIS+yxtlVdQ== + dependencies: + "@aws-sdk/middleware-user-agent" "3.696.0" + "@aws-sdk/types" "3.696.0" + "@smithy/node-config-provider" "^3.1.11" + "@smithy/types" "^3.7.1" + tslib "^2.6.2" + +"@aws-sdk/xml-builder@3.696.0": + version "3.696.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/xml-builder/-/xml-builder-3.696.0.tgz#ff3e37ee23208f9986f20d326cc278c0ee341164" + integrity sha512-dn1mX+EeqivoLYnY7p2qLrir0waPnCgS/0YdRCAVU2x14FgfUYCH6Im3w3oi2dMwhxfKY5lYVB5NKvZu7uI9lQ== + dependencies: + "@smithy/types" "^3.7.1" + tslib "^2.6.2" + +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.25.9", "@babel/code-frame@^7.26.0", "@babel/code-frame@^7.26.2": + version "7.26.2" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.26.2.tgz#4b5fab97d33338eff916235055f0ebc21e573a85" + integrity sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ== + dependencies: + "@babel/helper-validator-identifier" "^7.25.9" + js-tokens "^4.0.0" + picocolors "^1.0.0" + +"@babel/compat-data@^7.25.9": + version "7.26.3" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.26.3.tgz#99488264a56b2aded63983abd6a417f03b92ed02" + integrity sha512-nHIxvKPniQXpmQLb0vhY3VaFb3S0YrTAwpOWJZh1wn3oJPjJk9Asva204PsBdmAE8vpzfHudT8DB0scYvy9q0g== + +"@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.23.9": + version "7.26.0" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.26.0.tgz#d78b6023cc8f3114ccf049eb219613f74a747b40" + integrity sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg== + dependencies: + "@ampproject/remapping" "^2.2.0" + "@babel/code-frame" "^7.26.0" + "@babel/generator" "^7.26.0" + "@babel/helper-compilation-targets" "^7.25.9" + "@babel/helper-module-transforms" "^7.26.0" + "@babel/helpers" "^7.26.0" + "@babel/parser" "^7.26.0" + "@babel/template" "^7.25.9" + "@babel/traverse" "^7.25.9" + "@babel/types" "^7.26.0" + convert-source-map "^2.0.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.3" + semver "^6.3.1" + +"@babel/generator@^7.26.0", "@babel/generator@^7.26.3", "@babel/generator@^7.7.2": + version "7.26.3" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.26.3.tgz#ab8d4360544a425c90c248df7059881f4b2ce019" + integrity sha512-6FF/urZvD0sTeO7k6/B15pMLC4CHUv1426lzr3N01aHJTl046uCAh9LXW/fzeXXjPNCJ6iABW5XaWOsIZB93aQ== + dependencies: + "@babel/parser" "^7.26.3" + "@babel/types" "^7.26.3" + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.25" + jsesc "^3.0.2" + +"@babel/helper-compilation-targets@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.9.tgz#55af025ce365be3cdc0c1c1e56c6af617ce88875" + integrity sha512-j9Db8Suy6yV/VHa4qzrj9yZfZxhLWQdVnRlXxmKLYlhWUVB1sB2G5sxuWYXk/whHD9iW76PmNzxZ4UCnTQTVEQ== + dependencies: + "@babel/compat-data" "^7.25.9" + "@babel/helper-validator-option" "^7.25.9" + browserslist "^4.24.0" + lru-cache "^5.1.1" + semver "^6.3.1" + +"@babel/helper-module-imports@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz#e7f8d20602ebdbf9ebbea0a0751fb0f2a4141715" + integrity sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw== + dependencies: + "@babel/traverse" "^7.25.9" + "@babel/types" "^7.25.9" + +"@babel/helper-module-transforms@^7.26.0": + version "7.26.0" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz#8ce54ec9d592695e58d84cd884b7b5c6a2fdeeae" + integrity sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw== + dependencies: + "@babel/helper-module-imports" "^7.25.9" + "@babel/helper-validator-identifier" "^7.25.9" + "@babel/traverse" "^7.25.9" + +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.25.9", "@babel/helper-plugin-utils@^7.8.0": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.25.9.tgz#9cbdd63a9443a2c92a725cca7ebca12cc8dd9f46" + integrity sha512-kSMlyUVdWe25rEsRGviIgOWnoT/nfABVWlqt9N19/dIPWViAOW2s9wznP5tURbs/IDuNk4gPy3YdYRgH3uxhBw== + +"@babel/helper-string-parser@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz#1aabb72ee72ed35789b4bbcad3ca2862ce614e8c" + integrity sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA== + +"@babel/helper-validator-identifier@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz#24b64e2c3ec7cd3b3c547729b8d16871f22cbdc7" + integrity sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ== + +"@babel/helper-validator-option@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz#86e45bd8a49ab7e03f276577f96179653d41da72" + integrity sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw== + +"@babel/helpers@^7.26.0": + version "7.26.0" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.26.0.tgz#30e621f1eba5aa45fe6f4868d2e9154d884119a4" + integrity sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw== + dependencies: + "@babel/template" "^7.25.9" + "@babel/types" "^7.26.0" + +"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.23.9", "@babel/parser@^7.25.9", "@babel/parser@^7.26.0", "@babel/parser@^7.26.3": + version "7.26.3" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.26.3.tgz#8c51c5db6ddf08134af1ddbacf16aaab48bac234" + integrity sha512-WJ/CvmY8Mea8iDXo6a7RK2wbmJITT5fN3BEkRuFlxVyNx8jOKIIhmC4fSkTcPcf8JyavbBwIe6OpiCOBXt/IcA== + dependencies: + "@babel/types" "^7.26.3" + +"@babel/plugin-syntax-async-generators@^7.8.4": + version "7.8.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" + integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-bigint@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz#4c9a6f669f5d0cdf1b90a1671e9a146be5300cea" + integrity sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-class-properties@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz#b5c987274c4a3a82b89714796931a6b53544ae10" + integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-syntax-class-static-block@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz#195df89b146b4b78b3bf897fd7a257c84659d406" + integrity sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-import-attributes@^7.24.7": + version "7.26.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.26.0.tgz#3b1412847699eea739b4f2602c74ce36f6b0b0f7" + integrity sha512-e2dttdsJ1ZTpi3B9UYGLw41hifAubg19AtCu/2I/F1QNVclOBr1dYpTdmdyZ84Xiz43BS/tCUkMAZNLv12Pi+A== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/plugin-syntax-import-meta@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" + integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-json-strings@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" + integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-jsx@^7.7.2": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.25.9.tgz#a34313a178ea56f1951599b929c1ceacee719290" + integrity sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/plugin-syntax-logical-assignment-operators@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" + integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" + integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-numeric-separator@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97" + integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-object-rest-spread@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" + integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-catch-binding@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1" + integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-chaining@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a" + integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-private-property-in-object@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz#0dc6671ec0ea22b6e94a1114f857970cd39de1ad" + integrity sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-top-level-await@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz#c1cfdadc35a646240001f06138247b741c34d94c" + integrity sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-typescript@^7.7.2": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.9.tgz#67dda2b74da43727cf21d46cf9afef23f4365399" + integrity sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/template@^7.25.9", "@babel/template@^7.3.3": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.25.9.tgz#ecb62d81a8a6f5dc5fe8abfc3901fc52ddf15016" + integrity sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg== + dependencies: + "@babel/code-frame" "^7.25.9" + "@babel/parser" "^7.25.9" + "@babel/types" "^7.25.9" + +"@babel/traverse@^7.25.9": + version "7.26.4" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.26.4.tgz#ac3a2a84b908dde6d463c3bfa2c5fdc1653574bd" + integrity sha512-fH+b7Y4p3yqvApJALCPJcwb0/XaOSgtK4pzV6WVjPR5GLFQBRI7pfoX2V2iM48NXvX07NUxxm1Vw98YjqTcU5w== + dependencies: + "@babel/code-frame" "^7.26.2" + "@babel/generator" "^7.26.3" + "@babel/parser" "^7.26.3" + "@babel/template" "^7.25.9" + "@babel/types" "^7.26.3" + debug "^4.3.1" + globals "^11.1.0" + +"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.25.9", "@babel/types@^7.26.0", "@babel/types@^7.26.3", "@babel/types@^7.3.3": + version "7.26.3" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.26.3.tgz#37e79830f04c2b5687acc77db97fbc75fb81f3c0" + integrity sha512-vN5p+1kl59GVKMvTHt55NzzmYVxprfJD+ql7U9NFIfKCBkYE55LYtS+WtPlaYOyzydrKI8Nezd+aZextrd+FMA== + dependencies: + "@babel/helper-string-parser" "^7.25.9" + "@babel/helper-validator-identifier" "^7.25.9" + +"@bcoe/v8-coverage@^0.2.3": + version "0.2.3" + resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" + integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== + +"@colors/colors@1.6.0", "@colors/colors@^1.6.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.6.0.tgz#ec6cd237440700bc23ca23087f513c75508958b0" + integrity sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA== + +"@cspotcode/source-map-support@^0.8.0": + version "0.8.1" + resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" + integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw== + dependencies: + "@jridgewell/trace-mapping" "0.3.9" + +"@dabh/diagnostics@^2.0.2": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@dabh/diagnostics/-/diagnostics-2.0.3.tgz#7f7e97ee9a725dffc7808d93668cc984e1dc477a" + integrity sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA== + dependencies: + colorspace "1.1.x" + enabled "2.0.x" + kuler "^2.0.0" + +"@esbuild/aix-ppc64@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.23.1.tgz#51299374de171dbd80bb7d838e1cfce9af36f353" + integrity sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ== + +"@esbuild/aix-ppc64@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.24.0.tgz#b57697945b50e99007b4c2521507dc613d4a648c" + integrity sha512-WtKdFM7ls47zkKHFVzMz8opM7LkcsIp9amDUBIAWirg70RM71WRSjdILPsY5Uv1D42ZpUfaPILDlfactHgsRkw== + +"@esbuild/android-arm64@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.23.1.tgz#58565291a1fe548638adb9c584237449e5e14018" + integrity sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw== + +"@esbuild/android-arm64@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.24.0.tgz#1add7e0af67acefd556e407f8497e81fddad79c0" + integrity sha512-Vsm497xFM7tTIPYK9bNTYJyF/lsP590Qc1WxJdlB6ljCbdZKU9SY8i7+Iin4kyhV/KV5J2rOKsBQbB77Ab7L/w== + +"@esbuild/android-arm@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.23.1.tgz#5eb8c652d4c82a2421e3395b808e6d9c42c862ee" + integrity sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ== + +"@esbuild/android-arm@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.24.0.tgz#ab7263045fa8e090833a8e3c393b60d59a789810" + integrity sha512-arAtTPo76fJ/ICkXWetLCc9EwEHKaeya4vMrReVlEIUCAUncH7M4bhMQ+M9Vf+FFOZJdTNMXNBrWwW+OXWpSew== + +"@esbuild/android-x64@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.23.1.tgz#ae19d665d2f06f0f48a6ac9a224b3f672e65d517" + integrity sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg== + +"@esbuild/android-x64@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.24.0.tgz#e8f8b196cfdfdd5aeaebbdb0110983460440e705" + integrity sha512-t8GrvnFkiIY7pa7mMgJd7p8p8qqYIz1NYiAoKc75Zyv73L3DZW++oYMSHPRarcotTKuSs6m3hTOa5CKHaS02TQ== + +"@esbuild/darwin-arm64@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.23.1.tgz#05b17f91a87e557b468a9c75e9d85ab10c121b16" + integrity sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q== + +"@esbuild/darwin-arm64@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.24.0.tgz#2d0d9414f2acbffd2d86e98253914fca603a53dd" + integrity sha512-CKyDpRbK1hXwv79soeTJNHb5EiG6ct3efd/FTPdzOWdbZZfGhpbcqIpiD0+vwmpu0wTIL97ZRPZu8vUt46nBSw== + +"@esbuild/darwin-x64@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.23.1.tgz#c58353b982f4e04f0d022284b8ba2733f5ff0931" + integrity sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw== + +"@esbuild/darwin-x64@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.24.0.tgz#33087aab31a1eb64c89daf3d2cf8ce1775656107" + integrity sha512-rgtz6flkVkh58od4PwTRqxbKH9cOjaXCMZgWD905JOzjFKW+7EiUObfd/Kav+A6Gyud6WZk9w+xu6QLytdi2OA== + +"@esbuild/freebsd-arm64@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.23.1.tgz#f9220dc65f80f03635e1ef96cfad5da1f446f3bc" + integrity sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA== + +"@esbuild/freebsd-arm64@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.0.tgz#bb76e5ea9e97fa3c753472f19421075d3a33e8a7" + integrity sha512-6Mtdq5nHggwfDNLAHkPlyLBpE5L6hwsuXZX8XNmHno9JuL2+bg2BX5tRkwjyfn6sKbxZTq68suOjgWqCicvPXA== + +"@esbuild/freebsd-x64@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.23.1.tgz#69bd8511fa013b59f0226d1609ac43f7ce489730" + integrity sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g== + +"@esbuild/freebsd-x64@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.24.0.tgz#e0e2ce9249fdf6ee29e5dc3d420c7007fa579b93" + integrity sha512-D3H+xh3/zphoX8ck4S2RxKR6gHlHDXXzOf6f/9dbFt/NRBDIE33+cVa49Kil4WUjxMGW0ZIYBYtaGCa2+OsQwQ== + +"@esbuild/linux-arm64@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.23.1.tgz#8050af6d51ddb388c75653ef9871f5ccd8f12383" + integrity sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g== + +"@esbuild/linux-arm64@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.24.0.tgz#d1b2aa58085f73ecf45533c07c82d81235388e75" + integrity sha512-TDijPXTOeE3eaMkRYpcy3LarIg13dS9wWHRdwYRnzlwlA370rNdZqbcp0WTyyV/k2zSxfko52+C7jU5F9Tfj1g== + +"@esbuild/linux-arm@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.23.1.tgz#ecaabd1c23b701070484990db9a82f382f99e771" + integrity sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ== + +"@esbuild/linux-arm@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.24.0.tgz#8e4915df8ea3e12b690a057e77a47b1d5935ef6d" + integrity sha512-gJKIi2IjRo5G6Glxb8d3DzYXlxdEj2NlkixPsqePSZMhLudqPhtZ4BUrpIuTjJYXxvF9njql+vRjB2oaC9XpBw== + +"@esbuild/linux-ia32@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.23.1.tgz#3ed2273214178109741c09bd0687098a0243b333" + integrity sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ== + +"@esbuild/linux-ia32@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.24.0.tgz#8200b1110666c39ab316572324b7af63d82013fb" + integrity sha512-K40ip1LAcA0byL05TbCQ4yJ4swvnbzHscRmUilrmP9Am7//0UjPreh4lpYzvThT2Quw66MhjG//20mrufm40mA== + +"@esbuild/linux-loong64@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.23.1.tgz#a0fdf440b5485c81b0fbb316b08933d217f5d3ac" + integrity sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw== + +"@esbuild/linux-loong64@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.24.0.tgz#6ff0c99cf647504df321d0640f0d32e557da745c" + integrity sha512-0mswrYP/9ai+CU0BzBfPMZ8RVm3RGAN/lmOMgW4aFUSOQBjA31UP8Mr6DDhWSuMwj7jaWOT0p0WoZ6jeHhrD7g== + +"@esbuild/linux-mips64el@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.23.1.tgz#e11a2806346db8375b18f5e104c5a9d4e81807f6" + integrity sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q== + +"@esbuild/linux-mips64el@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.24.0.tgz#3f720ccd4d59bfeb4c2ce276a46b77ad380fa1f3" + integrity sha512-hIKvXm0/3w/5+RDtCJeXqMZGkI2s4oMUGj3/jM0QzhgIASWrGO5/RlzAzm5nNh/awHE0A19h/CvHQe6FaBNrRA== + +"@esbuild/linux-ppc64@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.23.1.tgz#06a2744c5eaf562b1a90937855b4d6cf7c75ec96" + integrity sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw== + +"@esbuild/linux-ppc64@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.24.0.tgz#9d6b188b15c25afd2e213474bf5f31e42e3aa09e" + integrity sha512-HcZh5BNq0aC52UoocJxaKORfFODWXZxtBaaZNuN3PUX3MoDsChsZqopzi5UupRhPHSEHotoiptqikjN/B77mYQ== + +"@esbuild/linux-riscv64@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.23.1.tgz#65b46a2892fc0d1af4ba342af3fe0fa4a8fe08e7" + integrity sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA== + +"@esbuild/linux-riscv64@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.24.0.tgz#f989fdc9752dfda286c9cd87c46248e4dfecbc25" + integrity sha512-bEh7dMn/h3QxeR2KTy1DUszQjUrIHPZKyO6aN1X4BCnhfYhuQqedHaa5MxSQA/06j3GpiIlFGSsy1c7Gf9padw== + +"@esbuild/linux-s390x@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.23.1.tgz#e71ea18c70c3f604e241d16e4e5ab193a9785d6f" + integrity sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw== + +"@esbuild/linux-s390x@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.24.0.tgz#29ebf87e4132ea659c1489fce63cd8509d1c7319" + integrity sha512-ZcQ6+qRkw1UcZGPyrCiHHkmBaj9SiCD8Oqd556HldP+QlpUIe2Wgn3ehQGVoPOvZvtHm8HPx+bH20c9pvbkX3g== + +"@esbuild/linux-x64@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.23.1.tgz#d47f97391e80690d4dfe811a2e7d6927ad9eed24" + integrity sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ== + +"@esbuild/linux-x64@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.24.0.tgz#4af48c5c0479569b1f359ffbce22d15f261c0cef" + integrity sha512-vbutsFqQ+foy3wSSbmjBXXIJ6PL3scghJoM8zCL142cGaZKAdCZHyf+Bpu/MmX9zT9Q0zFBVKb36Ma5Fzfa8xA== + +"@esbuild/netbsd-x64@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.23.1.tgz#44e743c9778d57a8ace4b72f3c6b839a3b74a653" + integrity sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA== + +"@esbuild/netbsd-x64@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.24.0.tgz#1ae73d23cc044a0ebd4f198334416fb26c31366c" + integrity sha512-hjQ0R/ulkO8fCYFsG0FZoH+pWgTTDreqpqY7UnQntnaKv95uP5iW3+dChxnx7C3trQQU40S+OgWhUVwCjVFLvg== + +"@esbuild/openbsd-arm64@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.23.1.tgz#05c5a1faf67b9881834758c69f3e51b7dee015d7" + integrity sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q== + +"@esbuild/openbsd-arm64@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.0.tgz#5d904a4f5158c89859fd902c427f96d6a9e632e2" + integrity sha512-MD9uzzkPQbYehwcN583yx3Tu5M8EIoTD+tUgKF982WYL9Pf5rKy9ltgD0eUgs8pvKnmizxjXZyLt0z6DC3rRXg== + +"@esbuild/openbsd-x64@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.23.1.tgz#2e58ae511bacf67d19f9f2dcd9e8c5a93f00c273" + integrity sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA== + +"@esbuild/openbsd-x64@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.24.0.tgz#4c8aa88c49187c601bae2971e71c6dc5e0ad1cdf" + integrity sha512-4ir0aY1NGUhIC1hdoCzr1+5b43mw99uNwVzhIq1OY3QcEwPDO3B7WNXBzaKY5Nsf1+N11i1eOfFcq+D/gOS15Q== + +"@esbuild/sunos-x64@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.23.1.tgz#adb022b959d18d3389ac70769cef5a03d3abd403" + integrity sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA== + +"@esbuild/sunos-x64@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.24.0.tgz#8ddc35a0ea38575fa44eda30a5ee01ae2fa54dd4" + integrity sha512-jVzdzsbM5xrotH+W5f1s+JtUy1UWgjU0Cf4wMvffTB8m6wP5/kx0KiaLHlbJO+dMgtxKV8RQ/JvtlFcdZ1zCPA== + +"@esbuild/win32-arm64@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.23.1.tgz#84906f50c212b72ec360f48461d43202f4c8b9a2" + integrity sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A== + +"@esbuild/win32-arm64@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.24.0.tgz#6e79c8543f282c4539db684a207ae0e174a9007b" + integrity sha512-iKc8GAslzRpBytO2/aN3d2yb2z8XTVfNV0PjGlCxKo5SgWmNXx82I/Q3aG1tFfS+A2igVCY97TJ8tnYwpUWLCA== + +"@esbuild/win32-ia32@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.23.1.tgz#5e3eacc515820ff729e90d0cb463183128e82fac" + integrity sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ== + +"@esbuild/win32-ia32@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.24.0.tgz#057af345da256b7192d18b676a02e95d0fa39103" + integrity sha512-vQW36KZolfIudCcTnaTpmLQ24Ha1RjygBo39/aLkM2kmjkWmZGEJ5Gn9l5/7tzXA42QGIoWbICfg6KLLkIw6yw== + +"@esbuild/win32-x64@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.23.1.tgz#81fd50d11e2c32b2d6241470e3185b70c7b30699" + integrity sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg== + +"@esbuild/win32-x64@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.24.0.tgz#168ab1c7e1c318b922637fad8f339d48b01e1244" + integrity sha512-7IAFPrjSQIJrGsK6flwg7NFmwBoSTyF3rl7If0hNUFQU4ilTsEPL6GuMuU9BfIWVVGuRnuIidkSMC+c0Otu8IA== + +"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": + version "4.4.1" + resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz#d1145bf2c20132d6400495d6df4bf59362fd9d56" + integrity sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA== + dependencies: + eslint-visitor-keys "^3.4.3" + +"@eslint-community/regexpp@^4.10.0", "@eslint-community/regexpp@^4.12.1": + version "4.12.1" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.1.tgz#cfc6cffe39df390a3841cde2abccf92eaa7ae0e0" + integrity sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ== + +"@eslint/config-array@^0.19.0": + version "0.19.1" + resolved "https://registry.yarnpkg.com/@eslint/config-array/-/config-array-0.19.1.tgz#734aaea2c40be22bbb1f2a9dac687c57a6a4c984" + integrity sha512-fo6Mtm5mWyKjA/Chy1BYTdn5mGJoDNjC7C64ug20ADsRDGrA85bN3uK3MaKbeRkRuuIEAR5N33Jr1pbm411/PA== + dependencies: + "@eslint/object-schema" "^2.1.5" + debug "^4.3.1" + minimatch "^3.1.2" + +"@eslint/core@^0.9.0": + version "0.9.1" + resolved "https://registry.yarnpkg.com/@eslint/core/-/core-0.9.1.tgz#31763847308ef6b7084a4505573ac9402c51f9d1" + integrity sha512-GuUdqkyyzQI5RMIWkHhvTWLCyLo1jNK3vzkSyaExH5kHPDHcuL2VOpHjmMY+y3+NC69qAKToBqldTBgYeLSr9Q== + dependencies: + "@types/json-schema" "^7.0.15" + +"@eslint/eslintrc@^3.2.0": + version "3.2.0" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-3.2.0.tgz#57470ac4e2e283a6bf76044d63281196e370542c" + integrity sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w== + dependencies: + ajv "^6.12.4" + debug "^4.3.2" + espree "^10.0.1" + globals "^14.0.0" + ignore "^5.2.0" + import-fresh "^3.2.1" + js-yaml "^4.1.0" + minimatch "^3.1.2" + strip-json-comments "^3.1.1" + +"@eslint/js@9.16.0": + version "9.16.0" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.16.0.tgz#3df2b2dd3b9163056616886c86e4082f45dbf3f4" + integrity sha512-tw2HxzQkrbeuvyj1tG2Yqq+0H9wGoI2IMk4EOsQeX+vmd75FtJAzf+gTA69WF+baUKRYQ3x2kbLE08js5OsTVg== + +"@eslint/object-schema@^2.1.5": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@eslint/object-schema/-/object-schema-2.1.5.tgz#8670a8f6258a2be5b2c620ff314a1d984c23eb2e" + integrity sha512-o0bhxnL89h5Bae5T318nFoFzGy+YE5i/gGkoPAgkmTVdRKTiv3p8JHevPiPaMwoloKfEiiaHlawCqaZMqRm+XQ== + +"@eslint/plugin-kit@^0.2.3": + version "0.2.4" + resolved "https://registry.yarnpkg.com/@eslint/plugin-kit/-/plugin-kit-0.2.4.tgz#2b78e7bb3755784bb13faa8932a1d994d6537792" + integrity sha512-zSkKow6H5Kdm0ZUQUB2kV5JIXqoG0+uH5YADhaEHswm664N9Db8dXSi0nMJpacpMf+MyyglF1vnZohpEg5yUtg== + dependencies: + levn "^0.4.1" + +"@humanfs/core@^0.19.1": + version "0.19.1" + resolved "https://registry.yarnpkg.com/@humanfs/core/-/core-0.19.1.tgz#17c55ca7d426733fe3c561906b8173c336b40a77" + integrity sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA== + +"@humanfs/node@^0.16.6": + version "0.16.6" + resolved "https://registry.yarnpkg.com/@humanfs/node/-/node-0.16.6.tgz#ee2a10eaabd1131987bf0488fd9b820174cd765e" + integrity sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw== + dependencies: + "@humanfs/core" "^0.19.1" + "@humanwhocodes/retry" "^0.3.0" + +"@humanwhocodes/module-importer@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" + integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== + +"@humanwhocodes/retry@^0.3.0": + version "0.3.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/retry/-/retry-0.3.1.tgz#c72a5c76a9fbaf3488e231b13dc52c0da7bab42a" + integrity sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA== + +"@humanwhocodes/retry@^0.4.1": + version "0.4.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/retry/-/retry-0.4.1.tgz#9a96ce501bc62df46c4031fbd970e3cc6b10f07b" + integrity sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA== + +"@isaacs/cliui@^8.0.2": + version "8.0.2" + resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550" + integrity sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA== + dependencies: + string-width "^5.1.2" + string-width-cjs "npm:string-width@^4.2.0" + strip-ansi "^7.0.1" + strip-ansi-cjs "npm:strip-ansi@^6.0.1" + wrap-ansi "^8.1.0" + wrap-ansi-cjs "npm:wrap-ansi@^7.0.0" + +"@istanbuljs/load-nyc-config@^1.0.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" + integrity sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ== + dependencies: + camelcase "^5.3.1" + find-up "^4.1.0" + get-package-type "^0.1.0" + js-yaml "^3.13.1" + resolve-from "^5.0.0" + +"@istanbuljs/schema@^0.1.2", "@istanbuljs/schema@^0.1.3": + version "0.1.3" + resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" + integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== + +"@jest/console@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/console/-/console-29.7.0.tgz#cd4822dbdb84529265c5a2bdb529a3c9cc950ffc" + integrity sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg== + dependencies: + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + slash "^3.0.0" + +"@jest/core@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/core/-/core-29.7.0.tgz#b6cccc239f30ff36609658c5a5e2291757ce448f" + integrity sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg== + dependencies: + "@jest/console" "^29.7.0" + "@jest/reporters" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + ci-info "^3.2.0" + exit "^0.1.2" + graceful-fs "^4.2.9" + jest-changed-files "^29.7.0" + jest-config "^29.7.0" + 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" + jest-runner "^29.7.0" + jest-runtime "^29.7.0" + jest-snapshot "^29.7.0" + jest-util "^29.7.0" + jest-validate "^29.7.0" + jest-watcher "^29.7.0" + micromatch "^4.0.4" + pretty-format "^29.7.0" + slash "^3.0.0" + strip-ansi "^6.0.0" + +"@jest/environment@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.7.0.tgz#24d61f54ff1f786f3cd4073b4b94416383baf2a7" + integrity sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw== + dependencies: + "@jest/fake-timers" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + jest-mock "^29.7.0" + +"@jest/expect-utils@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.7.0.tgz#023efe5d26a8a70f21677d0a1afc0f0a44e3a1c6" + integrity sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA== + dependencies: + jest-get-type "^29.6.3" + +"@jest/expect@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-29.7.0.tgz#76a3edb0cb753b70dfbfe23283510d3d45432bf2" + integrity sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ== + dependencies: + expect "^29.7.0" + jest-snapshot "^29.7.0" + +"@jest/fake-timers@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-29.7.0.tgz#fd91bf1fffb16d7d0d24a426ab1a47a49881a565" + integrity sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ== + dependencies: + "@jest/types" "^29.6.3" + "@sinonjs/fake-timers" "^10.0.2" + "@types/node" "*" + jest-message-util "^29.7.0" + jest-mock "^29.7.0" + jest-util "^29.7.0" + +"@jest/globals@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-29.7.0.tgz#8d9290f9ec47ff772607fa864ca1d5a2efae1d4d" + integrity sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/expect" "^29.7.0" + "@jest/types" "^29.6.3" + jest-mock "^29.7.0" + +"@jest/reporters@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-29.7.0.tgz#04b262ecb3b8faa83b0b3d321623972393e8f4c7" + integrity sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg== + dependencies: + "@bcoe/v8-coverage" "^0.2.3" + "@jest/console" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@jridgewell/trace-mapping" "^0.3.18" + "@types/node" "*" + chalk "^4.0.0" + collect-v8-coverage "^1.0.0" + exit "^0.1.2" + glob "^7.1.3" + graceful-fs "^4.2.9" + istanbul-lib-coverage "^3.0.0" + istanbul-lib-instrument "^6.0.0" + istanbul-lib-report "^3.0.0" + istanbul-lib-source-maps "^4.0.0" + istanbul-reports "^3.1.3" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + jest-worker "^29.7.0" + slash "^3.0.0" + string-length "^4.0.1" + strip-ansi "^6.0.0" + v8-to-istanbul "^9.0.1" + +"@jest/schemas@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.3.tgz#430b5ce8a4e0044a7e3819663305a7b3091c8e03" + integrity sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA== + dependencies: + "@sinclair/typebox" "^0.27.8" + +"@jest/source-map@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-29.6.3.tgz#d90ba772095cf37a34a5eb9413f1b562a08554c4" + integrity sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw== + dependencies: + "@jridgewell/trace-mapping" "^0.3.18" + callsites "^3.0.0" + graceful-fs "^4.2.9" + +"@jest/test-result@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-29.7.0.tgz#8db9a80aa1a097bb2262572686734baed9b1657c" + integrity sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA== + dependencies: + "@jest/console" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/istanbul-lib-coverage" "^2.0.0" + collect-v8-coverage "^1.0.0" + +"@jest/test-sequencer@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz#6cef977ce1d39834a3aea887a1726628a6f072ce" + integrity sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw== + dependencies: + "@jest/test-result" "^29.7.0" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + slash "^3.0.0" + +"@jest/transform@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-29.7.0.tgz#df2dd9c346c7d7768b8a06639994640c642e284c" + integrity sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw== + dependencies: + "@babel/core" "^7.11.6" + "@jest/types" "^29.6.3" + "@jridgewell/trace-mapping" "^0.3.18" + babel-plugin-istanbul "^6.1.1" + chalk "^4.0.0" + convert-source-map "^2.0.0" + fast-json-stable-stringify "^2.1.0" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + jest-regex-util "^29.6.3" + jest-util "^29.7.0" + micromatch "^4.0.4" + pirates "^4.0.4" + slash "^3.0.0" + write-file-atomic "^4.0.2" + +"@jest/types@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.6.3.tgz#1131f8cf634e7e84c5e77bab12f052af585fba59" + integrity sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw== + dependencies: + "@jest/schemas" "^29.6.3" + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^17.0.8" + chalk "^4.0.0" + +"@jridgewell/gen-mapping@^0.3.5": + version "0.3.5" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz#dcce6aff74bdf6dad1a95802b69b04a2fcb1fb36" + integrity sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg== + dependencies: + "@jridgewell/set-array" "^1.2.1" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping" "^0.3.24" + +"@jridgewell/resolve-uri@^3.0.3", "@jridgewell/resolve-uri@^3.1.0": + version "3.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" + integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== + +"@jridgewell/set-array@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.2.1.tgz#558fb6472ed16a4c850b889530e6b36438c49280" + integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A== + +"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz#3188bcb273a414b0d215fd22a58540b989b9409a" + integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ== + +"@jridgewell/trace-mapping@0.3.9": + version "0.3.9" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9" + integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ== + dependencies: + "@jridgewell/resolve-uri" "^3.0.3" + "@jridgewell/sourcemap-codec" "^1.4.10" + +"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.18", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": + version "0.3.25" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz#15f190e98895f3fc23276ee14bc76b675c2e50f0" + integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ== + dependencies: + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" + +"@nodelib/fs.scandir@2.1.5": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" + integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== + dependencies: + "@nodelib/fs.stat" "2.0.5" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" + integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== + +"@nodelib/fs.walk@^1.2.3": + version "1.2.8" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" + integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== + dependencies: + "@nodelib/fs.scandir" "2.1.5" + fastq "^1.6.0" + +"@pkgjs/parseargs@^0.11.0": + version "0.11.0" + resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" + integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== + +"@sinclair/typebox@^0.27.8": + version "0.27.8" + resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" + integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== + +"@sinonjs/commons@^3.0.0", "@sinonjs/commons@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-3.0.1.tgz#1029357e44ca901a615585f6d27738dbc89084cd" + integrity sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ== + dependencies: + type-detect "4.0.8" + +"@sinonjs/fake-timers@11.2.2": + version "11.2.2" + resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-11.2.2.tgz#50063cc3574f4a27bd8453180a04171c85cc9699" + integrity sha512-G2piCSxQ7oWOxwGSAyFHfPIsyeJGXYtc6mFbnFA+kRXkiEnTl8c/8jul2S329iFBnDI9HGoeWWAZvuvOkZccgw== + dependencies: + "@sinonjs/commons" "^3.0.0" + +"@sinonjs/fake-timers@^10.0.2": + version "10.3.0" + resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz#55fdff1ecab9f354019129daf4df0dd4d923ea66" + integrity sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA== + dependencies: + "@sinonjs/commons" "^3.0.0" + +"@sinonjs/fake-timers@^13.0.1": + version "13.0.5" + resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-13.0.5.tgz#36b9dbc21ad5546486ea9173d6bea063eb1717d5" + integrity sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw== + dependencies: + "@sinonjs/commons" "^3.0.1" + +"@sinonjs/samsam@^8.0.0": + version "8.0.2" + resolved "https://registry.yarnpkg.com/@sinonjs/samsam/-/samsam-8.0.2.tgz#e4386bf668ff36c95949e55a38dc5f5892fc2689" + integrity sha512-v46t/fwnhejRSFTGqbpn9u+LQ9xJDse10gNnPgAcxgdoCDMXj/G2asWAC/8Qs+BAZDicX+MNZouXT1A7c83kVw== + dependencies: + "@sinonjs/commons" "^3.0.1" + lodash.get "^4.4.2" + type-detect "^4.1.0" + +"@sinonjs/text-encoding@^0.7.3": + version "0.7.3" + resolved "https://registry.yarnpkg.com/@sinonjs/text-encoding/-/text-encoding-0.7.3.tgz#282046f03e886e352b2d5f5da5eb755e01457f3f" + integrity sha512-DE427ROAphMQzU4ENbliGYrBSYPXF+TtLg9S8vzeA+OF4ZKzoDdzfL8sxuMUGS/lgRhM6j1URSk9ghf7Xo1tyA== + +"@smithy/abort-controller@^3.1.9": + version "3.1.9" + resolved "https://registry.yarnpkg.com/@smithy/abort-controller/-/abort-controller-3.1.9.tgz#47d323f754136a489e972d7fd465d534d72fcbff" + integrity sha512-yiW0WI30zj8ZKoSYNx90no7ugVn3khlyH/z5W8qtKBtVE6awRALbhSG+2SAHA1r6bO/6M9utxYKVZ3PCJ1rWxw== + dependencies: + "@smithy/types" "^3.7.2" + tslib "^2.6.2" + +"@smithy/chunked-blob-reader-native@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@smithy/chunked-blob-reader-native/-/chunked-blob-reader-native-3.0.1.tgz#39045ed278ee1b6f4c12715c7565678557274c29" + integrity sha512-VEYtPvh5rs/xlyqpm5NRnfYLZn+q0SRPELbvBV+C/G7IQ+ouTuo+NKKa3ShG5OaFR8NYVMXls9hPYLTvIKKDrQ== + dependencies: + "@smithy/util-base64" "^3.0.0" + tslib "^2.6.2" + +"@smithy/chunked-blob-reader@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@smithy/chunked-blob-reader/-/chunked-blob-reader-4.0.0.tgz#754099909957fb1986c16eb88afad75919d7129d" + integrity sha512-jSqRnZvkT4egkq/7b6/QRCNXmmYVcHwnJldqJ3IhVpQE2atObVJ137xmGeuGFhjFUr8gCEVAOKwSY79OvpbDaQ== + dependencies: + tslib "^2.6.2" + +"@smithy/config-resolver@^3.0.12", "@smithy/config-resolver@^3.0.13": + version "3.0.13" + resolved "https://registry.yarnpkg.com/@smithy/config-resolver/-/config-resolver-3.0.13.tgz#653643a77a33d0f5907a5e7582353886b07ba752" + integrity sha512-Gr/qwzyPaTL1tZcq8WQyHhTZREER5R1Wytmz4WnVGL4onA3dNk6Btll55c8Vr58pLdvWZmtG8oZxJTw3t3q7Jg== + dependencies: + "@smithy/node-config-provider" "^3.1.12" + "@smithy/types" "^3.7.2" + "@smithy/util-config-provider" "^3.0.0" + "@smithy/util-middleware" "^3.0.11" + tslib "^2.6.2" + +"@smithy/core@^2.5.3", "@smithy/core@^2.5.5": + version "2.5.5" + resolved "https://registry.yarnpkg.com/@smithy/core/-/core-2.5.5.tgz#c75b15caee9e58c800db3e6b99e9e373532d394a" + integrity sha512-G8G/sDDhXA7o0bOvkc7bgai6POuSld/+XhNnWAbpQTpLv2OZPvyqQ58tLPPlz0bSNsXktldDDREIv1LczFeNEw== + dependencies: + "@smithy/middleware-serde" "^3.0.11" + "@smithy/protocol-http" "^4.1.8" + "@smithy/types" "^3.7.2" + "@smithy/util-body-length-browser" "^3.0.0" + "@smithy/util-middleware" "^3.0.11" + "@smithy/util-stream" "^3.3.2" + "@smithy/util-utf8" "^3.0.0" + tslib "^2.6.2" + +"@smithy/credential-provider-imds@^3.2.6", "@smithy/credential-provider-imds@^3.2.8": + version "3.2.8" + resolved "https://registry.yarnpkg.com/@smithy/credential-provider-imds/-/credential-provider-imds-3.2.8.tgz#27ed2747074c86a7d627a98e56f324a65cba88de" + integrity sha512-ZCY2yD0BY+K9iMXkkbnjo+08T2h8/34oHd0Jmh6BZUSZwaaGlGCyBT/3wnS7u7Xl33/EEfN4B6nQr3Gx5bYxgw== + dependencies: + "@smithy/node-config-provider" "^3.1.12" + "@smithy/property-provider" "^3.1.11" + "@smithy/types" "^3.7.2" + "@smithy/url-parser" "^3.0.11" + tslib "^2.6.2" + +"@smithy/eventstream-codec@^3.1.10": + version "3.1.10" + resolved "https://registry.yarnpkg.com/@smithy/eventstream-codec/-/eventstream-codec-3.1.10.tgz#0c1a3457e7a23b71cd71525ceb668f8569a84dad" + integrity sha512-323B8YckSbUH0nMIpXn7HZsAVKHYHFUODa8gG9cHo0ySvA1fr5iWaNT+iIL0UCqUzG6QPHA3BSsBtRQou4mMqQ== + dependencies: + "@aws-crypto/crc32" "5.2.0" + "@smithy/types" "^3.7.2" + "@smithy/util-hex-encoding" "^3.0.0" + tslib "^2.6.2" + +"@smithy/eventstream-serde-browser@^3.0.13": + version "3.0.14" + resolved "https://registry.yarnpkg.com/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-3.0.14.tgz#0c3584c7cde2e210aacdfbbd2b57c1d7e2ca3b95" + integrity sha512-kbrt0vjOIihW3V7Cqj1SXQvAI5BR8SnyQYsandva0AOR307cXAc+IhPngxIPslxTLfxwDpNu0HzCAq6g42kCPg== + dependencies: + "@smithy/eventstream-serde-universal" "^3.0.13" + "@smithy/types" "^3.7.2" + tslib "^2.6.2" + +"@smithy/eventstream-serde-config-resolver@^3.0.10": + version "3.0.11" + resolved "https://registry.yarnpkg.com/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-3.0.11.tgz#5edceba836debea165ea93145231036f6286d67c" + integrity sha512-P2pnEp4n75O+QHjyO7cbw/vsw5l93K/8EWyjNCAAybYwUmj3M+hjSQZ9P5TVdUgEG08ueMAP5R4FkuSkElZ5tQ== + dependencies: + "@smithy/types" "^3.7.2" + tslib "^2.6.2" + +"@smithy/eventstream-serde-node@^3.0.12": + version "3.0.13" + resolved "https://registry.yarnpkg.com/@smithy/eventstream-serde-node/-/eventstream-serde-node-3.0.13.tgz#5aebd7b553becee277e411a2b69f6af8c9d7b3a6" + integrity sha512-zqy/9iwbj8Wysmvi7Lq7XFLeDgjRpTbCfwBhJa8WbrylTAHiAu6oQTwdY7iu2lxigbc9YYr9vPv5SzYny5tCXQ== + dependencies: + "@smithy/eventstream-serde-universal" "^3.0.13" + "@smithy/types" "^3.7.2" + tslib "^2.6.2" + +"@smithy/eventstream-serde-universal@^3.0.13": + version "3.0.13" + resolved "https://registry.yarnpkg.com/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-3.0.13.tgz#609c922ea14a0a3eed23a28ac110344c935704eb" + integrity sha512-L1Ib66+gg9uTnqp/18Gz4MDpJPKRE44geOjOQ2SVc0eiaO5l255ADziATZgjQjqumC7yPtp1XnjHlF1srcwjKw== + dependencies: + "@smithy/eventstream-codec" "^3.1.10" + "@smithy/types" "^3.7.2" + tslib "^2.6.2" + +"@smithy/fetch-http-handler@^4.1.1", "@smithy/fetch-http-handler@^4.1.2": + version "4.1.2" + resolved "https://registry.yarnpkg.com/@smithy/fetch-http-handler/-/fetch-http-handler-4.1.2.tgz#f034ff16416b37d92908a1381ef5fddbf4ef1879" + integrity sha512-R7rU7Ae3ItU4rC0c5mB2sP5mJNbCfoDc8I5XlYjIZnquyUwec7fEo78F6DA3SmgJgkU1qTMcZJuGblxZsl10ZA== + dependencies: + "@smithy/protocol-http" "^4.1.8" + "@smithy/querystring-builder" "^3.0.11" + "@smithy/types" "^3.7.2" + "@smithy/util-base64" "^3.0.0" + tslib "^2.6.2" + +"@smithy/hash-blob-browser@^3.1.9": + version "3.1.10" + resolved "https://registry.yarnpkg.com/@smithy/hash-blob-browser/-/hash-blob-browser-3.1.10.tgz#985e308189c2687a15004152b97506882ffb2b13" + integrity sha512-elwslXOoNunmfS0fh55jHggyhccobFkexLYC1ZeZ1xP2BTSrcIBaHV2b4xUQOdctrSNOpMqOZH1r2XzWTEhyfA== + dependencies: + "@smithy/chunked-blob-reader" "^4.0.0" + "@smithy/chunked-blob-reader-native" "^3.0.1" + "@smithy/types" "^3.7.2" + tslib "^2.6.2" + +"@smithy/hash-node@^3.0.10": + version "3.0.11" + resolved "https://registry.yarnpkg.com/@smithy/hash-node/-/hash-node-3.0.11.tgz#99e09ead3fc99c8cd7ca0f254ea0e35714f2a0d3" + integrity sha512-emP23rwYyZhQBvklqTtwetkQlqbNYirDiEEwXl2v0GYWMnCzxst7ZaRAnWuy28njp5kAH54lvkdG37MblZzaHA== + dependencies: + "@smithy/types" "^3.7.2" + "@smithy/util-buffer-from" "^3.0.0" + "@smithy/util-utf8" "^3.0.0" + tslib "^2.6.2" + +"@smithy/hash-stream-node@^3.1.9": + version "3.1.10" + resolved "https://registry.yarnpkg.com/@smithy/hash-stream-node/-/hash-stream-node-3.1.10.tgz#94716b4556f4ccf2807e605f47bb5b018ed7dfb0" + integrity sha512-olomK/jZQ93OMayW1zfTHwcbwBdhcZOHsyWyiZ9h9IXvc1mCD/VuvzbLb3Gy/qNJwI4MANPLctTp2BucV2oU/Q== + dependencies: + "@smithy/types" "^3.7.2" + "@smithy/util-utf8" "^3.0.0" + tslib "^2.6.2" + +"@smithy/invalid-dependency@^3.0.10": + version "3.0.11" + resolved "https://registry.yarnpkg.com/@smithy/invalid-dependency/-/invalid-dependency-3.0.11.tgz#8144d7b0af9d34ab5f672e1f674f97f8740bb9ae" + integrity sha512-NuQmVPEJjUX6c+UELyVz8kUx8Q539EDeNwbRyu4IIF8MeV7hUtq1FB3SHVyki2u++5XLMFqngeMKk7ccspnNyQ== + dependencies: + "@smithy/types" "^3.7.2" + tslib "^2.6.2" + +"@smithy/is-array-buffer@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz#f84f0d9f9a36601a9ca9381688bd1b726fd39111" + integrity sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA== + dependencies: + tslib "^2.6.2" + +"@smithy/is-array-buffer@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@smithy/is-array-buffer/-/is-array-buffer-3.0.0.tgz#9a95c2d46b8768946a9eec7f935feaddcffa5e7a" + integrity sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ== + dependencies: + tslib "^2.6.2" + +"@smithy/md5-js@^3.0.10": + version "3.0.11" + resolved "https://registry.yarnpkg.com/@smithy/md5-js/-/md5-js-3.0.11.tgz#27e4dab616348ff94aed24dc75e4017c582df40f" + integrity sha512-3NM0L3i2Zm4bbgG6Ymi9NBcxXhryi3uE8fIfHJZIOfZVxOkGdjdgjR9A06SFIZCfnEIWKXZdm6Yq5/aPXFFhsQ== + dependencies: + "@smithy/types" "^3.7.2" + "@smithy/util-utf8" "^3.0.0" + tslib "^2.6.2" + +"@smithy/middleware-content-length@^3.0.12": + version "3.0.13" + resolved "https://registry.yarnpkg.com/@smithy/middleware-content-length/-/middleware-content-length-3.0.13.tgz#6e08fe52739ac8fb3996088e0f8837e4b2ea187f" + integrity sha512-zfMhzojhFpIX3P5ug7jxTjfUcIPcGjcQYzB9t+rv0g1TX7B0QdwONW+ATouaLoD7h7LOw/ZlXfkq4xJ/g2TrIw== + dependencies: + "@smithy/protocol-http" "^4.1.8" + "@smithy/types" "^3.7.2" + tslib "^2.6.2" + +"@smithy/middleware-endpoint@^3.2.3", "@smithy/middleware-endpoint@^3.2.5": + version "3.2.5" + resolved "https://registry.yarnpkg.com/@smithy/middleware-endpoint/-/middleware-endpoint-3.2.5.tgz#bdcfdf1f342cf933b0b8a709996f9a8fbb8148f4" + integrity sha512-VhJNs/s/lyx4weiZdXSloBgoLoS8osV0dKIain8nGmx7of3QFKu5BSdEuk1z/U8x9iwes1i+XCiNusEvuK1ijg== + dependencies: + "@smithy/core" "^2.5.5" + "@smithy/middleware-serde" "^3.0.11" + "@smithy/node-config-provider" "^3.1.12" + "@smithy/shared-ini-file-loader" "^3.1.12" + "@smithy/types" "^3.7.2" + "@smithy/url-parser" "^3.0.11" + "@smithy/util-middleware" "^3.0.11" + tslib "^2.6.2" + +"@smithy/middleware-retry@^3.0.27": + version "3.0.30" + resolved "https://registry.yarnpkg.com/@smithy/middleware-retry/-/middleware-retry-3.0.30.tgz#2580322d0d28ad782b5b8c07c150b14efdc3b2f9" + integrity sha512-6323RL2BvAR3VQpTjHpa52kH/iSHyxd/G9ohb2MkBk2Ucu+oMtRXT8yi7KTSIS9nb58aupG6nO0OlXnQOAcvmQ== + dependencies: + "@smithy/node-config-provider" "^3.1.12" + "@smithy/protocol-http" "^4.1.8" + "@smithy/service-error-classification" "^3.0.11" + "@smithy/smithy-client" "^3.5.0" + "@smithy/types" "^3.7.2" + "@smithy/util-middleware" "^3.0.11" + "@smithy/util-retry" "^3.0.11" + tslib "^2.6.2" + uuid "^9.0.1" + +"@smithy/middleware-serde@^3.0.10", "@smithy/middleware-serde@^3.0.11": + version "3.0.11" + resolved "https://registry.yarnpkg.com/@smithy/middleware-serde/-/middleware-serde-3.0.11.tgz#c7d54e0add4f83e05c6878a011fc664e21022f12" + integrity sha512-KzPAeySp/fOoQA82TpnwItvX8BBURecpx6ZMu75EZDkAcnPtO6vf7q4aH5QHs/F1s3/snQaSFbbUMcFFZ086Mw== + dependencies: + "@smithy/types" "^3.7.2" + tslib "^2.6.2" + +"@smithy/middleware-stack@^3.0.10", "@smithy/middleware-stack@^3.0.11": + version "3.0.11" + resolved "https://registry.yarnpkg.com/@smithy/middleware-stack/-/middleware-stack-3.0.11.tgz#453af2096924e4064d9da4e053cfdf65d9a36acc" + integrity sha512-1HGo9a6/ikgOMrTrWL/WiN9N8GSVYpuRQO5kjstAq4CvV59bjqnh7TbdXGQ4vxLD3xlSjfBjq5t1SOELePsLnA== + dependencies: + "@smithy/types" "^3.7.2" + tslib "^2.6.2" + +"@smithy/node-config-provider@^3.1.11", "@smithy/node-config-provider@^3.1.12": + version "3.1.12" + resolved "https://registry.yarnpkg.com/@smithy/node-config-provider/-/node-config-provider-3.1.12.tgz#1b1d674fc83f943dc7b3017e37f16f374e878a6c" + integrity sha512-O9LVEu5J/u/FuNlZs+L7Ikn3lz7VB9hb0GtPT9MQeiBmtK8RSY3ULmsZgXhe6VAlgTw0YO+paQx4p8xdbs43vQ== + dependencies: + "@smithy/property-provider" "^3.1.11" + "@smithy/shared-ini-file-loader" "^3.1.12" + "@smithy/types" "^3.7.2" + tslib "^2.6.2" + +"@smithy/node-http-handler@^3.3.1", "@smithy/node-http-handler@^3.3.2": + version "3.3.2" + resolved "https://registry.yarnpkg.com/@smithy/node-http-handler/-/node-http-handler-3.3.2.tgz#b34685863b74dabdaf7860aa81b42d0d5437c7e0" + integrity sha512-t4ng1DAd527vlxvOfKFYEe6/QFBcsj7WpNlWTyjorwXXcKw3XlltBGbyHfSJ24QT84nF+agDha9tNYpzmSRZPA== + dependencies: + "@smithy/abort-controller" "^3.1.9" + "@smithy/protocol-http" "^4.1.8" + "@smithy/querystring-builder" "^3.0.11" + "@smithy/types" "^3.7.2" + tslib "^2.6.2" + +"@smithy/property-provider@^3.1.11", "@smithy/property-provider@^3.1.9": + version "3.1.11" + resolved "https://registry.yarnpkg.com/@smithy/property-provider/-/property-provider-3.1.11.tgz#161cf1c2a2ada361e417382c57f5ba6fbca8acad" + integrity sha512-I/+TMc4XTQ3QAjXfOcUWbSS073oOEAxgx4aZy8jHaf8JQnRkq2SZWw8+PfDtBvLUjcGMdxl+YwtzWe6i5uhL/A== + dependencies: + "@smithy/types" "^3.7.2" + tslib "^2.6.2" + +"@smithy/protocol-http@^4.1.7", "@smithy/protocol-http@^4.1.8": + version "4.1.8" + resolved "https://registry.yarnpkg.com/@smithy/protocol-http/-/protocol-http-4.1.8.tgz#0461758671335f65e8ff3fc0885ab7ed253819c9" + integrity sha512-hmgIAVyxw1LySOwkgMIUN0kjN8TG9Nc85LJeEmEE/cNEe2rkHDUWhnJf2gxcSRFLWsyqWsrZGw40ROjUogg+Iw== + dependencies: + "@smithy/types" "^3.7.2" + tslib "^2.6.2" + +"@smithy/querystring-builder@^3.0.11": + version "3.0.11" + resolved "https://registry.yarnpkg.com/@smithy/querystring-builder/-/querystring-builder-3.0.11.tgz#2ed04adbe725671824c5613d0d6f9376d791a909" + integrity sha512-u+5HV/9uJaeLj5XTb6+IEF/dokWWkEqJ0XiaRRogyREmKGUgZnNecLucADLdauWFKUNbQfulHFEZEdjwEBjXRg== + dependencies: + "@smithy/types" "^3.7.2" + "@smithy/util-uri-escape" "^3.0.0" + tslib "^2.6.2" + +"@smithy/querystring-parser@^3.0.11": + version "3.0.11" + resolved "https://registry.yarnpkg.com/@smithy/querystring-parser/-/querystring-parser-3.0.11.tgz#9d3177ea19ce8462f18d9712b395239e1ca1f969" + integrity sha512-Je3kFvCsFMnso1ilPwA7GtlbPaTixa3WwC+K21kmMZHsBEOZYQaqxcMqeFFoU7/slFjKDIpiiPydvdJm8Q/MCw== + dependencies: + "@smithy/types" "^3.7.2" + tslib "^2.6.2" + +"@smithy/service-error-classification@^3.0.11": + version "3.0.11" + resolved "https://registry.yarnpkg.com/@smithy/service-error-classification/-/service-error-classification-3.0.11.tgz#d3d7fc0aacd2e60d022507367e55c7939e5bcb8a" + integrity sha512-QnYDPkyewrJzCyaeI2Rmp7pDwbUETe+hU8ADkXmgNusO1bgHBH7ovXJiYmba8t0fNfJx75fE8dlM6SEmZxheog== + dependencies: + "@smithy/types" "^3.7.2" + +"@smithy/shared-ini-file-loader@^3.1.10", "@smithy/shared-ini-file-loader@^3.1.12": + version "3.1.12" + resolved "https://registry.yarnpkg.com/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-3.1.12.tgz#d98b1b663eb18935ce2cbc79024631d34f54042a" + integrity sha512-1xKSGI+U9KKdbG2qDvIR9dGrw3CNx+baqJfyr0igKEpjbHL5stsqAesYBzHChYHlelWtb87VnLWlhvfCz13H8Q== + dependencies: + "@smithy/types" "^3.7.2" + tslib "^2.6.2" + +"@smithy/signature-v4@^4.2.2": + version "4.2.4" + resolved "https://registry.yarnpkg.com/@smithy/signature-v4/-/signature-v4-4.2.4.tgz#3501d3d09fd82768867bfc00a7be4bad62f62f4d" + integrity sha512-5JWeMQYg81TgU4cG+OexAWdvDTs5JDdbEZx+Qr1iPbvo91QFGzjy0IkXAKaXUHqmKUJgSHK0ZxnCkgZpzkeNTA== + dependencies: + "@smithy/is-array-buffer" "^3.0.0" + "@smithy/protocol-http" "^4.1.8" + "@smithy/types" "^3.7.2" + "@smithy/util-hex-encoding" "^3.0.0" + "@smithy/util-middleware" "^3.0.11" + "@smithy/util-uri-escape" "^3.0.0" + "@smithy/util-utf8" "^3.0.0" + tslib "^2.6.2" + +"@smithy/smithy-client@^3.4.4", "@smithy/smithy-client@^3.5.0": + version "3.5.0" + resolved "https://registry.yarnpkg.com/@smithy/smithy-client/-/smithy-client-3.5.0.tgz#65cff262801b009998c1196764ee69929ee06f8a" + integrity sha512-Y8FeOa7gbDfCWf7njrkoRATPa5eNLUEjlJS5z5rXatYuGkCb80LbHcu8AQR8qgAZZaNHCLyo2N+pxPsV7l+ivg== + dependencies: + "@smithy/core" "^2.5.5" + "@smithy/middleware-endpoint" "^3.2.5" + "@smithy/middleware-stack" "^3.0.11" + "@smithy/protocol-http" "^4.1.8" + "@smithy/types" "^3.7.2" + "@smithy/util-stream" "^3.3.2" + tslib "^2.6.2" + +"@smithy/types@^3.7.1", "@smithy/types@^3.7.2": + version "3.7.2" + resolved "https://registry.yarnpkg.com/@smithy/types/-/types-3.7.2.tgz#05cb14840ada6f966de1bf9a9c7dd86027343e10" + integrity sha512-bNwBYYmN8Eh9RyjS1p2gW6MIhSO2rl7X9QeLM8iTdcGRP+eDiIWDt66c9IysCc22gefKszZv+ubV9qZc7hdESg== + dependencies: + tslib "^2.6.2" + +"@smithy/url-parser@^3.0.10", "@smithy/url-parser@^3.0.11": + version "3.0.11" + resolved "https://registry.yarnpkg.com/@smithy/url-parser/-/url-parser-3.0.11.tgz#e5f5ffabfb6230159167cf4cc970705fca6b8b2d" + integrity sha512-TmlqXkSk8ZPhfc+SQutjmFr5FjC0av3GZP4B/10caK1SbRwe/v+Wzu/R6xEKxoNqL+8nY18s1byiy6HqPG37Aw== + dependencies: + "@smithy/querystring-parser" "^3.0.11" + "@smithy/types" "^3.7.2" + tslib "^2.6.2" + +"@smithy/util-base64@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@smithy/util-base64/-/util-base64-3.0.0.tgz#f7a9a82adf34e27a72d0719395713edf0e493017" + integrity sha512-Kxvoh5Qtt0CDsfajiZOCpJxgtPHXOKwmM+Zy4waD43UoEMA+qPxxa98aE/7ZhdnBFZFXMOiBR5xbcaMhLtznQQ== + dependencies: + "@smithy/util-buffer-from" "^3.0.0" + "@smithy/util-utf8" "^3.0.0" + tslib "^2.6.2" + +"@smithy/util-body-length-browser@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@smithy/util-body-length-browser/-/util-body-length-browser-3.0.0.tgz#86ec2f6256310b4845a2f064e2f571c1ca164ded" + integrity sha512-cbjJs2A1mLYmqmyVl80uoLTJhAcfzMOyPgjwAYusWKMdLeNtzmMz9YxNl3/jRLoxSS3wkqkf0jwNdtXWtyEBaQ== + dependencies: + tslib "^2.6.2" + +"@smithy/util-body-length-node@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@smithy/util-body-length-node/-/util-body-length-node-3.0.0.tgz#99a291bae40d8932166907fe981d6a1f54298a6d" + integrity sha512-Tj7pZ4bUloNUP6PzwhN7K386tmSmEET9QtQg0TgdNOnxhZvCssHji+oZTUIuzxECRfG8rdm2PMw2WCFs6eIYkA== + dependencies: + tslib "^2.6.2" + +"@smithy/util-buffer-from@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz#6fc88585165ec73f8681d426d96de5d402021e4b" + integrity sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA== + dependencies: + "@smithy/is-array-buffer" "^2.2.0" + tslib "^2.6.2" + +"@smithy/util-buffer-from@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@smithy/util-buffer-from/-/util-buffer-from-3.0.0.tgz#559fc1c86138a89b2edaefc1e6677780c24594e3" + integrity sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA== + dependencies: + "@smithy/is-array-buffer" "^3.0.0" + tslib "^2.6.2" + +"@smithy/util-config-provider@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@smithy/util-config-provider/-/util-config-provider-3.0.0.tgz#62c6b73b22a430e84888a8f8da4b6029dd5b8efe" + integrity sha512-pbjk4s0fwq3Di/ANL+rCvJMKM5bzAQdE5S/6RL5NXgMExFAi6UgQMPOm5yPaIWPpr+EOXKXRonJ3FoxKf4mCJQ== + dependencies: + tslib "^2.6.2" + +"@smithy/util-defaults-mode-browser@^3.0.27": + version "3.0.30" + resolved "https://registry.yarnpkg.com/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-3.0.30.tgz#6c0d95af3f15bef8f1fe3f6217cc4f5ba8df5554" + integrity sha512-nLuGmgfcr0gzm64pqF2UT4SGWVG8UGviAdayDlVzJPNa6Z4lqvpDzdRXmLxtOdEjVlTOEdpZ9dd3ZMMu488mzg== + dependencies: + "@smithy/property-provider" "^3.1.11" + "@smithy/smithy-client" "^3.5.0" + "@smithy/types" "^3.7.2" + bowser "^2.11.0" + tslib "^2.6.2" + +"@smithy/util-defaults-mode-node@^3.0.27": + version "3.0.30" + resolved "https://registry.yarnpkg.com/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-3.0.30.tgz#33cdb02f90944b9ff221e2f8e0904a63ac1e335f" + integrity sha512-OD63eWoH68vp75mYcfYyuVH+p7Li/mY4sYOROnauDrtObo1cS4uWfsy/zhOTW8F8ZPxQC1ZXZKVxoxvMGUv2Ow== + dependencies: + "@smithy/config-resolver" "^3.0.13" + "@smithy/credential-provider-imds" "^3.2.8" + "@smithy/node-config-provider" "^3.1.12" + "@smithy/property-provider" "^3.1.11" + "@smithy/smithy-client" "^3.5.0" + "@smithy/types" "^3.7.2" + tslib "^2.6.2" + +"@smithy/util-endpoints@^2.1.6": + version "2.1.7" + resolved "https://registry.yarnpkg.com/@smithy/util-endpoints/-/util-endpoints-2.1.7.tgz#a088ebfab946a7219dd4763bfced82709894b82d" + integrity sha512-tSfcqKcN/Oo2STEYCABVuKgJ76nyyr6skGl9t15hs+YaiU06sgMkN7QYjo0BbVw+KT26zok3IzbdSOksQ4YzVw== + dependencies: + "@smithy/node-config-provider" "^3.1.12" + "@smithy/types" "^3.7.2" + tslib "^2.6.2" + +"@smithy/util-hex-encoding@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@smithy/util-hex-encoding/-/util-hex-encoding-3.0.0.tgz#32938b33d5bf2a15796cd3f178a55b4155c535e6" + integrity sha512-eFndh1WEK5YMUYvy3lPlVmYY/fZcQE1D8oSf41Id2vCeIkKJXPcYDCZD+4+xViI6b1XSd7tE+s5AmXzz5ilabQ== + dependencies: + tslib "^2.6.2" + +"@smithy/util-middleware@^3.0.10", "@smithy/util-middleware@^3.0.11": + version "3.0.11" + resolved "https://registry.yarnpkg.com/@smithy/util-middleware/-/util-middleware-3.0.11.tgz#2ab5c17266b42c225e62befcffb048afa682b5bf" + integrity sha512-dWpyc1e1R6VoXrwLoLDd57U1z6CwNSdkM69Ie4+6uYh2GC7Vg51Qtan7ITzczuVpqezdDTKJGJB95fFvvjU/ow== + dependencies: + "@smithy/types" "^3.7.2" + tslib "^2.6.2" + +"@smithy/util-retry@^3.0.10", "@smithy/util-retry@^3.0.11": + version "3.0.11" + resolved "https://registry.yarnpkg.com/@smithy/util-retry/-/util-retry-3.0.11.tgz#d267e5ccb290165cee69732547fea17b695a7425" + integrity sha512-hJUC6W7A3DQgaee3Hp9ZFcOxVDZzmBIRBPlUAk8/fSOEl7pE/aX7Dci0JycNOnm9Mfr0KV2XjIlUOcGWXQUdVQ== + dependencies: + "@smithy/service-error-classification" "^3.0.11" + "@smithy/types" "^3.7.2" + tslib "^2.6.2" + +"@smithy/util-stream@^3.3.1", "@smithy/util-stream@^3.3.2": + version "3.3.2" + resolved "https://registry.yarnpkg.com/@smithy/util-stream/-/util-stream-3.3.2.tgz#daeea26397e8541cf2499ce65bf0b8d528cba421" + integrity sha512-sInAqdiVeisUGYAv/FrXpmJ0b4WTFmciTRqzhb7wVuem9BHvhIG7tpiYHLDWrl2stOokNZpTTGqz3mzB2qFwXg== + dependencies: + "@smithy/fetch-http-handler" "^4.1.2" + "@smithy/node-http-handler" "^3.3.2" + "@smithy/types" "^3.7.2" + "@smithy/util-base64" "^3.0.0" + "@smithy/util-buffer-from" "^3.0.0" + "@smithy/util-hex-encoding" "^3.0.0" + "@smithy/util-utf8" "^3.0.0" + tslib "^2.6.2" + +"@smithy/util-uri-escape@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@smithy/util-uri-escape/-/util-uri-escape-3.0.0.tgz#e43358a78bf45d50bb736770077f0f09195b6f54" + integrity sha512-LqR7qYLgZTD7nWLBecUi4aqolw8Mhza9ArpNEQ881MJJIU2sE5iHCK6TdyqqzcDLy0OPe10IY4T8ctVdtynubg== + dependencies: + tslib "^2.6.2" + +"@smithy/util-utf8@^2.0.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@smithy/util-utf8/-/util-utf8-2.3.0.tgz#dd96d7640363259924a214313c3cf16e7dd329c5" + integrity sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A== + dependencies: + "@smithy/util-buffer-from" "^2.2.0" + tslib "^2.6.2" + +"@smithy/util-utf8@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@smithy/util-utf8/-/util-utf8-3.0.0.tgz#1a6a823d47cbec1fd6933e5fc87df975286d9d6a" + integrity sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA== + dependencies: + "@smithy/util-buffer-from" "^3.0.0" + tslib "^2.6.2" + +"@smithy/util-waiter@^3.1.9": + version "3.2.0" + resolved "https://registry.yarnpkg.com/@smithy/util-waiter/-/util-waiter-3.2.0.tgz#1e52f870e77d2e5572025f7606053e6ff00df93d" + integrity sha512-PpjSboaDUE6yl+1qlg3Si57++e84oXdWGbuFUSAciXsVfEZJJJupR2Nb0QuXHiunt2vGR+1PTizOMvnUPaG2Qg== + dependencies: + "@smithy/abort-controller" "^3.1.9" + "@smithy/types" "^3.7.2" + tslib "^2.6.2" + +"@tsconfig/node10@^1.0.7": + version "1.0.11" + resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.11.tgz#6ee46400685f130e278128c7b38b7e031ff5b2f2" + integrity sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw== + +"@tsconfig/node12@^1.0.7": + version "1.0.11" + resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.11.tgz#ee3def1f27d9ed66dac6e46a295cffb0152e058d" + integrity sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag== + +"@tsconfig/node14@^1.0.0": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.3.tgz#e4386316284f00b98435bf40f72f75a09dabf6c1" + integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow== + +"@tsconfig/node16@^1.0.2": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.4.tgz#0b92dcc0cc1c81f6f306a381f28e31b1a56536e9" + integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA== + +"@types/aws-lambda@8.10.145": + version "8.10.145" + resolved "https://registry.yarnpkg.com/@types/aws-lambda/-/aws-lambda-8.10.145.tgz#b2d31a987f4888e5553ff1819f57cafa475594d9" + integrity sha512-dtByW6WiFk5W5Jfgz1VM+YPA21xMXTuSFoLYIDY0L44jDLLflVPtZkYuu3/YxpGcvjzKFBZLU+GyKjR0HOYtyw== + +"@types/babel__core@^7.1.14": + version "7.20.5" + resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.5.tgz#3df15f27ba85319caa07ba08d0721889bb39c017" + integrity sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA== + dependencies: + "@babel/parser" "^7.20.7" + "@babel/types" "^7.20.7" + "@types/babel__generator" "*" + "@types/babel__template" "*" + "@types/babel__traverse" "*" + +"@types/babel__generator@*": + version "7.6.8" + resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.8.tgz#f836c61f48b1346e7d2b0d93c6dacc5b9535d3ab" + integrity sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw== + dependencies: + "@babel/types" "^7.0.0" + +"@types/babel__template@*": + version "7.4.4" + resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.4.tgz#5672513701c1b2199bc6dad636a9d7491586766f" + integrity sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A== + dependencies: + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.0" + +"@types/babel__traverse@*", "@types/babel__traverse@^7.0.6": + version "7.20.6" + resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.20.6.tgz#8dc9f0ae0f202c08d8d4dab648912c8d6038e3f7" + integrity sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg== + dependencies: + "@babel/types" "^7.20.7" + +"@types/estree@^1.0.6": + version "1.0.6" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.6.tgz#628effeeae2064a1b4e79f78e81d87b7e5fc7b50" + integrity sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw== + +"@types/graceful-fs@^4.1.3": + version "4.1.9" + resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.9.tgz#2a06bc0f68a20ab37b3e36aa238be6abdf49e8b4" + integrity sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ== + dependencies: + "@types/node" "*" + +"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": + version "2.0.6" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz#7739c232a1fee9b4d3ce8985f314c0c6d33549d7" + integrity sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w== + +"@types/istanbul-lib-report@*": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz#53047614ae72e19fc0401d872de3ae2b4ce350bf" + integrity sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA== + dependencies: + "@types/istanbul-lib-coverage" "*" + +"@types/istanbul-reports@^3.0.0": + version "3.0.4" + resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz#0f03e3d2f670fbdac586e34b433783070cc16f54" + integrity sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ== + dependencies: + "@types/istanbul-lib-report" "*" + +"@types/jest@^29.5.12": + version "29.5.14" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.14.tgz#2b910912fa1d6856cadcd0c1f95af7df1d6049e5" + integrity sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ== + dependencies: + expect "^29.0.0" + pretty-format "^29.0.0" + +"@types/json-schema@^7.0.15": + version "7.0.15" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" + integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== + +"@types/node@*": + version "22.10.1" + resolved "https://registry.yarnpkg.com/@types/node/-/node-22.10.1.tgz#41ffeee127b8975a05f8c4f83fb89bcb2987d766" + integrity sha512-qKgsUwfHZV2WCWLAnVP1JqnpE6Im6h3Y0+fYgMTasNQ7V++CBX5OT1as0g0f+OyubbFqhf6XVNIsmN4IIhEgGQ== + dependencies: + undici-types "~6.20.0" + +"@types/node@22.5.4": + version "22.5.4" + resolved "https://registry.yarnpkg.com/@types/node/-/node-22.5.4.tgz#83f7d1f65bc2ed223bdbf57c7884f1d5a4fa84e8" + integrity sha512-FDuKUJQm/ju9fT/SeX/6+gBzoPzlVCzfzmGkwKvRHQVxi4BntVbyIwf6a4Xn62mrvndLiml6z/UBXIdEVjQLXg== + dependencies: + undici-types "~6.19.2" + +"@types/nodemailer@^6.4.14": + version "6.4.17" + resolved "https://registry.yarnpkg.com/@types/nodemailer/-/nodemailer-6.4.17.tgz#5c82a42aee16a3dd6ea31446a1bd6a447f1ac1a4" + integrity sha512-I9CCaIp6DTldEg7vyUTZi8+9Vo0hi1/T8gv3C89yk1rSAAzoKQ8H8ki/jBYJSFoH/BisgLP8tkZMlQ91CIquww== + dependencies: + "@types/node" "*" + +"@types/prop-types@*": + version "15.7.14" + resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.14.tgz#1433419d73b2a7ebfc6918dcefd2ec0d5cd698f2" + integrity sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ== + +"@types/react@^18.3.12": + version "18.3.14" + resolved "https://registry.yarnpkg.com/@types/react/-/react-18.3.14.tgz#7ce43bbca0e15e1c4f67ad33ea3f83d75aa6756b" + integrity sha512-NzahNKvjNhVjuPBQ+2G7WlxstQ+47kXZNHlUvFakDViuIEfGY926GqhMueQFZ7woG+sPiQKlF36XfrIUVSUfFg== + dependencies: + "@types/prop-types" "*" + csstype "^3.0.2" + +"@types/sinon@^17.0.3": + version "17.0.3" + resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-17.0.3.tgz#9aa7e62f0a323b9ead177ed23a36ea757141a5fa" + integrity sha512-j3uovdn8ewky9kRBG19bOwaZbexJu/XjtkHyjvUgt4xfPFz18dcORIMqnYh66Fx3Powhcr85NT5+er3+oViapw== + dependencies: + "@types/sinonjs__fake-timers" "*" + +"@types/sinonjs__fake-timers@*": + version "8.1.5" + resolved "https://registry.yarnpkg.com/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.5.tgz#5fd3592ff10c1e9695d377020c033116cc2889f2" + integrity sha512-mQkU2jY8jJEF7YHjHvsQO8+3ughTL1mcnn96igfhONmR+fUPSKIkefQYpSe8bsly2Ep7oQbn/6VG5/9/0qcArQ== + +"@types/stack-utils@^2.0.0": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.3.tgz#6209321eb2c1712a7e7466422b8cb1fc0d9dd5d8" + integrity sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw== + +"@types/triple-beam@^1.3.2": + version "1.3.5" + resolved "https://registry.yarnpkg.com/@types/triple-beam/-/triple-beam-1.3.5.tgz#74fef9ffbaa198eb8b588be029f38b00299caa2c" + integrity sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw== + +"@types/uuid@^9.0.1": + version "9.0.8" + resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-9.0.8.tgz#7545ba4fc3c003d6c756f651f3bf163d8f0f29ba" + integrity sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA== + +"@types/yargs-parser@*": + version "21.0.3" + resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.3.tgz#815e30b786d2e8f0dcd85fd5bcf5e1a04d008f15" + integrity sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ== + +"@types/yargs@^17.0.8": + version "17.0.33" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.33.tgz#8c32303da83eec050a84b3c7ae7b9f922d13e32d" + integrity sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA== + dependencies: + "@types/yargs-parser" "*" + +"@typescript-eslint/eslint-plugin@^8.12.2": + version "8.18.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.18.0.tgz#0901933326aea4443b81df3f740ca7dfc45c7bea" + integrity sha512-NR2yS7qUqCL7AIxdJUQf2MKKNDVNaig/dEB0GBLU7D+ZdHgK1NoH/3wsgO3OnPVipn51tG3MAwaODEGil70WEw== + dependencies: + "@eslint-community/regexpp" "^4.10.0" + "@typescript-eslint/scope-manager" "8.18.0" + "@typescript-eslint/type-utils" "8.18.0" + "@typescript-eslint/utils" "8.18.0" + "@typescript-eslint/visitor-keys" "8.18.0" + graphemer "^1.4.0" + ignore "^5.3.1" + natural-compare "^1.4.0" + ts-api-utils "^1.3.0" + +"@typescript-eslint/parser@^8.12.2": + version "8.18.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.18.0.tgz#a1c9456cbb6a089730bf1d3fc47946c5fb5fe67b" + integrity sha512-hgUZ3kTEpVzKaK3uNibExUYm6SKKOmTU2BOxBSvOYwtJEPdVQ70kZJpPjstlnhCHcuc2WGfSbpKlb/69ttyN5Q== + dependencies: + "@typescript-eslint/scope-manager" "8.18.0" + "@typescript-eslint/types" "8.18.0" + "@typescript-eslint/typescript-estree" "8.18.0" + "@typescript-eslint/visitor-keys" "8.18.0" + debug "^4.3.4" + +"@typescript-eslint/scope-manager@8.18.0": + version "8.18.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.18.0.tgz#30b040cb4557804a7e2bcc65cf8fdb630c96546f" + integrity sha512-PNGcHop0jkK2WVYGotk/hxj+UFLhXtGPiGtiaWgVBVP1jhMoMCHlTyJA+hEj4rszoSdLTK3fN4oOatrL0Cp+Xw== + dependencies: + "@typescript-eslint/types" "8.18.0" + "@typescript-eslint/visitor-keys" "8.18.0" + +"@typescript-eslint/type-utils@8.18.0": + version "8.18.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.18.0.tgz#6f0d12cf923b6fd95ae4d877708c0adaad93c471" + integrity sha512-er224jRepVAVLnMF2Q7MZJCq5CsdH2oqjP4dT7K6ij09Kyd+R21r7UVJrF0buMVdZS5QRhDzpvzAxHxabQadow== + dependencies: + "@typescript-eslint/typescript-estree" "8.18.0" + "@typescript-eslint/utils" "8.18.0" + debug "^4.3.4" + ts-api-utils "^1.3.0" + +"@typescript-eslint/types@8.18.0": + version "8.18.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.18.0.tgz#3afcd30def8756bc78541268ea819a043221d5f3" + integrity sha512-FNYxgyTCAnFwTrzpBGq+zrnoTO4x0c1CKYY5MuUTzpScqmY5fmsh2o3+57lqdI3NZucBDCzDgdEbIaNfAjAHQA== + +"@typescript-eslint/typescript-estree@8.18.0": + version "8.18.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.18.0.tgz#d8ca785799fbb9c700cdff1a79c046c3e633c7f9" + integrity sha512-rqQgFRu6yPkauz+ms3nQpohwejS8bvgbPyIDq13cgEDbkXt4LH4OkDMT0/fN1RUtzG8e8AKJyDBoocuQh8qNeg== + dependencies: + "@typescript-eslint/types" "8.18.0" + "@typescript-eslint/visitor-keys" "8.18.0" + debug "^4.3.4" + fast-glob "^3.3.2" + is-glob "^4.0.3" + minimatch "^9.0.4" + semver "^7.6.0" + ts-api-utils "^1.3.0" + +"@typescript-eslint/utils@8.18.0": + version "8.18.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.18.0.tgz#48f67205d42b65d895797bb7349d1be5c39a62f7" + integrity sha512-p6GLdY383i7h5b0Qrfbix3Vc3+J2k6QWw6UMUeY5JGfm3C5LbZ4QIZzJNoNOfgyRe0uuYKjvVOsO/jD4SJO+xg== + dependencies: + "@eslint-community/eslint-utils" "^4.4.0" + "@typescript-eslint/scope-manager" "8.18.0" + "@typescript-eslint/types" "8.18.0" + "@typescript-eslint/typescript-estree" "8.18.0" + +"@typescript-eslint/visitor-keys@8.18.0": + version "8.18.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.18.0.tgz#7b6d33534fa808e33a19951907231ad2ea5c36dd" + integrity sha512-pCh/qEA8Lb1wVIqNvBke8UaRjJ6wrAWkJO5yyIbs8Yx6TNGYyfNjOo61tLv+WwLvoLPp4BQ8B7AHKijl8NGUfw== + dependencies: + "@typescript-eslint/types" "8.18.0" + eslint-visitor-keys "^4.2.0" + +"@usewaypoint/block-avatar@^0.0.3": + version "0.0.3" + resolved "https://registry.yarnpkg.com/@usewaypoint/block-avatar/-/block-avatar-0.0.3.tgz#f63510ed82690a2b4b68ead62a191d80b20c2957" + integrity sha512-3BM6P4ztMmqDbSijtVQqI1canRkcENOEHZ2X9BYNv8BZGJbmitTrzANvwmmYXfFEuWPCAyABvujdZds15Zg8Qg== + +"@usewaypoint/block-button@^0.0.3": + version "0.0.3" + resolved "https://registry.yarnpkg.com/@usewaypoint/block-button/-/block-button-0.0.3.tgz#1e9257601b452ab5687ce806cd24efd88a1185f3" + integrity sha512-LXSI3FmCTv13voYX4wdHY7iJdsfyRfpDJZCFKSun5EF1j9FXrqMDGScpk/yokopkQWvWkYXQNAne7W0yWhRQlg== + +"@usewaypoint/block-columns-container@^0.0.3": + version "0.0.3" + resolved "https://registry.yarnpkg.com/@usewaypoint/block-columns-container/-/block-columns-container-0.0.3.tgz#e6d9148f06523aa964a41f937e0b295c165d59d2" + integrity sha512-r5jaojU1Fr6Svtl0a9dDlBHgslJQ04M+XaXaEO+GZ12+35fdAirpLkrEhuyBIA1FFXzRTG740wkbkr++iv1kuA== + +"@usewaypoint/block-container@^0.0.2": + version "0.0.2" + resolved "https://registry.yarnpkg.com/@usewaypoint/block-container/-/block-container-0.0.2.tgz#de06a31242c799c7d1caaf80ab91107ca4b25fc5" + integrity sha512-li9GVdiahVpJ+MNRdkoCkP6/hBTdcpaLRGpaFBSQRkVt+cYAeB7qPNIo+242hUvVTm5Qky8ceGLDVblGYSZb7A== + +"@usewaypoint/block-divider@^0.0.4": + version "0.0.4" + resolved "https://registry.yarnpkg.com/@usewaypoint/block-divider/-/block-divider-0.0.4.tgz#29a938293a76ad8a5e9738d4ac4370f9fa3efbd8" + integrity sha512-q54ydWvKdg7Zwc4hzIwE6i/mC8dFYxfPRACEEEyu2dvSNa9cbKFIsPD9ipVSntK+Ib3Ml84uT4aHQmOlzP6hZA== + +"@usewaypoint/block-heading@^0.0.3": + version "0.0.3" + resolved "https://registry.yarnpkg.com/@usewaypoint/block-heading/-/block-heading-0.0.3.tgz#a300894acfc39556d1577be17a2d7b3d58c7c95e" + integrity sha512-1dMrf1U34nq2FuwTUfsq+hBOdLQz1H+lVMEH9xvyCq5I7nSXCzpeo7QgumZ3zZEHtu3QgSEGafJaZyrj2paC0w== + +"@usewaypoint/block-html@^0.0.3": + version "0.0.3" + resolved "https://registry.yarnpkg.com/@usewaypoint/block-html/-/block-html-0.0.3.tgz#296550235b2974679afcad4772f7bc56cf8ba520" + integrity sha512-ZI9oYDibMzs5y/YzfvUwuUBzHDKHOIjiStiVCvlmIA+VtJTycqT8X/ECjn+KmwesLTg5DhG07CC4WY2SL3AnJw== + +"@usewaypoint/block-image@^0.0.5": + version "0.0.5" + resolved "https://registry.yarnpkg.com/@usewaypoint/block-image/-/block-image-0.0.5.tgz#e9e866673d2bbdb628fd57c0798ba70e3cdc3b12" + integrity sha512-b66jAXF79idsrIRc2QoBlZctIXdqg/qOAL7/QvKvENZH2KmuXoZhEUx+Z7sACvEQD/VI0u7TK5msDsA5S0/oVQ== + +"@usewaypoint/block-spacer@^0.0.3": + version "0.0.3" + resolved "https://registry.yarnpkg.com/@usewaypoint/block-spacer/-/block-spacer-0.0.3.tgz#6655628dd74085ffcf3fbcd0f0aa286e40e69a10" + integrity sha512-CCcMtwcpeC2rHvawQdh5f0Hez7o4xA/edWl/6I3RuA6Yb6STyyrGjmPFs2ZxHQsLOGUK+0OvBenuHlSTCZwuuA== + +"@usewaypoint/block-text@^0.0.4": + version "0.0.4" + resolved "https://registry.yarnpkg.com/@usewaypoint/block-text/-/block-text-0.0.4.tgz#22be615f81f91749eac78501b1de9dfb5bd95560" + integrity sha512-c+CiTkwFSrclPxRx9Gt+nE6KkAmY5tWDumBa3qQnVrxdCjCmGK0qOj9avm9vqf9hd5JxaX4tgWhG/oi2u/zMxA== + dependencies: + markdown-parser-react "^1.1.2" + +"@usewaypoint/document-core@^0.0.6": + version "0.0.6" + resolved "https://registry.yarnpkg.com/@usewaypoint/document-core/-/document-core-0.0.6.tgz#c97468c84c85ccac46a06f82ac332e20e415cfb7" + integrity sha512-Hg10gszVCZRJhA4nIWwAi2rTXuoxPL+ATMe0hU243PFBIUZOwDIQus4XZSeoHsenMCq1uBFCRiFW4hl2+tVwgA== + +"@usewaypoint/email-builder@^0.0.6": + version "0.0.6" + resolved "https://registry.yarnpkg.com/@usewaypoint/email-builder/-/email-builder-0.0.6.tgz#e6bb5ad39acb0710814df1d925ee366276bec49f" + integrity sha512-OullFjVUwlPXkq7b+HSGMPKJcSSEbH1gm6a2TQ5uIsPIHeFb/af6EmXd8IIctf0pz1cH41USg6rBR504vuuXZw== + dependencies: + "@usewaypoint/block-avatar" "^0.0.3" + "@usewaypoint/block-button" "^0.0.3" + "@usewaypoint/block-columns-container" "^0.0.3" + "@usewaypoint/block-container" "^0.0.2" + "@usewaypoint/block-divider" "^0.0.4" + "@usewaypoint/block-heading" "^0.0.3" + "@usewaypoint/block-html" "^0.0.3" + "@usewaypoint/block-image" "^0.0.5" + "@usewaypoint/block-spacer" "^0.0.3" + "@usewaypoint/block-text" "^0.0.4" + "@usewaypoint/document-core" "^0.0.6" + +"@vitest/expect@>1.6.0": + version "2.1.8" + resolved "https://registry.yarnpkg.com/@vitest/expect/-/expect-2.1.8.tgz#13fad0e8d5a0bf0feb675dcf1d1f1a36a1773bc1" + integrity sha512-8ytZ/fFHq2g4PJVAtDX57mayemKgDR6X3Oa2Foro+EygiOJHUXhCqBAAKQYYajZpFoIfvBCF1j6R6IYRSIUFuw== + dependencies: + "@vitest/spy" "2.1.8" + "@vitest/utils" "2.1.8" + chai "^5.1.2" + tinyrainbow "^1.2.0" + +"@vitest/pretty-format@2.1.8": + version "2.1.8" + resolved "https://registry.yarnpkg.com/@vitest/pretty-format/-/pretty-format-2.1.8.tgz#88f47726e5d0cf4ba873d50c135b02e4395e2bca" + integrity sha512-9HiSZ9zpqNLKlbIDRWOnAWqgcA7xu+8YxXSekhr0Ykab7PAYFkhkwoqVArPOtJhPmYeE2YHgKZlj3CP36z2AJQ== + dependencies: + tinyrainbow "^1.2.0" + +"@vitest/spy@2.1.8": + version "2.1.8" + resolved "https://registry.yarnpkg.com/@vitest/spy/-/spy-2.1.8.tgz#bc41af3e1e6a41ae3b67e51f09724136b88fa447" + integrity sha512-5swjf2q95gXeYPevtW0BLk6H8+bPlMb4Vw/9Em4hFxDcaOxS+e0LOX4yqNxoHzMR2akEB2xfpnWUzkZokmgWDg== + dependencies: + tinyspy "^3.0.2" + +"@vitest/utils@2.1.8": + version "2.1.8" + resolved "https://registry.yarnpkg.com/@vitest/utils/-/utils-2.1.8.tgz#f8ef85525f3362ebd37fd25d268745108d6ae388" + integrity sha512-dwSoui6djdwbfFmIgbIjX2ZhIoG7Ex/+xpxyiEgIGzjliY8xGkcpITKTlp6B4MgtGkF2ilvm97cPM96XZaAgcA== + dependencies: + "@vitest/pretty-format" "2.1.8" + loupe "^3.1.2" + tinyrainbow "^1.2.0" + +abbrev@1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" + integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== + +acorn-jsx@^5.3.2: + version "5.3.2" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== + +acorn-walk@^8.1.1: + version "8.3.4" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.4.tgz#794dd169c3977edf4ba4ea47583587c5866236b7" + integrity sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g== + dependencies: + acorn "^8.11.0" + +acorn@^8.11.0, acorn@^8.14.0, acorn@^8.4.1: + version "8.14.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.14.0.tgz#063e2c70cac5fb4f6467f0b11152e04c682795b0" + integrity sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA== + +ajv@^6.12.4: + version "6.12.6" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +ansi-colors@^4.1.3: + version "4.1.3" + resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.3.tgz#37611340eb2243e70cc604cad35d63270d48781b" + integrity sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw== + +ansi-escapes@^4.2.1: + version "4.3.2" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" + integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== + dependencies: + type-fest "^0.21.3" + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-regex@^6.0.1: + version "6.1.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.1.0.tgz#95ec409c69619d6cb1b8b34f14b660ef28ebd654" + integrity sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA== + +ansi-styles@^4.0.0, ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +ansi-styles@^5.0.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" + integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== + +ansi-styles@^6.1.0: + version "6.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5" + integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug== + +anymatch@^3.0.3, anymatch@~3.1.2: + version "3.1.3" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" + integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +arg@^4.1.0: + version "4.1.3" + resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" + integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== + +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + +array-each@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/array-each/-/array-each-1.0.1.tgz#a794af0c05ab1752846ee753a1f211a05ba0c44f" + integrity sha512-zHjL5SZa68hkKHBFBK6DJCTtr9sfTCPCaph/L7tMSLcTFgy+zX7E+6q5UArbtOtMBCtxdICpfTCspRse+ywyXA== + +array-slice@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/array-slice/-/array-slice-1.1.0.tgz#e368ea15f89bc7069f7ffb89aec3a6c7d4ac22d4" + integrity sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w== + +assertion-error@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b" + integrity sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw== + +assertion-error@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-2.0.1.tgz#f641a196b335690b1070bf00b6e7593fec190bf7" + integrity sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA== + +async@^2.6.0: + version "2.6.4" + resolved "https://registry.yarnpkg.com/async/-/async-2.6.4.tgz#706b7ff6084664cd7eae713f6f965433b5504221" + integrity sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA== + dependencies: + lodash "^4.17.14" + +async@^3.2.3, async@~3.2.0: + version "3.2.6" + resolved "https://registry.yarnpkg.com/async/-/async-3.2.6.tgz#1b0728e14929d51b85b449b7f06e27c1145e38ce" + integrity sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA== + +available-typed-arrays@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz#a5cc375d6a03c2efc87a553f3e0b1522def14846" + integrity sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ== + dependencies: + possible-typed-array-names "^1.0.0" + +aws-lambda@1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/aws-lambda/-/aws-lambda-1.0.7.tgz#c6b674df47458b5ecd43ab734899ad2e2d457013" + integrity sha512-9GNFMRrEMG5y3Jvv+V4azWvc+qNWdWLTjDdhf/zgMlz8haaaLWv0xeAIWxz9PuWUBawsVxy0zZotjCdR3Xq+2w== + dependencies: + aws-sdk "^2.814.0" + commander "^3.0.2" + js-yaml "^3.14.1" + watchpack "^2.0.0-beta.10" + +aws-sdk-client-mock-jest@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/aws-sdk-client-mock-jest/-/aws-sdk-client-mock-jest-4.1.0.tgz#40a3bdedd8d551cf2a836b77239038c0ca10e25c" + integrity sha512-+g4a5Hp+MmPqqNnvwfLitByggrqf+xSbk1pm6fBYHNcon6+aQjL5iB+3YB6HuGPemY+/mUKN34iP62S14R61bA== + dependencies: + "@vitest/expect" ">1.6.0" + expect ">28.1.3" + tslib "^2.1.0" + +aws-sdk-client-mock@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/aws-sdk-client-mock/-/aws-sdk-client-mock-4.1.0.tgz#ae1950b2277f8e65f9a039975d79ff9fffab39e3" + integrity sha512-h/tOYTkXEsAcV3//6C1/7U4ifSpKyJvb6auveAepqqNJl6TdZaPFEtKjBQNf8UxQdDP850knB2i/whq4zlsxJw== + dependencies: + "@types/sinon" "^17.0.3" + sinon "^18.0.1" + tslib "^2.1.0" + +aws-sdk@^2.814.0: + version "2.1692.0" + resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.1692.0.tgz#9dac5f7bfcc5ab45825cc8591b12753aa7d2902c" + integrity sha512-x511uiJ/57FIsbgUe5csJ13k3uzu25uWQE+XqfBis/sB0SFoiElJWXRkgEAUh0U6n40eT3ay5Ue4oPkRMu1LYw== + dependencies: + buffer "4.9.2" + events "1.1.1" + ieee754 "1.1.13" + jmespath "0.16.0" + querystring "0.2.0" + sax "1.2.1" + url "0.10.3" + util "^0.12.4" + uuid "8.0.0" + xml2js "0.6.2" + +babel-jest@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.7.0.tgz#f4369919225b684c56085998ac63dbd05be020d5" + integrity sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg== + dependencies: + "@jest/transform" "^29.7.0" + "@types/babel__core" "^7.1.14" + babel-plugin-istanbul "^6.1.1" + babel-preset-jest "^29.6.3" + chalk "^4.0.0" + graceful-fs "^4.2.9" + slash "^3.0.0" + +babel-plugin-istanbul@^6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz#fa88ec59232fd9b4e36dbbc540a8ec9a9b47da73" + integrity sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@istanbuljs/load-nyc-config" "^1.0.0" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-instrument "^5.0.4" + test-exclude "^6.0.0" + +babel-plugin-jest-hoist@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz#aadbe943464182a8922c3c927c3067ff40d24626" + integrity sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg== + dependencies: + "@babel/template" "^7.3.3" + "@babel/types" "^7.3.3" + "@types/babel__core" "^7.1.14" + "@types/babel__traverse" "^7.0.6" + +babel-preset-current-node-syntax@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.0.tgz#9a929eafece419612ef4ae4f60b1862ebad8ef30" + integrity sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw== + dependencies: + "@babel/plugin-syntax-async-generators" "^7.8.4" + "@babel/plugin-syntax-bigint" "^7.8.3" + "@babel/plugin-syntax-class-properties" "^7.12.13" + "@babel/plugin-syntax-class-static-block" "^7.14.5" + "@babel/plugin-syntax-import-attributes" "^7.24.7" + "@babel/plugin-syntax-import-meta" "^7.10.4" + "@babel/plugin-syntax-json-strings" "^7.8.3" + "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + "@babel/plugin-syntax-numeric-separator" "^7.10.4" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + "@babel/plugin-syntax-private-property-in-object" "^7.14.5" + "@babel/plugin-syntax-top-level-await" "^7.14.5" + +babel-preset-jest@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz#fa05fa510e7d493896d7b0dd2033601c840f171c" + integrity sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA== + dependencies: + babel-plugin-jest-hoist "^29.6.3" + babel-preset-current-node-syntax "^1.0.0" + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +base64-js@^1.0.2: + version "1.5.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + +binary-extensions@^2.0.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522" + integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw== + +body@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/body/-/body-5.1.0.tgz#e4ba0ce410a46936323367609ecb4e6553125069" + integrity sha512-chUsBxGRtuElD6fmw1gHLpvnKdVLK302peeFa9ZqAEk8TyzZ3fygLyUEDDPTJvL9+Bor0dIwn6ePOsRM2y0zQQ== + dependencies: + continuable-cache "^0.3.1" + error "^7.0.0" + raw-body "~1.1.0" + safe-json-parse "~1.0.1" + +bowser@^2.11.0: + version "2.11.0" + resolved "https://registry.yarnpkg.com/bowser/-/bowser-2.11.0.tgz#5ca3c35757a7aa5771500c70a73a9f91ef420a8f" + integrity sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA== + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +brace-expansion@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" + integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== + dependencies: + balanced-match "^1.0.0" + +braces@^3.0.3, braces@~3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" + integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== + dependencies: + fill-range "^7.1.1" + +browser-stdout@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" + integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw== + +browserslist@^4.24.0: + version "4.24.2" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.24.2.tgz#f5845bc91069dbd55ee89faf9822e1d885d16580" + integrity sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg== + dependencies: + caniuse-lite "^1.0.30001669" + electron-to-chromium "^1.5.41" + node-releases "^2.0.18" + update-browserslist-db "^1.1.1" + +bs-logger@^0.2.6: + version "0.2.6" + resolved "https://registry.yarnpkg.com/bs-logger/-/bs-logger-0.2.6.tgz#eb7d365307a72cf974cc6cda76b68354ad336bd8" + integrity sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog== + dependencies: + fast-json-stable-stringify "2.x" + +bser@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" + integrity sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ== + dependencies: + node-int64 "^0.4.0" + +buffer-from@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" + integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== + +buffer@4.9.2: + version "4.9.2" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.2.tgz#230ead344002988644841ab0244af8c44bbe3ef8" + integrity sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg== + dependencies: + base64-js "^1.0.2" + ieee754 "^1.1.4" + isarray "^1.0.0" + +bytes@1: + version "1.0.0" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-1.0.0.tgz#3569ede8ba34315fab99c3e92cb04c7220de1fa8" + integrity sha512-/x68VkHLeTl3/Ll8IvxdwzhrT+IyKc52e/oyHhA2RwqPqswSnjVbSddfPRwAsJtbilMAPSRWwAlpxdYsSWOTKQ== + +call-bind-apply-helpers@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz#32e5892e6361b29b0b545ba6f7763378daca2840" + integrity sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g== + dependencies: + es-errors "^1.3.0" + function-bind "^1.1.2" + +call-bind@^1.0.2, call-bind@^1.0.7: + version "1.0.8" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.8.tgz#0736a9660f537e3388826f440d5ec45f744eaa4c" + integrity sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww== + dependencies: + call-bind-apply-helpers "^1.0.0" + es-define-property "^1.0.0" + get-intrinsic "^1.2.4" + set-function-length "^1.2.2" + +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + +camelcase@^5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" + integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== + +camelcase@^6.0.0, camelcase@^6.2.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" + integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== + +caniuse-lite@^1.0.30001669: + version "1.0.30001687" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001687.tgz#d0ac634d043648498eedf7a3932836beba90ebae" + integrity sha512-0S/FDhf4ZiqrTUiQ39dKeUjYRjkv7lOZU1Dgif2rIqrTzX/1wV2hfKu9TOm1IHkdSijfLswxTFzl/cvir+SLSQ== + +chai-match-pattern@^1.1.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/chai-match-pattern/-/chai-match-pattern-1.3.0.tgz#cefd4437de465860f4f87922c31049eb9d979104" + integrity sha512-DflyfI8lZ56YuYAZMTBPWghjqFQfqY1IR0ZZXrjlGZJuRvtN0TjJMBpLsrMfc45kjivXJ06iayuP7lzG6ij1bQ== + dependencies: + lodash-match-pattern "^2.3.1" + +chai@^4.1.2: + version "4.5.0" + resolved "https://registry.yarnpkg.com/chai/-/chai-4.5.0.tgz#707e49923afdd9b13a8b0b47d33d732d13812fd8" + integrity sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw== + dependencies: + assertion-error "^1.1.0" + check-error "^1.0.3" + deep-eql "^4.1.3" + get-func-name "^2.0.2" + loupe "^2.3.6" + pathval "^1.1.1" + type-detect "^4.1.0" + +chai@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/chai/-/chai-5.1.2.tgz#3afbc340b994ae3610ca519a6c70ace77ad4378d" + integrity sha512-aGtmf24DW6MLHHG5gCx4zaI3uBq3KRtxeVs0DjFH6Z0rDNbsvTxFASFvdj79pxjxZ8/5u3PIiN3IwEIQkiiuPw== + dependencies: + assertion-error "^2.0.1" + check-error "^2.1.1" + deep-eql "^5.0.1" + loupe "^3.1.0" + pathval "^2.0.0" + +chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.0, chalk@^4.1.2, chalk@~4.1.0: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +char-regex@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" + integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== + +check-error@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.3.tgz#a6502e4312a7ee969f646e83bb3ddd56281bd694" + integrity sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg== + dependencies: + get-func-name "^2.0.2" + +check-error@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/check-error/-/check-error-2.1.1.tgz#87eb876ae71ee388fa0471fe423f494be1d96ccc" + integrity sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw== + +checkit@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/checkit/-/checkit-0.7.0.tgz#14979abc93018346bfcfdcbabc19ab54c0bfd74a" + integrity sha512-QgiWB8gMdF/CbmWyuxCk+f2MPQe0G1DfJfHCTbrfZlY3FnJWdnW+EGsRJctcYz/IrXxPYJmjRjdgmKUkyIZl/Q== + dependencies: + inherits "^2.0.1" + lodash "^4.0.0" + +chokidar@^3.5.3: + version "3.6.0" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b" + integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw== + dependencies: + anymatch "~3.1.2" + braces "~3.0.2" + glob-parent "~5.1.2" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.6.0" + optionalDependencies: + fsevents "~2.3.2" + +ci-info@^3.2.0: + version "3.9.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4" + integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ== + +cjs-module-lexer@^1.0.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.4.1.tgz#707413784dbb3a72aa11c2f2b042a0bef4004170" + integrity sha512-cuSVIHi9/9E/+821Qjdvngor+xpnlwnuwIyZOaLmHBVdXL+gP+I6QQB9VkO7RI77YIcTV+S1W9AreJ5eN63JBA== + +cliui@^7.0.2: + version "7.0.4" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" + integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.0" + wrap-ansi "^7.0.0" + +cliui@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" + integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.1" + wrap-ansi "^7.0.0" + +co@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" + integrity sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ== + +collect-v8-coverage@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz#c0b29bcd33bcd0779a1344c2136051e6afd3d9e9" + integrity sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q== + +color-convert@^1.9.3: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== + +color-name@^1.0.0, color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +color-string@^1.6.0: + version "1.9.1" + resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.9.1.tgz#4467f9146f036f855b764dfb5bf8582bf342c7a4" + integrity sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg== + dependencies: + color-name "^1.0.0" + simple-swizzle "^0.2.2" + +color@^3.1.3: + version "3.2.1" + resolved "https://registry.yarnpkg.com/color/-/color-3.2.1.tgz#3544dc198caf4490c3ecc9a790b54fe9ff45e164" + integrity sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA== + dependencies: + color-convert "^1.9.3" + color-string "^1.6.0" + +colors@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/colors/-/colors-1.1.2.tgz#168a4701756b6a7f51a12ce0c97bfa28c084ed63" + integrity sha512-ENwblkFQpqqia6b++zLD/KUWafYlVY/UNnAp7oz7LY7E924wmpye416wBOmvv/HMWzl8gL1kJlfvId/1Dg176w== + +colorspace@1.1.x: + version "1.1.4" + resolved "https://registry.yarnpkg.com/colorspace/-/colorspace-1.1.4.tgz#8d442d1186152f60453bf8070cd66eb364e59243" + integrity sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w== + dependencies: + color "^3.1.3" + text-hex "1.0.x" + +commander@^10.0.1: + version "10.0.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-10.0.1.tgz#881ee46b4f77d1c1dccc5823433aa39b022cbe06" + integrity sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug== + +commander@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/commander/-/commander-3.0.2.tgz#6837c3fb677ad9933d1cfba42dd14d5117d6b39e" + integrity sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + +continuable-cache@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/continuable-cache/-/continuable-cache-0.3.1.tgz#bd727a7faed77e71ff3985ac93351a912733ad0f" + integrity sha512-TF30kpKhTH8AGCG3dut0rdd/19B7Z+qCnrMoBLpyQu/2drZdNrrpcjPEoJeSVsQM+8KmWG5O56oPDjSSUsuTyA== + +convert-source-map@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" + integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== + +create-jest@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/create-jest/-/create-jest-29.7.0.tgz#a355c5b3cb1e1af02ba177fe7afd7feee49a5320" + integrity sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q== + dependencies: + "@jest/types" "^29.6.3" + chalk "^4.0.0" + exit "^0.1.2" + graceful-fs "^4.2.9" + jest-config "^29.7.0" + jest-util "^29.7.0" + prompts "^2.0.1" + +create-require@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" + integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== + +cross-spawn@^7.0.0, cross-spawn@^7.0.3, cross-spawn@^7.0.5: + version "7.0.6" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" + integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +csstype@^3.0.2: + version "3.1.3" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81" + integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw== + +dateformat@~4.6.2: + version "4.6.3" + resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-4.6.3.tgz#556fa6497e5217fedb78821424f8a1c22fa3f4b5" + integrity sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA== + +debug@^3.1.0: + version "3.2.7" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" + integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== + dependencies: + ms "^2.1.1" + +debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@^4.3.5: + version "4.4.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.0.tgz#2b3f2aea2ffeb776477460267377dc8710faba8a" + integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA== + dependencies: + ms "^2.1.3" + +decamelize@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-4.0.0.tgz#aa472d7bf660eb15f3494efd531cab7f2a709837" + integrity sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ== + +dedent@^1.0.0: + version "1.5.3" + resolved "https://registry.yarnpkg.com/dedent/-/dedent-1.5.3.tgz#99aee19eb9bae55a67327717b6e848d0bf777e5a" + integrity sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ== + +deep-eql@^4.1.3: + version "4.1.4" + resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-4.1.4.tgz#d0d3912865911bb8fac5afb4e3acfa6a28dc72b7" + integrity sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg== + dependencies: + type-detect "^4.0.0" + +deep-eql@^5.0.1: + version "5.0.2" + resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-5.0.2.tgz#4b756d8d770a9257300825d52a2c2cff99c3a341" + integrity sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q== + +deep-is@^0.1.3: + version "0.1.4" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" + integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== + +deepmerge@^4.2.2: + version "4.3.1" + resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" + integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== + +define-data-property@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e" + integrity sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A== + dependencies: + es-define-property "^1.0.0" + es-errors "^1.3.0" + gopd "^1.0.1" + +detect-file@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/detect-file/-/detect-file-1.0.0.tgz#f0d66d03672a825cb1b73bdb3fe62310c8e552b7" + integrity sha512-DtCOLG98P007x7wiiOmfI0fi3eIKyWiLTGJ2MDnVi/E04lWGbf+JzrRHMm0rgIIZJGtHpKpbVgLWHrv8xXpc3Q== + +detect-newline@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" + integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== + +diff-sequences@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.6.3.tgz#4deaf894d11407c51efc8418012f9e70b84ea921" + integrity sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q== + +diff@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" + integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== + +diff@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/diff/-/diff-5.2.0.tgz#26ded047cd1179b78b9537d5ef725503ce1ae531" + integrity sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A== + +dotenv@^16.3.1: + version "16.4.7" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.7.tgz#0e20c5b82950140aa99be360a8a5f52335f53c26" + integrity sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ== + +dunder-proto@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.0.tgz#c2fce098b3c8f8899554905f4377b6d85dabaa80" + integrity sha512-9+Sj30DIu+4KvHqMfLUGLFYL2PkURSYMVXJyXe92nFRvlYq5hBjLEhblKB+vkd/WVlUYMWigiY07T91Fkk0+4A== + dependencies: + call-bind-apply-helpers "^1.0.0" + es-errors "^1.3.0" + gopd "^1.2.0" + +eastasianwidth@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" + integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== + +ejs@^3.1.10: + version "3.1.10" + resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.10.tgz#69ab8358b14e896f80cc39e62087b88500c3ac3b" + integrity sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA== + dependencies: + jake "^10.8.5" + +electron-to-chromium@^1.5.41: + version "1.5.71" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.71.tgz#d8b5dba1e55b320f2f4e9b1ca80738f53fcfec2b" + integrity sha512-dB68l59BI75W1BUGVTAEJy45CEVuEGy9qPVVQ8pnHyHMn36PLPPoE1mjLH+lo9rKulO3HC2OhbACI/8tCqJBcA== + +emittery@^0.13.1: + version "0.13.1" + resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.13.1.tgz#c04b8c3457490e0847ae51fced3af52d338e3dad" + integrity sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +emoji-regex@^9.2.2: + version "9.2.2" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" + integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== + +enabled@2.0.x: + version "2.0.0" + resolved "https://registry.yarnpkg.com/enabled/-/enabled-2.0.0.tgz#f9dd92ec2d6f4bbc0d5d1e64e21d61cd4665e7c2" + integrity sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ== + +error-ex@^1.3.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" + integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== + dependencies: + is-arrayish "^0.2.1" + +error@^7.0.0: + version "7.2.1" + resolved "https://registry.yarnpkg.com/error/-/error-7.2.1.tgz#eab21a4689b5f684fc83da84a0e390de82d94894" + integrity sha512-fo9HBvWnx3NGUKMvMwB/CBCMMrfEJgbDTVDEkPygA3Bdd3lM1OyCd+rbQ8BwnpF6GdVeOLDNmyL4N5Bg80ZvdA== + dependencies: + string-template "~0.2.1" + +es-define-property@^1.0.0, es-define-property@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa" + integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== + +es-errors@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" + integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== + +esbuild@0.24.0: + version "0.24.0" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.24.0.tgz#f2d470596885fcb2e91c21eb3da3b3c89c0b55e7" + integrity sha512-FuLPevChGDshgSicjisSooU0cemp/sGXR841D5LHMB7mTVOmsEHcAxaH3irL53+8YDIeVNQEySh4DaYU/iuPqQ== + optionalDependencies: + "@esbuild/aix-ppc64" "0.24.0" + "@esbuild/android-arm" "0.24.0" + "@esbuild/android-arm64" "0.24.0" + "@esbuild/android-x64" "0.24.0" + "@esbuild/darwin-arm64" "0.24.0" + "@esbuild/darwin-x64" "0.24.0" + "@esbuild/freebsd-arm64" "0.24.0" + "@esbuild/freebsd-x64" "0.24.0" + "@esbuild/linux-arm" "0.24.0" + "@esbuild/linux-arm64" "0.24.0" + "@esbuild/linux-ia32" "0.24.0" + "@esbuild/linux-loong64" "0.24.0" + "@esbuild/linux-mips64el" "0.24.0" + "@esbuild/linux-ppc64" "0.24.0" + "@esbuild/linux-riscv64" "0.24.0" + "@esbuild/linux-s390x" "0.24.0" + "@esbuild/linux-x64" "0.24.0" + "@esbuild/netbsd-x64" "0.24.0" + "@esbuild/openbsd-arm64" "0.24.0" + "@esbuild/openbsd-x64" "0.24.0" + "@esbuild/sunos-x64" "0.24.0" + "@esbuild/win32-arm64" "0.24.0" + "@esbuild/win32-ia32" "0.24.0" + "@esbuild/win32-x64" "0.24.0" + +esbuild@~0.23.0: + version "0.23.1" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.23.1.tgz#40fdc3f9265ec0beae6f59824ade1bd3d3d2dab8" + integrity sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg== + optionalDependencies: + "@esbuild/aix-ppc64" "0.23.1" + "@esbuild/android-arm" "0.23.1" + "@esbuild/android-arm64" "0.23.1" + "@esbuild/android-x64" "0.23.1" + "@esbuild/darwin-arm64" "0.23.1" + "@esbuild/darwin-x64" "0.23.1" + "@esbuild/freebsd-arm64" "0.23.1" + "@esbuild/freebsd-x64" "0.23.1" + "@esbuild/linux-arm" "0.23.1" + "@esbuild/linux-arm64" "0.23.1" + "@esbuild/linux-ia32" "0.23.1" + "@esbuild/linux-loong64" "0.23.1" + "@esbuild/linux-mips64el" "0.23.1" + "@esbuild/linux-ppc64" "0.23.1" + "@esbuild/linux-riscv64" "0.23.1" + "@esbuild/linux-s390x" "0.23.1" + "@esbuild/linux-x64" "0.23.1" + "@esbuild/netbsd-x64" "0.23.1" + "@esbuild/openbsd-arm64" "0.23.1" + "@esbuild/openbsd-x64" "0.23.1" + "@esbuild/sunos-x64" "0.23.1" + "@esbuild/win32-arm64" "0.23.1" + "@esbuild/win32-ia32" "0.23.1" + "@esbuild/win32-x64" "0.23.1" + +escalade@^3.1.1, escalade@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" + integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== + +escape-string-regexp@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" + integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== + +escape-string-regexp@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + +eslint-scope@^8.2.0: + version "8.2.0" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-8.2.0.tgz#377aa6f1cb5dc7592cfd0b7f892fd0cf352ce442" + integrity sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A== + dependencies: + esrecurse "^4.3.0" + estraverse "^5.2.0" + +eslint-visitor-keys@^3.4.3: + version "3.4.3" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" + integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== + +eslint-visitor-keys@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz#687bacb2af884fcdda8a6e7d65c606f46a14cd45" + integrity sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw== + +eslint@^9.0.0, eslint@^9.13.0: + version "9.16.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.16.0.tgz#66832e66258922ac0a626f803a9273e37747f2a6" + integrity sha512-whp8mSQI4C8VXd+fLgSM0lh3UlmcFtVwUQjyKCFfsp+2ItAIYhlq/hqGahGqHE6cv9unM41VlqKk2VtKYR2TaA== + dependencies: + "@eslint-community/eslint-utils" "^4.2.0" + "@eslint-community/regexpp" "^4.12.1" + "@eslint/config-array" "^0.19.0" + "@eslint/core" "^0.9.0" + "@eslint/eslintrc" "^3.2.0" + "@eslint/js" "9.16.0" + "@eslint/plugin-kit" "^0.2.3" + "@humanfs/node" "^0.16.6" + "@humanwhocodes/module-importer" "^1.0.1" + "@humanwhocodes/retry" "^0.4.1" + "@types/estree" "^1.0.6" + "@types/json-schema" "^7.0.15" + ajv "^6.12.4" + chalk "^4.0.0" + cross-spawn "^7.0.5" + debug "^4.3.2" + escape-string-regexp "^4.0.0" + eslint-scope "^8.2.0" + eslint-visitor-keys "^4.2.0" + espree "^10.3.0" + esquery "^1.5.0" + esutils "^2.0.2" + fast-deep-equal "^3.1.3" + file-entry-cache "^8.0.0" + find-up "^5.0.0" + glob-parent "^6.0.2" + ignore "^5.2.0" + imurmurhash "^0.1.4" + is-glob "^4.0.0" + json-stable-stringify-without-jsonify "^1.0.1" + lodash.merge "^4.6.2" + minimatch "^3.1.2" + natural-compare "^1.4.0" + optionator "^0.9.3" + +espree@^10.0.1, espree@^10.3.0: + version "10.3.0" + resolved "https://registry.yarnpkg.com/espree/-/espree-10.3.0.tgz#29267cf5b0cb98735b65e64ba07e0ed49d1eed8a" + integrity sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg== + dependencies: + acorn "^8.14.0" + acorn-jsx "^5.3.2" + eslint-visitor-keys "^4.2.0" + +esprima@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +esquery@^1.5.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.6.0.tgz#91419234f804d852a82dceec3e16cdc22cf9dae7" + integrity sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg== + dependencies: + estraverse "^5.1.0" + +esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + +estraverse@^5.1.0, estraverse@^5.2.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +eventemitter2@~0.4.13: + version "0.4.14" + resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-0.4.14.tgz#8f61b75cde012b2e9eb284d4545583b5643b61ab" + integrity sha512-K7J4xq5xAD5jHsGM5ReWXRTFa3JRGofHiMcVgQ8PRwgWxzjHpMWCIzsmyf60+mh8KLsqYPcjUMa0AC4hd6lPyQ== + +events@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924" + integrity sha512-kEcvvCBByWXGnZy6JUlgAp2gBIUjfCAV6P6TgT1/aaQKcmuAEC4OZTV1I4EWQLz2gxZw76atuVyvHhTxvi0Flw== + +execa@^5.0.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" + integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== + dependencies: + cross-spawn "^7.0.3" + get-stream "^6.0.0" + human-signals "^2.1.0" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.1" + onetime "^5.1.2" + signal-exit "^3.0.3" + strip-final-newline "^2.0.0" + +exit@^0.1.2, exit@~0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" + integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ== + +expand-tilde@^2.0.0, expand-tilde@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/expand-tilde/-/expand-tilde-2.0.2.tgz#97e801aa052df02454de46b02bf621642cdc8502" + integrity sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw== + dependencies: + homedir-polyfill "^1.0.1" + +expect@>28.1.3, expect@^29.0.0, expect@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/expect/-/expect-29.7.0.tgz#578874590dcb3214514084c08115d8aee61e11bc" + integrity sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw== + 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" + +extend@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" + integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== + +fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-glob@^3.3.2: + version "3.3.2" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129" + integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.4" + +fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fast-levenshtein@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== + +fast-xml-parser@4.4.1: + version "4.4.1" + resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-4.4.1.tgz#86dbf3f18edf8739326447bcaac31b4ae7f6514f" + integrity sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw== + dependencies: + strnum "^1.0.5" + +fastq@^1.6.0: + version "1.17.1" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.17.1.tgz#2a523f07a4e7b1e81a42b91b8bf2254107753b47" + integrity sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w== + dependencies: + reusify "^1.0.4" + +faye-websocket@~0.10.0: + version "0.10.0" + resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.10.0.tgz#4e492f8d04dfb6f89003507f6edbf2d501e7c6f4" + integrity sha512-Xhj93RXbMSq8urNCUq4p9l0P6hnySJ/7YNRhYNug0bLOuii7pKO7xQFb5mx9xZXWCar88pLPb805PvUkwrLZpQ== + dependencies: + websocket-driver ">=0.5.1" + +fb-watchman@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.2.tgz#e9524ee6b5c77e9e5001af0f85f3adbb8623255c" + integrity sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA== + dependencies: + bser "2.1.1" + +fecha@^4.2.0: + version "4.2.3" + resolved "https://registry.yarnpkg.com/fecha/-/fecha-4.2.3.tgz#4d9ccdbc61e8629b259fdca67e65891448d569fd" + integrity sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw== + +file-entry-cache@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-8.0.0.tgz#7787bddcf1131bffb92636c69457bbc0edd6d81f" + integrity sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ== + dependencies: + flat-cache "^4.0.0" + +filelist@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/filelist/-/filelist-1.0.4.tgz#f78978a1e944775ff9e62e744424f215e58352b5" + integrity sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q== + dependencies: + minimatch "^5.0.1" + +fill-range@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" + integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== + dependencies: + to-regex-range "^5.0.1" + +find-up@^4.0.0, find-up@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" + integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== + dependencies: + locate-path "^5.0.0" + path-exists "^4.0.0" + +find-up@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" + integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== + dependencies: + locate-path "^6.0.0" + path-exists "^4.0.0" + +findup-sync@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-4.0.0.tgz#956c9cdde804052b881b428512905c4a5f2cdef0" + integrity sha512-6jvvn/12IC4quLBL1KNokxC7wWTvYncaVUYSoxWw7YykPLuRrnv4qdHcSOywOI5RpkOVGeQRtWM8/q+G6W6qfQ== + dependencies: + detect-file "^1.0.0" + is-glob "^4.0.0" + micromatch "^4.0.2" + resolve-dir "^1.0.1" + +findup-sync@~5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-5.0.0.tgz#54380ad965a7edca00cc8f63113559aadc541bd2" + integrity sha512-MzwXju70AuyflbgeOhzvQWAvvQdo1XL0A9bVvlXsYcFEBM87WR4OakL4OfZq+QRmr+duJubio+UtNQCPsVESzQ== + dependencies: + detect-file "^1.0.0" + is-glob "^4.0.3" + micromatch "^4.0.4" + resolve-dir "^1.0.1" + +fined@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/fined/-/fined-1.2.0.tgz#d00beccf1aa2b475d16d423b0238b713a2c4a37b" + integrity sha512-ZYDqPLGxDkDhDZBjZBb+oD1+j0rA4E0pXY50eplAAOPg2N/gUBSSk5IM1/QhPfyVo19lJ+CvXpqfvk+b2p/8Ng== + dependencies: + expand-tilde "^2.0.2" + is-plain-object "^2.0.3" + object.defaults "^1.1.0" + object.pick "^1.2.0" + parse-filepath "^1.0.1" + +flagged-respawn@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/flagged-respawn/-/flagged-respawn-1.0.1.tgz#e7de6f1279ddd9ca9aac8a5971d618606b3aab41" + integrity sha512-lNaHNVymajmk0OJMBn8fVUAU1BtDeKIqKoVhk4xAALB57aALg6b4W0MfJ/cUE0g9YBXy5XhSlPIpYIJ7HaY/3Q== + +flat-cache@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-4.0.1.tgz#0ece39fcb14ee012f4b0410bd33dd9c1f011127c" + integrity sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw== + dependencies: + flatted "^3.2.9" + keyv "^4.5.4" + +flat@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241" + integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== + +flatted@^3.2.9: + version "3.3.2" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.2.tgz#adba1448a9841bec72b42c532ea23dbbedef1a27" + integrity sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA== + +fn.name@1.x.x: + version "1.1.0" + resolved "https://registry.yarnpkg.com/fn.name/-/fn.name-1.1.0.tgz#26cad8017967aea8731bc42961d04a3d5988accc" + integrity sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw== + +for-each@^0.3.3: + version "0.3.3" + resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e" + integrity sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw== + dependencies: + is-callable "^1.1.3" + +for-in@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" + integrity sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ== + +for-own@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/for-own/-/for-own-1.0.0.tgz#c63332f415cedc4b04dbfe70cf836494c53cb44b" + integrity sha512-0OABksIGrxKK8K4kynWkQ7y1zounQxP+CWnyclVwj81KW3vlLlGUx57DKGcP/LH216GzqnstnPocF16Nxs0Ycg== + dependencies: + for-in "^1.0.1" + +foreground-child@^3.1.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.3.0.tgz#0ac8644c06e431439f8561db8ecf29a7b5519c77" + integrity sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg== + dependencies: + cross-spawn "^7.0.0" + signal-exit "^4.0.1" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== + +fsevents@^2.3.2, fsevents@~2.3.2, fsevents@~2.3.3: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + +function-bind@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" + integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== + +gaze@^1.1.0: + version "1.1.3" + resolved "https://registry.yarnpkg.com/gaze/-/gaze-1.1.3.tgz#c441733e13b927ac8c0ff0b4c3b033f28812924a" + integrity sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g== + dependencies: + globule "^1.0.0" + +gensync@^1.0.0-beta.2: + version "1.0.0-beta.2" + resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" + integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== + +get-caller-file@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + +get-func-name@^2.0.1, get-func-name@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.2.tgz#0d7cf20cd13fda808669ffa88f4ffc7a3943fc41" + integrity sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ== + +get-intrinsic@^1.2.4: + version "1.2.5" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.5.tgz#dfe7dd1b30761b464fe51bf4bb00ac7c37b681e7" + integrity sha512-Y4+pKa7XeRUPWFNvOOYHkRYrfzW07oraURSvjDmRVOJ748OrVmeXtpE4+GCEHncjCjkTxPNRt8kEbxDhsn6VTg== + dependencies: + call-bind-apply-helpers "^1.0.0" + dunder-proto "^1.0.0" + es-define-property "^1.0.1" + es-errors "^1.3.0" + function-bind "^1.1.2" + gopd "^1.2.0" + has-symbols "^1.1.0" + hasown "^2.0.2" + +get-package-type@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" + integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== + +get-stream@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" + integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== + +get-tsconfig@^4.7.5: + version "4.8.1" + resolved "https://registry.yarnpkg.com/get-tsconfig/-/get-tsconfig-4.8.1.tgz#8995eb391ae6e1638d251118c7b56de7eb425471" + integrity sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg== + dependencies: + resolve-pkg-maps "^1.0.0" + +getobject@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/getobject/-/getobject-1.0.2.tgz#25ec87a50370f6dcc3c6ba7ef43c4c16215c4c89" + integrity sha512-2zblDBaFcb3rB4rF77XVnuINOE2h2k/OnqXAiy0IrTxUfV1iFp3la33oAQVY9pCpWU268WFYVt2t71hlMuLsOg== + +glob-parent@^5.1.2, glob-parent@~5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +glob-parent@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" + integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== + dependencies: + is-glob "^4.0.3" + +glob-to-regexp@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" + integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== + +glob@^10.4.5: + version "10.4.5" + resolved "https://registry.yarnpkg.com/glob/-/glob-10.4.5.tgz#f4d9f0b90ffdbab09c9d77f5f29b4262517b0956" + integrity sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg== + dependencies: + foreground-child "^3.1.0" + jackspeak "^3.1.2" + minimatch "^9.0.4" + minipass "^7.1.2" + package-json-from-dist "^1.0.0" + path-scurry "^1.11.1" + +glob@^7.1.3, glob@^7.1.4: + version "7.2.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.1.1" + once "^1.3.0" + path-is-absolute "^1.0.0" + +glob@~7.1.1, glob@~7.1.6: + version "7.1.7" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90" + integrity sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +global-modules@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-1.0.0.tgz#6d770f0eb523ac78164d72b5e71a8877265cc3ea" + integrity sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg== + dependencies: + global-prefix "^1.0.1" + is-windows "^1.0.1" + resolve-dir "^1.0.0" + +global-prefix@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-1.0.2.tgz#dbf743c6c14992593c655568cb66ed32c0122ebe" + integrity sha512-5lsx1NUDHtSjfg0eHlmYvZKv8/nVqX4ckFbM+FrGcQ+04KWcWFo9P5MxPZYSzUvyzmdTbI7Eix8Q4IbELDqzKg== + dependencies: + expand-tilde "^2.0.2" + homedir-polyfill "^1.0.1" + ini "^1.3.4" + is-windows "^1.0.1" + which "^1.2.14" + +globals@^11.1.0: + version "11.12.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" + integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== + +globals@^14.0.0: + version "14.0.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-14.0.0.tgz#898d7413c29babcf6bafe56fcadded858ada724e" + integrity sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ== + +globule@^1.0.0: + version "1.3.4" + resolved "https://registry.yarnpkg.com/globule/-/globule-1.3.4.tgz#7c11c43056055a75a6e68294453c17f2796170fb" + integrity sha512-OPTIfhMBh7JbBYDpa5b+Q5ptmMWKwcNcFSR/0c6t8V4f3ZAVBEsKNY37QdVqmLRYSMhOUGYrY0QhSoEpzGr/Eg== + dependencies: + glob "~7.1.1" + lodash "^4.17.21" + minimatch "~3.0.2" + +gopd@^1.0.1, gopd@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1" + integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== + +graceful-fs@^4.1.2, graceful-fs@^4.2.9: + version "4.2.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" + integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== + +graphemer@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" + integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== + +grunt-cli@~1.4.3: + version "1.4.3" + resolved "https://registry.yarnpkg.com/grunt-cli/-/grunt-cli-1.4.3.tgz#22c9f1a3d2780bf9b0d206e832e40f8f499175ff" + integrity sha512-9Dtx/AhVeB4LYzsViCjUQkd0Kw0McN2gYpdmGYKtE2a5Yt7v1Q+HYZVWhqXc/kGnxlMtqKDxSwotiGeFmkrCoQ== + dependencies: + grunt-known-options "~2.0.0" + interpret "~1.1.0" + liftup "~3.0.1" + nopt "~4.0.1" + v8flags "~3.2.0" + +grunt-contrib-watch@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/grunt-contrib-watch/-/grunt-contrib-watch-1.1.0.tgz#c143ca5b824b288a024b856639a5345aedb78ed4" + integrity sha512-yGweN+0DW5yM+oo58fRu/XIRrPcn3r4tQx+nL7eMRwjpvk+rQY6R8o94BPK0i2UhTg9FN21hS+m8vR8v9vXfeg== + dependencies: + async "^2.6.0" + gaze "^1.1.0" + lodash "^4.17.10" + tiny-lr "^1.1.1" + +grunt-eslint@^25.0.0: + version "25.0.0" + resolved "https://registry.yarnpkg.com/grunt-eslint/-/grunt-eslint-25.0.0.tgz#e04d21fdc8e772511dae201d4c13e8f4f60823bc" + integrity sha512-JIV5IPgOuacorFLmYtUTq0n+0qGIL9FSQJ4KVnNfCg/8Fm+K1t6OWrzXXI8TxWTwq2K9E3parFVXCpn1sGLbKQ== + dependencies: + chalk "^4.1.2" + eslint "^9.0.0" + +grunt-known-options@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/grunt-known-options/-/grunt-known-options-2.0.0.tgz#cac641e897f9a0a680b8c9839803d35f3325103c" + integrity sha512-GD7cTz0I4SAede1/+pAbmJRG44zFLPipVtdL9o3vqx9IEyb7b4/Y3s7r6ofI3CchR5GvYJ+8buCSioDv5dQLiA== + +grunt-legacy-log-utils@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/grunt-legacy-log-utils/-/grunt-legacy-log-utils-2.1.0.tgz#49a8c7dc74051476dcc116c32faf9db8646856ef" + integrity sha512-lwquaPXJtKQk0rUM1IQAop5noEpwFqOXasVoedLeNzaibf/OPWjKYvvdqnEHNmU+0T0CaReAXIbGo747ZD+Aaw== + dependencies: + chalk "~4.1.0" + lodash "~4.17.19" + +grunt-legacy-log@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/grunt-legacy-log/-/grunt-legacy-log-3.0.0.tgz#1c6eaf92371ea415af31ea84ce50d434ef6d39c4" + integrity sha512-GHZQzZmhyq0u3hr7aHW4qUH0xDzwp2YXldLPZTCjlOeGscAOWWPftZG3XioW8MasGp+OBRIu39LFx14SLjXRcA== + dependencies: + colors "~1.1.2" + grunt-legacy-log-utils "~2.1.0" + hooker "~0.2.3" + lodash "~4.17.19" + +grunt-legacy-util@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/grunt-legacy-util/-/grunt-legacy-util-2.0.1.tgz#0f929d13a2faf9988c9917c82bff609e2d9ba255" + integrity sha512-2bQiD4fzXqX8rhNdXkAywCadeqiPiay0oQny77wA2F3WF4grPJXCvAcyoWUJV+po/b15glGkxuSiQCK299UC2w== + dependencies: + async "~3.2.0" + exit "~0.1.2" + getobject "~1.0.0" + hooker "~0.2.3" + lodash "~4.17.21" + underscore.string "~3.3.5" + which "~2.0.2" + +grunt@^1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/grunt/-/grunt-1.6.1.tgz#0b4dd1524f26676dcf45d8f636b8d9061a8ede16" + integrity sha512-/ABUy3gYWu5iBmrUSRBP97JLpQUm0GgVveDCp6t3yRNIoltIYw7rEj3g5y1o2PGPR2vfTRGa7WC/LZHLTXnEzA== + dependencies: + dateformat "~4.6.2" + eventemitter2 "~0.4.13" + exit "~0.1.2" + findup-sync "~5.0.0" + glob "~7.1.6" + grunt-cli "~1.4.3" + grunt-known-options "~2.0.0" + grunt-legacy-log "~3.0.0" + grunt-legacy-util "~2.0.1" + iconv-lite "~0.6.3" + js-yaml "~3.14.0" + minimatch "~3.0.4" + nopt "~3.0.6" + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +has-property-descriptors@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz#963ed7d071dc7bf5f084c5bfbe0d1b6222586854" + integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg== + dependencies: + es-define-property "^1.0.0" + +has-symbols@^1.0.3, has-symbols@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338" + integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== + +has-tostringtag@^1.0.0, has-tostringtag@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc" + integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw== + dependencies: + has-symbols "^1.0.3" + +hasown@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" + integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== + dependencies: + function-bind "^1.1.2" + +he@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" + integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== + +homedir-polyfill@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz#743298cef4e5af3e194161fbadcc2151d3a058e8" + integrity sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA== + dependencies: + parse-passwd "^1.0.0" + +hooker@~0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/hooker/-/hooker-0.2.3.tgz#b834f723cc4a242aa65963459df6d984c5d3d959" + integrity sha512-t+UerCsQviSymAInD01Pw+Dn/usmz1sRO+3Zk1+lx8eg+WKpD2ulcwWqHHL0+aseRBr+3+vIhiG1K1JTwaIcTA== + +html-escaper@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" + integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== + +http-parser-js@>=0.5.1: + version "0.5.8" + resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.8.tgz#af23090d9ac4e24573de6f6aecc9d84a48bf20e3" + integrity sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q== + +human-signals@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" + integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== + +iconv-lite@~0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" + integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== + dependencies: + safer-buffer ">= 2.1.2 < 3.0.0" + +ieee754@1.1.13: + version "1.1.13" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84" + integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg== + +ieee754@^1.1.4: + version "1.2.1" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== + +ignore@^5.2.0, ignore@^5.3.1: + version "5.3.2" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5" + integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== + +import-fresh@^3.2.1: + version "3.3.0" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" + integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + +import-local@^3.0.2: + version "3.2.0" + resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.2.0.tgz#c3d5c745798c02a6f8b897726aba5100186ee260" + integrity sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA== + dependencies: + pkg-dir "^4.2.0" + resolve-cwd "^3.0.0" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@^2.0.1, inherits@^2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +ini@^1.3.4: + version "1.3.8" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" + integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== + +interpret@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.1.0.tgz#7ed1b1410c6a0e0f78cf95d3b8440c63f78b8614" + integrity sha512-CLM8SNMDu7C5psFCn6Wg/tgpj/bKAg7hc2gWqcuR9OD5Ft9PhBpIu8PLicPeis+xDd6YX2ncI8MCA64I9tftIA== + +is-absolute@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-absolute/-/is-absolute-1.0.0.tgz#395e1ae84b11f26ad1795e73c17378e48a301576" + integrity sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA== + dependencies: + is-relative "^1.0.0" + is-windows "^1.0.1" + +is-arguments@^1.0.4: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.1.1.tgz#15b3f88fda01f2a97fec84ca761a560f123efa9b" + integrity sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA== + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" + +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== + +is-arrayish@^0.3.1: + version "0.3.2" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03" + integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== + +is-binary-path@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== + dependencies: + binary-extensions "^2.0.0" + +is-callable@^1.1.3: + version "1.2.7" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" + integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== + +is-core-module@^2.13.0: + version "2.15.1" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.15.1.tgz#a7363a25bee942fefab0de13bf6aa372c82dcc37" + integrity sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ== + dependencies: + hasown "^2.0.2" + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-generator-fn@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" + integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== + +is-generator-function@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.0.10.tgz#f1558baf1ac17e0deea7c0415c438351ff2b3c72" + integrity sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A== + dependencies: + has-tostringtag "^1.0.0" + +is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-plain-obj@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287" + integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA== + +is-plain-object@^2.0.3, is-plain-object@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" + integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== + dependencies: + isobject "^3.0.1" + +is-relative@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-relative/-/is-relative-1.0.0.tgz#a1bb6935ce8c5dba1e8b9754b9b2dcc020e2260d" + integrity sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA== + dependencies: + is-unc-path "^1.0.0" + +is-stream@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" + integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== + +is-typed-array@^1.1.3: + version "1.1.13" + resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.13.tgz#d6c5ca56df62334959322d7d7dd1cca50debe229" + integrity sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw== + dependencies: + which-typed-array "^1.1.14" + +is-unc-path@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-unc-path/-/is-unc-path-1.0.0.tgz#d731e8898ed090a12c352ad2eaed5095ad322c9d" + integrity sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ== + dependencies: + unc-path-regex "^0.1.2" + +is-unicode-supported@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" + integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== + +is-windows@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" + integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== + +isarray@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== + +isobject@^3.0.0, isobject@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" + integrity sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg== + +istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: + version "3.2.2" + resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz#2d166c4b0644d43a39f04bf6c2edd1e585f31756" + integrity sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg== + +istanbul-lib-instrument@^5.0.4: + version "5.2.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz#d10c8885c2125574e1c231cacadf955675e1ce3d" + integrity sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg== + dependencies: + "@babel/core" "^7.12.3" + "@babel/parser" "^7.14.7" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-coverage "^3.2.0" + semver "^6.3.0" + +istanbul-lib-instrument@^6.0.0: + version "6.0.3" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz#fa15401df6c15874bcb2105f773325d78c666765" + integrity sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q== + dependencies: + "@babel/core" "^7.23.9" + "@babel/parser" "^7.23.9" + "@istanbuljs/schema" "^0.1.3" + istanbul-lib-coverage "^3.2.0" + semver "^7.5.4" + +istanbul-lib-report@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz#908305bac9a5bd175ac6a74489eafd0fc2445a7d" + integrity sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw== + dependencies: + istanbul-lib-coverage "^3.0.0" + make-dir "^4.0.0" + supports-color "^7.1.0" + +istanbul-lib-source-maps@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz#895f3a709fcfba34c6de5a42939022f3e4358551" + integrity sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw== + dependencies: + debug "^4.1.1" + istanbul-lib-coverage "^3.0.0" + source-map "^0.6.1" + +istanbul-reports@^3.1.3: + version "3.1.7" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.7.tgz#daed12b9e1dca518e15c056e1e537e741280fa0b" + integrity sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g== + dependencies: + html-escaper "^2.0.0" + istanbul-lib-report "^3.0.0" + +jackspeak@^3.1.2: + version "3.4.3" + resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-3.4.3.tgz#8833a9d89ab4acde6188942bd1c53b6390ed5a8a" + integrity sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw== + dependencies: + "@isaacs/cliui" "^8.0.2" + optionalDependencies: + "@pkgjs/parseargs" "^0.11.0" + +jake@^10.8.5: + version "10.9.2" + resolved "https://registry.yarnpkg.com/jake/-/jake-10.9.2.tgz#6ae487e6a69afec3a5e167628996b59f35ae2b7f" + integrity sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA== + dependencies: + async "^3.2.3" + chalk "^4.0.2" + filelist "^1.0.4" + minimatch "^3.1.2" + +jest-changed-files@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-29.7.0.tgz#1c06d07e77c78e1585d020424dedc10d6e17ac3a" + integrity sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w== + dependencies: + execa "^5.0.0" + jest-util "^29.7.0" + p-limit "^3.1.0" + +jest-circus@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-29.7.0.tgz#b6817a45fcc835d8b16d5962d0c026473ee3668a" + integrity sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/expect" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + co "^4.6.0" + dedent "^1.0.0" + is-generator-fn "^2.0.0" + jest-each "^29.7.0" + jest-matcher-utils "^29.7.0" + jest-message-util "^29.7.0" + jest-runtime "^29.7.0" + jest-snapshot "^29.7.0" + jest-util "^29.7.0" + p-limit "^3.1.0" + pretty-format "^29.7.0" + pure-rand "^6.0.0" + slash "^3.0.0" + stack-utils "^2.0.3" + +jest-cli@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-29.7.0.tgz#5592c940798e0cae677eec169264f2d839a37995" + integrity sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg== + dependencies: + "@jest/core" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/types" "^29.6.3" + chalk "^4.0.0" + create-jest "^29.7.0" + exit "^0.1.2" + import-local "^3.0.2" + jest-config "^29.7.0" + jest-util "^29.7.0" + jest-validate "^29.7.0" + yargs "^17.3.1" + +jest-config@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-29.7.0.tgz#bcbda8806dbcc01b1e316a46bb74085a84b0245f" + integrity sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ== + dependencies: + "@babel/core" "^7.11.6" + "@jest/test-sequencer" "^29.7.0" + "@jest/types" "^29.6.3" + babel-jest "^29.7.0" + chalk "^4.0.0" + ci-info "^3.2.0" + deepmerge "^4.2.2" + glob "^7.1.3" + graceful-fs "^4.2.9" + jest-circus "^29.7.0" + 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" + jest-util "^29.7.0" + jest-validate "^29.7.0" + micromatch "^4.0.4" + parse-json "^5.2.0" + pretty-format "^29.7.0" + slash "^3.0.0" + strip-json-comments "^3.1.1" + +jest-diff@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.7.0.tgz#017934a66ebb7ecf6f205e84699be10afd70458a" + integrity sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw== + dependencies: + chalk "^4.0.0" + diff-sequences "^29.6.3" + jest-get-type "^29.6.3" + pretty-format "^29.7.0" + +jest-docblock@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-29.7.0.tgz#8fddb6adc3cdc955c93e2a87f61cfd350d5d119a" + integrity sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g== + dependencies: + detect-newline "^3.0.0" + +jest-each@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-29.7.0.tgz#162a9b3f2328bdd991beaabffbb74745e56577d1" + integrity sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ== + dependencies: + "@jest/types" "^29.6.3" + chalk "^4.0.0" + jest-get-type "^29.6.3" + jest-util "^29.7.0" + pretty-format "^29.7.0" + +jest-environment-node@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-29.7.0.tgz#0b93e111dda8ec120bc8300e6d1fb9576e164376" + integrity sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/fake-timers" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + jest-mock "^29.7.0" + jest-util "^29.7.0" + +jest-get-type@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.6.3.tgz#36f499fdcea197c1045a127319c0481723908fd1" + integrity sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw== + +jest-haste-map@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-29.7.0.tgz#3c2396524482f5a0506376e6c858c3bbcc17b104" + integrity sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA== + dependencies: + "@jest/types" "^29.6.3" + "@types/graceful-fs" "^4.1.3" + "@types/node" "*" + anymatch "^3.0.3" + fb-watchman "^2.0.0" + graceful-fs "^4.2.9" + jest-regex-util "^29.6.3" + jest-util "^29.7.0" + jest-worker "^29.7.0" + micromatch "^4.0.4" + walker "^1.0.8" + optionalDependencies: + fsevents "^2.3.2" + +jest-leak-detector@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz#5b7ec0dadfdfec0ca383dc9aa016d36b5ea4c728" + integrity sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw== + dependencies: + jest-get-type "^29.6.3" + pretty-format "^29.7.0" + +jest-matcher-utils@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz#ae8fec79ff249fd592ce80e3ee474e83a6c44f12" + integrity sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g== + dependencies: + chalk "^4.0.0" + jest-diff "^29.7.0" + jest-get-type "^29.6.3" + pretty-format "^29.7.0" + +jest-message-util@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.7.0.tgz#8bc392e204e95dfe7564abbe72a404e28e51f7f3" + integrity sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w== + dependencies: + "@babel/code-frame" "^7.12.13" + "@jest/types" "^29.6.3" + "@types/stack-utils" "^2.0.0" + chalk "^4.0.0" + graceful-fs "^4.2.9" + micromatch "^4.0.4" + pretty-format "^29.7.0" + slash "^3.0.0" + stack-utils "^2.0.3" + +jest-mock@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.7.0.tgz#4e836cf60e99c6fcfabe9f99d017f3fdd50a6347" + integrity sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw== + dependencies: + "@jest/types" "^29.6.3" + "@types/node" "*" + jest-util "^29.7.0" + +jest-pnp-resolver@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz#930b1546164d4ad5937d5540e711d4d38d4cad2e" + integrity sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w== + +jest-regex-util@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-29.6.3.tgz#4a556d9c776af68e1c5f48194f4d0327d24e8a52" + integrity sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg== + +jest-resolve-dependencies@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz#1b04f2c095f37fc776ff40803dc92921b1e88428" + integrity sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA== + dependencies: + jest-regex-util "^29.6.3" + jest-snapshot "^29.7.0" + +jest-resolve@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-29.7.0.tgz#64d6a8992dd26f635ab0c01e5eef4399c6bcbc30" + integrity sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA== + dependencies: + chalk "^4.0.0" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + jest-pnp-resolver "^1.2.2" + jest-util "^29.7.0" + jest-validate "^29.7.0" + resolve "^1.20.0" + resolve.exports "^2.0.0" + slash "^3.0.0" + +jest-runner@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-29.7.0.tgz#809af072d408a53dcfd2e849a4c976d3132f718e" + integrity sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ== + dependencies: + "@jest/console" "^29.7.0" + "@jest/environment" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + emittery "^0.13.1" + graceful-fs "^4.2.9" + 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" + 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" + +jest-runtime@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-29.7.0.tgz#efecb3141cf7d3767a3a0cc8f7c9990587d3d817" + integrity sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/fake-timers" "^29.7.0" + "@jest/globals" "^29.7.0" + "@jest/source-map" "^29.6.3" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + cjs-module-lexer "^1.0.0" + collect-v8-coverage "^1.0.0" + glob "^7.1.3" + graceful-fs "^4.2.9" + 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" + jest-util "^29.7.0" + slash "^3.0.0" + strip-bom "^4.0.0" + +jest-snapshot@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-29.7.0.tgz#c2c574c3f51865da1bb329036778a69bf88a6be5" + integrity sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw== + dependencies: + "@babel/core" "^7.11.6" + "@babel/generator" "^7.7.2" + "@babel/plugin-syntax-jsx" "^7.7.2" + "@babel/plugin-syntax-typescript" "^7.7.2" + "@babel/types" "^7.3.3" + "@jest/expect-utils" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + babel-preset-current-node-syntax "^1.0.0" + chalk "^4.0.0" + expect "^29.7.0" + graceful-fs "^4.2.9" + 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.5.3" + +jest-util@^29.0.0, jest-util@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.7.0.tgz#23c2b62bfb22be82b44de98055802ff3710fc0bc" + integrity sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA== + dependencies: + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + ci-info "^3.2.0" + graceful-fs "^4.2.9" + picomatch "^2.2.3" + +jest-validate@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-29.7.0.tgz#7bf705511c64da591d46b15fce41400d52147d9c" + integrity sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw== + dependencies: + "@jest/types" "^29.6.3" + camelcase "^6.2.0" + chalk "^4.0.0" + jest-get-type "^29.6.3" + leven "^3.1.0" + pretty-format "^29.7.0" + +jest-watcher@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-29.7.0.tgz#7810d30d619c3a62093223ce6bb359ca1b28a2f2" + integrity sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g== + dependencies: + "@jest/test-result" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + emittery "^0.13.1" + jest-util "^29.7.0" + string-length "^4.0.1" + +jest-worker@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.7.0.tgz#acad073acbbaeb7262bd5389e1bcf43e10058d4a" + integrity sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw== + dependencies: + "@types/node" "*" + jest-util "^29.7.0" + merge-stream "^2.0.0" + supports-color "^8.0.0" + +jest@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest/-/jest-29.7.0.tgz#994676fc24177f088f1c5e3737f5697204ff2613" + integrity sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw== + dependencies: + "@jest/core" "^29.7.0" + "@jest/types" "^29.6.3" + import-local "^3.0.2" + jest-cli "^29.7.0" + +jit-grunt@^0.10.0: + version "0.10.0" + resolved "https://registry.yarnpkg.com/jit-grunt/-/jit-grunt-0.10.0.tgz#008c3a7fe1e96bd0d84e260ea1fa1783457f79c2" + integrity sha512-eT/f4c9wgZ3buXB7X1JY1w6uNtAV0bhrbOGf/mFmBb0CDNLUETJ/VRoydayWOI54tOoam0cz9RooVCn3QY1WoA== + +jmespath@0.16.0: + version "0.16.0" + resolved "https://registry.yarnpkg.com/jmespath/-/jmespath-0.16.0.tgz#b15b0a85dfd4d930d43e69ed605943c802785076" + integrity sha512-9FzQjJ7MATs1tSpnco1K6ayiYE3figslrXA72G2HQ/n76RzvYlofyi5QM+iX4YRs/pu3yzxlVQSST23+dMDknw== + +"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-yaml@^3.13.1, js-yaml@^3.14.1, js-yaml@~3.14.0: + version "3.14.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" + integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +js-yaml@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + +jsesc@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.0.2.tgz#bb8b09a6597ba426425f2e4a07245c3d00b9343e" + integrity sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g== + +json-buffer@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" + integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== + +json-parse-even-better-errors@^2.3.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" + integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== + +json5@^2.2.3: + version "2.2.3" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" + integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== + +just-extend@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/just-extend/-/just-extend-6.2.0.tgz#b816abfb3d67ee860482e7401564672558163947" + integrity sha512-cYofQu2Xpom82S6qD778jBDpwvvy39s1l/hrYij2u9AMdQcGRpaBu6kY4mVhuno5kJVi1DAz4aiphA2WI1/OAw== + +keyv@^4.5.4: + version "4.5.4" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" + integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== + dependencies: + json-buffer "3.0.1" + +kind-of@^6.0.2: + version "6.0.3" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" + integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== + +kleur@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" + integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== + +kuler@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/kuler/-/kuler-2.0.0.tgz#e2c570a3800388fb44407e851531c1d670b061b3" + integrity sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A== + +lambda-local@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/lambda-local/-/lambda-local-2.2.0.tgz#733d183a4c3f2b16c6499b9ea72cec2f13278eef" + integrity sha512-bPcgpIXbHnVGfI/omZIlgucDqlf4LrsunwoKue5JdZeGybt8L6KyJz2Zu19ffuZwIwLj2NAI2ZyaqNT6/cetcg== + dependencies: + commander "^10.0.1" + dotenv "^16.3.1" + winston "^3.10.0" + +leven@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" + integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== + +levn@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" + integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== + dependencies: + prelude-ls "^1.2.1" + type-check "~0.4.0" + +liftup@~3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/liftup/-/liftup-3.0.1.tgz#1cb81aff0f368464ed3a5f1a7286372d6b1a60ce" + integrity sha512-yRHaiQDizWSzoXk3APcA71eOI/UuhEkNN9DiW2Tt44mhYzX4joFoCZlxsSOF7RyeLlfqzFLQI1ngFq3ggMPhOw== + dependencies: + extend "^3.0.2" + findup-sync "^4.0.0" + fined "^1.2.0" + flagged-respawn "^1.0.1" + is-plain-object "^2.0.4" + object.map "^1.0.1" + rechoir "^0.7.0" + resolve "^1.19.0" + +lines-and-columns@^1.1.6: + version "1.2.4" + resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" + integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== + +livereload-js@^2.3.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/livereload-js/-/livereload-js-2.4.0.tgz#447c31cf1ea9ab52fc20db615c5ddf678f78009c" + integrity sha512-XPQH8Z2GDP/Hwz2PCDrh2mth4yFejwA1OZ/81Ti3LgKyhDcEjsSsqFWZojHG0va/duGd+WyosY7eXLDoOyqcPw== + +locate-path@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" + integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== + dependencies: + p-locate "^4.1.0" + +locate-path@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" + integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== + dependencies: + p-locate "^5.0.0" + +lodash-checkit@^2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/lodash-checkit/-/lodash-checkit-2.4.1.tgz#8b09c6b359a5d4de86f752ff9c231f1db5d23fd4" + integrity sha512-OAg5CqY04/dnsO8izxXqlleuj7z/dOk6yV0pm0TVtRaUwG5v2PGw4XWSIG/dLK0UWYk7g0/TCk8OCf50oVwv6w== + dependencies: + checkit "^0.7.0" + lodash "^4.17.21" + +lodash-match-pattern@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/lodash-match-pattern/-/lodash-match-pattern-2.3.1.tgz#d38f455a8b310bd91f7b2b4378297102a9b473c8" + integrity sha512-dpltpxoTqs94gGFm24VwHDyFh3/eNtqNjKrlnifIBLtnzYq0nAlNM6BIeLdGAfCWC/BwNtiLL1eKZTQpLVnY6A== + dependencies: + chalk "^4.1.0" + he "^1.2.0" + lodash-checkit "^2.4.1" + +lodash.get@^4.4.2: + version "4.4.2" + resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" + integrity sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ== + +lodash.memoize@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" + integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== + +lodash.merge@^4.6.2: + version "4.6.2" + resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" + integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== + +lodash@^4.0.0, lodash@^4.17.10, lodash@^4.17.14, lodash@^4.17.21, lodash@~4.17.19, lodash@~4.17.21: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + +log-symbols@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" + integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== + dependencies: + chalk "^4.1.0" + is-unicode-supported "^0.1.0" + +logform@^2.7.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/logform/-/logform-2.7.0.tgz#cfca97528ef290f2e125a08396805002b2d060d1" + integrity sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ== + dependencies: + "@colors/colors" "1.6.0" + "@types/triple-beam" "^1.3.2" + fecha "^4.2.0" + ms "^2.1.1" + safe-stable-stringify "^2.3.1" + triple-beam "^1.3.0" + +loose-envify@^1.1.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" + integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== + dependencies: + js-tokens "^3.0.0 || ^4.0.0" + +loupe@^2.3.6: + version "2.3.7" + resolved "https://registry.yarnpkg.com/loupe/-/loupe-2.3.7.tgz#6e69b7d4db7d3ab436328013d37d1c8c3540c697" + integrity sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA== + dependencies: + get-func-name "^2.0.1" + +loupe@^3.1.0, loupe@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/loupe/-/loupe-3.1.2.tgz#c86e0696804a02218f2206124c45d8b15291a240" + integrity sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg== + +lru-cache@^10.2.0: + version "10.4.3" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119" + integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ== + +lru-cache@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" + integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== + dependencies: + yallist "^3.0.2" + +make-dir@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-4.0.0.tgz#c3c2307a771277cd9638305f915c29ae741b614e" + integrity sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw== + dependencies: + semver "^7.5.3" + +make-error@^1.1.1, make-error@^1.3.6: + version "1.3.6" + resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" + integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== + +make-iterator@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/make-iterator/-/make-iterator-1.0.1.tgz#29b33f312aa8f547c4a5e490f56afcec99133ad6" + integrity sha512-pxiuXh0iVEq7VM7KMIhs5gxsfxCux2URptUQaXo4iZZJxBAzTPOLE2BumO5dbfVYq/hBJFBR/a1mFDmOx5AGmw== + dependencies: + kind-of "^6.0.2" + +makeerror@1.0.12: + version "1.0.12" + resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.12.tgz#3e5dd2079a82e812e983cc6610c4a2cb0eaa801a" + integrity sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg== + dependencies: + tmpl "1.0.5" + +map-cache@^0.2.0: + version "0.2.2" + resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" + integrity sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg== + +markdown-parser-react@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/markdown-parser-react/-/markdown-parser-react-1.1.2.tgz#5817a708ea1edc33579f436346cba866d58d4792" + integrity sha512-MNLHekU1xOwKZLJK4NMWJDL9pNnJdKx2jdsHfAF4+Y5rF4tD/S/OuNehd4X46/KcJzBfas19pePVcwQoibpeNg== + dependencies: + react "^18.2.0" + +merge-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" + integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== + +merge2@^1.3.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" + integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== + +micromatch@^4.0.2, micromatch@^4.0.4: + version "4.0.8" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" + integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== + dependencies: + braces "^3.0.3" + picomatch "^2.3.1" + +mimic-fn@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" + integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + +minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +minimatch@^5.0.1, minimatch@^5.1.6: + version "5.1.6" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96" + integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== + dependencies: + brace-expansion "^2.0.1" + +minimatch@^9.0.4: + version "9.0.5" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5" + integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow== + dependencies: + brace-expansion "^2.0.1" + +minimatch@~3.0.2, minimatch@~3.0.4: + version "3.0.8" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.8.tgz#5e6a59bd11e2ab0de1cfb843eb2d82e546c321c1" + integrity sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q== + dependencies: + brace-expansion "^1.1.7" + +"minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.1.2: + version "7.1.2" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.2.tgz#93a9626ce5e5e66bd4db86849e7515e92340a707" + integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw== + +mnemonist@0.38.3: + version "0.38.3" + resolved "https://registry.yarnpkg.com/mnemonist/-/mnemonist-0.38.3.tgz#35ec79c1c1f4357cfda2fe264659c2775ccd7d9d" + integrity sha512-2K9QYubXx/NAjv4VLq1d1Ly8pWNC5L3BrixtdkyTegXWJIqY+zLNDhhX/A+ZwWt70tB1S8H4BE8FLYEFyNoOBw== + dependencies: + obliterator "^1.6.1" + +mocha@^11.0.0: + version "11.0.1" + resolved "https://registry.yarnpkg.com/mocha/-/mocha-11.0.1.tgz#85c1c0e806275fe2479245be4ac4a0d81f533aa8" + integrity sha512-+3GkODfsDG71KSCQhc4IekSW+ItCK/kiez1Z28ksWvYhKXV/syxMlerR/sC7whDp7IyreZ4YxceMLdTs5hQE8A== + dependencies: + ansi-colors "^4.1.3" + browser-stdout "^1.3.1" + chokidar "^3.5.3" + debug "^4.3.5" + diff "^5.2.0" + escape-string-regexp "^4.0.0" + find-up "^5.0.0" + glob "^10.4.5" + he "^1.2.0" + js-yaml "^4.1.0" + log-symbols "^4.1.0" + minimatch "^5.1.6" + ms "^2.1.3" + serialize-javascript "^6.0.2" + strip-json-comments "^3.1.1" + supports-color "^8.1.1" + workerpool "^6.5.1" + yargs "^16.2.0" + yargs-parser "^20.2.9" + yargs-unparser "^2.0.0" + +ms@^2.1.1, ms@^2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== + +nise@^6.0.0: + version "6.1.1" + resolved "https://registry.yarnpkg.com/nise/-/nise-6.1.1.tgz#78ea93cc49be122e44cb7c8fdf597b0e8778b64a" + integrity sha512-aMSAzLVY7LyeM60gvBS423nBmIPP+Wy7St7hsb+8/fc1HmeoHJfLO8CKse4u3BtOZvQLJghYPI2i/1WZrEj5/g== + dependencies: + "@sinonjs/commons" "^3.0.1" + "@sinonjs/fake-timers" "^13.0.1" + "@sinonjs/text-encoding" "^0.7.3" + just-extend "^6.2.0" + path-to-regexp "^8.1.0" + +node-int64@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" + integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw== + +node-releases@^2.0.18: + version "2.0.18" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.18.tgz#f010e8d35e2fe8d6b2944f03f70213ecedc4ca3f" + integrity sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g== + +nodemailer@^6.9.12: + version "6.9.16" + resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-6.9.16.tgz#3ebdf6c6f477c571c0facb0727b33892635e0b8b" + integrity sha512-psAuZdTIRN08HKVd/E8ObdV6NO7NTBY3KsC30F7M4H1OnmLCUNaS56FpYxyb26zWLSyYF9Ozch9KYHhHegsiOQ== + +nopt@~3.0.6: + version "3.0.6" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9" + integrity sha512-4GUt3kSEYmk4ITxzB/b9vaIDfUVWN/Ml1Fwl11IlnIG2iaJ9O6WXZ9SrYM9NLI8OCBieN2Y8SWC2oJV0RQ7qYg== + dependencies: + abbrev "1" + +nopt@~4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.3.tgz#a375cad9d02fd921278d954c2254d5aa57e15e48" + integrity sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg== + dependencies: + abbrev "1" + osenv "^0.1.4" + +normalize-path@^3.0.0, normalize-path@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +npm-run-path@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" + integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== + dependencies: + path-key "^3.0.0" + +object-assign@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== + +object-inspect@^1.13.1: + version "1.13.3" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.3.tgz#f14c183de51130243d6d18ae149375ff50ea488a" + integrity sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA== + +object.defaults@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/object.defaults/-/object.defaults-1.1.0.tgz#3a7f868334b407dea06da16d88d5cd29e435fecf" + integrity sha512-c/K0mw/F11k4dEUBMW8naXUuBuhxRCfG7W+yFy8EcijU/rSmazOUd1XAEEe6bC0OuXY4HUKjTJv7xbxIMqdxrA== + dependencies: + array-each "^1.0.1" + array-slice "^1.0.0" + for-own "^1.0.0" + isobject "^3.0.0" + +object.map@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/object.map/-/object.map-1.0.1.tgz#cf83e59dc8fcc0ad5f4250e1f78b3b81bd801d37" + integrity sha512-3+mAJu2PLfnSVGHwIWubpOFLscJANBKuB/6A4CxBstc4aqwQY0FWcsppuy4jU5GSB95yES5JHSI+33AWuS4k6w== + dependencies: + for-own "^1.0.0" + make-iterator "^1.0.0" + +object.pick@^1.2.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747" + integrity sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ== + dependencies: + isobject "^3.0.1" + +obliterator@^1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/obliterator/-/obliterator-1.6.1.tgz#dea03e8ab821f6c4d96a299e17aef6a3af994ef3" + integrity sha512-9WXswnqINnnhOG/5SLimUlzuU1hFJUc8zkwyD59Sd+dPOMf05PmnYG/d6Q7HZ+KmgkZJa1PxRso6QdM3sTNHig== + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + +one-time@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/one-time/-/one-time-1.0.0.tgz#e06bc174aed214ed58edede573b433bbf827cb45" + integrity sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g== + dependencies: + fn.name "1.x.x" + +onetime@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" + integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== + dependencies: + mimic-fn "^2.1.0" + +optionator@^0.9.3: + version "0.9.4" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.4.tgz#7ea1c1a5d91d764fb282139c88fe11e182a3a734" + integrity sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g== + dependencies: + deep-is "^0.1.3" + fast-levenshtein "^2.0.6" + levn "^0.4.1" + prelude-ls "^1.2.1" + type-check "^0.4.0" + word-wrap "^1.2.5" + +os-homedir@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" + integrity sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ== + +os-tmpdir@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" + integrity sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g== + +osenv@^0.1.4: + version "0.1.5" + resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.5.tgz#85cdfafaeb28e8677f416e287592b5f3f49ea410" + integrity sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g== + dependencies: + os-homedir "^1.0.0" + os-tmpdir "^1.0.0" + +p-limit@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" + integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== + dependencies: + p-try "^2.0.0" + +p-limit@^3.0.2, p-limit@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" + integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== + dependencies: + yocto-queue "^0.1.0" + +p-locate@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" + integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== + dependencies: + p-limit "^2.2.0" + +p-locate@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" + integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== + dependencies: + p-limit "^3.0.2" + +p-try@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== + +package-json-from-dist@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz#4f1471a010827a86f94cfd9b0727e36d267de505" + integrity sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw== + +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + dependencies: + callsites "^3.0.0" + +parse-filepath@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/parse-filepath/-/parse-filepath-1.0.2.tgz#a632127f53aaf3d15876f5872f3ffac763d6c891" + integrity sha512-FwdRXKCohSVeXqwtYonZTXtbGJKrn+HNyWDYVcp5yuJlesTwNH4rsmRZ+GrKAPJ5bLpRxESMeS+Rl0VCHRvB2Q== + dependencies: + is-absolute "^1.0.0" + map-cache "^0.2.0" + path-root "^0.1.1" + +parse-json@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" + integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== + dependencies: + "@babel/code-frame" "^7.0.0" + error-ex "^1.3.1" + json-parse-even-better-errors "^2.3.0" + lines-and-columns "^1.1.6" + +parse-passwd@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6" + integrity sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q== + +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== + +path-key@^3.0.0, path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +path-parse@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + +path-root-regex@^0.1.0: + version "0.1.2" + resolved "https://registry.yarnpkg.com/path-root-regex/-/path-root-regex-0.1.2.tgz#bfccdc8df5b12dc52c8b43ec38d18d72c04ba96d" + integrity sha512-4GlJ6rZDhQZFE0DPVKh0e9jmZ5egZfxTkp7bcRDuPlJXbAwhxcl2dINPUAsjLdejqaLsCeg8axcLjIbvBjN4pQ== + +path-root@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/path-root/-/path-root-0.1.1.tgz#9a4a6814cac1c0cd73360a95f32083c8ea4745b7" + integrity sha512-QLcPegTHF11axjfojBIoDygmS2E3Lf+8+jI6wOVmNVenrKSo3mFdSGiIgdSHenczw3wPtlVMQaFVwGmM7BJdtg== + dependencies: + path-root-regex "^0.1.0" + +path-scurry@^1.11.1: + version "1.11.1" + resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.11.1.tgz#7960a668888594a0720b12a911d1a742ab9f11d2" + integrity sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA== + dependencies: + lru-cache "^10.2.0" + minipass "^5.0.0 || ^6.0.2 || ^7.0.0" + +path-to-regexp@^8.1.0: + version "8.2.0" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-8.2.0.tgz#73990cc29e57a3ff2a0d914095156df5db79e8b4" + integrity sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ== + +pathval@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.1.tgz#8534e77a77ce7ac5a2512ea21e0fdb8fcf6c3d8d" + integrity sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ== + +pathval@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/pathval/-/pathval-2.0.0.tgz#7e2550b422601d4f6b8e26f1301bc8f15a741a25" + integrity sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA== + +picocolors@^1.0.0, picocolors@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" + integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== + +picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3, picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +pirates@^4.0.4: + version "4.0.6" + resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.6.tgz#3018ae32ecfcff6c29ba2267cbf21166ac1f36b9" + integrity sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg== + +pkg-dir@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" + integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== + dependencies: + find-up "^4.0.0" + +possible-typed-array-names@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz#89bb63c6fada2c3e90adc4a647beeeb39cc7bf8f" + integrity sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q== + +prelude-ls@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" + integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== + +pretty-format@^29.0.0, pretty-format@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.7.0.tgz#ca42c758310f365bfa71a0bda0a807160b776812" + integrity sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ== + dependencies: + "@jest/schemas" "^29.6.3" + ansi-styles "^5.0.0" + react-is "^18.0.0" + +prompts@^2.0.1: + version "2.4.2" + resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" + integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q== + dependencies: + kleur "^3.0.3" + sisteransi "^1.0.5" + +punycode@1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" + integrity sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw== + +punycode@^2.1.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" + integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== + +pure-rand@^6.0.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/pure-rand/-/pure-rand-6.1.0.tgz#d173cf23258231976ccbdb05247c9787957604f2" + integrity sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA== + +qs@^6.4.0: + version "6.13.1" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.13.1.tgz#3ce5fc72bd3a8171b85c99b93c65dd20b7d1b16e" + integrity sha512-EJPeIn0CYrGu+hli1xilKAPXODtJ12T0sP63Ijx2/khC2JtuaN3JyNIpvmnkmaEtha9ocbG4A4cMcr+TvqvwQg== + dependencies: + side-channel "^1.0.6" + +querystring@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" + integrity sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g== + +queue-microtask@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" + integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== + +randombytes@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" + integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== + dependencies: + safe-buffer "^5.1.0" + +raw-body@~1.1.0: + version "1.1.7" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-1.1.7.tgz#1d027c2bfa116acc6623bca8f00016572a87d425" + integrity sha512-WmJJU2e9Y6M5UzTOkHaM7xJGAPQD8PNzx3bAd2+uhZAim6wDk6dAZxPVYLF67XhbR4hmKGh33Lpmh4XWrCH5Mg== + dependencies: + bytes "1" + string_decoder "0.10" + +react-dom@^18.3.1: + version "18.3.1" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.3.1.tgz#c2265d79511b57d479b3dd3fdfa51536494c5cb4" + integrity sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw== + dependencies: + loose-envify "^1.1.0" + scheduler "^0.23.2" + +react-is@^18.0.0: + version "18.3.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e" + integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg== + +react@^18.2.0, react@^18.3.1: + version "18.3.1" + resolved "https://registry.yarnpkg.com/react/-/react-18.3.1.tgz#49ab892009c53933625bd16b2533fc754cab2891" + integrity sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ== + dependencies: + loose-envify "^1.1.0" + +readable-stream@^3.4.0, readable-stream@^3.6.2: + version "3.6.2" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" + integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + +readdirp@~3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" + integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== + dependencies: + picomatch "^2.2.1" + +rechoir@^0.7.0: + version "0.7.1" + resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.7.1.tgz#9478a96a1ca135b5e88fc027f03ee92d6c645686" + integrity sha512-/njmZ8s1wVeR6pjTZ+0nCnv8SpZNRMT2D1RLOJQESlYFDBvwpTA4KWJpZ+sBJ4+vhjILRcK7JIFdGCdxEAAitg== + dependencies: + resolve "^1.9.0" + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== + +resolve-cwd@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" + integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== + dependencies: + resolve-from "^5.0.0" + +resolve-dir@^1.0.0, resolve-dir@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/resolve-dir/-/resolve-dir-1.0.1.tgz#79a40644c362be82f26effe739c9bb5382046f43" + integrity sha512-R7uiTjECzvOsWSfdM0QKFNBVFcK27aHOUwdvK53BcW8zqnGdYp0Fbj82cy54+2A4P2tFM22J5kRfe1R+lM/1yg== + dependencies: + expand-tilde "^2.0.0" + global-modules "^1.0.0" + +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + +resolve-from@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" + integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== + +resolve-pkg-maps@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz#616b3dc2c57056b5588c31cdf4b3d64db133720f" + integrity sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw== + +resolve.exports@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-2.0.3.tgz#41955e6f1b4013b7586f873749a635dea07ebe3f" + integrity sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A== + +resolve@^1.19.0, resolve@^1.20.0, resolve@^1.9.0: + version "1.22.8" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d" + integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== + dependencies: + is-core-module "^2.13.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +reusify@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" + integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== + +run-parallel@^1.1.9: + version "1.2.0" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" + integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== + dependencies: + queue-microtask "^1.2.2" + +safe-buffer@>=5.1.0, safe-buffer@^5.1.0, safe-buffer@~5.2.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +safe-json-parse@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/safe-json-parse/-/safe-json-parse-1.0.1.tgz#3e76723e38dfdda13c9b1d29a1e07ffee4b30b57" + integrity sha512-o0JmTu17WGUaUOHa1l0FPGXKBfijbxK6qoHzlkihsDXxzBHvJcA7zgviKR92Xs841rX9pK16unfphLq0/KqX7A== + +safe-stable-stringify@^2.3.1: + version "2.5.0" + resolved "https://registry.yarnpkg.com/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz#4ca2f8e385f2831c432a719b108a3bf7af42a1dd" + integrity sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA== + +"safer-buffer@>= 2.1.2 < 3.0.0": + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +sax@1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.1.tgz#7b8e656190b228e81a66aea748480d828cd2d37a" + integrity sha512-8I2a3LovHTOpm7NV5yOyO8IHqgVsfK4+UuySrXU8YXkSRX7k6hCV9b3HrkKCr3nMpgj+0bmocaJJWpvp1oc7ZA== + +sax@>=0.6.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.4.1.tgz#44cc8988377f126304d3b3fc1010c733b929ef0f" + integrity sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg== + +scheduler@^0.23.2: + version "0.23.2" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.2.tgz#414ba64a3b282892e944cf2108ecc078d115cdc3" + integrity sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ== + dependencies: + loose-envify "^1.1.0" + +semver@^6.3.0, semver@^6.3.1: + version "6.3.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" + integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== + +semver@^7.5.3, semver@^7.5.4, semver@^7.6.0, semver@^7.6.3: + version "7.6.3" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143" + integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== + +serialize-javascript@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.2.tgz#defa1e055c83bf6d59ea805d8da862254eb6a6c2" + integrity sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g== + dependencies: + randombytes "^2.1.0" + +set-function-length@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449" + integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg== + dependencies: + define-data-property "^1.1.4" + es-errors "^1.3.0" + function-bind "^1.1.2" + get-intrinsic "^1.2.4" + gopd "^1.0.1" + has-property-descriptors "^1.0.2" + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +side-channel@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.6.tgz#abd25fb7cd24baf45466406b1096b7831c9215f2" + integrity sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA== + dependencies: + call-bind "^1.0.7" + es-errors "^1.3.0" + get-intrinsic "^1.2.4" + object-inspect "^1.13.1" + +signal-exit@^3.0.3, signal-exit@^3.0.7: + version "3.0.7" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" + integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== + +signal-exit@^4.0.1: + version "4.1.0" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" + integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== + +simple-swizzle@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" + integrity sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg== + dependencies: + is-arrayish "^0.3.1" + +sinon@^18.0.1: + version "18.0.1" + resolved "https://registry.yarnpkg.com/sinon/-/sinon-18.0.1.tgz#464334cdfea2cddc5eda9a4ea7e2e3f0c7a91c5e" + integrity sha512-a2N2TDY1uGviajJ6r4D1CyRAkzE9NNVlYOV1wX5xQDuAk0ONgzgRl0EjCQuRCPxOwp13ghsMwt9Gdldujs39qw== + dependencies: + "@sinonjs/commons" "^3.0.1" + "@sinonjs/fake-timers" "11.2.2" + "@sinonjs/samsam" "^8.0.0" + diff "^5.2.0" + nise "^6.0.0" + supports-color "^7" + +sisteransi@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" + integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== + +slash@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" + integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== + +source-map-support@0.5.13: + version "0.5.13" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.13.tgz#31b24a9c2e73c2de85066c0feb7d44767ed52932" + integrity sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map@^0.6.0, source-map@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +sprintf-js@^1.1.1: + version "1.1.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.3.tgz#4914b903a2f8b685d17fdf78a70e917e872e444a" + integrity sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA== + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== + +stack-trace@0.0.x: + version "0.0.10" + resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0" + integrity sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg== + +stack-utils@^2.0.3: + version "2.0.6" + resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.6.tgz#aaf0748169c02fc33c8232abccf933f54a1cc34f" + integrity sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ== + dependencies: + escape-string-regexp "^2.0.0" + +string-length@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a" + integrity sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ== + dependencies: + char-regex "^1.0.2" + strip-ansi "^6.0.0" + +string-template@~0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/string-template/-/string-template-0.2.1.tgz#42932e598a352d01fc22ec3367d9d84eec6c9add" + integrity sha512-Yptehjogou2xm4UJbxJ4CxgZx12HBfeystp0y3x7s4Dj32ltVVG1Gg8YhKjHZkHicuKpZX/ffilA8505VbUbpw== + +"string-width-cjs@npm:string-width@^4.2.0": + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string-width@^5.0.1, string-width@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" + integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA== + dependencies: + eastasianwidth "^0.2.0" + emoji-regex "^9.2.2" + strip-ansi "^7.0.1" + +string_decoder@0.10: + version "0.10.31" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" + integrity sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ== + +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@^7.0.1: + version "7.1.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" + integrity sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ== + dependencies: + ansi-regex "^6.0.1" + +strip-bom@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" + integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== + +strip-final-newline@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" + integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== + +strip-json-comments@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + +strnum@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/strnum/-/strnum-1.0.5.tgz#5c4e829fe15ad4ff0d20c3db5ac97b73c9b072db" + integrity sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA== + +supports-color@^7, supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +supports-color@^8.0.0, supports-color@^8.1.1: + version "8.1.1" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== + dependencies: + has-flag "^4.0.0" + +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + +test-exclude@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" + integrity sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w== + dependencies: + "@istanbuljs/schema" "^0.1.2" + glob "^7.1.4" + minimatch "^3.0.4" + +text-hex@1.0.x: + version "1.0.0" + resolved "https://registry.yarnpkg.com/text-hex/-/text-hex-1.0.0.tgz#69dc9c1b17446ee79a92bf5b884bb4b9127506f5" + integrity sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg== + +tiny-lr@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/tiny-lr/-/tiny-lr-1.1.1.tgz#9fa547412f238fedb068ee295af8b682c98b2aab" + integrity sha512-44yhA3tsaRoMOjQQ+5v5mVdqef+kH6Qze9jTpqtVufgYjYt08zyZAwNwwVBj3i1rJMnR52IxOW0LK0vBzgAkuA== + dependencies: + body "^5.1.0" + debug "^3.1.0" + faye-websocket "~0.10.0" + livereload-js "^2.3.0" + object-assign "^4.1.0" + qs "^6.4.0" + +tinyrainbow@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/tinyrainbow/-/tinyrainbow-1.2.0.tgz#5c57d2fc0fb3d1afd78465c33ca885d04f02abb5" + integrity sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ== + +tinyspy@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/tinyspy/-/tinyspy-3.0.2.tgz#86dd3cf3d737b15adcf17d7887c84a75201df20a" + integrity sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q== + +tmpl@1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" + integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw== + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +triple-beam@^1.3.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/triple-beam/-/triple-beam-1.4.1.tgz#6fde70271dc6e5d73ca0c3b24e2d92afb7441984" + integrity sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg== + +ts-api-utils@^1.3.0: + version "1.4.3" + resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.4.3.tgz#bfc2215fe6528fecab2b0fba570a2e8a4263b064" + integrity sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw== + +ts-jest@^29.2.5: + version "29.2.5" + resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.2.5.tgz#591a3c108e1f5ebd013d3152142cb5472b399d63" + integrity sha512-KD8zB2aAZrcKIdGk4OwpJggeLcH1FgrICqDSROWqlnJXGCXK4Mn6FcdK2B6670Xr73lHMG1kHw8R87A0ecZ+vA== + dependencies: + bs-logger "^0.2.6" + ejs "^3.1.10" + fast-json-stable-stringify "^2.1.0" + jest-util "^29.0.0" + json5 "^2.2.3" + lodash.memoize "^4.1.2" + make-error "^1.3.6" + semver "^7.6.3" + yargs-parser "^21.1.1" + +ts-node@^10.9.2: + version "10.9.2" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.2.tgz#70f021c9e185bccdca820e26dc413805c101c71f" + integrity sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ== + dependencies: + "@cspotcode/source-map-support" "^0.8.0" + "@tsconfig/node10" "^1.0.7" + "@tsconfig/node12" "^1.0.7" + "@tsconfig/node14" "^1.0.0" + "@tsconfig/node16" "^1.0.2" + acorn "^8.4.1" + acorn-walk "^8.1.1" + arg "^4.1.0" + create-require "^1.1.0" + diff "^4.0.1" + make-error "^1.1.1" + v8-compile-cache-lib "^3.0.1" + yn "3.1.1" + +tslib@^2.1.0, tslib@^2.6.2, tslib@^2.8.0: + version "2.8.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" + integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== + +tsx@^4.19.2: + version "4.19.2" + resolved "https://registry.yarnpkg.com/tsx/-/tsx-4.19.2.tgz#2d7814783440e0ae42354d0417d9c2989a2ae92c" + integrity sha512-pOUl6Vo2LUq/bSa8S5q7b91cgNSjctn9ugq/+Mvow99qW6x/UZYwzxy/3NmqoT66eHYfCVvFvACC58UBPFf28g== + dependencies: + esbuild "~0.23.0" + get-tsconfig "^4.7.5" + optionalDependencies: + fsevents "~2.3.3" + +type-check@^0.4.0, type-check@~0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" + integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== + dependencies: + prelude-ls "^1.2.1" + +type-detect@4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" + integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== + +type-detect@^4.0.0, type-detect@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.1.0.tgz#deb2453e8f08dcae7ae98c626b13dddb0155906c" + integrity sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw== + +type-fest@^0.21.3: + version "0.21.3" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" + integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== + +typescript@5.6.3: + version "5.6.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.6.3.tgz#5f3449e31c9d94febb17de03cc081dd56d81db5b" + integrity sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw== + +unc-path-regex@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/unc-path-regex/-/unc-path-regex-0.1.2.tgz#e73dd3d7b0d7c5ed86fbac6b0ae7d8c6a69d50fa" + integrity sha512-eXL4nmJT7oCpkZsHZUOJo8hcX3GbsiDOa0Qu9F646fi8dT3XuSVopVqAcEiVzSKKH7UoDti23wNX3qGFxcW5Qg== + +underscore.string@~3.3.5: + version "3.3.6" + resolved "https://registry.yarnpkg.com/underscore.string/-/underscore.string-3.3.6.tgz#ad8cf23d7423cb3b53b898476117588f4e2f9159" + integrity sha512-VoC83HWXmCrF6rgkyxS9GHv8W9Q5nhMKho+OadDJGzL2oDYbYEppBaCMH6pFlwLeqj2QS+hhkw2kpXkSdD1JxQ== + dependencies: + sprintf-js "^1.1.1" + util-deprecate "^1.0.2" + +undici-types@~6.19.2: + version "6.19.8" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.19.8.tgz#35111c9d1437ab83a7cdc0abae2f26d88eda0a02" + integrity sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw== + +undici-types@~6.20.0: + version "6.20.0" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.20.0.tgz#8171bf22c1f588d1554d55bf204bc624af388433" + integrity sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg== + +update-browserslist-db@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz#80846fba1d79e82547fb661f8d141e0945755fe5" + integrity sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A== + dependencies: + escalade "^3.2.0" + picocolors "^1.1.0" + +uri-js@^4.2.2: + version "4.4.1" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + +url@0.10.3: + version "0.10.3" + resolved "https://registry.yarnpkg.com/url/-/url-0.10.3.tgz#021e4d9c7705f21bbf37d03ceb58767402774c64" + integrity sha512-hzSUW2q06EqL1gKM/a+obYHLIO6ct2hwPuviqTTOcfFVc61UbfJ2Q32+uGL/HCPxKqrdGB5QUwIe7UqlDgwsOQ== + dependencies: + punycode "1.3.2" + querystring "0.2.0" + +util-deprecate@^1.0.1, util-deprecate@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== + +util@^0.12.4: + version "0.12.5" + resolved "https://registry.yarnpkg.com/util/-/util-0.12.5.tgz#5f17a6059b73db61a875668781a1c2b136bd6fbc" + integrity sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA== + dependencies: + inherits "^2.0.3" + is-arguments "^1.0.4" + is-generator-function "^1.0.7" + is-typed-array "^1.1.3" + which-typed-array "^1.1.2" + +uuid@8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.0.0.tgz#bc6ccf91b5ff0ac07bbcdbf1c7c4e150db4dbb6c" + integrity sha512-jOXGuXZAWdsTH7eZLtyXMqUb9EcWMGZNbL9YcGBJl4MH4nrxHmZJhEHvyLFrkxo+28uLb/NYRcStH48fnD0Vzw== + +uuid@^9.0.1: + version "9.0.1" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.1.tgz#e188d4c8853cc722220392c424cd637f32293f30" + integrity sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA== + +v8-compile-cache-lib@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" + integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== + +v8-to-istanbul@^9.0.1: + version "9.3.0" + resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz#b9572abfa62bd556c16d75fdebc1a411d5ff3175" + integrity sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA== + dependencies: + "@jridgewell/trace-mapping" "^0.3.12" + "@types/istanbul-lib-coverage" "^2.0.1" + convert-source-map "^2.0.0" + +v8flags@~3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/v8flags/-/v8flags-3.2.0.tgz#b243e3b4dfd731fa774e7492128109a0fe66d656" + integrity sha512-mH8etigqMfiGWdeXpaaqGfs6BndypxusHHcv2qSHyZkGEznCd/qAXCWWRzeowtL54147cktFOC4P5y+kl8d8Jg== + dependencies: + homedir-polyfill "^1.0.1" + +walker@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f" + integrity sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ== + dependencies: + makeerror "1.0.12" + +watchpack@^2.0.0-beta.10: + version "2.4.2" + resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.2.tgz#2feeaed67412e7c33184e5a79ca738fbd38564da" + integrity sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw== + dependencies: + glob-to-regexp "^0.4.1" + graceful-fs "^4.1.2" + +websocket-driver@>=0.5.1: + version "0.7.4" + resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.4.tgz#89ad5295bbf64b480abcba31e4953aca706f5760" + integrity sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg== + dependencies: + http-parser-js ">=0.5.1" + safe-buffer ">=5.1.0" + websocket-extensions ">=0.1.1" + +websocket-extensions@>=0.1.1: + version "0.1.4" + resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42" + integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg== + +which-typed-array@^1.1.14, which-typed-array@^1.1.2: + version "1.1.16" + resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.16.tgz#db4db429c4706feca2f01677a144278e4a8c216b" + integrity sha512-g+N+GAWiRj66DngFwHvISJd+ITsyphZvD1vChfVg6cEdnzy53GzB3oy0fUNlvhz7H7+MiqhYr26qxQShCpKTTQ== + dependencies: + available-typed-arrays "^1.0.7" + call-bind "^1.0.7" + for-each "^0.3.3" + gopd "^1.0.1" + has-tostringtag "^1.0.2" + +which@^1.2.14: + version "1.3.1" + resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" + integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== + dependencies: + isexe "^2.0.0" + +which@^2.0.1, which@~2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +winston-transport@^4.9.0: + version "4.9.0" + resolved "https://registry.yarnpkg.com/winston-transport/-/winston-transport-4.9.0.tgz#3bba345de10297654ea6f33519424560003b3bf9" + integrity sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A== + dependencies: + logform "^2.7.0" + readable-stream "^3.6.2" + triple-beam "^1.3.0" + +winston@^3.10.0: + version "3.17.0" + resolved "https://registry.yarnpkg.com/winston/-/winston-3.17.0.tgz#74b8665ce9b4ea7b29d0922cfccf852a08a11423" + integrity sha512-DLiFIXYC5fMPxaRg832S6F5mJYvePtmO5G9v9IgUFPhXm9/GkXarH/TUrBAVzhTCzAj9anE/+GjrgXp/54nOgw== + dependencies: + "@colors/colors" "^1.6.0" + "@dabh/diagnostics" "^2.0.2" + async "^3.2.3" + is-stream "^2.0.0" + logform "^2.7.0" + one-time "^1.0.0" + readable-stream "^3.4.0" + safe-stable-stringify "^2.3.1" + stack-trace "0.0.x" + triple-beam "^1.3.0" + winston-transport "^4.9.0" + +word-wrap@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" + integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== + +workerpool@^6.5.1: + version "6.5.1" + resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.5.1.tgz#060f73b39d0caf97c6db64da004cd01b4c099544" + integrity sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA== + +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrap-ansi@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" + integrity sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ== + dependencies: + ansi-styles "^6.1.0" + string-width "^5.0.1" + strip-ansi "^7.0.1" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + +write-file-atomic@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-4.0.2.tgz#a9df01ae5b77858a027fd2e80768ee433555fcfd" + integrity sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg== + dependencies: + imurmurhash "^0.1.4" + signal-exit "^3.0.7" + +xml2js@0.6.2: + version "0.6.2" + resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.6.2.tgz#dd0b630083aa09c161e25a4d0901e2b2a929b499" + integrity sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA== + dependencies: + sax ">=0.6.0" + xmlbuilder "~11.0.0" + +xmlbuilder@~11.0.0: + version "11.0.1" + resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-11.0.1.tgz#be9bae1c8a046e76b31127726347d0ad7002beb3" + integrity sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA== + +y18n@^5.0.5: + version "5.0.8" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== + +yallist@^3.0.2: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" + integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== + +yargs-parser@^20.2.2, yargs-parser@^20.2.9: + version "20.2.9" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" + integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== + +yargs-parser@^21.1.1: + version "21.1.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" + integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== + +yargs-unparser@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/yargs-unparser/-/yargs-unparser-2.0.0.tgz#f131f9226911ae5d9ad38c432fe809366c2325eb" + integrity sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA== + dependencies: + camelcase "^6.0.0" + decamelize "^4.0.0" + flat "^5.0.2" + is-plain-obj "^2.1.0" + +yargs@^16.2.0: + version "16.2.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" + integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== + dependencies: + cliui "^7.0.2" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.0" + y18n "^5.0.5" + yargs-parser "^20.2.2" + +yargs@^17.3.1: + version "17.7.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" + integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== + dependencies: + cliui "^8.0.1" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.3" + y18n "^5.0.5" + yargs-parser "^21.1.1" + +yn@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" + integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== + +yocto-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== + +zod@^3.23.8: + version "3.23.8" + resolved "https://registry.yarnpkg.com/zod/-/zod-3.23.8.tgz#e37b957b5d52079769fb8097099b592f0ef4067d" + integrity sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g== diff --git a/backend/compact-connect-ui-app/pipeline/__init__.py b/backend/compact-connect-ui-app/pipeline/__init__.py new file mode 100644 index 000000000..eacc3d6dc --- /dev/null +++ b/backend/compact-connect-ui-app/pipeline/__init__.py @@ -0,0 +1,399 @@ +import json + +from aws_cdk import Environment, RemovalPolicy +from aws_cdk.aws_iam import Effect, PolicyStatement +from aws_cdk.aws_kms import IKey, Key +from aws_cdk.aws_s3 import IBucket +from aws_cdk.aws_sns import ITopic +from aws_cdk.aws_ssm import StringParameter +from aws_cdk.pipelines import CodePipeline as CdkCodePipeline +from cdk_nag import NagSuppressions +from common_constructs.access_logs_bucket import AccessLogsBucket +from common_constructs.alarm_topic import AlarmTopic +from common_constructs.stack import Stack +from constructs import Construct + +from pipeline.frontend_pipeline import FrontendPipeline +from pipeline.frontend_stage import FrontendStage +from pipeline.synth_substitute_stage import SynthSubstituteStage + +TEST_ENVIRONMENT_NAME = 'test' +BETA_ENVIRONMENT_NAME = 'beta' +PROD_ENVIRONMENT_NAME = 'prod' + +ALLOWED_ENVIRONMENT_NAMES = [TEST_ENVIRONMENT_NAME, BETA_ENVIRONMENT_NAME, PROD_ENVIRONMENT_NAME] + +DEPLOY_ENVIRONMENT_NAME = 'deploy' + +# Action constants +ACTION_CONTEXT_KEY = 'action' +PIPELINE_STACK_CONTEXT_KEY = 'pipelineStack' +PIPELINE_SYNTH_ACTION = 'pipelineSynth' +BOOTSTRAP_DEPLOY_ACTION = 'bootstrapDeploy' + + +class DeploymentResourcesStack(Stack): + """Stack that manages all shared resources for all pipeline stacks.""" + + def __init__( + self, + scope: Construct, + construct_id: str, + **kwargs, + ): + super().__init__(scope, construct_id, environment_name='deploy', **kwargs) + + pipeline_context_parameter_name = f'{DEPLOY_ENVIRONMENT_NAME}-compact-connect-context' + + # Fetch ssm_context if not provided locally + self.parameter = StringParameter.from_string_parameter_name( + self, + 'PipelineContext', + string_parameter_name=pipeline_context_parameter_name, + ) + value = StringParameter.value_from_lookup(self, self.parameter.parameter_name) + # When CDK runs for the first time, it synthesizes fully without actually retrieving the SSM Parameter + # value. It, instead, populates parameters and other look-ups with dummy values, synthesizes, collects all + # the look-ups together, populates them for real, then re-synthesizes with real values. + # To accommodate this pattern, we have to replace this dummy value with one that will actually + # let CDK complete its first round of synthesis, so that it can get to its second, real, synthesis. + if value != f'dummy-value-for-{pipeline_context_parameter_name}': + self.ssm_context = json.loads(value) + else: + with open(f'cdk.context.{DEPLOY_ENVIRONMENT_NAME}-example.json') as f: + self.ssm_context = json.load(f)['ssm_context'] + + self.deploy_environment_context = self.ssm_context['environments'][DEPLOY_ENVIRONMENT_NAME] + + self.pipeline_shared_encryption_key = Key( + self, + 'PipelineSharedEncryptionKey', + enable_key_rotation=True, + alias='pipeline-shared-encryption-key', + removal_policy=RemovalPolicy.RETAIN, + ) + + notifications = self.deploy_environment_context.get('notifications', {}) + self.pipeline_alarm_topic = AlarmTopic( + self, + 'AlarmTopic', + master_key=self.pipeline_shared_encryption_key, + email_subscriptions=notifications.get('email', []), + slack_subscriptions=notifications.get('slack', []), + ) + + self.pipeline_access_logs_bucket = AccessLogsBucket( + self, + 'AccessLogsBucket', + removal_policy=RemovalPolicy.RETAIN, + auto_delete_objects=False, + ) + + NagSuppressions.add_resource_suppressions_by_path( + self, + f'{self.pipeline_access_logs_bucket.node.path}/Resource', + suppressions=[ + { + 'id': 'HIPAA.Security-S3BucketLoggingEnabled', + 'reason': 'This is the access logging bucket.', + }, + ], + ) + + +class BasePipelineStack(Stack): + """Base stack with common functionality for all pipeline stacks (both backend and frontend).""" + + def __init__( + self, + scope: Construct, + construct_id: str, + environment_name: str, + env: Environment, + removal_policy: RemovalPolicy, + pipeline_access_logs_bucket: IBucket, + **kwargs, + ): + super().__init__(scope, construct_id, environment_name='pipeline', env=env, **kwargs) + + self.env = env + self.environment_name = environment_name + self.removal_policy = removal_policy + self.access_logs_bucket = pipeline_access_logs_bucket + + pipeline_context_parameter_name = f'{self.environment_name}-compact-connect-context' + + # Fetch ssm_context if not provided locally + self.parameter = StringParameter.from_string_parameter_name( + self, + 'PipelineContext', + string_parameter_name=pipeline_context_parameter_name, + ) + value = StringParameter.value_from_lookup(self, self.parameter.parameter_name) + # When CDK runs for the first time, it synthesizes fully without actually retrieving the SSM Parameter + # value. It, instead, populates parameters and other look-ups with dummy values, synthesizes, collects all + # the look-ups together, populates them for real, then re-synthesizes with real values. + # To accommodate this pattern, we have to replace this dummy value with one that will actually + # let CDK complete its first round of synthesis, so that it can get to its second, real, synthesis. + if value != f'dummy-value-for-{pipeline_context_parameter_name}': + self.ssm_context = json.loads(value) + else: + with open(f'cdk.context.{environment_name}-example.json') as f: + self.ssm_context = json.load(f)['ssm_context'] + + self.pipeline_environment_context = self.ssm_context['environments']['pipeline'] + self.connection_arn = self.pipeline_environment_context['connection_arn'] + self.github_repo_string = self.ssm_context['github_repo_string'] + self.backup_config = self.ssm_context.get('backup_config', {}) + self.app_name = self.ssm_context['app_name'] + + def _add_pipeline_cdk_assume_role_policy(self, pipeline: CdkCodePipeline): + pipeline.synth_project.add_to_role_policy( + PolicyStatement( + effect=Effect.ALLOW, + actions=['sts:AssumeRole'], + resources=[ + self.format_arn( + partition=self.partition, + service='iam', + region='', + account='*', + resource='role', + resource_name='cdk-hnb659fds-lookup-role-*', + ), + ], + ), + ) + + def _get_frontend_pipeline_name(self): + if self.environment_name not in ALLOWED_ENVIRONMENT_NAMES: + raise ValueError(f'Environment name must be one of {ALLOWED_ENVIRONMENT_NAMES}') + + return f'{self.environment_name}-compactConnect-frontendPipeline' + + def _get_frontend_pipeline_arn(self): + pipeline_name = self._get_frontend_pipeline_name() + + return self.format_arn( + partition=self.partition, + service='codepipeline', + region=self.env.region, + account=self.env.account, + resource=pipeline_name, + ) + + +class BaseFrontendPipelineStack(BasePipelineStack): + """ + Base class for frontend pipeline stacks. + Implements common functionality for all frontend pipeline stacks. + """ + + def __init__( + self, + scope: Construct, + construct_id: str, + environment_name: str, + env: Environment, + removal_policy: RemovalPolicy, + pipeline_access_logs_bucket: IBucket, + **kwargs, + ): + super().__init__( + scope, + construct_id, + environment_name=environment_name, + env=env, + removal_policy=removal_policy, + pipeline_access_logs_bucket=pipeline_access_logs_bucket, + **kwargs, + ) + + def _determine_frontend_stage(self, construct_id, environment_name, environment_context): + """ + Return either a real FrontendStage or a SynthSubstituteStage depending on pipeline synthesis context. + + This method centralizes the stage creation logic to conditionally create a lightweight substitute + stage during pipeline synthesis when the stage is not part of the pipeline being synthesized. + """ + # Check if we're in pipeline synthesis mode and if we're synthesizing this specific pipeline + action = self.node.try_get_context('action') + pipeline_stack_name = self.node.try_get_context('pipelineStack') + + # If we're in pipeline synthesis mode and this is not the pipeline being synthesized, + # use a lightweight substitute stage + if ( + action == PIPELINE_SYNTH_ACTION and pipeline_stack_name != self.stack_name + ) or action == BOOTSTRAP_DEPLOY_ACTION: + return SynthSubstituteStage( + self, + 'SubstituteFrontendStage', + environment_context=environment_context, + ) + + # Otherwise, use the real stage for deployment + return FrontendStage( + self, + construct_id, + environment_name=environment_name, + environment_context=environment_context, + ) + + +class TestFrontendPipelineStack(BaseFrontendPipelineStack): + """Pipeline stack for the test frontend environment.""" + + def __init__( + self, + scope: Construct, + construct_id: str, + *, + pipeline_shared_encryption_key: IKey, + pipeline_alarm_topic: ITopic, + pipeline_access_logs_bucket: IBucket, + cdk_path: str, + **kwargs, + ): + super().__init__( + scope, + construct_id, + environment_name=TEST_ENVIRONMENT_NAME, + removal_policy=RemovalPolicy.DESTROY, + pipeline_access_logs_bucket=pipeline_access_logs_bucket, + **kwargs, + ) + + # Allows us to override the default branching scheme for the test environment, via context variable + pre_prod_trigger_branch = self.pipeline_environment_context.get('pre_prod_trigger_branch', 'development') + + self.pre_prod_frontend_pipeline = FrontendPipeline( + self, + 'TestFrontendPipeline', + pipeline_name=self._get_frontend_pipeline_name(), + github_repo_string=self.github_repo_string, + cdk_path=cdk_path, + connection_arn=self.connection_arn, + source_branch=pre_prod_trigger_branch, + encryption_key=pipeline_shared_encryption_key, + alarm_topic=pipeline_alarm_topic, + access_logs_bucket=self.access_logs_bucket, + ssm_parameter=self.parameter, + pipeline_stack_name=self.stack_name, + environment_context=self.pipeline_environment_context, + self_mutation=True, + removal_policy=self.removal_policy, + ) + + self.pre_prod_frontend_stage = self._determine_frontend_stage( + construct_id='TestFrontend', + environment_name=TEST_ENVIRONMENT_NAME, + environment_context=self.ssm_context['environments'][TEST_ENVIRONMENT_NAME], + ) + + self.pre_prod_frontend_pipeline.add_stage(self.pre_prod_frontend_stage) + self.pre_prod_frontend_pipeline.build_pipeline() + self._add_pipeline_cdk_assume_role_policy(self.pre_prod_frontend_pipeline) + + +class BetaFrontendPipelineStack(BaseFrontendPipelineStack): + """Pipeline stack for the beta frontend environment.""" + + def __init__( + self, + scope: Construct, + construct_id: str, + *, + pipeline_shared_encryption_key: IKey, + pipeline_alarm_topic: ITopic, + pipeline_access_logs_bucket: IBucket, + cdk_path: str, + **kwargs, + ): + super().__init__( + scope, + construct_id, + environment_name=BETA_ENVIRONMENT_NAME, + removal_policy=RemovalPolicy.RETAIN, + pipeline_access_logs_bucket=pipeline_access_logs_bucket, + **kwargs, + ) + + self.beta_frontend_pipeline = FrontendPipeline( + self, + 'BetaFrontendPipeline', + pipeline_name=self._get_frontend_pipeline_name(), + github_repo_string=self.github_repo_string, + cdk_path=cdk_path, + connection_arn=self.connection_arn, + source_branch='main', + encryption_key=pipeline_shared_encryption_key, + alarm_topic=pipeline_alarm_topic, + access_logs_bucket=self.access_logs_bucket, + ssm_parameter=self.parameter, + pipeline_stack_name=self.stack_name, + environment_context=self.pipeline_environment_context, + self_mutation=True, + removal_policy=self.removal_policy, + ) + + self.beta_frontend_stage = self._determine_frontend_stage( + construct_id='BetaFrontend', + environment_name=BETA_ENVIRONMENT_NAME, + environment_context=self.ssm_context['environments'][BETA_ENVIRONMENT_NAME], + ) + + self.beta_frontend_pipeline.add_stage(self.beta_frontend_stage) + self.beta_frontend_pipeline.build_pipeline() + self._add_pipeline_cdk_assume_role_policy(self.beta_frontend_pipeline) + + +class ProdFrontendPipelineStack(BaseFrontendPipelineStack): + """Pipeline stack for the production frontend environment.""" + + def __init__( + self, + scope: Construct, + construct_id: str, + *, + pipeline_shared_encryption_key: IKey, + pipeline_alarm_topic: ITopic, + pipeline_access_logs_bucket: IBucket, + cdk_path: str, + **kwargs, + ): + super().__init__( + scope, + construct_id, + environment_name=PROD_ENVIRONMENT_NAME, + removal_policy=RemovalPolicy.RETAIN, + pipeline_access_logs_bucket=pipeline_access_logs_bucket, + **kwargs, + ) + + self.prod_frontend_pipeline = FrontendPipeline( + self, + 'ProdFrontendPipeline', + pipeline_name=self._get_frontend_pipeline_name(), + github_repo_string=self.github_repo_string, + cdk_path=cdk_path, + connection_arn=self.connection_arn, + source_branch='main', + encryption_key=pipeline_shared_encryption_key, + alarm_topic=pipeline_alarm_topic, + access_logs_bucket=self.access_logs_bucket, + ssm_parameter=self.parameter, + pipeline_stack_name=self.stack_name, + environment_context=self.pipeline_environment_context, + self_mutation=True, + removal_policy=self.removal_policy, + ) + + self.prod_frontend_stage = self._determine_frontend_stage( + construct_id='ProdFrontend', + environment_name=PROD_ENVIRONMENT_NAME, + environment_context=self.ssm_context['environments'][PROD_ENVIRONMENT_NAME], + ) + + self.prod_frontend_pipeline.add_stage(self.prod_frontend_stage) + self.prod_frontend_pipeline.build_pipeline() + self._add_pipeline_cdk_assume_role_policy(self.prod_frontend_pipeline) diff --git a/backend/compact-connect-ui-app/pipeline/design/.!35012!pipeline-architecture.pdf b/backend/compact-connect-ui-app/pipeline/design/.!35012!pipeline-architecture.pdf new file mode 100644 index 0000000000000000000000000000000000000000..e284855032cb0402825a714221c3a4a48b24824f GIT binary patch literal 353 zcmZ9H&1!={6ovOb#cTrUB3?(0p(P0UL7FxY(RL+S=s2ipoROL9qPzaQnNOCnU0Axf zaJl!K!*^!!K3Y(FWzB|<;dA&JzAZ`|B8!W)Uc<|K$05!uu3;y8frP?yQ|in!lH?qQ z78m6x^(V{5*iv{+4&%&g#7;i|R1MaBVKdnR)73(%$^^Du&TuD%;0*7=&|Cy^fqMS$ z1&?rm#E3HL-rEjY`hgt~*ISKy;Mx8e(!Riv_1K8FU+Rqz5+Oy^GmLX(at-`es*=$kT literal 0 HcmV?d00001 diff --git a/backend/compact-connect-ui-app/pipeline/design/README.md b/backend/compact-connect-ui-app/pipeline/design/README.md new file mode 100644 index 000000000..26d8ad82d --- /dev/null +++ b/backend/compact-connect-ui-app/pipeline/design/README.md @@ -0,0 +1,96 @@ +# CDK Pipeline Architecture Design + +[View Pipeline Architecture (PDF)](./pipeline-architecture.pdf) + +## Overview + +The CompactConnect CI/CD pipeline architecture implements an optimized deployment strategy built around AWS CDK Pipelines (see https://docs.aws.amazon.com/cdk/v2/guide/cdk_pipeline.html). It follows a multi-pipeline approach with separate backend and frontend pipelines to improve deployment speed, reliability, and security. + +## Key Components + +- **Backend Pipelines**: Deploy infrastructure resources and backend components first +- **Frontend Pipelines**: Deploy frontend applications with backend configuration values +- **Deployment Resources Stack**: Shared resources used by all pipeline stacks across all environments +- **Environments**: Test, Beta, and Production environments + +## Pipeline Flow + +1. GitHub push → Backend Pipeline +2. Backend Pipeline successful completion → Trigger Frontend Pipeline +3. Frontend Pipeline deploys web application using configuration values from Backend + +Commits pushed to the 'development' branch trigger the test pipelines. Commits pushed to the 'main' branch trigger the beta and prod pipelines. + +## Self-Mutation Feature and Optimization + +### Understanding CDK Pipeline Self-Mutation + +AWS CDK Pipelines include a powerful "self-mutation" feature that allows the pipeline to update itself. When code changes affecting the pipeline's structure are pushed, the pipeline: + +1. Executes with its current configuration +2. Synthesizes CloudFormation templates for all stacks in the app +3. Deploys a "self-mutation" step that updates the pipeline's own definition +4. Continues deployment with the updated pipeline definition + +While powerful, this feature presents challenges: + +1. **Performance Impact**: By default, CDK synthesizes all stacks in the application even when only one pipeline needs to be updated. This can be extremely slow, especially for complex applications. + +2. **Unnecessary Processing**: Every pipeline synthesis includes bundling operations (like frontend builds) even when those components aren't changing. + +### The SynthSubstituteStage and SynthSubstituteStack Solution + +To address these challenges, we've implemented the `SynthSubstituteStage` and `SynthSubstituteStack` classes that act as lightweight placeholders during the synthesis process: + +#### How It Works + +1. During pipeline synthesis, we pass in cdk context variables to determine which specific pipeline is being synthesized. + +2. For any stage that isn't part of the current pipeline being synthesized, we replace it with a `SynthSubstituteStage` containing a minimal `SynthSubstituteStack`. + +3. The substitute stack synths a single SSM parameter resource, dramatically reducing synthesis time compared to full application stacks. + +## Implementation Details + +The substitution mechanism relies on CDK context values which we pass in during the CDK synth step of the pipeline definition (see the [BackendPipeline](../backend_pipeline.py) and [FrontendPipeline](../frontend_pipeline.py) class constructors, specifically the `synth.commands` property): + +```python +commands=[ + ... other commands + # Only synthesize the specific pipeline stack needed + f'cdk synth --context pipelineStack={pipeline_stack_name} --context action=pipelineSynth', +], +``` +The following context values are used to determine which pipeline to fully synthesize: + +- `action`: Specifies the current action (e.g., `pipelineSynth`, `bootstrapDeploy`) +- `pipelineStack`: The specific pipeline stack being synthesized + +In the pipeline stack classes, the `_determine_backend_stage` and `_determine_frontend_stage` methods handle the stage substitution logic: + +```python +def _determine_backend_stage(self, construct_id, app_name, environment_name, environment_context): + # Check if we're in pipeline synthesis mode and if we're synthesizing this specific pipeline + action = self.node.try_get_context('action') + pipeline_stack_name = self.node.try_get_context('pipelineStack') + + # Use substitute stage if not synthesizing this specific pipeline or during bootstrap + if (action == PIPELINE_SYNTH_ACTION and pipeline_stack_name != self.stack_name) or action == BOOTSTRAP_DEPLOY_ACTION: + return SynthSubstituteStage( + self, + 'SubstituteBackendStage', + environment_context=environment_context, + ) + + # Otherwise, use the real stage + return BackendStage( + self, + construct_id, + app_name=app_name, + environment_name=environment_name, + environment_context=environment_context, + ) +``` + +# Bootstrapping the piplines +See this [README.md](../../README.md) for details on performing a bootstrap deployment of the pipelines. diff --git a/backend/compact-connect-ui-app/pipeline/design/pipeline-architecture.pdf b/backend/compact-connect-ui-app/pipeline/design/pipeline-architecture.pdf new file mode 100644 index 0000000000000000000000000000000000000000..c5146c2e2864567e8077cee448b9ef6524bb8bc7 GIT binary patch literal 89240 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%0A=wxbf58988*?(rcE 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

lVHA>R}}g8ztm5Bg=(YkiTeF1 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 literal 0 HcmV?d00001 diff --git a/backend/compact-connect/pipeline/frontend_pipeline.py b/backend/compact-connect-ui-app/pipeline/frontend_pipeline.py similarity index 100% rename from backend/compact-connect/pipeline/frontend_pipeline.py rename to backend/compact-connect-ui-app/pipeline/frontend_pipeline.py diff --git a/backend/compact-connect/pipeline/frontend_stage.py b/backend/compact-connect-ui-app/pipeline/frontend_stage.py similarity index 99% rename from backend/compact-connect/pipeline/frontend_stage.py rename to backend/compact-connect-ui-app/pipeline/frontend_stage.py index e4aef258e..1e34a981d 100644 --- a/backend/compact-connect/pipeline/frontend_stage.py +++ b/backend/compact-connect-ui-app/pipeline/frontend_stage.py @@ -1,6 +1,7 @@ from aws_cdk import Environment, Stage from common_constructs.stack import StandardTags from constructs import Construct + from stacks.frontend_deployment_stack import FrontendDeploymentStack diff --git a/backend/compact-connect-ui-app/pipeline/synth_substitute_stack.py b/backend/compact-connect-ui-app/pipeline/synth_substitute_stack.py new file mode 100644 index 000000000..c5dda2739 --- /dev/null +++ b/backend/compact-connect-ui-app/pipeline/synth_substitute_stack.py @@ -0,0 +1,30 @@ +from aws_cdk import Stack, aws_ssm +from constructs import Construct + + +class SynthSubstituteStack(Stack): + """ + A lightweight stack used as a substitute during pipeline synthesis. + + This stack is used to optimize CDK pipeline synthesis by replacing + heavyweight stacks with a minimal stack that contains just a dummy + SSM parameter. This dramatically reduces synthesis time when only + a specific pipeline's stacks need to be synthesized. + """ + + def __init__( + self, + scope: Construct, + construct_id: str, + **kwargs, + ): + super().__init__(scope, construct_id, **kwargs) + + # Create a simple SSM parameter as a lightweight substitute + self.dummy_parameter = aws_ssm.StringParameter( + self, + 'DummyParameter', + parameter_name=f'/compact-connect/{construct_id}/dummy-parameter', + string_value='dummy parameter value', + description='Dummy parameter used for CDK synthesis optimization', + ) diff --git a/backend/compact-connect-ui-app/pipeline/synth_substitute_stage.py b/backend/compact-connect-ui-app/pipeline/synth_substitute_stage.py new file mode 100644 index 000000000..d7aca239c --- /dev/null +++ b/backend/compact-connect-ui-app/pipeline/synth_substitute_stage.py @@ -0,0 +1,38 @@ +from aws_cdk import Environment, Stage +from constructs import Construct + +from pipeline.synth_substitute_stack import SynthSubstituteStack + + +class SynthSubstituteStage(Stage): + """ + A lightweight stage used as a substitute during pipeline synthesis. + + This stage is used to optimize CDK pipeline synthesis by replacing + heavyweight stages with a minimal stage that contains just a single + SynthSubstituteStack. This dramatically reduces synthesis time when + only a specific pipeline's stages need to be synthesized. + + Using a separate stage rather than conditional logic within existing + stages provides an additional safety layer - preventing accidental + deletion of production resources due to typos in pipeline names. + """ + + def __init__( + self, + scope: Construct, + construct_id: str, + *, + environment_context: dict, + **kwargs, + ): + super().__init__(scope, construct_id, **kwargs) + + environment = Environment(account=environment_context['account_id'], region=environment_context['region']) + + # Create a simple substitute stack + self.substitute_stack = SynthSubstituteStack( + self, + 'SubstituteStack', + env=environment, + ) diff --git a/backend/compact-connect-ui-app/requirements-dev.in b/backend/compact-connect-ui-app/requirements-dev.in new file mode 100644 index 000000000..34e2caeca --- /dev/null +++ b/backend/compact-connect-ui-app/requirements-dev.in @@ -0,0 +1,6 @@ +pytest>=6.2.5 +pytest-cov +coverage +ruff +pip-tools +pip-audit diff --git a/backend/compact-connect-ui-app/requirements-dev.txt b/backend/compact-connect-ui-app/requirements-dev.txt new file mode 100644 index 000000000..8a13d2148 --- /dev/null +++ b/backend/compact-connect-ui-app/requirements-dev.txt @@ -0,0 +1,108 @@ +# +# This file is autogenerated by pip-compile with Python 3.12 +# by the following command: +# +# pip-compile --no-emit-index-url compact-connect/requirements-dev.in +# +boolean-py==5.0 + # via license-expression +build==1.3.0 + # via pip-tools +cachecontrol[filecache]==0.14.3 + # via + # cachecontrol + # pip-audit +certifi==2025.8.3 + # via requests +charset-normalizer==3.4.3 + # via requests +click==8.2.1 + # via pip-tools +coverage[toml]==7.10.5 + # via + # -r compact-connect/requirements-dev.in + # pytest-cov +cyclonedx-python-lib==9.1.0 + # via pip-audit +defusedxml==0.7.1 + # via py-serializable +faker==28.4.1 + # via -r compact-connect/requirements-dev.in +filelock==3.19.1 + # via cachecontrol +idna==3.10 + # via requests +iniconfig==2.1.0 + # via pytest +license-expression==30.4.4 + # via cyclonedx-python-lib +markdown-it-py==4.0.0 + # via rich +mdurl==0.1.2 + # via markdown-it-py +msgpack==1.1.1 + # via cachecontrol +packageurl-python==0.17.5 + # via cyclonedx-python-lib +packaging==25.0 + # via + # build + # pip-audit + # pip-requirements-parser + # pytest +pip-api==0.0.34 + # via pip-audit +pip-audit==2.9.0 + # via -r compact-connect/requirements-dev.in +pip-requirements-parser==32.0.1 + # via pip-audit +pip-tools==7.5.0 + # via -r compact-connect/requirements-dev.in +platformdirs==4.4.0 + # via pip-audit +pluggy==1.6.0 + # via + # pytest + # pytest-cov +py-serializable==2.1.0 + # via cyclonedx-python-lib +pygments==2.19.2 + # via + # pytest + # rich +pyparsing==3.2.3 + # via pip-requirements-parser +pyproject-hooks==1.2.0 + # via + # build + # pip-tools +pytest==8.4.1 + # via + # -r compact-connect/requirements-dev.in + # pytest-cov +pytest-cov==6.2.1 + # via -r compact-connect/requirements-dev.in +python-dateutil==2.9.0.post0 + # via faker +requests==2.32.5 + # via + # cachecontrol + # pip-audit +rich==14.1.0 + # via pip-audit +ruff==0.12.10 + # via -r compact-connect/requirements-dev.in +six==1.17.0 + # via python-dateutil +sortedcontainers==2.4.0 + # via cyclonedx-python-lib +toml==0.10.2 + # via pip-audit +urllib3==2.5.0 + # via requests +wheel==0.45.1 + # via pip-tools + +# The following packages are considered to be unsafe in a requirements file: +# pip +# setuptools diff --git a/backend/compact-connect-ui-app/requirements.in b/backend/compact-connect-ui-app/requirements.in new file mode 100644 index 000000000..0ae037f07 --- /dev/null +++ b/backend/compact-connect-ui-app/requirements.in @@ -0,0 +1,6 @@ +aws-cdk-lib>=2.140.0 +aws-cdk-aws-lambda-python-alpha>=2.142.0a0 +constructs>=10.0.0,<11.0.0 +cdk-nag>=2.28.10, <3 +# pyyaml required for compact configuration uploader +pyyaml>=6.0.2, <7 diff --git a/backend/compact-connect-ui-app/requirements.txt b/backend/compact-connect-ui-app/requirements.txt new file mode 100644 index 000000000..681812b16 --- /dev/null +++ b/backend/compact-connect-ui-app/requirements.txt @@ -0,0 +1,74 @@ +# +# This file is autogenerated by pip-compile with Python 3.12 +# by the following command: +# +# pip-compile --no-emit-index-url compact-connect/requirements.in +# +attrs==25.3.0 + # via + # cattrs + # jsii +aws-cdk-asset-awscli-v1==2.2.242 + # via aws-cdk-lib +aws-cdk-asset-node-proxy-agent-v6==2.1.0 + # via aws-cdk-lib +aws-cdk-aws-lambda-python-alpha==2.212.0a0 + # via -r compact-connect/requirements.in +aws-cdk-cloud-assembly-schema==48.5.0 + # via aws-cdk-lib +aws-cdk-lib==2.212.0 + # via + # -r compact-connect/requirements.in + # aws-cdk-aws-lambda-python-alpha + # cdk-nag +cattrs==25.1.1 + # via jsii +cdk-nag==2.37.9 + # via -r compact-connect/requirements.in +constructs==10.4.2 + # via + # -r compact-connect/requirements.in + # aws-cdk-aws-lambda-python-alpha + # aws-cdk-lib + # cdk-nag +importlib-resources==6.5.2 + # via jsii +jsii==1.113.0 + # via + # aws-cdk-asset-awscli-v1 + # aws-cdk-asset-node-proxy-agent-v6 + # aws-cdk-aws-lambda-python-alpha + # aws-cdk-cloud-assembly-schema + # aws-cdk-lib + # cdk-nag + # constructs +publication==0.0.3 + # via + # aws-cdk-asset-awscli-v1 + # aws-cdk-asset-node-proxy-agent-v6 + # aws-cdk-aws-lambda-python-alpha + # aws-cdk-cloud-assembly-schema + # aws-cdk-lib + # cdk-nag + # constructs + # jsii +python-dateutil==2.9.0.post0 + # via jsii +pyyaml==6.0.2 + # via -r compact-connect/requirements.in +six==1.17.0 + # via python-dateutil +typeguard==2.13.3 + # via + # aws-cdk-asset-awscli-v1 + # aws-cdk-asset-node-proxy-agent-v6 + # aws-cdk-aws-lambda-python-alpha + # aws-cdk-cloud-assembly-schema + # aws-cdk-lib + # cdk-nag + # constructs + # jsii +typing-extensions==4.15.0 + # via + # cattrs + # jsii diff --git a/backend/compact-connect-ui-app/ruff.toml b/backend/compact-connect-ui-app/ruff.toml new file mode 100644 index 000000000..66682be80 --- /dev/null +++ b/backend/compact-connect-ui-app/ruff.toml @@ -0,0 +1,112 @@ +# Exclude a variety of commonly ignored directories. +exclude = [ + ".bzr", + ".direnv", + ".eggs", + ".git", + ".git-rewrite", + ".hg", + ".ipynb_checkpoints", + ".mypy_cache", + ".nox", + ".pants.d", + ".pyenv", + ".pytest_cache", + ".pytype", + ".ruff_cache", + ".svn", + ".tox", + ".venv", + ".vscode", + "__pypackages__", + "_build", + "buck-out", + "build", + "dist", + "node_modules", + "site-packages", + "venv" +] + +line-length = 120 +indent-width = 4 + +# Assume Python 3.12 +target-version = "py312" + +[lint] +select = [ + "A", # flake8-builtins + "ARG", # flake8-unused-arguments + "B", # flake8-bugbear + "BLE", # flake8-blind-except + "DTZ", # flake8-datetimez + "E", # pycodestyle + "F", # Pyflakes + "FIX", # flake8-fixme + "FA", # flake8-future-annotations + "G", # flake8-logging-format + "I", # isort + "N", # pep8-naming + "PIE", # flake8-pie + "SLF", # flake8-self + "RET", # flake8-return + "RSE", # flake8-raise + "S", # flake8-bandit + "T", # flake8-print + "UP", # pyupgrade + "W", # warning +] +ignore = [ + "S311", # suspicious-non-cryptographic-random-usage + "N818", # error-suffix-on-exception-name + # the following are ignored by recommendation of ruff documentation + # due to conflicts with the ruff formatter see https://docs.astral.sh/ruff/formatter/#conflicting-lint-rules + "W191", # indentation contains tabs + "E111", # indentation-with-invalid-multiple + "E114", # indentation-with-invalid-multiple-comment + "E117", # over-indented + "D206", # indent-with-spaces + "D300", # triple-single-quotes + "Q000", # bad-quotes-inline-string + "Q001", # bad-quotes-multiline-string + "Q002", # bad-quotes-docstring + "Q003", # avoidable-escaped-quote + "COM812", # missing-trailing-comma + "COM819", # prohibited-trailing-comma + "ISC001", # single-line-implicit-string-concatenation + "ISC002", # multi-line-implicit-string-concatenation +] + +# Allow fix for all enabled rules (when `--fix`) is provided. +fixable = ["ALL"] +unfixable = [] + +# Allow unused variables when underscore-prefixed. +dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" + +[format] +quote-style = "single" + +# Like Black, indent with spaces, rather than tabs. +indent-style = "space" + +# Like Black, respect magic trailing commas. +skip-magic-trailing-comma = false + +# Like Black, automatically detect the appropriate line ending. +line-ending = "auto" + +# Enable auto-formatting of code examples in docstrings. Markdown, +# reStructuredText code/literal blocks and doctests are all supported. +# +# This is currently disabled by default, but it is planned for this +# to be opt-out in the future. +docstring-code-format = false + +# Set the line length limit used when formatting code snippets in +# docstrings. +# +# This only has an effect when the `docstring-code-format` setting is +# enabled. +docstring-code-line-length = "dynamic" diff --git a/backend/compact-connect-ui-app/stacks/__init__.py b/backend/compact-connect-ui-app/stacks/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/backend/compact-connect/stacks/frontend_deployment_stack/__init__.py b/backend/compact-connect-ui-app/stacks/frontend_deployment_stack/__init__.py similarity index 100% rename from backend/compact-connect/stacks/frontend_deployment_stack/__init__.py rename to backend/compact-connect-ui-app/stacks/frontend_deployment_stack/__init__.py diff --git a/backend/compact-connect/stacks/frontend_deployment_stack/deployment.py b/backend/compact-connect-ui-app/stacks/frontend_deployment_stack/deployment.py similarity index 100% rename from backend/compact-connect/stacks/frontend_deployment_stack/deployment.py rename to backend/compact-connect-ui-app/stacks/frontend_deployment_stack/deployment.py diff --git a/backend/compact-connect/stacks/frontend_deployment_stack/distribution.py b/backend/compact-connect-ui-app/stacks/frontend_deployment_stack/distribution.py similarity index 100% rename from backend/compact-connect/stacks/frontend_deployment_stack/distribution.py rename to backend/compact-connect-ui-app/stacks/frontend_deployment_stack/distribution.py diff --git a/backend/compact-connect-ui-app/tests/__init__.py b/backend/compact-connect-ui-app/tests/__init__.py new file mode 100644 index 000000000..f56f03fdc --- /dev/null +++ b/backend/compact-connect-ui-app/tests/__init__.py @@ -0,0 +1,5 @@ +import os +import sys + +# Make the `common_constructs` namespace package under `common-cdk` available to Python +sys.path.insert(0, os.path.abspath(os.path.join('..', 'common-cdk'))) diff --git a/backend/compact-connect-ui-app/tests/app/__init__.py b/backend/compact-connect-ui-app/tests/app/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/backend/compact-connect-ui-app/tests/app/base.py b/backend/compact-connect-ui-app/tests/app/base.py new file mode 100644 index 000000000..9bcefeaed --- /dev/null +++ b/backend/compact-connect-ui-app/tests/app/base.py @@ -0,0 +1,149 @@ +import json +import os +import sys +from abc import ABC, abstractmethod +from collections.abc import Mapping +from unittest.mock import patch + +from aws_cdk.assertions import Annotations, Match, Template +from aws_cdk.aws_cloudfront import CfnDistribution +from aws_cdk.aws_lambda import CfnFunction +from aws_cdk.aws_s3 import CfnBucket +from common_constructs.stack import Stack + +from app import CompactConnectApp +from pipeline import FrontendStage +from stacks.frontend_deployment_stack import FrontendDeploymentStack + + +class _AppSynthesizer: + """ + A helper class to cache apps based on context. + This is useful to avoid re-synthesizing the app for each test. + """ + + def __init__(self): + super().__init__() + self._cached_apps: dict[int, CompactConnectApp] = {} + + def get_app(self, context: Mapping) -> CompactConnectApp: + context_hash = self._get_context_hash(context) + if context_hash not in self._cached_apps.keys(): + self._cached_apps[context_hash] = CompactConnectApp(context=context) + return self._cached_apps[context_hash] + + def _get_context_hash(self, context: Mapping) -> int: + return hash(json.dumps(context, sort_keys=True)) + + +_app_synthesizer = _AppSynthesizer() + + +class TstAppABC(ABC): + """ + Base class for common test elements across configurations. + + Note: Concrete classes must also inherit from TestCase + """ + + @classmethod + @abstractmethod + def get_context(cls) -> Mapping: + pass + + @classmethod + @patch.dict(os.environ, {'CDK_DEFAULT_ACCOUNT': '000000000000', 'CDK_DEFAULT_REGION': 'us-east-1'}) + def setUpClass(cls): # pylint: disable=invalid-name + """ + We build the app once per TestCase, to save compute time in the test suite + """ + cls.context = cls.get_context() + cls.app = _app_synthesizer.get_app(cls.context) + + @staticmethod + def get_resource_properties_by_logical_id(logical_id: str, resources: Mapping[str, Mapping]) -> Mapping: + """ + Helper function to retrieve a resource from a CloudFormation template by its logical ID. + """ + try: + return resources[logical_id]['Properties'] + except KeyError as exc: + raise RuntimeError(f'{logical_id} not found in resources!') from exc + + def _inspect_frontend_deployment_stack(self, ui_stack: FrontendDeploymentStack): + with self.subTest(ui_stack.stack_name): + ui_stack_template = Template.from_stack(ui_stack) + # Ensure we have a CloudFront distribution + ui_stack_template.resource_count_is('AWS::CloudFront::Distribution', 1) + # This stack is not anticipated to do much changing, so we'll just snapshot-test key resources + ui_bucket = ui_stack_template.find_resources(CfnBucket.CFN_RESOURCE_TYPE_NAME)[ + ui_stack.get_logical_id(ui_stack.ui_bucket.node.default_child) + ] + self.compare_snapshot(ui_bucket, snapshot_name=f'{ui_stack.stack_name}-UI_BUCKET', overwrite_snapshot=False) + distribution = ui_stack_template.find_resources(CfnDistribution.CFN_RESOURCE_TYPE_NAME) + self.assertEqual(len(distribution), 1) + self.compare_snapshot(distribution, f'{ui_stack.stack_name}-UI_DISTRIBUTION', overwrite_snapshot=False) + # take a snapshot of the lambda@edge code to ensure placeholder values are being injected + distribution_function = ui_stack_template.find_resources(CfnFunction.CFN_RESOURCE_TYPE_NAME)[ + ui_stack.get_logical_id(ui_stack.distribution.csp_function.node.default_child) + ] + self.compare_snapshot( + distribution_function, + f'{ui_stack.stack_name}-UI_DISTRIBUTION_LAMBDA_FUNCTION', + overwrite_snapshot=False, + ) + + def _check_no_stack_annotations(self, stack: Stack): + with self.subTest(f'Security Rules: {stack.stack_name}'): + errors = Annotations.from_stack(stack).find_error('*', Match.string_like_regexp('.*')) + self.assertEqual(0, len(errors), msg='\n'.join(f'{err.id}: {err.entry.data.strip()}' for err in errors)) + + warnings = Annotations.from_stack(stack).find_warning('*', Match.string_like_regexp('.*')) + self.assertEqual( + 0, len(warnings), msg='\n'.join(f'{warn.id}: {warn.entry.data.strip()}' for warn in warnings) + ) + + def _check_no_frontend_stage_annotations(self, stage: FrontendStage): + self._check_no_stack_annotations(stage.frontend_deployment_stack) + + def _count_stack_resources(self, stack: Stack) -> int: + """ + Count the number of resources in a CloudFormation stack. + + :param stack: The CDK Stack to analyze + :returns: Number of resources in the stack + """ + template = Template.from_stack(stack) + # Get template as dictionary and count resources + template_dict = template.to_json() + resources = template_dict.get('Resources', {}) + return len(resources) + + def compare_snapshot(self, actual: Mapping | list, snapshot_name: str, overwrite_snapshot: bool = False): + """ + Compare the actual dictionary to the snapshot with the given name. + If overwrite_snapshot is True, overwrite the snapshot with the actual data. + """ + snapshot_path = os.path.join('tests', 'resources', 'snapshots', f'{snapshot_name}.json') + + if os.path.exists(snapshot_path): + with open(snapshot_path) as f: + snapshot = json.load(f) + else: + sys.stdout.write(f"Snapshot at path '{snapshot_path}' does not exist.") + snapshot = None + + if snapshot != actual and overwrite_snapshot: + with open(snapshot_path, 'w') as f: + json.dump(actual, f, indent=2) + # So the data files will end with a newline + f.write('\n') + sys.stdout.write(f"Snapshot '{snapshot_name}' has been overwritten.") + else: + self.maxDiff = None # pylint: disable=invalid-name,attribute-defined-outside-init + self.assertEqual( + snapshot, + actual, + f"Snapshot '{snapshot_name}' does not match the actual data. " + 'To overwrite the snapshot, set overwrite_snapshot=True.', + ) diff --git a/backend/compact-connect-ui-app/tests/app/test_pipeline.py b/backend/compact-connect-ui-app/tests/app/test_pipeline.py new file mode 100644 index 000000000..08b20dfa0 --- /dev/null +++ b/backend/compact-connect-ui-app/tests/app/test_pipeline.py @@ -0,0 +1,41 @@ +import json +from unittest import TestCase + +from tests.app.base import TstAppABC + + +class TestFrontendPipeline(TstAppABC, TestCase): + @classmethod + def get_context(cls): + with open('cdk.json') as f: + context = json.load(f)['context'] + # For pipeline deployments, the pipelines pull their CDK context values from SSM Parameter Store, rather + # than the cdk.context.json files used in local development. We can override the context values used in the + # tests by adding values here. + + # Suppresses lambda bundling for tests + context['aws:cdk:bundling-stacks'] = [] + + return context + + def test_synth_pipeline(self): + """ + Test infrastructure as deployed via the pipeline + """ + # Identify any findings from our AwsSolutions rule sets + self._check_no_stack_annotations(self.app.deployment_resources_stack) + self._check_no_stack_annotations(self.app.test_frontend_pipeline_stack) + self._check_no_stack_annotations(self.app.prod_frontend_pipeline_stack) + for stage in ( + self.app.test_frontend_pipeline_stack.pre_prod_frontend_stage, + self.app.beta_frontend_pipeline_stack.beta_frontend_stage, + self.app.prod_frontend_pipeline_stack.prod_frontend_stage, + ): + self._check_no_frontend_stage_annotations(stage) + + for frontend_deployment_stack in ( + self.app.test_frontend_pipeline_stack.pre_prod_frontend_stage.frontend_deployment_stack, + self.app.beta_frontend_pipeline_stack.beta_frontend_stage.frontend_deployment_stack, + self.app.prod_frontend_pipeline_stack.prod_frontend_stage.frontend_deployment_stack, + ): + self._inspect_frontend_deployment_stack(frontend_deployment_stack) diff --git a/backend/compact-connect/tests/resources/snapshots/BetaFrontend-FrontendDeploymentStack-UI_BUCKET.json b/backend/compact-connect-ui-app/tests/resources/snapshots/BetaFrontend-FrontendDeploymentStack-UI_BUCKET.json similarity index 100% rename from backend/compact-connect/tests/resources/snapshots/BetaFrontend-FrontendDeploymentStack-UI_BUCKET.json rename to backend/compact-connect-ui-app/tests/resources/snapshots/BetaFrontend-FrontendDeploymentStack-UI_BUCKET.json diff --git a/backend/compact-connect/tests/resources/snapshots/BetaFrontend-FrontendDeploymentStack-UI_DISTRIBUTION.json b/backend/compact-connect-ui-app/tests/resources/snapshots/BetaFrontend-FrontendDeploymentStack-UI_DISTRIBUTION.json similarity index 100% rename from backend/compact-connect/tests/resources/snapshots/BetaFrontend-FrontendDeploymentStack-UI_DISTRIBUTION.json rename to backend/compact-connect-ui-app/tests/resources/snapshots/BetaFrontend-FrontendDeploymentStack-UI_DISTRIBUTION.json diff --git a/backend/compact-connect/tests/resources/snapshots/BetaFrontend-FrontendDeploymentStack-UI_DISTRIBUTION_LAMBDA_FUNCTION.json b/backend/compact-connect-ui-app/tests/resources/snapshots/BetaFrontend-FrontendDeploymentStack-UI_DISTRIBUTION_LAMBDA_FUNCTION.json similarity index 100% rename from backend/compact-connect/tests/resources/snapshots/BetaFrontend-FrontendDeploymentStack-UI_DISTRIBUTION_LAMBDA_FUNCTION.json rename to backend/compact-connect-ui-app/tests/resources/snapshots/BetaFrontend-FrontendDeploymentStack-UI_DISTRIBUTION_LAMBDA_FUNCTION.json diff --git a/backend/compact-connect/tests/resources/snapshots/ProdFrontend-FrontendDeploymentStack-UI_BUCKET.json b/backend/compact-connect-ui-app/tests/resources/snapshots/ProdFrontend-FrontendDeploymentStack-UI_BUCKET.json similarity index 100% rename from backend/compact-connect/tests/resources/snapshots/ProdFrontend-FrontendDeploymentStack-UI_BUCKET.json rename to backend/compact-connect-ui-app/tests/resources/snapshots/ProdFrontend-FrontendDeploymentStack-UI_BUCKET.json diff --git a/backend/compact-connect/tests/resources/snapshots/ProdFrontend-FrontendDeploymentStack-UI_DISTRIBUTION.json b/backend/compact-connect-ui-app/tests/resources/snapshots/ProdFrontend-FrontendDeploymentStack-UI_DISTRIBUTION.json similarity index 100% rename from backend/compact-connect/tests/resources/snapshots/ProdFrontend-FrontendDeploymentStack-UI_DISTRIBUTION.json rename to backend/compact-connect-ui-app/tests/resources/snapshots/ProdFrontend-FrontendDeploymentStack-UI_DISTRIBUTION.json diff --git a/backend/compact-connect/tests/resources/snapshots/ProdFrontend-FrontendDeploymentStack-UI_DISTRIBUTION_LAMBDA_FUNCTION.json b/backend/compact-connect-ui-app/tests/resources/snapshots/ProdFrontend-FrontendDeploymentStack-UI_DISTRIBUTION_LAMBDA_FUNCTION.json similarity index 100% rename from backend/compact-connect/tests/resources/snapshots/ProdFrontend-FrontendDeploymentStack-UI_DISTRIBUTION_LAMBDA_FUNCTION.json rename to backend/compact-connect-ui-app/tests/resources/snapshots/ProdFrontend-FrontendDeploymentStack-UI_DISTRIBUTION_LAMBDA_FUNCTION.json diff --git a/backend/compact-connect/tests/resources/snapshots/TestFrontend-FrontendDeploymentStack-UI_BUCKET.json b/backend/compact-connect-ui-app/tests/resources/snapshots/TestFrontend-FrontendDeploymentStack-UI_BUCKET.json similarity index 100% rename from backend/compact-connect/tests/resources/snapshots/TestFrontend-FrontendDeploymentStack-UI_BUCKET.json rename to backend/compact-connect-ui-app/tests/resources/snapshots/TestFrontend-FrontendDeploymentStack-UI_BUCKET.json diff --git a/backend/compact-connect/tests/resources/snapshots/TestFrontend-FrontendDeploymentStack-UI_DISTRIBUTION.json b/backend/compact-connect-ui-app/tests/resources/snapshots/TestFrontend-FrontendDeploymentStack-UI_DISTRIBUTION.json similarity index 100% rename from backend/compact-connect/tests/resources/snapshots/TestFrontend-FrontendDeploymentStack-UI_DISTRIBUTION.json rename to backend/compact-connect-ui-app/tests/resources/snapshots/TestFrontend-FrontendDeploymentStack-UI_DISTRIBUTION.json diff --git a/backend/compact-connect/tests/resources/snapshots/TestFrontend-FrontendDeploymentStack-UI_DISTRIBUTION_LAMBDA_FUNCTION.json b/backend/compact-connect-ui-app/tests/resources/snapshots/TestFrontend-FrontendDeploymentStack-UI_DISTRIBUTION_LAMBDA_FUNCTION.json similarity index 100% rename from backend/compact-connect/tests/resources/snapshots/TestFrontend-FrontendDeploymentStack-UI_DISTRIBUTION_LAMBDA_FUNCTION.json rename to backend/compact-connect-ui-app/tests/resources/snapshots/TestFrontend-FrontendDeploymentStack-UI_DISTRIBUTION_LAMBDA_FUNCTION.json diff --git a/backend/compact-connect-ui-app/tests/resources/test_files/.!35287!military_affiliation.pdf b/backend/compact-connect-ui-app/tests/resources/test_files/.!35287!military_affiliation.pdf new file mode 100644 index 0000000000000000000000000000000000000000..2fbf8bb39211c9538298936703d12721cce882c4 GIT binary patch literal 9 QcmY!laB)k={E&s+53& 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 iDAE(DbxP*{oBYvR6rdZZpK=P61_F@*gH5bVW&Q 0: + pytest_args.append(f'-{"v" * args.verbose}') + + # Run tests for each directory with the correct working directory + for test_dir in TEST_DIRS: + dir_path = APP_DIR / test_dir + if not dir_path.exists(): + sys.stdout.write(f'Warning: Directory {dir_path} does not exist, skipping\n') + continue + + tests_dir = dir_path / 'tests' + if not tests_dir.exists(): + sys.stdout.write(f'Warning: Tests directory {tests_dir} does not exist, skipping\n') + continue + + sys.stdout.write('\n' + '=' * 80 + '\n') + sys.stdout.write(f'Running tests in {test_dir}\n') + sys.stdout.write('=' * 80 + '\n') + + # Save the original working directory and sys.path + original_dir = os.getcwd() + original_path = sys.path.copy() + original_env = os.environ.copy() + + try: + # Change to the test directory + os.chdir(dir_path) + + # Set up PYTHONPATH for common code if needed + if test_dir != 'lambdas/python/common' and 'python' in test_dir: + common_path = APP_DIR / 'lambdas/python/common' + if str(common_path) not in sys.path: + sys.path.insert(0, str(common_path)) + + # Clean up modules before running tests + clean_modules() + + # Run pytest for this directory + test_result = pytest.main(pytest_args) + + # Update exit code if any tests fail + if test_result != 0 and exit_code == 0: + return test_result + + # Restore the original environment + os.environ = original_env # noqa: B003 + + # Thorough module cleanup after each test suite + clean_modules() + + finally: + # Restore the original working directory + os.chdir(original_dir) + + # Restore the original sys.path + sys.path = original_path + finally: + # Stop coverage measurement + cov.stop() + cov.save() + + return exit_code + + +def main(): + parser = ArgumentParser(description='Run all Python tests in a unified pytest session') + parser.add_argument('--report', action='store_true', help='Generate HTML coverage report') + parser.add_argument('--open-report', action='store_true', help='Open HTML coverage report after generation') + parser.add_argument('--fail-under', type=float, default=90, help='Fail if coverage is under this percentage') + parser.add_argument('--verbose', '-v', action='count', default=0, help='Increase verbosity') + args = parser.parse_args() + + cov = get_coverage() + exit_code = run_tests(cov, args) + + # Generate coverage report if requested + if args.report: + sys.stdout.write('\nGenerating coverage report...\n') + cov.html_report() + + if args.open_report: + # Open the coverage report + report_path = APP_DIR / 'coverage' / 'index.html' + webbrowser.open(f'file://{report_path}') + + # Check coverage against threshold + sys.stdout.write('\nCalculating coverage...\n') + coverage_percent = cov.report() + if coverage_percent < args.fail_under: + sys.stdout.write( + f'Coverage {coverage_percent:.2f}% is below the required threshold of {args.fail_under:.2f}%\n' + ) + exit_code = 1 + + if exit_code > 0: + sys.stdout.write('\n================= TESTS FAILED =================\n') + return exit_code + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/backend/compact-connect/bin/run_tests.sh b/backend/compact-connect/bin/run_tests.sh new file mode 100755 index 000000000..ad8dcb5ee --- /dev/null +++ b/backend/compact-connect/bin/run_tests.sh @@ -0,0 +1,129 @@ +#!/bin/bash +set -e + +# Default values +LANGUAGE="all" +REPORT=true +OPEN_REPORT=true +VERBOSE="" +FAIL_UNDER=90 + +# Print usage information +usage() { + echo "Usage: $0 [options]" + echo "Options:" + echo " -l, --language LANG Specify language to test (nodejs, python, all) [default: all]" + echo " -n, --no-report Don't generate coverage report" + echo " -o, --no-open Don't open coverage report in browser" + echo " -v, --verbose Increase python verbosity (can be used multiple times: -vv)" + echo " -f, --fail-under PCT Set minimum python coverage percentage [default: 90]" + echo " -h, --help Display this help message" + exit 1 +} + +# Parse command line arguments +while getopts "l:nov:f:h-:" opt; do + case $opt in + -) + case "${OPTARG}" in + language) + LANGUAGE="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 )) + ;; + no-report) + REPORT=false + ;; + no-open) + OPEN_REPORT=false + ;; + verbose) + VERBOSE="v${VERBOSE}" + ;; + fail-under) + FAIL_UNDER="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 )) + ;; + help) + usage + ;; + *) + echo "Invalid option: --${OPTARG}" >&2 + usage + ;; + esac + ;; + l) + LANGUAGE="$OPTARG" + ;; + n) + REPORT=false + ;; + o) + OPEN_REPORT=false + ;; + v) + VERBOSE="v${VERBOSE}" + ;; + f) + FAIL_UNDER="$OPTARG" + ;; + h) + usage + ;; + \?) + echo "Invalid option: -$OPTARG" >&2 + usage + ;; + esac +done + +# Validate language argument +if [[ "$LANGUAGE" != "nodejs" && "$LANGUAGE" != "python" && "$LANGUAGE" != "all" ]]; then + echo "Error: Language must be 'nodejs', 'python', or 'all'" >&2 + usage +fi + +# Set this to 1 ahead of running tests, so this script will fail if neither node or python tests ran +EXIT=1 + +# Run NodeJS tests if requested +if [[ "$LANGUAGE" == 'nodejs' || "$LANGUAGE" == 'all' ]]; then + echo "Running NodeJS tests..." + ( + cd lambdas/nodejs + yarn test || exit "$?" + if [[ "$REPORT" == true && "$OPEN_REPORT" == true ]]; then + open 'coverage/lcov-report/index.html' + fi + ) || exit "$?" + # If this didn't exit already, we'll set our exit status to success for now + EXIT=0 +fi + +# Run Python tests if requested +if [[ "$LANGUAGE" == 'python' || "$LANGUAGE" == 'all' ]]; then + echo "Running Python tests..." + + # Build Python test arguments + PYTHON_ARGS=() + + # Add report flags + if [[ "$REPORT" == true ]]; then + PYTHON_ARGS+=("--report") + if [[ "$OPEN_REPORT" == true ]]; then + PYTHON_ARGS+=("--open-report") + fi + fi + + # Add verbosity flag + if [[ -n "$VERBOSE" ]]; then + PYTHON_ARGS+=("-${VERBOSE}") + fi + + # Add fail-under threshold + PYTHON_ARGS+=("--fail-under" "$FAIL_UNDER") + + # Run the Python tests + python3 bin/run_python_tests.py "${PYTHON_ARGS[@]}" || exit "$?" + EXIT=0 +fi + +exit "$EXIT" diff --git a/backend/compact-connect/bin/sync_deps.sh b/backend/compact-connect/bin/sync_deps.sh new file mode 100755 index 000000000..9a9d80983 --- /dev/null +++ b/backend/compact-connect/bin/sync_deps.sh @@ -0,0 +1,28 @@ +( + cd compact-connect/lambdas/nodejs + yarn install +) + +pip-sync \ + requirements-dev.txt \ + requirements.txt \ + lambdas/python/cognito-backup/requirements-dev.txt \ + lambdas/python/cognito-backup/requirements.txt \ + lambdas/python/compact-configuration/requirements-dev.txt \ + lambdas/python/compact-configuration/requirements.txt \ + lambdas/python/common/requirements-dev.txt \ + lambdas/python/common/requirements.txt \ + lambdas/python/custom-resources/requirements-dev.txt \ + lambdas/python/custom-resources/requirements.txt \ + lambdas/python/data-events/requirements-dev.txt \ + lambdas/python/data-events/requirements.txt \ + lambdas/python/disaster-recovery/requirements-dev.txt \ + lambdas/python/disaster-recovery/requirements.txt \ + lambdas/python/provider-data-v1/requirements-dev.txt \ + lambdas/python/provider-data-v1/requirements.txt \ + lambdas/python/purchases/requirements-dev.txt \ + lambdas/python/purchases/requirements.txt \ + lambdas/python/staff-user-pre-token/requirements-dev.txt \ + lambdas/python/staff-user-pre-token/requirements.txt \ + lambdas/python/staff-users/requirements-dev.txt \ + lambdas/python/staff-users/requirements.txt diff --git a/backend/compact-connect/common_constructs/README.md b/backend/compact-connect/common_constructs/README.md new file mode 100644 index 000000000..2e486b201 --- /dev/null +++ b/backend/compact-connect/common_constructs/README.md @@ -0,0 +1,8 @@ +# Common Constructs + +This is the application node of a [namespace package](https://docs.python.org/3/glossary.html#term-namespace-package), which +houses common CDK constructs that are used across different apps in the CompactConnect project. Modules in this package +will be merged in Python with similarly-named namespace packages in each app's specific folder. + +> **Note: Do not add an `__init__.py` file to any of the `common_constructs` packages, or they will break the +> import behavior. diff --git a/backend/compact-connect/common_constructs/cc_api.py b/backend/compact-connect/common_constructs/cc_api.py index a42fb727c..49d1d1fe6 100644 --- a/backend/compact-connect/common_constructs/cc_api.py +++ b/backend/compact-connect/common_constructs/cc_api.py @@ -27,11 +27,11 @@ from aws_cdk.aws_route53_targets import ApiGateway from cdk_nag import NagSuppressions from constructs import IConstruct -from stacks import persistent_stack as ps from common_constructs.security_profile import SecurityProfile from common_constructs.stack import AppStack from common_constructs.webacl import WebACL, WebACLScope +from stacks import persistent_stack as ps MD_FORMAT = r'^[01]{1}[0-9]{1}-[0-3]{1}[0-9]{1}$' YMD_FORMAT = r'^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$' diff --git a/backend/compact-connect/common_constructs/cognito_user_backup.py b/backend/compact-connect/common_constructs/cognito_user_backup.py index b883ce187..b821bd273 100644 --- a/backend/compact-connect/common_constructs/cognito_user_backup.py +++ b/backend/compact-connect/common_constructs/cognito_user_backup.py @@ -22,13 +22,13 @@ from aws_cdk.aws_sns import ITopic from cdk_nag import NagSuppressions from constructs import Construct -from stacks.backup_infrastructure_stack import BackupInfrastructureStack from common_constructs.access_logs_bucket import AccessLogsBucket from common_constructs.backup_plan import CCBackupPlan from common_constructs.bucket import Bucket from common_constructs.python_function import PythonFunction from common_constructs.stack import Stack +from stacks.backup_infrastructure_stack import BackupInfrastructureStack class CognitoUserBackup(Construct): diff --git a/backend/compact-connect/common_constructs/resource_scope_mixin.py b/backend/compact-connect/common_constructs/resource_scope_mixin.py index ee1f6fbe5..be20a536e 100644 --- a/backend/compact-connect/common_constructs/resource_scope_mixin.py +++ b/backend/compact-connect/common_constructs/resource_scope_mixin.py @@ -1,6 +1,7 @@ from __future__ import annotations from aws_cdk.aws_cognito import ResourceServerScope, UserPoolResourceServer + from stacks import persistent_stack as ps diff --git a/backend/compact-connect/lambdas/nodejs/cloudfront-csp/yarn.lock b/backend/compact-connect/lambdas/nodejs/cloudfront-csp/yarn.lock deleted file mode 100644 index 6b1673444..000000000 --- a/backend/compact-connect/lambdas/nodejs/cloudfront-csp/yarn.lock +++ /dev/null @@ -1,2971 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -"@babel/code-frame@7.12.11": - version "7.12.11" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.11.tgz#f4ad435aa263db935b8f10f2c552d23fb716a63f" - integrity sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw== - dependencies: - "@babel/highlight" "^7.10.4" - -"@babel/helper-validator-identifier@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz#75b889cfaf9e35c2aaf42cf0d72c8e91719251db" - integrity sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w== - -"@babel/highlight@^7.10.4": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.24.7.tgz#a05ab1df134b286558aae0ed41e6c5f731bf409d" - integrity sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw== - dependencies: - "@babel/helper-validator-identifier" "^7.24.7" - chalk "^2.4.2" - js-tokens "^4.0.0" - picocolors "^1.0.0" - -"@colors/colors@1.6.0", "@colors/colors@^1.6.0": - version "1.6.0" - resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.6.0.tgz#ec6cd237440700bc23ca23087f513c75508958b0" - integrity sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA== - -"@dabh/diagnostics@^2.0.2": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@dabh/diagnostics/-/diagnostics-2.0.3.tgz#7f7e97ee9a725dffc7808d93668cc984e1dc477a" - integrity sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA== - dependencies: - colorspace "1.1.x" - enabled "2.0.x" - kuler "^2.0.0" - -"@eslint-community/eslint-utils@^4.2.0": - version "4.4.0" - resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" - integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA== - dependencies: - eslint-visitor-keys "^3.3.0" - -"@eslint-community/regexpp@^4.6.1": - version "4.11.0" - resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.11.0.tgz#b0ffd0312b4a3fd2d6f77237e7248a5ad3a680ae" - integrity sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A== - -"@eslint/eslintrc@^0.4.3": - version "0.4.3" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.4.3.tgz#9e42981ef035beb3dd49add17acb96e8ff6f394c" - integrity sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw== - dependencies: - ajv "^6.12.4" - debug "^4.1.1" - espree "^7.3.0" - globals "^13.9.0" - ignore "^4.0.6" - import-fresh "^3.2.1" - js-yaml "^3.13.1" - minimatch "^3.0.4" - strip-json-comments "^3.1.1" - -"@eslint/eslintrc@^2.1.4": - version "2.1.4" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.4.tgz#388a269f0f25c1b6adc317b5a2c55714894c70ad" - integrity sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ== - dependencies: - ajv "^6.12.4" - debug "^4.3.2" - espree "^9.6.0" - globals "^13.19.0" - ignore "^5.2.0" - import-fresh "^3.2.1" - js-yaml "^4.1.0" - minimatch "^3.1.2" - strip-json-comments "^3.1.1" - -"@eslint/js@8.57.0": - version "8.57.0" - resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.57.0.tgz#a5417ae8427873f1dd08b70b3574b453e67b5f7f" - integrity sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g== - -"@humanwhocodes/config-array@^0.11.14": - version "0.11.14" - resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.14.tgz#d78e481a039f7566ecc9660b4ea7fe6b1fec442b" - integrity sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg== - dependencies: - "@humanwhocodes/object-schema" "^2.0.2" - debug "^4.3.1" - minimatch "^3.0.5" - -"@humanwhocodes/config-array@^0.5.0": - version "0.5.0" - resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.5.0.tgz#1407967d4c6eecd7388f83acf1eaf4d0c6e58ef9" - integrity sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg== - dependencies: - "@humanwhocodes/object-schema" "^1.2.0" - debug "^4.1.1" - minimatch "^3.0.4" - -"@humanwhocodes/module-importer@^1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" - integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== - -"@humanwhocodes/object-schema@^1.2.0": - version "1.2.1" - resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" - integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== - -"@humanwhocodes/object-schema@^2.0.2": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz#4a2868d75d6d6963e423bcf90b7fd1be343409d3" - integrity sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA== - -"@nodelib/fs.scandir@2.1.5": - version "2.1.5" - resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" - integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== - dependencies: - "@nodelib/fs.stat" "2.0.5" - run-parallel "^1.1.9" - -"@nodelib/fs.stat@2.0.5": - version "2.0.5" - resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" - integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== - -"@nodelib/fs.walk@^1.2.8": - version "1.2.8" - resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" - integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== - dependencies: - "@nodelib/fs.scandir" "2.1.5" - fastq "^1.6.0" - -"@types/json5@^0.0.29": - version "0.0.29" - resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" - integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== - -"@types/triple-beam@^1.3.2": - version "1.3.5" - resolved "https://registry.yarnpkg.com/@types/triple-beam/-/triple-beam-1.3.5.tgz#74fef9ffbaa198eb8b588be029f38b00299caa2c" - integrity sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw== - -"@ungap/structured-clone@^1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406" - integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ== - -abbrev@1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" - integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== - -acorn-jsx@^5.3.1, acorn-jsx@^5.3.2: - version "5.3.2" - resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" - integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== - -acorn@^7.4.0: - version "7.4.1" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" - integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== - -acorn@^8.9.0: - version "8.12.1" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.12.1.tgz#71616bdccbe25e27a54439e0046e89ca76df2248" - integrity sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg== - -ajv@^6.10.0, ajv@^6.12.4: - version "6.12.6" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" - integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== - dependencies: - fast-deep-equal "^3.1.1" - fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.4.1" - uri-js "^4.2.2" - -ajv@^8.0.1: - version "8.17.1" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.17.1.tgz#37d9a5c776af6bc92d7f4f9510eba4c0a60d11a6" - integrity sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g== - dependencies: - fast-deep-equal "^3.1.3" - fast-uri "^3.0.1" - json-schema-traverse "^1.0.0" - require-from-string "^2.0.2" - -ansi-colors@^4.1.1, ansi-colors@^4.1.3: - version "4.1.3" - resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.3.tgz#37611340eb2243e70cc604cad35d63270d48781b" - integrity sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw== - -ansi-regex@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" - integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== - -ansi-styles@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" - integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== - dependencies: - color-convert "^1.9.0" - -ansi-styles@^4.0.0, ansi-styles@^4.1.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" - integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== - dependencies: - color-convert "^2.0.1" - -anymatch@~3.1.2: - version "3.1.3" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" - integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== - dependencies: - normalize-path "^3.0.0" - picomatch "^2.0.4" - -argparse@^1.0.7: - version "1.0.10" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" - integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== - dependencies: - sprintf-js "~1.0.2" - -argparse@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" - integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== - -array-buffer-byte-length@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz#1e5583ec16763540a27ae52eed99ff899223568f" - integrity sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg== - dependencies: - call-bind "^1.0.5" - is-array-buffer "^3.0.4" - -array-each@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/array-each/-/array-each-1.0.1.tgz#a794af0c05ab1752846ee753a1f211a05ba0c44f" - integrity sha512-zHjL5SZa68hkKHBFBK6DJCTtr9sfTCPCaph/L7tMSLcTFgy+zX7E+6q5UArbtOtMBCtxdICpfTCspRse+ywyXA== - -array-includes@^3.1.7: - version "3.1.8" - resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.8.tgz#5e370cbe172fdd5dd6530c1d4aadda25281ba97d" - integrity sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ== - dependencies: - call-bind "^1.0.7" - define-properties "^1.2.1" - es-abstract "^1.23.2" - es-object-atoms "^1.0.0" - get-intrinsic "^1.2.4" - is-string "^1.0.7" - -array-slice@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/array-slice/-/array-slice-1.1.0.tgz#e368ea15f89bc7069f7ffb89aec3a6c7d4ac22d4" - integrity sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w== - -array.prototype.findlastindex@^1.2.3: - version "1.2.5" - resolved "https://registry.yarnpkg.com/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.5.tgz#8c35a755c72908719453f87145ca011e39334d0d" - integrity sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ== - dependencies: - call-bind "^1.0.7" - define-properties "^1.2.1" - es-abstract "^1.23.2" - es-errors "^1.3.0" - es-object-atoms "^1.0.0" - es-shim-unscopables "^1.0.2" - -array.prototype.flat@^1.3.2: - version "1.3.2" - resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz#1476217df8cff17d72ee8f3ba06738db5b387d18" - integrity sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA== - dependencies: - call-bind "^1.0.2" - define-properties "^1.2.0" - es-abstract "^1.22.1" - es-shim-unscopables "^1.0.0" - -array.prototype.flatmap@^1.3.2: - version "1.3.2" - resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz#c9a7c6831db8e719d6ce639190146c24bbd3e527" - integrity sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ== - dependencies: - call-bind "^1.0.2" - define-properties "^1.2.0" - es-abstract "^1.22.1" - es-shim-unscopables "^1.0.0" - -arraybuffer.prototype.slice@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz#097972f4255e41bc3425e37dc3f6421cf9aefde6" - integrity sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A== - dependencies: - array-buffer-byte-length "^1.0.1" - call-bind "^1.0.5" - define-properties "^1.2.1" - es-abstract "^1.22.3" - es-errors "^1.2.1" - get-intrinsic "^1.2.3" - is-array-buffer "^3.0.4" - is-shared-array-buffer "^1.0.2" - -assertion-error@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b" - integrity sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw== - -astral-regex@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31" - integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ== - -async@^2.6.0: - version "2.6.4" - resolved "https://registry.yarnpkg.com/async/-/async-2.6.4.tgz#706b7ff6084664cd7eae713f6f965433b5504221" - integrity sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA== - dependencies: - lodash "^4.17.14" - -async@^3.2.3, async@~3.2.0: - version "3.2.5" - resolved "https://registry.yarnpkg.com/async/-/async-3.2.5.tgz#ebd52a8fdaf7a2289a24df399f8d8485c8a46b66" - integrity sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg== - -available-typed-arrays@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz#a5cc375d6a03c2efc87a553f3e0b1522def14846" - integrity sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ== - dependencies: - possible-typed-array-names "^1.0.0" - -balanced-match@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" - integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== - -binary-extensions@^2.0.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522" - integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw== - -body@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/body/-/body-5.1.0.tgz#e4ba0ce410a46936323367609ecb4e6553125069" - integrity sha512-chUsBxGRtuElD6fmw1gHLpvnKdVLK302peeFa9ZqAEk8TyzZ3fygLyUEDDPTJvL9+Bor0dIwn6ePOsRM2y0zQQ== - dependencies: - continuable-cache "^0.3.1" - error "^7.0.0" - raw-body "~1.1.0" - safe-json-parse "~1.0.1" - -brace-expansion@^1.1.7: - version "1.1.11" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" - integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== - dependencies: - balanced-match "^1.0.0" - concat-map "0.0.1" - -brace-expansion@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" - integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== - dependencies: - balanced-match "^1.0.0" - -braces@^3.0.3, braces@~3.0.2: - version "3.0.3" - resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" - integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== - dependencies: - fill-range "^7.1.1" - -browser-stdout@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" - integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw== - -bytes@1: - version "1.0.0" - resolved "https://registry.yarnpkg.com/bytes/-/bytes-1.0.0.tgz#3569ede8ba34315fab99c3e92cb04c7220de1fa8" - integrity sha512-/x68VkHLeTl3/Ll8IvxdwzhrT+IyKc52e/oyHhA2RwqPqswSnjVbSddfPRwAsJtbilMAPSRWwAlpxdYsSWOTKQ== - -call-bind@^1.0.2, call-bind@^1.0.5, call-bind@^1.0.6, call-bind@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.7.tgz#06016599c40c56498c18769d2730be242b6fa3b9" - integrity sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w== - dependencies: - es-define-property "^1.0.0" - es-errors "^1.3.0" - function-bind "^1.1.2" - get-intrinsic "^1.2.4" - set-function-length "^1.2.1" - -callsites@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" - integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== - -camelcase@^6.0.0: - version "6.3.0" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" - integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== - -chai-match-pattern@^1.1.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/chai-match-pattern/-/chai-match-pattern-1.3.0.tgz#cefd4437de465860f4f87922c31049eb9d979104" - integrity sha512-DflyfI8lZ56YuYAZMTBPWghjqFQfqY1IR0ZZXrjlGZJuRvtN0TjJMBpLsrMfc45kjivXJ06iayuP7lzG6ij1bQ== - dependencies: - lodash-match-pattern "^2.3.1" - -chai@^4.1.2: - version "4.4.1" - resolved "https://registry.yarnpkg.com/chai/-/chai-4.4.1.tgz#3603fa6eba35425b0f2ac91a009fe924106e50d1" - integrity sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g== - dependencies: - assertion-error "^1.1.0" - check-error "^1.0.3" - deep-eql "^4.1.3" - get-func-name "^2.0.2" - loupe "^2.3.6" - pathval "^1.1.1" - type-detect "^4.0.8" - -chalk@^2.4.2: - version "2.4.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" - integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== - dependencies: - ansi-styles "^3.2.1" - escape-string-regexp "^1.0.5" - supports-color "^5.3.0" - -chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.2, chalk@~4.1.0: - version "4.1.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" - integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - -check-error@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.3.tgz#a6502e4312a7ee969f646e83bb3ddd56281bd694" - integrity sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg== - dependencies: - get-func-name "^2.0.2" - -checkit@^0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/checkit/-/checkit-0.7.0.tgz#14979abc93018346bfcfdcbabc19ab54c0bfd74a" - integrity sha512-QgiWB8gMdF/CbmWyuxCk+f2MPQe0G1DfJfHCTbrfZlY3FnJWdnW+EGsRJctcYz/IrXxPYJmjRjdgmKUkyIZl/Q== - dependencies: - inherits "^2.0.1" - lodash "^4.0.0" - -chokidar@^3.5.3: - version "3.6.0" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b" - integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw== - dependencies: - anymatch "~3.1.2" - braces "~3.0.2" - glob-parent "~5.1.2" - is-binary-path "~2.1.0" - is-glob "~4.0.1" - normalize-path "~3.0.0" - readdirp "~3.6.0" - optionalDependencies: - fsevents "~2.3.2" - -cliui@^7.0.2: - version "7.0.4" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" - integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== - dependencies: - string-width "^4.2.0" - strip-ansi "^6.0.0" - wrap-ansi "^7.0.0" - -color-convert@^1.9.0, color-convert@^1.9.3: - version "1.9.3" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" - integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== - dependencies: - color-name "1.1.3" - -color-convert@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" - integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== - dependencies: - color-name "~1.1.4" - -color-name@1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" - integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== - -color-name@^1.0.0, color-name@~1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" - integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== - -color-string@^1.6.0: - version "1.9.1" - resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.9.1.tgz#4467f9146f036f855b764dfb5bf8582bf342c7a4" - integrity sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg== - dependencies: - color-name "^1.0.0" - simple-swizzle "^0.2.2" - -color@^3.1.3: - version "3.2.1" - resolved "https://registry.yarnpkg.com/color/-/color-3.2.1.tgz#3544dc198caf4490c3ecc9a790b54fe9ff45e164" - integrity sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA== - dependencies: - color-convert "^1.9.3" - color-string "^1.6.0" - -colors@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/colors/-/colors-1.1.2.tgz#168a4701756b6a7f51a12ce0c97bfa28c084ed63" - integrity sha512-ENwblkFQpqqia6b++zLD/KUWafYlVY/UNnAp7oz7LY7E924wmpye416wBOmvv/HMWzl8gL1kJlfvId/1Dg176w== - -colorspace@1.1.x: - version "1.1.4" - resolved "https://registry.yarnpkg.com/colorspace/-/colorspace-1.1.4.tgz#8d442d1186152f60453bf8070cd66eb364e59243" - integrity sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w== - dependencies: - color "^3.1.3" - text-hex "1.0.x" - -commander@^10.0.1: - version "10.0.1" - resolved "https://registry.yarnpkg.com/commander/-/commander-10.0.1.tgz#881ee46b4f77d1c1dccc5823433aa39b022cbe06" - integrity sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug== - -concat-map@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" - integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== - -confusing-browser-globals@^1.0.10: - version "1.0.11" - resolved "https://registry.yarnpkg.com/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz#ae40e9b57cdd3915408a2805ebd3a5585608dc81" - integrity sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA== - -continuable-cache@^0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/continuable-cache/-/continuable-cache-0.3.1.tgz#bd727a7faed77e71ff3985ac93351a912733ad0f" - integrity sha512-TF30kpKhTH8AGCG3dut0rdd/19B7Z+qCnrMoBLpyQu/2drZdNrrpcjPEoJeSVsQM+8KmWG5O56oPDjSSUsuTyA== - -cross-spawn@^7.0.2: - version "7.0.6" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" - integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== - dependencies: - path-key "^3.1.0" - shebang-command "^2.0.0" - which "^2.0.1" - -data-view-buffer@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/data-view-buffer/-/data-view-buffer-1.0.1.tgz#8ea6326efec17a2e42620696e671d7d5a8bc66b2" - integrity sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA== - dependencies: - call-bind "^1.0.6" - es-errors "^1.3.0" - is-data-view "^1.0.1" - -data-view-byte-length@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz#90721ca95ff280677eb793749fce1011347669e2" - integrity sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ== - dependencies: - call-bind "^1.0.7" - es-errors "^1.3.0" - is-data-view "^1.0.1" - -data-view-byte-offset@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz#5e0bbfb4828ed2d1b9b400cd8a7d119bca0ff18a" - integrity sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA== - dependencies: - call-bind "^1.0.6" - es-errors "^1.3.0" - is-data-view "^1.0.1" - -dateformat@~4.6.2: - version "4.6.3" - resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-4.6.3.tgz#556fa6497e5217fedb78821424f8a1c22fa3f4b5" - integrity sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA== - -debug@^3.1.0, debug@^3.2.7: - version "3.2.7" - resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" - integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== - dependencies: - ms "^2.1.1" - -debug@^4.0.1, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.5: - version "4.3.5" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.5.tgz#e83444eceb9fedd4a1da56d671ae2446a01a6e1e" - integrity sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg== - dependencies: - ms "2.1.2" - -decamelize@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-4.0.0.tgz#aa472d7bf660eb15f3494efd531cab7f2a709837" - integrity sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ== - -deep-eql@^4.1.3: - version "4.1.4" - resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-4.1.4.tgz#d0d3912865911bb8fac5afb4e3acfa6a28dc72b7" - integrity sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg== - dependencies: - type-detect "^4.0.0" - -deep-is@^0.1.3: - version "0.1.4" - resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" - integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== - -define-data-property@^1.0.1, define-data-property@^1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e" - integrity sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A== - dependencies: - es-define-property "^1.0.0" - es-errors "^1.3.0" - gopd "^1.0.1" - -define-properties@^1.2.0, define-properties@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.1.tgz#10781cc616eb951a80a034bafcaa7377f6af2b6c" - integrity sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg== - dependencies: - define-data-property "^1.0.1" - has-property-descriptors "^1.0.0" - object-keys "^1.1.1" - -detect-file@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/detect-file/-/detect-file-1.0.0.tgz#f0d66d03672a825cb1b73bdb3fe62310c8e552b7" - integrity sha512-DtCOLG98P007x7wiiOmfI0fi3eIKyWiLTGJ2MDnVi/E04lWGbf+JzrRHMm0rgIIZJGtHpKpbVgLWHrv8xXpc3Q== - -diff@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/diff/-/diff-5.2.0.tgz#26ded047cd1179b78b9537d5ef725503ce1ae531" - integrity sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A== - -doctrine@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d" - integrity sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw== - dependencies: - esutils "^2.0.2" - -doctrine@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" - integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== - dependencies: - esutils "^2.0.2" - -dotenv@^16.3.1: - version "16.4.5" - resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.5.tgz#cdd3b3b604cb327e286b4762e13502f717cb099f" - integrity sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg== - -emoji-regex@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" - integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== - -enabled@2.0.x: - version "2.0.0" - resolved "https://registry.yarnpkg.com/enabled/-/enabled-2.0.0.tgz#f9dd92ec2d6f4bbc0d5d1e64e21d61cd4665e7c2" - integrity sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ== - -enquirer@^2.3.5: - version "2.4.1" - resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.4.1.tgz#93334b3fbd74fc7097b224ab4a8fb7e40bf4ae56" - integrity sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ== - dependencies: - ansi-colors "^4.1.1" - strip-ansi "^6.0.1" - -error@^7.0.0: - version "7.2.1" - resolved "https://registry.yarnpkg.com/error/-/error-7.2.1.tgz#eab21a4689b5f684fc83da84a0e390de82d94894" - integrity sha512-fo9HBvWnx3NGUKMvMwB/CBCMMrfEJgbDTVDEkPygA3Bdd3lM1OyCd+rbQ8BwnpF6GdVeOLDNmyL4N5Bg80ZvdA== - dependencies: - string-template "~0.2.1" - -es-abstract@^1.22.1, es-abstract@^1.22.3, es-abstract@^1.23.0, es-abstract@^1.23.2: - version "1.23.3" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.23.3.tgz#8f0c5a35cd215312573c5a27c87dfd6c881a0aa0" - integrity sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A== - dependencies: - array-buffer-byte-length "^1.0.1" - arraybuffer.prototype.slice "^1.0.3" - available-typed-arrays "^1.0.7" - call-bind "^1.0.7" - data-view-buffer "^1.0.1" - data-view-byte-length "^1.0.1" - data-view-byte-offset "^1.0.0" - es-define-property "^1.0.0" - es-errors "^1.3.0" - es-object-atoms "^1.0.0" - es-set-tostringtag "^2.0.3" - es-to-primitive "^1.2.1" - function.prototype.name "^1.1.6" - get-intrinsic "^1.2.4" - get-symbol-description "^1.0.2" - globalthis "^1.0.3" - gopd "^1.0.1" - has-property-descriptors "^1.0.2" - has-proto "^1.0.3" - has-symbols "^1.0.3" - hasown "^2.0.2" - internal-slot "^1.0.7" - is-array-buffer "^3.0.4" - is-callable "^1.2.7" - is-data-view "^1.0.1" - is-negative-zero "^2.0.3" - is-regex "^1.1.4" - is-shared-array-buffer "^1.0.3" - is-string "^1.0.7" - is-typed-array "^1.1.13" - is-weakref "^1.0.2" - object-inspect "^1.13.1" - object-keys "^1.1.1" - object.assign "^4.1.5" - regexp.prototype.flags "^1.5.2" - safe-array-concat "^1.1.2" - safe-regex-test "^1.0.3" - string.prototype.trim "^1.2.9" - string.prototype.trimend "^1.0.8" - string.prototype.trimstart "^1.0.8" - typed-array-buffer "^1.0.2" - typed-array-byte-length "^1.0.1" - typed-array-byte-offset "^1.0.2" - typed-array-length "^1.0.6" - unbox-primitive "^1.0.2" - which-typed-array "^1.1.15" - -es-define-property@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.0.tgz#c7faefbdff8b2696cf5f46921edfb77cc4ba3845" - integrity sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ== - dependencies: - get-intrinsic "^1.2.4" - -es-errors@^1.2.1, es-errors@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" - integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== - -es-object-atoms@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.0.0.tgz#ddb55cd47ac2e240701260bc2a8e31ecb643d941" - integrity sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw== - dependencies: - es-errors "^1.3.0" - -es-set-tostringtag@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz#8bb60f0a440c2e4281962428438d58545af39777" - integrity sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ== - dependencies: - get-intrinsic "^1.2.4" - has-tostringtag "^1.0.2" - hasown "^2.0.1" - -es-shim-unscopables@^1.0.0, es-shim-unscopables@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz#1f6942e71ecc7835ed1c8a83006d8771a63a3763" - integrity sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw== - dependencies: - hasown "^2.0.0" - -es-to-primitive@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" - integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== - dependencies: - is-callable "^1.1.4" - is-date-object "^1.0.1" - is-symbol "^1.0.2" - -escalade@^3.1.1: - version "3.1.2" - resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.2.tgz#54076e9ab29ea5bf3d8f1ed62acffbb88272df27" - integrity sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA== - -escape-string-regexp@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" - integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== - -escape-string-regexp@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" - integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== - -eslint-config-airbnb-base@^15.0.0: - version "15.0.0" - resolved "https://registry.yarnpkg.com/eslint-config-airbnb-base/-/eslint-config-airbnb-base-15.0.0.tgz#6b09add90ac79c2f8d723a2580e07f3925afd236" - integrity sha512-xaX3z4ZZIcFLvh2oUNvcX5oEofXda7giYmuplVxoOg5A7EXJMrUyqRgR+mhDhPK8LZ4PttFOBvCYDbX3sUoUig== - dependencies: - confusing-browser-globals "^1.0.10" - object.assign "^4.1.2" - object.entries "^1.1.5" - semver "^6.3.0" - -eslint-import-resolver-node@^0.3.9: - version "0.3.9" - resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz#d4eaac52b8a2e7c3cd1903eb00f7e053356118ac" - integrity sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g== - dependencies: - debug "^3.2.7" - is-core-module "^2.13.0" - resolve "^1.22.4" - -eslint-module-utils@^2.8.0: - version "2.8.1" - resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.8.1.tgz#52f2404300c3bd33deece9d7372fb337cc1d7c34" - integrity sha512-rXDXR3h7cs7dy9RNpUlQf80nX31XWJEyGq1tRMo+6GsO5VmTe4UTwtmonAD4ZkAsrfMVDA2wlGJ3790Ys+D49Q== - dependencies: - debug "^3.2.7" - -eslint-plugin-import@^2.25.3: - version "2.29.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz#d45b37b5ef5901d639c15270d74d46d161150643" - integrity sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw== - dependencies: - array-includes "^3.1.7" - array.prototype.findlastindex "^1.2.3" - array.prototype.flat "^1.3.2" - array.prototype.flatmap "^1.3.2" - debug "^3.2.7" - doctrine "^2.1.0" - eslint-import-resolver-node "^0.3.9" - eslint-module-utils "^2.8.0" - hasown "^2.0.0" - is-core-module "^2.13.1" - is-glob "^4.0.3" - minimatch "^3.1.2" - object.fromentries "^2.0.7" - object.groupby "^1.0.1" - object.values "^1.1.7" - semver "^6.3.1" - tsconfig-paths "^3.15.0" - -eslint-scope@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" - integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== - dependencies: - esrecurse "^4.3.0" - estraverse "^4.1.1" - -eslint-scope@^7.2.2: - version "7.2.2" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.2.2.tgz#deb4f92563390f32006894af62a22dba1c46423f" - integrity sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg== - dependencies: - esrecurse "^4.3.0" - estraverse "^5.2.0" - -eslint-utils@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-2.1.0.tgz#d2de5e03424e707dc10c74068ddedae708741b27" - integrity sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg== - dependencies: - eslint-visitor-keys "^1.1.0" - -eslint-visitor-keys@^1.1.0, eslint-visitor-keys@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e" - integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ== - -eslint-visitor-keys@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303" - integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== - -eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.3: - version "3.4.3" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" - integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== - -eslint@^7.32.0: - version "7.32.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.32.0.tgz#c6d328a14be3fb08c8d1d21e12c02fdb7a2a812d" - integrity sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA== - dependencies: - "@babel/code-frame" "7.12.11" - "@eslint/eslintrc" "^0.4.3" - "@humanwhocodes/config-array" "^0.5.0" - ajv "^6.10.0" - chalk "^4.0.0" - cross-spawn "^7.0.2" - debug "^4.0.1" - doctrine "^3.0.0" - enquirer "^2.3.5" - escape-string-regexp "^4.0.0" - eslint-scope "^5.1.1" - eslint-utils "^2.1.0" - eslint-visitor-keys "^2.0.0" - espree "^7.3.1" - esquery "^1.4.0" - esutils "^2.0.2" - fast-deep-equal "^3.1.3" - file-entry-cache "^6.0.1" - functional-red-black-tree "^1.0.1" - glob-parent "^5.1.2" - globals "^13.6.0" - ignore "^4.0.6" - import-fresh "^3.0.0" - imurmurhash "^0.1.4" - is-glob "^4.0.0" - js-yaml "^3.13.1" - json-stable-stringify-without-jsonify "^1.0.1" - levn "^0.4.1" - lodash.merge "^4.6.2" - minimatch "^3.0.4" - natural-compare "^1.4.0" - optionator "^0.9.1" - progress "^2.0.0" - regexpp "^3.1.0" - semver "^7.2.1" - strip-ansi "^6.0.0" - strip-json-comments "^3.1.0" - table "^6.0.9" - text-table "^0.2.0" - v8-compile-cache "^2.0.3" - -eslint@^8.44.0: - version "8.57.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.57.0.tgz#c786a6fd0e0b68941aaf624596fb987089195668" - integrity sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ== - dependencies: - "@eslint-community/eslint-utils" "^4.2.0" - "@eslint-community/regexpp" "^4.6.1" - "@eslint/eslintrc" "^2.1.4" - "@eslint/js" "8.57.0" - "@humanwhocodes/config-array" "^0.11.14" - "@humanwhocodes/module-importer" "^1.0.1" - "@nodelib/fs.walk" "^1.2.8" - "@ungap/structured-clone" "^1.2.0" - ajv "^6.12.4" - chalk "^4.0.0" - cross-spawn "^7.0.2" - debug "^4.3.2" - doctrine "^3.0.0" - escape-string-regexp "^4.0.0" - eslint-scope "^7.2.2" - eslint-visitor-keys "^3.4.3" - espree "^9.6.1" - esquery "^1.4.2" - esutils "^2.0.2" - fast-deep-equal "^3.1.3" - file-entry-cache "^6.0.1" - find-up "^5.0.0" - glob-parent "^6.0.2" - globals "^13.19.0" - graphemer "^1.4.0" - ignore "^5.2.0" - imurmurhash "^0.1.4" - is-glob "^4.0.0" - is-path-inside "^3.0.3" - js-yaml "^4.1.0" - json-stable-stringify-without-jsonify "^1.0.1" - levn "^0.4.1" - lodash.merge "^4.6.2" - minimatch "^3.1.2" - natural-compare "^1.4.0" - optionator "^0.9.3" - strip-ansi "^6.0.1" - text-table "^0.2.0" - -espree@^7.3.0, espree@^7.3.1: - version "7.3.1" - resolved "https://registry.yarnpkg.com/espree/-/espree-7.3.1.tgz#f2df330b752c6f55019f8bd89b7660039c1bbbb6" - integrity sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g== - dependencies: - acorn "^7.4.0" - acorn-jsx "^5.3.1" - eslint-visitor-keys "^1.3.0" - -espree@^9.6.0, espree@^9.6.1: - version "9.6.1" - resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f" - integrity sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ== - dependencies: - acorn "^8.9.0" - acorn-jsx "^5.3.2" - eslint-visitor-keys "^3.4.1" - -esprima@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" - integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== - -esquery@^1.4.0, esquery@^1.4.2: - version "1.6.0" - resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.6.0.tgz#91419234f804d852a82dceec3e16cdc22cf9dae7" - integrity sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg== - dependencies: - estraverse "^5.1.0" - -esrecurse@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" - integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== - dependencies: - estraverse "^5.2.0" - -estraverse@^4.1.1: - version "4.3.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" - integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== - -estraverse@^5.1.0, estraverse@^5.2.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" - integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== - -esutils@^2.0.2: - version "2.0.3" - resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" - integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== - -eventemitter2@~0.4.13: - version "0.4.14" - resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-0.4.14.tgz#8f61b75cde012b2e9eb284d4545583b5643b61ab" - integrity sha512-K7J4xq5xAD5jHsGM5ReWXRTFa3JRGofHiMcVgQ8PRwgWxzjHpMWCIzsmyf60+mh8KLsqYPcjUMa0AC4hd6lPyQ== - -exit@~0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" - integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ== - -expand-tilde@^2.0.0, expand-tilde@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/expand-tilde/-/expand-tilde-2.0.2.tgz#97e801aa052df02454de46b02bf621642cdc8502" - integrity sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw== - dependencies: - homedir-polyfill "^1.0.1" - -extend@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" - integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== - -fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: - version "3.1.3" - resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" - integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== - -fast-json-stable-stringify@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" - integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== - -fast-levenshtein@^2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" - integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== - -fast-uri@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.0.1.tgz#cddd2eecfc83a71c1be2cc2ef2061331be8a7134" - integrity sha512-MWipKbbYiYI0UC7cl8m/i/IWTqfC8YXsqjzybjddLsFjStroQzsHXkc73JutMvBiXmOvapk+axIl79ig5t55Bw== - -fastq@^1.6.0: - version "1.17.1" - resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.17.1.tgz#2a523f07a4e7b1e81a42b91b8bf2254107753b47" - integrity sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w== - dependencies: - reusify "^1.0.4" - -faye-websocket@~0.10.0: - version "0.10.0" - resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.10.0.tgz#4e492f8d04dfb6f89003507f6edbf2d501e7c6f4" - integrity sha512-Xhj93RXbMSq8urNCUq4p9l0P6hnySJ/7YNRhYNug0bLOuii7pKO7xQFb5mx9xZXWCar88pLPb805PvUkwrLZpQ== - dependencies: - websocket-driver ">=0.5.1" - -fecha@^4.2.0: - version "4.2.3" - resolved "https://registry.yarnpkg.com/fecha/-/fecha-4.2.3.tgz#4d9ccdbc61e8629b259fdca67e65891448d569fd" - integrity sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw== - -file-entry-cache@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" - integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== - dependencies: - flat-cache "^3.0.4" - -fill-range@^7.1.1: - version "7.1.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" - integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== - dependencies: - to-regex-range "^5.0.1" - -find-up@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" - integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== - dependencies: - locate-path "^6.0.0" - path-exists "^4.0.0" - -findup-sync@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-4.0.0.tgz#956c9cdde804052b881b428512905c4a5f2cdef0" - integrity sha512-6jvvn/12IC4quLBL1KNokxC7wWTvYncaVUYSoxWw7YykPLuRrnv4qdHcSOywOI5RpkOVGeQRtWM8/q+G6W6qfQ== - dependencies: - detect-file "^1.0.0" - is-glob "^4.0.0" - micromatch "^4.0.2" - resolve-dir "^1.0.1" - -findup-sync@~5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-5.0.0.tgz#54380ad965a7edca00cc8f63113559aadc541bd2" - integrity sha512-MzwXju70AuyflbgeOhzvQWAvvQdo1XL0A9bVvlXsYcFEBM87WR4OakL4OfZq+QRmr+duJubio+UtNQCPsVESzQ== - dependencies: - detect-file "^1.0.0" - is-glob "^4.0.3" - micromatch "^4.0.4" - resolve-dir "^1.0.1" - -fined@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/fined/-/fined-1.2.0.tgz#d00beccf1aa2b475d16d423b0238b713a2c4a37b" - integrity sha512-ZYDqPLGxDkDhDZBjZBb+oD1+j0rA4E0pXY50eplAAOPg2N/gUBSSk5IM1/QhPfyVo19lJ+CvXpqfvk+b2p/8Ng== - dependencies: - expand-tilde "^2.0.2" - is-plain-object "^2.0.3" - object.defaults "^1.1.0" - object.pick "^1.2.0" - parse-filepath "^1.0.1" - -flagged-respawn@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/flagged-respawn/-/flagged-respawn-1.0.1.tgz#e7de6f1279ddd9ca9aac8a5971d618606b3aab41" - integrity sha512-lNaHNVymajmk0OJMBn8fVUAU1BtDeKIqKoVhk4xAALB57aALg6b4W0MfJ/cUE0g9YBXy5XhSlPIpYIJ7HaY/3Q== - -flat-cache@^3.0.4: - version "3.2.0" - resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.2.0.tgz#2c0c2d5040c99b1632771a9d105725c0115363ee" - integrity sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw== - dependencies: - flatted "^3.2.9" - keyv "^4.5.3" - rimraf "^3.0.2" - -flat@^5.0.2: - version "5.0.2" - resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241" - integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== - -flatted@^3.2.9: - version "3.3.1" - resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.1.tgz#21db470729a6734d4997002f439cb308987f567a" - integrity sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw== - -fn.name@1.x.x: - version "1.1.0" - resolved "https://registry.yarnpkg.com/fn.name/-/fn.name-1.1.0.tgz#26cad8017967aea8731bc42961d04a3d5988accc" - integrity sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw== - -for-each@^0.3.3: - version "0.3.3" - resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e" - integrity sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw== - dependencies: - is-callable "^1.1.3" - -for-in@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" - integrity sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ== - -for-own@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/for-own/-/for-own-1.0.0.tgz#c63332f415cedc4b04dbfe70cf836494c53cb44b" - integrity sha512-0OABksIGrxKK8K4kynWkQ7y1zounQxP+CWnyclVwj81KW3vlLlGUx57DKGcP/LH216GzqnstnPocF16Nxs0Ycg== - dependencies: - for-in "^1.0.1" - -fs.realpath@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" - integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== - -fsevents@~2.3.2: - version "2.3.3" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" - integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== - -function-bind@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" - integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== - -function.prototype.name@^1.1.6: - version "1.1.6" - resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.6.tgz#cdf315b7d90ee77a4c6ee216c3c3362da07533fd" - integrity sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg== - dependencies: - call-bind "^1.0.2" - define-properties "^1.2.0" - es-abstract "^1.22.1" - functions-have-names "^1.2.3" - -functional-red-black-tree@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" - integrity sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g== - -functions-have-names@^1.2.3: - version "1.2.3" - resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" - integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== - -gaze@^1.1.0: - version "1.1.3" - resolved "https://registry.yarnpkg.com/gaze/-/gaze-1.1.3.tgz#c441733e13b927ac8c0ff0b4c3b033f28812924a" - integrity sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g== - dependencies: - globule "^1.0.0" - -get-caller-file@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" - integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== - -get-func-name@^2.0.1, get-func-name@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.2.tgz#0d7cf20cd13fda808669ffa88f4ffc7a3943fc41" - integrity sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ== - -get-intrinsic@^1.1.3, get-intrinsic@^1.2.1, get-intrinsic@^1.2.3, get-intrinsic@^1.2.4: - version "1.2.4" - resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.4.tgz#e385f5a4b5227d449c3eabbad05494ef0abbeadd" - integrity sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ== - dependencies: - es-errors "^1.3.0" - function-bind "^1.1.2" - has-proto "^1.0.1" - has-symbols "^1.0.3" - hasown "^2.0.0" - -get-symbol-description@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.2.tgz#533744d5aa20aca4e079c8e5daf7fd44202821f5" - integrity sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg== - dependencies: - call-bind "^1.0.5" - es-errors "^1.3.0" - get-intrinsic "^1.2.4" - -getobject@~1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/getobject/-/getobject-1.0.2.tgz#25ec87a50370f6dcc3c6ba7ef43c4c16215c4c89" - integrity sha512-2zblDBaFcb3rB4rF77XVnuINOE2h2k/OnqXAiy0IrTxUfV1iFp3la33oAQVY9pCpWU268WFYVt2t71hlMuLsOg== - -glob-parent@^5.1.2, glob-parent@~5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" - integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== - dependencies: - is-glob "^4.0.1" - -glob-parent@^6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" - integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== - dependencies: - is-glob "^4.0.3" - -glob@^7.1.3: - version "7.2.3" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" - integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.1.1" - once "^1.3.0" - path-is-absolute "^1.0.0" - -glob@^8.1.0: - version "8.1.0" - resolved "https://registry.yarnpkg.com/glob/-/glob-8.1.0.tgz#d388f656593ef708ee3e34640fdfb99a9fd1c33e" - integrity sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^5.0.1" - once "^1.3.0" - -glob@~7.1.1, glob@~7.1.6: - version "7.1.7" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90" - integrity sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.4" - once "^1.3.0" - path-is-absolute "^1.0.0" - -global-modules@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-1.0.0.tgz#6d770f0eb523ac78164d72b5e71a8877265cc3ea" - integrity sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg== - dependencies: - global-prefix "^1.0.1" - is-windows "^1.0.1" - resolve-dir "^1.0.0" - -global-prefix@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-1.0.2.tgz#dbf743c6c14992593c655568cb66ed32c0122ebe" - integrity sha512-5lsx1NUDHtSjfg0eHlmYvZKv8/nVqX4ckFbM+FrGcQ+04KWcWFo9P5MxPZYSzUvyzmdTbI7Eix8Q4IbELDqzKg== - dependencies: - expand-tilde "^2.0.2" - homedir-polyfill "^1.0.1" - ini "^1.3.4" - is-windows "^1.0.1" - which "^1.2.14" - -globals@^13.19.0, globals@^13.6.0, globals@^13.9.0: - version "13.24.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-13.24.0.tgz#8432a19d78ce0c1e833949c36adb345400bb1171" - integrity sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ== - dependencies: - type-fest "^0.20.2" - -globalthis@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.4.tgz#7430ed3a975d97bfb59bcce41f5cabbafa651236" - integrity sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ== - dependencies: - define-properties "^1.2.1" - gopd "^1.0.1" - -globule@^1.0.0: - version "1.3.4" - resolved "https://registry.yarnpkg.com/globule/-/globule-1.3.4.tgz#7c11c43056055a75a6e68294453c17f2796170fb" - integrity sha512-OPTIfhMBh7JbBYDpa5b+Q5ptmMWKwcNcFSR/0c6t8V4f3ZAVBEsKNY37QdVqmLRYSMhOUGYrY0QhSoEpzGr/Eg== - dependencies: - glob "~7.1.1" - lodash "^4.17.21" - minimatch "~3.0.2" - -gopd@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c" - integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA== - dependencies: - get-intrinsic "^1.1.3" - -graphemer@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" - integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== - -grunt-cli@~1.4.3: - version "1.4.3" - resolved "https://registry.yarnpkg.com/grunt-cli/-/grunt-cli-1.4.3.tgz#22c9f1a3d2780bf9b0d206e832e40f8f499175ff" - integrity sha512-9Dtx/AhVeB4LYzsViCjUQkd0Kw0McN2gYpdmGYKtE2a5Yt7v1Q+HYZVWhqXc/kGnxlMtqKDxSwotiGeFmkrCoQ== - dependencies: - grunt-known-options "~2.0.0" - interpret "~1.1.0" - liftup "~3.0.1" - nopt "~4.0.1" - v8flags "~3.2.0" - -grunt-contrib-watch@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/grunt-contrib-watch/-/grunt-contrib-watch-1.1.0.tgz#c143ca5b824b288a024b856639a5345aedb78ed4" - integrity sha512-yGweN+0DW5yM+oo58fRu/XIRrPcn3r4tQx+nL7eMRwjpvk+rQY6R8o94BPK0i2UhTg9FN21hS+m8vR8v9vXfeg== - dependencies: - async "^2.6.0" - gaze "^1.1.0" - lodash "^4.17.10" - tiny-lr "^1.1.1" - -grunt-eslint@^24.0.0: - version "24.3.0" - resolved "https://registry.yarnpkg.com/grunt-eslint/-/grunt-eslint-24.3.0.tgz#2b82d107f6963de91daf58cf8311221a806a6de5" - integrity sha512-dUPiRgX8fhmh4uwTAn9xrzg7HV5j5DhGmZZGJdHfjy/AN9G4jD+5IjfbcAJ209JcIG8m4B7xz3crIhuDSm3siQ== - dependencies: - chalk "^4.1.2" - eslint "^8.44.0" - -grunt-known-options@~2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/grunt-known-options/-/grunt-known-options-2.0.0.tgz#cac641e897f9a0a680b8c9839803d35f3325103c" - integrity sha512-GD7cTz0I4SAede1/+pAbmJRG44zFLPipVtdL9o3vqx9IEyb7b4/Y3s7r6ofI3CchR5GvYJ+8buCSioDv5dQLiA== - -grunt-legacy-log-utils@~2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/grunt-legacy-log-utils/-/grunt-legacy-log-utils-2.1.0.tgz#49a8c7dc74051476dcc116c32faf9db8646856ef" - integrity sha512-lwquaPXJtKQk0rUM1IQAop5noEpwFqOXasVoedLeNzaibf/OPWjKYvvdqnEHNmU+0T0CaReAXIbGo747ZD+Aaw== - dependencies: - chalk "~4.1.0" - lodash "~4.17.19" - -grunt-legacy-log@~3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/grunt-legacy-log/-/grunt-legacy-log-3.0.0.tgz#1c6eaf92371ea415af31ea84ce50d434ef6d39c4" - integrity sha512-GHZQzZmhyq0u3hr7aHW4qUH0xDzwp2YXldLPZTCjlOeGscAOWWPftZG3XioW8MasGp+OBRIu39LFx14SLjXRcA== - dependencies: - colors "~1.1.2" - grunt-legacy-log-utils "~2.1.0" - hooker "~0.2.3" - lodash "~4.17.19" - -grunt-legacy-util@~2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/grunt-legacy-util/-/grunt-legacy-util-2.0.1.tgz#0f929d13a2faf9988c9917c82bff609e2d9ba255" - integrity sha512-2bQiD4fzXqX8rhNdXkAywCadeqiPiay0oQny77wA2F3WF4grPJXCvAcyoWUJV+po/b15glGkxuSiQCK299UC2w== - dependencies: - async "~3.2.0" - exit "~0.1.2" - getobject "~1.0.0" - hooker "~0.2.3" - lodash "~4.17.21" - underscore.string "~3.3.5" - which "~2.0.2" - -grunt@^1.5.3: - version "1.6.1" - resolved "https://registry.yarnpkg.com/grunt/-/grunt-1.6.1.tgz#0b4dd1524f26676dcf45d8f636b8d9061a8ede16" - integrity sha512-/ABUy3gYWu5iBmrUSRBP97JLpQUm0GgVveDCp6t3yRNIoltIYw7rEj3g5y1o2PGPR2vfTRGa7WC/LZHLTXnEzA== - dependencies: - dateformat "~4.6.2" - eventemitter2 "~0.4.13" - exit "~0.1.2" - findup-sync "~5.0.0" - glob "~7.1.6" - grunt-cli "~1.4.3" - grunt-known-options "~2.0.0" - grunt-legacy-log "~3.0.0" - grunt-legacy-util "~2.0.1" - iconv-lite "~0.6.3" - js-yaml "~3.14.0" - minimatch "~3.0.4" - nopt "~3.0.6" - -has-bigints@^1.0.1, has-bigints@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa" - integrity sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ== - -has-flag@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" - integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== - -has-flag@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" - integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== - -has-property-descriptors@^1.0.0, has-property-descriptors@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz#963ed7d071dc7bf5f084c5bfbe0d1b6222586854" - integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg== - dependencies: - es-define-property "^1.0.0" - -has-proto@^1.0.1, has-proto@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.3.tgz#b31ddfe9b0e6e9914536a6ab286426d0214f77fd" - integrity sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q== - -has-symbols@^1.0.2, has-symbols@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" - integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== - -has-tostringtag@^1.0.0, has-tostringtag@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc" - integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw== - dependencies: - has-symbols "^1.0.3" - -hasown@^2.0.0, hasown@^2.0.1, hasown@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" - integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== - dependencies: - function-bind "^1.1.2" - -he@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" - integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== - -homedir-polyfill@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz#743298cef4e5af3e194161fbadcc2151d3a058e8" - integrity sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA== - dependencies: - parse-passwd "^1.0.0" - -hooker@~0.2.3: - version "0.2.3" - resolved "https://registry.yarnpkg.com/hooker/-/hooker-0.2.3.tgz#b834f723cc4a242aa65963459df6d984c5d3d959" - integrity sha512-t+UerCsQviSymAInD01Pw+Dn/usmz1sRO+3Zk1+lx8eg+WKpD2ulcwWqHHL0+aseRBr+3+vIhiG1K1JTwaIcTA== - -http-parser-js@>=0.5.1: - version "0.5.8" - resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.8.tgz#af23090d9ac4e24573de6f6aecc9d84a48bf20e3" - integrity sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q== - -iconv-lite@~0.6.3: - version "0.6.3" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" - integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== - dependencies: - safer-buffer ">= 2.1.2 < 3.0.0" - -ignore@^4.0.6: - version "4.0.6" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" - integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== - -ignore@^5.2.0: - version "5.3.1" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.1.tgz#5073e554cd42c5b33b394375f538b8593e34d4ef" - integrity sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw== - -import-fresh@^3.0.0, import-fresh@^3.2.1: - version "3.3.0" - resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" - integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== - dependencies: - parent-module "^1.0.0" - resolve-from "^4.0.0" - -imurmurhash@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" - integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== - -inflight@^1.0.4: - version "1.0.6" - resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" - integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== - dependencies: - once "^1.3.0" - wrappy "1" - -inherits@2, inherits@^2.0.1, inherits@^2.0.3: - version "2.0.4" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" - integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== - -ini@^1.3.4: - version "1.3.8" - resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" - integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== - -internal-slot@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.7.tgz#c06dcca3ed874249881007b0a5523b172a190802" - integrity sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g== - dependencies: - es-errors "^1.3.0" - hasown "^2.0.0" - side-channel "^1.0.4" - -interpret@~1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.1.0.tgz#7ed1b1410c6a0e0f78cf95d3b8440c63f78b8614" - integrity sha512-CLM8SNMDu7C5psFCn6Wg/tgpj/bKAg7hc2gWqcuR9OD5Ft9PhBpIu8PLicPeis+xDd6YX2ncI8MCA64I9tftIA== - -is-absolute@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-absolute/-/is-absolute-1.0.0.tgz#395e1ae84b11f26ad1795e73c17378e48a301576" - integrity sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA== - dependencies: - is-relative "^1.0.0" - is-windows "^1.0.1" - -is-array-buffer@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.4.tgz#7a1f92b3d61edd2bc65d24f130530ea93d7fae98" - integrity sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw== - dependencies: - call-bind "^1.0.2" - get-intrinsic "^1.2.1" - -is-arrayish@^0.3.1: - version "0.3.2" - resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03" - integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== - -is-bigint@^1.0.1: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3" - integrity sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg== - dependencies: - has-bigints "^1.0.1" - -is-binary-path@~2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" - integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== - dependencies: - binary-extensions "^2.0.0" - -is-boolean-object@^1.1.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.2.tgz#5c6dc200246dd9321ae4b885a114bb1f75f63719" - integrity sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA== - dependencies: - call-bind "^1.0.2" - has-tostringtag "^1.0.0" - -is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.7: - version "1.2.7" - resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" - integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== - -is-core-module@^2.13.0, is-core-module@^2.13.1: - version "2.15.0" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.15.0.tgz#71c72ec5442ace7e76b306e9d48db361f22699ea" - integrity sha512-Dd+Lb2/zvk9SKy1TGCt1wFJFo/MWBPMX5x7KcvLajWTGuomczdQX61PvY5yK6SVACwpoexWo81IfFyoKY2QnTA== - dependencies: - hasown "^2.0.2" - -is-data-view@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-data-view/-/is-data-view-1.0.1.tgz#4b4d3a511b70f3dc26d42c03ca9ca515d847759f" - integrity sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w== - dependencies: - is-typed-array "^1.1.13" - -is-date-object@^1.0.1: - version "1.0.5" - resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f" - integrity sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ== - dependencies: - has-tostringtag "^1.0.0" - -is-extglob@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" - integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== - -is-fullwidth-code-point@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" - integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== - -is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: - version "4.0.3" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" - integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== - dependencies: - is-extglob "^2.1.1" - -is-negative-zero@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.3.tgz#ced903a027aca6381b777a5743069d7376a49747" - integrity sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw== - -is-number-object@^1.0.4: - version "1.0.7" - resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.7.tgz#59d50ada4c45251784e9904f5246c742f07a42fc" - integrity sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ== - dependencies: - has-tostringtag "^1.0.0" - -is-number@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" - integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== - -is-path-inside@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" - integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== - -is-plain-obj@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287" - integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA== - -is-plain-object@^2.0.3, is-plain-object@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" - integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== - dependencies: - isobject "^3.0.1" - -is-regex@^1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958" - integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg== - dependencies: - call-bind "^1.0.2" - has-tostringtag "^1.0.0" - -is-relative@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-relative/-/is-relative-1.0.0.tgz#a1bb6935ce8c5dba1e8b9754b9b2dcc020e2260d" - integrity sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA== - dependencies: - is-unc-path "^1.0.0" - -is-shared-array-buffer@^1.0.2, is-shared-array-buffer@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz#1237f1cba059cdb62431d378dcc37d9680181688" - integrity sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg== - dependencies: - call-bind "^1.0.7" - -is-stream@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" - integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== - -is-string@^1.0.5, is-string@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.7.tgz#0dd12bf2006f255bb58f695110eff7491eebc0fd" - integrity sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg== - dependencies: - has-tostringtag "^1.0.0" - -is-symbol@^1.0.2, is-symbol@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.4.tgz#a6dac93b635b063ca6872236de88910a57af139c" - integrity sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg== - dependencies: - has-symbols "^1.0.2" - -is-typed-array@^1.1.13: - version "1.1.13" - resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.13.tgz#d6c5ca56df62334959322d7d7dd1cca50debe229" - integrity sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw== - dependencies: - which-typed-array "^1.1.14" - -is-unc-path@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-unc-path/-/is-unc-path-1.0.0.tgz#d731e8898ed090a12c352ad2eaed5095ad322c9d" - integrity sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ== - dependencies: - unc-path-regex "^0.1.2" - -is-unicode-supported@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" - integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== - -is-weakref@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.0.2.tgz#9529f383a9338205e89765e0392efc2f100f06f2" - integrity sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ== - dependencies: - call-bind "^1.0.2" - -is-windows@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" - integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== - -isarray@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" - integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== - -isexe@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" - integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== - -isobject@^3.0.0, isobject@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" - integrity sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg== - -jit-grunt@^0.10.0: - version "0.10.0" - resolved "https://registry.yarnpkg.com/jit-grunt/-/jit-grunt-0.10.0.tgz#008c3a7fe1e96bd0d84e260ea1fa1783457f79c2" - integrity sha512-eT/f4c9wgZ3buXB7X1JY1w6uNtAV0bhrbOGf/mFmBb0CDNLUETJ/VRoydayWOI54tOoam0cz9RooVCn3QY1WoA== - -js-tokens@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" - integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== - -js-yaml@^3.13.1, js-yaml@~3.14.0: - version "3.14.1" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" - integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== - dependencies: - argparse "^1.0.7" - esprima "^4.0.0" - -js-yaml@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" - integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== - dependencies: - argparse "^2.0.1" - -json-buffer@3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" - integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== - -json-schema-traverse@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" - integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== - -json-schema-traverse@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" - integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== - -json-stable-stringify-without-jsonify@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" - integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== - -json5@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.2.tgz#63d98d60f21b313b77c4d6da18bfa69d80e1d593" - integrity sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA== - dependencies: - minimist "^1.2.0" - -keyv@^4.5.3: - version "4.5.4" - resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" - integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== - dependencies: - json-buffer "3.0.1" - -kind-of@^6.0.2: - version "6.0.3" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" - integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== - -kuler@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/kuler/-/kuler-2.0.0.tgz#e2c570a3800388fb44407e851531c1d670b061b3" - integrity sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A== - -lambda-local@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/lambda-local/-/lambda-local-2.2.0.tgz#733d183a4c3f2b16c6499b9ea72cec2f13278eef" - integrity sha512-bPcgpIXbHnVGfI/omZIlgucDqlf4LrsunwoKue5JdZeGybt8L6KyJz2Zu19ffuZwIwLj2NAI2ZyaqNT6/cetcg== - dependencies: - commander "^10.0.1" - dotenv "^16.3.1" - winston "^3.10.0" - -levn@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" - integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== - dependencies: - prelude-ls "^1.2.1" - type-check "~0.4.0" - -liftup@~3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/liftup/-/liftup-3.0.1.tgz#1cb81aff0f368464ed3a5f1a7286372d6b1a60ce" - integrity sha512-yRHaiQDizWSzoXk3APcA71eOI/UuhEkNN9DiW2Tt44mhYzX4joFoCZlxsSOF7RyeLlfqzFLQI1ngFq3ggMPhOw== - dependencies: - extend "^3.0.2" - findup-sync "^4.0.0" - fined "^1.2.0" - flagged-respawn "^1.0.1" - is-plain-object "^2.0.4" - object.map "^1.0.1" - rechoir "^0.7.0" - resolve "^1.19.0" - -livereload-js@^2.3.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/livereload-js/-/livereload-js-2.4.0.tgz#447c31cf1ea9ab52fc20db615c5ddf678f78009c" - integrity sha512-XPQH8Z2GDP/Hwz2PCDrh2mth4yFejwA1OZ/81Ti3LgKyhDcEjsSsqFWZojHG0va/duGd+WyosY7eXLDoOyqcPw== - -locate-path@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" - integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== - dependencies: - p-locate "^5.0.0" - -lodash-checkit@^2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/lodash-checkit/-/lodash-checkit-2.4.1.tgz#8b09c6b359a5d4de86f752ff9c231f1db5d23fd4" - integrity sha512-OAg5CqY04/dnsO8izxXqlleuj7z/dOk6yV0pm0TVtRaUwG5v2PGw4XWSIG/dLK0UWYk7g0/TCk8OCf50oVwv6w== - dependencies: - checkit "^0.7.0" - lodash "^4.17.21" - -lodash-match-pattern@^2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/lodash-match-pattern/-/lodash-match-pattern-2.3.1.tgz#d38f455a8b310bd91f7b2b4378297102a9b473c8" - integrity sha512-dpltpxoTqs94gGFm24VwHDyFh3/eNtqNjKrlnifIBLtnzYq0nAlNM6BIeLdGAfCWC/BwNtiLL1eKZTQpLVnY6A== - dependencies: - chalk "^4.1.0" - he "^1.2.0" - lodash-checkit "^2.4.1" - -lodash.merge@^4.6.2: - version "4.6.2" - resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" - integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== - -lodash.truncate@^4.4.2: - version "4.4.2" - resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193" - integrity sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw== - -lodash@^4.0.0, lodash@^4.17.10, lodash@^4.17.14, lodash@^4.17.21, lodash@~4.17.19, lodash@~4.17.21: - version "4.17.21" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" - integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== - -log-symbols@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" - integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== - dependencies: - chalk "^4.1.0" - is-unicode-supported "^0.1.0" - -logform@^2.6.0, logform@^2.6.1: - version "2.6.1" - resolved "https://registry.yarnpkg.com/logform/-/logform-2.6.1.tgz#71403a7d8cae04b2b734147963236205db9b3df0" - integrity sha512-CdaO738xRapbKIMVn2m4F6KTj4j7ooJ8POVnebSgKo3KBz5axNXRAL7ZdRjIV6NOr2Uf4vjtRkxrFETOioCqSA== - dependencies: - "@colors/colors" "1.6.0" - "@types/triple-beam" "^1.3.2" - fecha "^4.2.0" - ms "^2.1.1" - safe-stable-stringify "^2.3.1" - triple-beam "^1.3.0" - -loupe@^2.3.6: - version "2.3.7" - resolved "https://registry.yarnpkg.com/loupe/-/loupe-2.3.7.tgz#6e69b7d4db7d3ab436328013d37d1c8c3540c697" - integrity sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA== - dependencies: - get-func-name "^2.0.1" - -make-iterator@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/make-iterator/-/make-iterator-1.0.1.tgz#29b33f312aa8f547c4a5e490f56afcec99133ad6" - integrity sha512-pxiuXh0iVEq7VM7KMIhs5gxsfxCux2URptUQaXo4iZZJxBAzTPOLE2BumO5dbfVYq/hBJFBR/a1mFDmOx5AGmw== - dependencies: - kind-of "^6.0.2" - -map-cache@^0.2.0: - version "0.2.2" - resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" - integrity sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg== - -micromatch@^4.0.2, micromatch@^4.0.4: - version "4.0.8" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" - integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== - dependencies: - braces "^3.0.3" - picomatch "^2.3.1" - -minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" - integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== - dependencies: - brace-expansion "^1.1.7" - -minimatch@^5.0.1, minimatch@^5.1.6: - version "5.1.6" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96" - integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== - dependencies: - brace-expansion "^2.0.1" - -minimatch@~3.0.2, minimatch@~3.0.4: - version "3.0.8" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.8.tgz#5e6a59bd11e2ab0de1cfb843eb2d82e546c321c1" - integrity sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q== - dependencies: - brace-expansion "^1.1.7" - -minimist@^1.2.0, minimist@^1.2.6: - version "1.2.8" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" - integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== - -mocha@^10.0.0: - version "10.6.0" - resolved "https://registry.yarnpkg.com/mocha/-/mocha-10.6.0.tgz#465fc66c52613088e10018989a3b98d5e11954b9" - integrity sha512-hxjt4+EEB0SA0ZDygSS015t65lJw/I2yRCS3Ae+SJ5FrbzrXgfYwJr96f0OvIXdj7h4lv/vLCrH3rkiuizFSvw== - dependencies: - ansi-colors "^4.1.3" - browser-stdout "^1.3.1" - chokidar "^3.5.3" - debug "^4.3.5" - diff "^5.2.0" - escape-string-regexp "^4.0.0" - find-up "^5.0.0" - glob "^8.1.0" - he "^1.2.0" - js-yaml "^4.1.0" - log-symbols "^4.1.0" - minimatch "^5.1.6" - ms "^2.1.3" - serialize-javascript "^6.0.2" - strip-json-comments "^3.1.1" - supports-color "^8.1.1" - workerpool "^6.5.1" - yargs "^16.2.0" - yargs-parser "^20.2.9" - yargs-unparser "^2.0.0" - -ms@2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" - integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== - -ms@^2.1.1, ms@^2.1.3: - version "2.1.3" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" - integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== - -natural-compare@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" - integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== - -nopt@~3.0.6: - version "3.0.6" - resolved "https://registry.yarnpkg.com/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9" - integrity sha512-4GUt3kSEYmk4ITxzB/b9vaIDfUVWN/Ml1Fwl11IlnIG2iaJ9O6WXZ9SrYM9NLI8OCBieN2Y8SWC2oJV0RQ7qYg== - dependencies: - abbrev "1" - -nopt@~4.0.1: - version "4.0.3" - resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.3.tgz#a375cad9d02fd921278d954c2254d5aa57e15e48" - integrity sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg== - dependencies: - abbrev "1" - osenv "^0.1.4" - -normalize-path@^3.0.0, normalize-path@~3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" - integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== - -object-assign@^4.1.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" - integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== - -object-inspect@^1.13.1: - version "1.13.2" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.2.tgz#dea0088467fb991e67af4058147a24824a3043ff" - integrity sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g== - -object-keys@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" - integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== - -object.assign@^4.1.2, object.assign@^4.1.5: - version "4.1.5" - resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.5.tgz#3a833f9ab7fdb80fc9e8d2300c803d216d8fdbb0" - integrity sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ== - dependencies: - call-bind "^1.0.5" - define-properties "^1.2.1" - has-symbols "^1.0.3" - object-keys "^1.1.1" - -object.defaults@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/object.defaults/-/object.defaults-1.1.0.tgz#3a7f868334b407dea06da16d88d5cd29e435fecf" - integrity sha512-c/K0mw/F11k4dEUBMW8naXUuBuhxRCfG7W+yFy8EcijU/rSmazOUd1XAEEe6bC0OuXY4HUKjTJv7xbxIMqdxrA== - dependencies: - array-each "^1.0.1" - array-slice "^1.0.0" - for-own "^1.0.0" - isobject "^3.0.0" - -object.entries@^1.1.5: - version "1.1.8" - resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.8.tgz#bffe6f282e01f4d17807204a24f8edd823599c41" - integrity sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ== - dependencies: - call-bind "^1.0.7" - define-properties "^1.2.1" - es-object-atoms "^1.0.0" - -object.fromentries@^2.0.7: - version "2.0.8" - resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.8.tgz#f7195d8a9b97bd95cbc1999ea939ecd1a2b00c65" - integrity sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ== - dependencies: - call-bind "^1.0.7" - define-properties "^1.2.1" - es-abstract "^1.23.2" - es-object-atoms "^1.0.0" - -object.groupby@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/object.groupby/-/object.groupby-1.0.3.tgz#9b125c36238129f6f7b61954a1e7176148d5002e" - integrity sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ== - dependencies: - call-bind "^1.0.7" - define-properties "^1.2.1" - es-abstract "^1.23.2" - -object.map@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/object.map/-/object.map-1.0.1.tgz#cf83e59dc8fcc0ad5f4250e1f78b3b81bd801d37" - integrity sha512-3+mAJu2PLfnSVGHwIWubpOFLscJANBKuB/6A4CxBstc4aqwQY0FWcsppuy4jU5GSB95yES5JHSI+33AWuS4k6w== - dependencies: - for-own "^1.0.0" - make-iterator "^1.0.0" - -object.pick@^1.2.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747" - integrity sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ== - dependencies: - isobject "^3.0.1" - -object.values@^1.1.7: - version "1.2.0" - resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.2.0.tgz#65405a9d92cee68ac2d303002e0b8470a4d9ab1b" - integrity sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ== - dependencies: - call-bind "^1.0.7" - define-properties "^1.2.1" - es-object-atoms "^1.0.0" - -once@^1.3.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" - integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== - dependencies: - wrappy "1" - -one-time@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/one-time/-/one-time-1.0.0.tgz#e06bc174aed214ed58edede573b433bbf827cb45" - integrity sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g== - dependencies: - fn.name "1.x.x" - -optionator@^0.9.1, optionator@^0.9.3: - version "0.9.4" - resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.4.tgz#7ea1c1a5d91d764fb282139c88fe11e182a3a734" - integrity sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g== - dependencies: - deep-is "^0.1.3" - fast-levenshtein "^2.0.6" - levn "^0.4.1" - prelude-ls "^1.2.1" - type-check "^0.4.0" - word-wrap "^1.2.5" - -os-homedir@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" - integrity sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ== - -os-tmpdir@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" - integrity sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g== - -osenv@^0.1.4: - version "0.1.5" - resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.5.tgz#85cdfafaeb28e8677f416e287592b5f3f49ea410" - integrity sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g== - dependencies: - os-homedir "^1.0.0" - os-tmpdir "^1.0.0" - -p-limit@^3.0.2: - version "3.1.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" - integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== - dependencies: - yocto-queue "^0.1.0" - -p-locate@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" - integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== - dependencies: - p-limit "^3.0.2" - -parent-module@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" - integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== - dependencies: - callsites "^3.0.0" - -parse-filepath@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/parse-filepath/-/parse-filepath-1.0.2.tgz#a632127f53aaf3d15876f5872f3ffac763d6c891" - integrity sha512-FwdRXKCohSVeXqwtYonZTXtbGJKrn+HNyWDYVcp5yuJlesTwNH4rsmRZ+GrKAPJ5bLpRxESMeS+Rl0VCHRvB2Q== - dependencies: - is-absolute "^1.0.0" - map-cache "^0.2.0" - path-root "^0.1.1" - -parse-passwd@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6" - integrity sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q== - -path-exists@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" - integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== - -path-is-absolute@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" - integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== - -path-key@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" - integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== - -path-parse@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" - integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== - -path-root-regex@^0.1.0: - version "0.1.2" - resolved "https://registry.yarnpkg.com/path-root-regex/-/path-root-regex-0.1.2.tgz#bfccdc8df5b12dc52c8b43ec38d18d72c04ba96d" - integrity sha512-4GlJ6rZDhQZFE0DPVKh0e9jmZ5egZfxTkp7bcRDuPlJXbAwhxcl2dINPUAsjLdejqaLsCeg8axcLjIbvBjN4pQ== - -path-root@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/path-root/-/path-root-0.1.1.tgz#9a4a6814cac1c0cd73360a95f32083c8ea4745b7" - integrity sha512-QLcPegTHF11axjfojBIoDygmS2E3Lf+8+jI6wOVmNVenrKSo3mFdSGiIgdSHenczw3wPtlVMQaFVwGmM7BJdtg== - dependencies: - path-root-regex "^0.1.0" - -pathval@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.1.tgz#8534e77a77ce7ac5a2512ea21e0fdb8fcf6c3d8d" - integrity sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ== - -picocolors@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.1.tgz#a8ad579b571952f0e5d25892de5445bcfe25aaa1" - integrity sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew== - -picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" - integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== - -possible-typed-array-names@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz#89bb63c6fada2c3e90adc4a647beeeb39cc7bf8f" - integrity sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q== - -prelude-ls@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" - integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== - -progress@^2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" - integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== - -punycode@^2.1.0: - version "2.3.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" - integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== - -qs@^6.4.0: - version "6.12.3" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.12.3.tgz#e43ce03c8521b9c7fd7f1f13e514e5ca37727754" - integrity sha512-AWJm14H1vVaO/iNZ4/hO+HyaTehuy9nRqVdkTqlJt0HWvBiBIEXFmb4C0DGeYo3Xes9rrEW+TxHsaigCbN5ICQ== - dependencies: - side-channel "^1.0.6" - -queue-microtask@^1.2.2: - version "1.2.3" - resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" - integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== - -randombytes@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" - integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== - dependencies: - safe-buffer "^5.1.0" - -raw-body@~1.1.0: - version "1.1.7" - resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-1.1.7.tgz#1d027c2bfa116acc6623bca8f00016572a87d425" - integrity sha512-WmJJU2e9Y6M5UzTOkHaM7xJGAPQD8PNzx3bAd2+uhZAim6wDk6dAZxPVYLF67XhbR4hmKGh33Lpmh4XWrCH5Mg== - dependencies: - bytes "1" - string_decoder "0.10" - -readable-stream@^3.4.0, readable-stream@^3.6.2: - version "3.6.2" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" - integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== - dependencies: - inherits "^2.0.3" - string_decoder "^1.1.1" - util-deprecate "^1.0.1" - -readdirp@~3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" - integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== - dependencies: - picomatch "^2.2.1" - -rechoir@^0.7.0: - version "0.7.1" - resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.7.1.tgz#9478a96a1ca135b5e88fc027f03ee92d6c645686" - integrity sha512-/njmZ8s1wVeR6pjTZ+0nCnv8SpZNRMT2D1RLOJQESlYFDBvwpTA4KWJpZ+sBJ4+vhjILRcK7JIFdGCdxEAAitg== - dependencies: - resolve "^1.9.0" - -regexp.prototype.flags@^1.5.2: - version "1.5.2" - resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz#138f644a3350f981a858c44f6bb1a61ff59be334" - integrity sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw== - dependencies: - call-bind "^1.0.6" - define-properties "^1.2.1" - es-errors "^1.3.0" - set-function-name "^2.0.1" - -regexpp@^3.1.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2" - integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg== - -require-directory@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" - integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== - -require-from-string@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" - integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== - -resolve-dir@^1.0.0, resolve-dir@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/resolve-dir/-/resolve-dir-1.0.1.tgz#79a40644c362be82f26effe739c9bb5382046f43" - integrity sha512-R7uiTjECzvOsWSfdM0QKFNBVFcK27aHOUwdvK53BcW8zqnGdYp0Fbj82cy54+2A4P2tFM22J5kRfe1R+lM/1yg== - dependencies: - expand-tilde "^2.0.0" - global-modules "^1.0.0" - -resolve-from@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" - integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== - -resolve@^1.19.0, resolve@^1.22.4, resolve@^1.9.0: - version "1.22.8" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d" - integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== - dependencies: - is-core-module "^2.13.0" - path-parse "^1.0.7" - supports-preserve-symlinks-flag "^1.0.0" - -reusify@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" - integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== - -rimraf@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" - integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== - dependencies: - glob "^7.1.3" - -run-parallel@^1.1.9: - version "1.2.0" - resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" - integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== - dependencies: - queue-microtask "^1.2.2" - -safe-array-concat@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.1.2.tgz#81d77ee0c4e8b863635227c721278dd524c20edb" - integrity sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q== - dependencies: - call-bind "^1.0.7" - get-intrinsic "^1.2.4" - has-symbols "^1.0.3" - isarray "^2.0.5" - -safe-buffer@>=5.1.0, safe-buffer@^5.1.0, safe-buffer@~5.2.0: - version "5.2.1" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" - integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== - -safe-json-parse@~1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/safe-json-parse/-/safe-json-parse-1.0.1.tgz#3e76723e38dfdda13c9b1d29a1e07ffee4b30b57" - integrity sha512-o0JmTu17WGUaUOHa1l0FPGXKBfijbxK6qoHzlkihsDXxzBHvJcA7zgviKR92Xs841rX9pK16unfphLq0/KqX7A== - -safe-regex-test@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.0.3.tgz#a5b4c0f06e0ab50ea2c395c14d8371232924c377" - integrity sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw== - dependencies: - call-bind "^1.0.6" - es-errors "^1.3.0" - is-regex "^1.1.4" - -safe-stable-stringify@^2.3.1: - version "2.4.3" - resolved "https://registry.yarnpkg.com/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz#138c84b6f6edb3db5f8ef3ef7115b8f55ccbf886" - integrity sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g== - -"safer-buffer@>= 2.1.2 < 3.0.0": - version "2.1.2" - resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" - integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== - -semver@^6.3.0, semver@^6.3.1: - version "6.3.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" - integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== - -semver@^7.2.1: - version "7.6.3" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143" - integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== - -serialize-javascript@^6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.2.tgz#defa1e055c83bf6d59ea805d8da862254eb6a6c2" - integrity sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g== - dependencies: - randombytes "^2.1.0" - -set-function-length@^1.2.1: - version "1.2.2" - resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449" - integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg== - dependencies: - define-data-property "^1.1.4" - es-errors "^1.3.0" - function-bind "^1.1.2" - get-intrinsic "^1.2.4" - gopd "^1.0.1" - has-property-descriptors "^1.0.2" - -set-function-name@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/set-function-name/-/set-function-name-2.0.2.tgz#16a705c5a0dc2f5e638ca96d8a8cd4e1c2b90985" - integrity sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ== - dependencies: - define-data-property "^1.1.4" - es-errors "^1.3.0" - functions-have-names "^1.2.3" - has-property-descriptors "^1.0.2" - -shebang-command@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" - integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== - dependencies: - shebang-regex "^3.0.0" - -shebang-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" - integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== - -side-channel@^1.0.4, side-channel@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.6.tgz#abd25fb7cd24baf45466406b1096b7831c9215f2" - integrity sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA== - dependencies: - call-bind "^1.0.7" - es-errors "^1.3.0" - get-intrinsic "^1.2.4" - object-inspect "^1.13.1" - -simple-swizzle@^0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" - integrity sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg== - dependencies: - is-arrayish "^0.3.1" - -slice-ansi@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-4.0.0.tgz#500e8dd0fd55b05815086255b3195adf2a45fe6b" - integrity sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ== - dependencies: - ansi-styles "^4.0.0" - astral-regex "^2.0.0" - is-fullwidth-code-point "^3.0.0" - -sprintf-js@^1.1.1: - version "1.1.3" - resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.3.tgz#4914b903a2f8b685d17fdf78a70e917e872e444a" - integrity sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA== - -sprintf-js@~1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" - integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== - -stack-trace@0.0.x: - version "0.0.10" - resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0" - integrity sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg== - -string-template@~0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/string-template/-/string-template-0.2.1.tgz#42932e598a352d01fc22ec3367d9d84eec6c9add" - integrity sha512-Yptehjogou2xm4UJbxJ4CxgZx12HBfeystp0y3x7s4Dj32ltVVG1Gg8YhKjHZkHicuKpZX/ffilA8505VbUbpw== - -string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -string.prototype.trim@^1.2.9: - version "1.2.9" - resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz#b6fa326d72d2c78b6df02f7759c73f8f6274faa4" - integrity sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw== - dependencies: - call-bind "^1.0.7" - define-properties "^1.2.1" - es-abstract "^1.23.0" - es-object-atoms "^1.0.0" - -string.prototype.trimend@^1.0.8: - version "1.0.8" - resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz#3651b8513719e8a9f48de7f2f77640b26652b229" - integrity sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ== - dependencies: - call-bind "^1.0.7" - define-properties "^1.2.1" - es-object-atoms "^1.0.0" - -string.prototype.trimstart@^1.0.8: - version "1.0.8" - resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz#7ee834dda8c7c17eff3118472bb35bfedaa34dde" - integrity sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg== - dependencies: - call-bind "^1.0.7" - define-properties "^1.2.1" - es-object-atoms "^1.0.0" - -string_decoder@0.10: - version "0.10.31" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" - integrity sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ== - -string_decoder@^1.1.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" - integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== - dependencies: - safe-buffer "~5.2.0" - -strip-ansi@^6.0.0, strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-bom@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" - integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA== - -strip-json-comments@^3.1.0, strip-json-comments@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" - integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== - -supports-color@^5.3.0: - version "5.5.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" - integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== - dependencies: - has-flag "^3.0.0" - -supports-color@^7.1.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" - integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== - dependencies: - has-flag "^4.0.0" - -supports-color@^8.1.1: - version "8.1.1" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" - integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== - dependencies: - has-flag "^4.0.0" - -supports-preserve-symlinks-flag@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" - integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== - -table@^6.0.9: - version "6.8.2" - resolved "https://registry.yarnpkg.com/table/-/table-6.8.2.tgz#c5504ccf201213fa227248bdc8c5569716ac6c58" - integrity sha512-w2sfv80nrAh2VCbqR5AK27wswXhqcck2AhfnNW76beQXskGZ1V12GwS//yYVa3d3fcvAip2OUnbDAjW2k3v9fA== - dependencies: - ajv "^8.0.1" - lodash.truncate "^4.4.2" - slice-ansi "^4.0.0" - string-width "^4.2.3" - strip-ansi "^6.0.1" - -text-hex@1.0.x: - version "1.0.0" - resolved "https://registry.yarnpkg.com/text-hex/-/text-hex-1.0.0.tgz#69dc9c1b17446ee79a92bf5b884bb4b9127506f5" - integrity sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg== - -text-table@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" - integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== - -tiny-lr@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/tiny-lr/-/tiny-lr-1.1.1.tgz#9fa547412f238fedb068ee295af8b682c98b2aab" - integrity sha512-44yhA3tsaRoMOjQQ+5v5mVdqef+kH6Qze9jTpqtVufgYjYt08zyZAwNwwVBj3i1rJMnR52IxOW0LK0vBzgAkuA== - dependencies: - body "^5.1.0" - debug "^3.1.0" - faye-websocket "~0.10.0" - livereload-js "^2.3.0" - object-assign "^4.1.0" - qs "^6.4.0" - -to-regex-range@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" - integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== - dependencies: - is-number "^7.0.0" - -triple-beam@^1.3.0: - version "1.4.1" - resolved "https://registry.yarnpkg.com/triple-beam/-/triple-beam-1.4.1.tgz#6fde70271dc6e5d73ca0c3b24e2d92afb7441984" - integrity sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg== - -tsconfig-paths@^3.15.0: - version "3.15.0" - resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz#5299ec605e55b1abb23ec939ef15edaf483070d4" - integrity sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg== - dependencies: - "@types/json5" "^0.0.29" - json5 "^1.0.2" - minimist "^1.2.6" - strip-bom "^3.0.0" - -type-check@^0.4.0, type-check@~0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" - integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== - dependencies: - prelude-ls "^1.2.1" - -type-detect@^4.0.0, type-detect@^4.0.8: - version "4.0.8" - resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" - integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== - -type-fest@^0.20.2: - version "0.20.2" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" - integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== - -typed-array-buffer@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz#1867c5d83b20fcb5ccf32649e5e2fc7424474ff3" - integrity sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ== - dependencies: - call-bind "^1.0.7" - es-errors "^1.3.0" - is-typed-array "^1.1.13" - -typed-array-byte-length@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz#d92972d3cff99a3fa2e765a28fcdc0f1d89dec67" - integrity sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw== - dependencies: - call-bind "^1.0.7" - for-each "^0.3.3" - gopd "^1.0.1" - has-proto "^1.0.3" - is-typed-array "^1.1.13" - -typed-array-byte-offset@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz#f9ec1acb9259f395093e4567eb3c28a580d02063" - integrity sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA== - dependencies: - available-typed-arrays "^1.0.7" - call-bind "^1.0.7" - for-each "^0.3.3" - gopd "^1.0.1" - has-proto "^1.0.3" - is-typed-array "^1.1.13" - -typed-array-length@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.6.tgz#57155207c76e64a3457482dfdc1c9d1d3c4c73a3" - integrity sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g== - dependencies: - call-bind "^1.0.7" - for-each "^0.3.3" - gopd "^1.0.1" - has-proto "^1.0.3" - is-typed-array "^1.1.13" - possible-typed-array-names "^1.0.0" - -unbox-primitive@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz#29032021057d5e6cdbd08c5129c226dff8ed6f9e" - integrity sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw== - dependencies: - call-bind "^1.0.2" - has-bigints "^1.0.2" - has-symbols "^1.0.3" - which-boxed-primitive "^1.0.2" - -unc-path-regex@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/unc-path-regex/-/unc-path-regex-0.1.2.tgz#e73dd3d7b0d7c5ed86fbac6b0ae7d8c6a69d50fa" - integrity sha512-eXL4nmJT7oCpkZsHZUOJo8hcX3GbsiDOa0Qu9F646fi8dT3XuSVopVqAcEiVzSKKH7UoDti23wNX3qGFxcW5Qg== - -underscore.string@~3.3.5: - version "3.3.6" - resolved "https://registry.yarnpkg.com/underscore.string/-/underscore.string-3.3.6.tgz#ad8cf23d7423cb3b53b898476117588f4e2f9159" - integrity sha512-VoC83HWXmCrF6rgkyxS9GHv8W9Q5nhMKho+OadDJGzL2oDYbYEppBaCMH6pFlwLeqj2QS+hhkw2kpXkSdD1JxQ== - dependencies: - sprintf-js "^1.1.1" - util-deprecate "^1.0.2" - -uri-js@^4.2.2: - version "4.4.1" - resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" - integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== - dependencies: - punycode "^2.1.0" - -util-deprecate@^1.0.1, util-deprecate@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" - integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== - -v8-compile-cache@^2.0.3: - version "2.4.0" - resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.4.0.tgz#cdada8bec61e15865f05d097c5f4fd30e94dc128" - integrity sha512-ocyWc3bAHBB/guyqJQVI5o4BZkPhznPYUG2ea80Gond/BgNWpap8TOmLSeeQG7bnh2KMISxskdADG59j7zruhw== - -v8flags@~3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/v8flags/-/v8flags-3.2.0.tgz#b243e3b4dfd731fa774e7492128109a0fe66d656" - integrity sha512-mH8etigqMfiGWdeXpaaqGfs6BndypxusHHcv2qSHyZkGEznCd/qAXCWWRzeowtL54147cktFOC4P5y+kl8d8Jg== - dependencies: - homedir-polyfill "^1.0.1" - -websocket-driver@>=0.5.1: - version "0.7.4" - resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.4.tgz#89ad5295bbf64b480abcba31e4953aca706f5760" - integrity sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg== - dependencies: - http-parser-js ">=0.5.1" - safe-buffer ">=5.1.0" - websocket-extensions ">=0.1.1" - -websocket-extensions@>=0.1.1: - version "0.1.4" - resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42" - integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg== - -which-boxed-primitive@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" - integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg== - dependencies: - is-bigint "^1.0.1" - is-boolean-object "^1.1.0" - is-number-object "^1.0.4" - is-string "^1.0.5" - is-symbol "^1.0.3" - -which-typed-array@^1.1.14, which-typed-array@^1.1.15: - version "1.1.15" - resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.15.tgz#264859e9b11a649b388bfaaf4f767df1f779b38d" - integrity sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA== - dependencies: - available-typed-arrays "^1.0.7" - call-bind "^1.0.7" - for-each "^0.3.3" - gopd "^1.0.1" - has-tostringtag "^1.0.2" - -which@^1.2.14: - version "1.3.1" - resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" - integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== - dependencies: - isexe "^2.0.0" - -which@^2.0.1, which@~2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" - integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== - dependencies: - isexe "^2.0.0" - -winston-transport@^4.7.0: - version "4.7.1" - resolved "https://registry.yarnpkg.com/winston-transport/-/winston-transport-4.7.1.tgz#52ff1bcfe452ad89991a0aaff9c3b18e7f392569" - integrity sha512-wQCXXVgfv/wUPOfb2x0ruxzwkcZfxcktz6JIMUaPLmcNhO4bZTwA/WtDWK74xV3F2dKu8YadrFv0qhwYjVEwhA== - dependencies: - logform "^2.6.1" - readable-stream "^3.6.2" - triple-beam "^1.3.0" - -winston@^3.10.0: - version "3.13.1" - resolved "https://registry.yarnpkg.com/winston/-/winston-3.13.1.tgz#53ddadb9c2332eb12cff8306413b3480dc82b6c3" - integrity sha512-SvZit7VFNvXRzbqGHsv5KSmgbEYR5EiQfDAL9gxYkRqa934Hnk++zze0wANKtMHcy/gI4W/3xmSDwlhf865WGw== - dependencies: - "@colors/colors" "^1.6.0" - "@dabh/diagnostics" "^2.0.2" - async "^3.2.3" - is-stream "^2.0.0" - logform "^2.6.0" - one-time "^1.0.0" - readable-stream "^3.4.0" - safe-stable-stringify "^2.3.1" - stack-trace "0.0.x" - triple-beam "^1.3.0" - winston-transport "^4.7.0" - -word-wrap@^1.2.5: - version "1.2.5" - resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" - integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== - -workerpool@^6.5.1: - version "6.5.1" - resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.5.1.tgz#060f73b39d0caf97c6db64da004cd01b4c099544" - integrity sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA== - -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - -wrappy@1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" - integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== - -y18n@^5.0.5: - version "5.0.8" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" - integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== - -yargs-parser@^20.2.2, yargs-parser@^20.2.9: - version "20.2.9" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" - integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== - -yargs-unparser@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/yargs-unparser/-/yargs-unparser-2.0.0.tgz#f131f9226911ae5d9ad38c432fe809366c2325eb" - integrity sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA== - dependencies: - camelcase "^6.0.0" - decamelize "^4.0.0" - flat "^5.0.2" - is-plain-obj "^2.1.0" - -yargs@^16.2.0: - version "16.2.0" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" - integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== - dependencies: - cliui "^7.0.2" - escalade "^3.1.1" - get-caller-file "^2.0.5" - require-directory "^2.1.1" - string-width "^4.2.0" - y18n "^5.0.5" - yargs-parser "^20.2.2" - -yocto-queue@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" - integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== diff --git a/backend/compact-connect/lambdas/nodejs/eslint.config.mjs b/backend/compact-connect/lambdas/nodejs/eslint.config.mjs index 7482c561b..bea30f936 100644 --- a/backend/compact-connect/lambdas/nodejs/eslint.config.mjs +++ b/backend/compact-connect/lambdas/nodejs/eslint.config.mjs @@ -1,11 +1,3 @@ -// -// eslint.config.mjs -// Adapted from IA Website (all vue-related stuff removed): -// https://github.com/InspiringApps/IA-Website/blob/cfbaf96be5841cb762a7dcd00a4ccca10fafa672/2021v2/webroot/.eslintrc.js -// -// Created by InspiringApps on 10/18/2024. -// Copyright © 2024. All rights reserved. -// import typescriptParser from '@typescript-eslint/parser'; import typescriptPlugin from '@typescript-eslint/eslint-plugin'; diff --git a/backend/compact-connect/lambdas/nodejs/package.json b/backend/compact-connect/lambdas/nodejs/package.json index accd6966b..18dd179c4 100644 --- a/backend/compact-connect/lambdas/nodejs/package.json +++ b/backend/compact-connect/lambdas/nodejs/package.json @@ -12,7 +12,7 @@ "test:csp": "mocha cloudfront-csp/test", "lint:csp": "grunt check", "lint": "eslint '**/*.ts' && grunt check", - "test": "NODE_OPTIONS=\"$NODE_OPTIONS --experimental-vm-modules\" jest --coverage && mocha cloudfront-csp/test", + "test": "NODE_OPTIONS=\"$NODE_OPTIONS --experimental-vm-modules\" jest --coverage", "audit:dependencies": "yarn audit --groups dependencies --level moderate", "grunt": "grunt" }, diff --git a/backend/compact-connect/lambdas/python/compact-configuration/.coveragerc b/backend/compact-connect/lambdas/python/compact-configuration/.coveragerc deleted file mode 100644 index 99c409d65..000000000 --- a/backend/compact-connect/lambdas/python/compact-configuration/.coveragerc +++ /dev/null @@ -1,10 +0,0 @@ -[run] -data_file = ../../../.coverage - -omit = - */cdk.out/* - */smoke-test/* - */tests/* - -[report] -skip_empty = true diff --git a/backend/compact-connect/lambdas/python/custom-resources/.coveragerc b/backend/compact-connect/lambdas/python/custom-resources/.coveragerc deleted file mode 100644 index 193e8ec8a..000000000 --- a/backend/compact-connect/lambdas/python/custom-resources/.coveragerc +++ /dev/null @@ -1,10 +0,0 @@ -[run] -data_file = ../../../../.coverage - -omit = - */cdk.out/* - */smoke-test/* - */tests/* - -[report] -skip_empty = true diff --git a/backend/compact-connect/lambdas/python/data-events/.coveragerc b/backend/compact-connect/lambdas/python/data-events/.coveragerc deleted file mode 100644 index 193e8ec8a..000000000 --- a/backend/compact-connect/lambdas/python/data-events/.coveragerc +++ /dev/null @@ -1,10 +0,0 @@ -[run] -data_file = ../../../../.coverage - -omit = - */cdk.out/* - */smoke-test/* - */tests/* - -[report] -skip_empty = true diff --git a/backend/compact-connect/lambdas/python/provider-data-v1/.coveragerc b/backend/compact-connect/lambdas/python/provider-data-v1/.coveragerc deleted file mode 100644 index 193e8ec8a..000000000 --- a/backend/compact-connect/lambdas/python/provider-data-v1/.coveragerc +++ /dev/null @@ -1,10 +0,0 @@ -[run] -data_file = ../../../../.coverage - -omit = - */cdk.out/* - */smoke-test/* - */tests/* - -[report] -skip_empty = true diff --git a/backend/compact-connect/lambdas/python/purchases/.coveragerc b/backend/compact-connect/lambdas/python/purchases/.coveragerc deleted file mode 100644 index 193e8ec8a..000000000 --- a/backend/compact-connect/lambdas/python/purchases/.coveragerc +++ /dev/null @@ -1,10 +0,0 @@ -[run] -data_file = ../../../../.coverage - -omit = - */cdk.out/* - */smoke-test/* - */tests/* - -[report] -skip_empty = true diff --git a/backend/compact-connect/lambdas/python/staff-user-pre-token/.coveragerc b/backend/compact-connect/lambdas/python/staff-user-pre-token/.coveragerc deleted file mode 100644 index 193e8ec8a..000000000 --- a/backend/compact-connect/lambdas/python/staff-user-pre-token/.coveragerc +++ /dev/null @@ -1,10 +0,0 @@ -[run] -data_file = ../../../../.coverage - -omit = - */cdk.out/* - */smoke-test/* - */tests/* - -[report] -skip_empty = true diff --git a/backend/compact-connect/lambdas/python/staff-users/.coveragerc b/backend/compact-connect/lambdas/python/staff-users/.coveragerc deleted file mode 100644 index 193e8ec8a..000000000 --- a/backend/compact-connect/lambdas/python/staff-users/.coveragerc +++ /dev/null @@ -1,10 +0,0 @@ -[run] -data_file = ../../../../.coverage - -omit = - */cdk.out/* - */smoke-test/* - */tests/* - -[report] -skip_empty = true diff --git a/backend/compact-connect/pipeline/__init__.py b/backend/compact-connect/pipeline/__init__.py index 61555e969..59a46dfa6 100644 --- a/backend/compact-connect/pipeline/__init__.py +++ b/backend/compact-connect/pipeline/__init__.py @@ -9,15 +9,13 @@ from aws_cdk.pipelines import CodeBuildStep from aws_cdk.pipelines import CodePipeline as CdkCodePipeline from cdk_nag import NagSuppressions +from constructs import Construct + from common_constructs.access_logs_bucket import AccessLogsBucket from common_constructs.alarm_topic import AlarmTopic from common_constructs.stack import Stack -from constructs import Construct - from pipeline.backend_pipeline import BackendPipeline from pipeline.backend_stage import BackendStage -from pipeline.frontend_pipeline import FrontendPipeline -from pipeline.frontend_stage import FrontendStage from pipeline.synth_substitute_stage import SynthSubstituteStage TEST_ENVIRONMENT_NAME = 'test' @@ -339,63 +337,6 @@ def _add_nag_suppressions_for_trigger_pipeline_step_role(self, trigger_pipeline_ ) -class BaseFrontendPipelineStack(BasePipelineStack): - """ - Base class for frontend pipeline stacks. - Implements common functionality for all frontend pipeline stacks. - """ - - def __init__( - self, - scope: Construct, - construct_id: str, - environment_name: str, - env: Environment, - removal_policy: RemovalPolicy, - pipeline_access_logs_bucket: IBucket, - **kwargs, - ): - super().__init__( - scope, - construct_id, - environment_name=environment_name, - env=env, - removal_policy=removal_policy, - pipeline_access_logs_bucket=pipeline_access_logs_bucket, - **kwargs, - ) - - def _determine_frontend_stage(self, construct_id, environment_name, environment_context): - """ - Return either a real FrontendStage or a SynthSubstituteStage depending on pipeline synthesis context. - - This method centralizes the stage creation logic to conditionally create a lightweight substitute - stage during pipeline synthesis when the stage is not part of the pipeline being synthesized. - """ - # Check if we're in pipeline synthesis mode and if we're synthesizing this specific pipeline - action = self.node.try_get_context('action') - pipeline_stack_name = self.node.try_get_context('pipelineStack') - - # If we're in pipeline synthesis mode and this is not the pipeline being synthesized, - # use a lightweight substitute stage - if ( - action == PIPELINE_SYNTH_ACTION and pipeline_stack_name != self.stack_name - ) or action == BOOTSTRAP_DEPLOY_ACTION: - return SynthSubstituteStage( - self, - 'SubstituteFrontendStage', - environment_context=environment_context, - ) - - # Otherwise, use the real stage for deployment - return FrontendStage( - self, - construct_id, - environment_name=environment_name, - environment_context=environment_context, - ) - - class TestBackendPipelineStack(BaseBackendPipelineStack): """Pipeline stack for the test backend environment, triggered by the development branch.""" @@ -458,61 +399,6 @@ def __init__( self._add_nag_suppressions_for_trigger_pipeline_step_role(trigger_frontend_pipeline_step) -class TestFrontendPipelineStack(BaseFrontendPipelineStack): - """Pipeline stack for the test frontend environment.""" - - def __init__( - self, - scope: Construct, - construct_id: str, - *, - pipeline_shared_encryption_key: IKey, - pipeline_alarm_topic: ITopic, - pipeline_access_logs_bucket: IBucket, - cdk_path: str, - **kwargs, - ): - super().__init__( - scope, - construct_id, - environment_name=TEST_ENVIRONMENT_NAME, - removal_policy=RemovalPolicy.DESTROY, - pipeline_access_logs_bucket=pipeline_access_logs_bucket, - **kwargs, - ) - - # Allows us to override the default branching scheme for the test environment, via context variable - pre_prod_trigger_branch = self.pipeline_environment_context.get('pre_prod_trigger_branch', 'development') - - self.pre_prod_frontend_pipeline = FrontendPipeline( - self, - 'TestFrontendPipeline', - pipeline_name=self._get_frontend_pipeline_name(), - github_repo_string=self.github_repo_string, - cdk_path=cdk_path, - connection_arn=self.connection_arn, - source_branch=pre_prod_trigger_branch, - encryption_key=pipeline_shared_encryption_key, - alarm_topic=pipeline_alarm_topic, - access_logs_bucket=self.access_logs_bucket, - ssm_parameter=self.parameter, - pipeline_stack_name=self.stack_name, - environment_context=self.pipeline_environment_context, - self_mutation=True, - removal_policy=self.removal_policy, - ) - - self.pre_prod_frontend_stage = self._determine_frontend_stage( - construct_id='TestFrontend', - environment_name=TEST_ENVIRONMENT_NAME, - environment_context=self.ssm_context['environments'][TEST_ENVIRONMENT_NAME], - ) - - self.pre_prod_frontend_pipeline.add_stage(self.pre_prod_frontend_stage) - self.pre_prod_frontend_pipeline.build_pipeline() - self._add_pipeline_cdk_assume_role_policy(self.pre_prod_frontend_pipeline) - - class BetaBackendPipelineStack(BaseBackendPipelineStack): """Pipeline stack for the beta backend environment, triggered by the main branch.""" @@ -572,58 +458,6 @@ def __init__( self._add_nag_suppressions_for_trigger_pipeline_step_role(trigger_frontend_pipeline_step) -class BetaFrontendPipelineStack(BaseFrontendPipelineStack): - """Pipeline stack for the beta frontend environment.""" - - def __init__( - self, - scope: Construct, - construct_id: str, - *, - pipeline_shared_encryption_key: IKey, - pipeline_alarm_topic: ITopic, - pipeline_access_logs_bucket: IBucket, - cdk_path: str, - **kwargs, - ): - super().__init__( - scope, - construct_id, - environment_name=BETA_ENVIRONMENT_NAME, - removal_policy=RemovalPolicy.RETAIN, - pipeline_access_logs_bucket=pipeline_access_logs_bucket, - **kwargs, - ) - - self.beta_frontend_pipeline = FrontendPipeline( - self, - 'BetaFrontendPipeline', - pipeline_name=self._get_frontend_pipeline_name(), - github_repo_string=self.github_repo_string, - cdk_path=cdk_path, - connection_arn=self.connection_arn, - source_branch='main', - encryption_key=pipeline_shared_encryption_key, - alarm_topic=pipeline_alarm_topic, - access_logs_bucket=self.access_logs_bucket, - ssm_parameter=self.parameter, - pipeline_stack_name=self.stack_name, - environment_context=self.pipeline_environment_context, - self_mutation=True, - removal_policy=self.removal_policy, - ) - - self.beta_frontend_stage = self._determine_frontend_stage( - construct_id='BetaFrontend', - environment_name=BETA_ENVIRONMENT_NAME, - environment_context=self.ssm_context['environments'][BETA_ENVIRONMENT_NAME], - ) - - self.beta_frontend_pipeline.add_stage(self.beta_frontend_stage) - self.beta_frontend_pipeline.build_pipeline() - self._add_pipeline_cdk_assume_role_policy(self.beta_frontend_pipeline) - - class ProdBackendPipelineStack(BaseBackendPipelineStack): """Pipeline stack for the production backend environment, triggered by the main branch.""" @@ -684,55 +518,3 @@ def __init__( # the following must be called after the pipeline is built self._add_pipeline_cdk_assume_role_policy(self.prod_pipeline) self._add_nag_suppressions_for_trigger_pipeline_step_role(trigger_frontend_pipeline_step) - - -class ProdFrontendPipelineStack(BaseFrontendPipelineStack): - """Pipeline stack for the production frontend environment.""" - - def __init__( - self, - scope: Construct, - construct_id: str, - *, - pipeline_shared_encryption_key: IKey, - pipeline_alarm_topic: ITopic, - pipeline_access_logs_bucket: IBucket, - cdk_path: str, - **kwargs, - ): - super().__init__( - scope, - construct_id, - environment_name=PROD_ENVIRONMENT_NAME, - removal_policy=RemovalPolicy.RETAIN, - pipeline_access_logs_bucket=pipeline_access_logs_bucket, - **kwargs, - ) - - self.prod_frontend_pipeline = FrontendPipeline( - self, - 'ProdFrontendPipeline', - pipeline_name=self._get_frontend_pipeline_name(), - github_repo_string=self.github_repo_string, - cdk_path=cdk_path, - connection_arn=self.connection_arn, - source_branch='main', - encryption_key=pipeline_shared_encryption_key, - alarm_topic=pipeline_alarm_topic, - access_logs_bucket=self.access_logs_bucket, - ssm_parameter=self.parameter, - pipeline_stack_name=self.stack_name, - environment_context=self.pipeline_environment_context, - self_mutation=True, - removal_policy=self.removal_policy, - ) - - self.prod_frontend_stage = self._determine_frontend_stage( - construct_id='ProdFrontend', - environment_name=PROD_ENVIRONMENT_NAME, - environment_context=self.ssm_context['environments'][PROD_ENVIRONMENT_NAME], - ) - - self.prod_frontend_pipeline.add_stage(self.prod_frontend_stage) - self.prod_frontend_pipeline.build_pipeline() - self._add_pipeline_cdk_assume_role_policy(self.prod_frontend_pipeline) diff --git a/backend/compact-connect/pipeline/backend_pipeline.py b/backend/compact-connect/pipeline/backend_pipeline.py index e16ffd921..1e1c7f775 100644 --- a/backend/compact-connect/pipeline/backend_pipeline.py +++ b/backend/compact-connect/pipeline/backend_pipeline.py @@ -17,6 +17,8 @@ import pipeline +from common_constructs.bucket import Bucket + class BackendPipeline(CdkCodePipeline): """ diff --git a/backend/compact-connect/pipeline/backend_stage.py b/backend/compact-connect/pipeline/backend_stage.py index 83bbbd0fc..ca8782998 100644 --- a/backend/compact-connect/pipeline/backend_stage.py +++ b/backend/compact-connect/pipeline/backend_stage.py @@ -1,6 +1,7 @@ from aws_cdk import Environment, Stage -from common_constructs.stack import StandardTags from constructs import Construct + +from common_constructs.stack import StandardTags from stacks.api_lambda_stack import ApiLambdaStack from stacks.api_stack import ApiStack from stacks.disaster_recovery_stack import DisasterRecoveryStack diff --git a/backend/compact-connect/ruff.toml b/backend/compact-connect/ruff.toml new file mode 100644 index 000000000..66682be80 --- /dev/null +++ b/backend/compact-connect/ruff.toml @@ -0,0 +1,112 @@ +# Exclude a variety of commonly ignored directories. +exclude = [ + ".bzr", + ".direnv", + ".eggs", + ".git", + ".git-rewrite", + ".hg", + ".ipynb_checkpoints", + ".mypy_cache", + ".nox", + ".pants.d", + ".pyenv", + ".pytest_cache", + ".pytype", + ".ruff_cache", + ".svn", + ".tox", + ".venv", + ".vscode", + "__pypackages__", + "_build", + "buck-out", + "build", + "dist", + "node_modules", + "site-packages", + "venv" +] + +line-length = 120 +indent-width = 4 + +# Assume Python 3.12 +target-version = "py312" + +[lint] +select = [ + "A", # flake8-builtins + "ARG", # flake8-unused-arguments + "B", # flake8-bugbear + "BLE", # flake8-blind-except + "DTZ", # flake8-datetimez + "E", # pycodestyle + "F", # Pyflakes + "FIX", # flake8-fixme + "FA", # flake8-future-annotations + "G", # flake8-logging-format + "I", # isort + "N", # pep8-naming + "PIE", # flake8-pie + "SLF", # flake8-self + "RET", # flake8-return + "RSE", # flake8-raise + "S", # flake8-bandit + "T", # flake8-print + "UP", # pyupgrade + "W", # warning +] +ignore = [ + "S311", # suspicious-non-cryptographic-random-usage + "N818", # error-suffix-on-exception-name + # the following are ignored by recommendation of ruff documentation + # due to conflicts with the ruff formatter see https://docs.astral.sh/ruff/formatter/#conflicting-lint-rules + "W191", # indentation contains tabs + "E111", # indentation-with-invalid-multiple + "E114", # indentation-with-invalid-multiple-comment + "E117", # over-indented + "D206", # indent-with-spaces + "D300", # triple-single-quotes + "Q000", # bad-quotes-inline-string + "Q001", # bad-quotes-multiline-string + "Q002", # bad-quotes-docstring + "Q003", # avoidable-escaped-quote + "COM812", # missing-trailing-comma + "COM819", # prohibited-trailing-comma + "ISC001", # single-line-implicit-string-concatenation + "ISC002", # multi-line-implicit-string-concatenation +] + +# Allow fix for all enabled rules (when `--fix`) is provided. +fixable = ["ALL"] +unfixable = [] + +# Allow unused variables when underscore-prefixed. +dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" + +[format] +quote-style = "single" + +# Like Black, indent with spaces, rather than tabs. +indent-style = "space" + +# Like Black, respect magic trailing commas. +skip-magic-trailing-comma = false + +# Like Black, automatically detect the appropriate line ending. +line-ending = "auto" + +# Enable auto-formatting of code examples in docstrings. Markdown, +# reStructuredText code/literal blocks and doctests are all supported. +# +# This is currently disabled by default, but it is planned for this +# to be opt-out in the future. +docstring-code-format = false + +# Set the line length limit used when formatting code snippets in +# docstrings. +# +# This only has an effect when the `docstring-code-format` setting is +# enabled. +docstring-code-line-length = "dynamic" diff --git a/backend/compact-connect/stacks/api_lambda_stack/__init__.py b/backend/compact-connect/stacks/api_lambda_stack/__init__.py index bf68bad61..ddd92358c 100644 --- a/backend/compact-connect/stacks/api_lambda_stack/__init__.py +++ b/backend/compact-connect/stacks/api_lambda_stack/__init__.py @@ -1,8 +1,8 @@ from __future__ import annotations -from common_constructs.stack import AppStack from constructs import Construct +from common_constructs.stack import AppStack from stacks import persistent_stack as ps from stacks.provider_users import ProviderUsersStack diff --git a/backend/compact-connect/stacks/api_lambda_stack/provider_users.py b/backend/compact-connect/stacks/api_lambda_stack/provider_users.py index 9fde2d555..809321727 100644 --- a/backend/compact-connect/stacks/api_lambda_stack/provider_users.py +++ b/backend/compact-connect/stacks/api_lambda_stack/provider_users.py @@ -7,9 +7,9 @@ from aws_cdk.aws_cloudwatch_actions import SnsAction from aws_cdk.aws_secretsmanager import Secret from cdk_nag import NagSuppressions + from common_constructs.python_function import PythonFunction from common_constructs.stack import Stack - from stacks import persistent_stack as ps from stacks.provider_users import ProviderUsersStack diff --git a/backend/compact-connect/stacks/api_stack/__init__.py b/backend/compact-connect/stacks/api_stack/__init__.py index 4d2578047..fdb5d4ead 100644 --- a/backend/compact-connect/stacks/api_stack/__init__.py +++ b/backend/compact-connect/stacks/api_stack/__init__.py @@ -1,9 +1,9 @@ from __future__ import annotations -from common_constructs.security_profile import SecurityProfile -from common_constructs.stack import AppStack from constructs import Construct +from common_constructs.security_profile import SecurityProfile +from common_constructs.stack import AppStack from stacks import persistent_stack as ps from stacks.api_lambda_stack import ApiLambdaStack from stacks.provider_users import ProviderUsersStack diff --git a/backend/compact-connect/stacks/api_stack/api.py b/backend/compact-connect/stacks/api_stack/api.py index 21a518cd1..351993d16 100644 --- a/backend/compact-connect/stacks/api_stack/api.py +++ b/backend/compact-connect/stacks/api_stack/api.py @@ -4,10 +4,10 @@ from functools import cached_property from aws_cdk import ArnFormat -from common_constructs.cc_api import CCApi -from common_constructs.stack import Stack from constructs import Construct +from common_constructs.cc_api import CCApi +from common_constructs.stack import Stack from stacks import persistent_stack as ps from stacks.api_lambda_stack import ApiLambdaStack from stacks.provider_users import ProviderUsersStack diff --git a/backend/compact-connect/stacks/api_stack/v1_api/api.py b/backend/compact-connect/stacks/api_stack/v1_api/api.py index 0d63fdfb1..4331277c9 100644 --- a/backend/compact-connect/stacks/api_stack/v1_api/api.py +++ b/backend/compact-connect/stacks/api_stack/v1_api/api.py @@ -4,10 +4,10 @@ from aws_cdk.aws_apigateway import AuthorizationType, IResource, MethodOptions from cdk_nag import NagSuppressions + from common_constructs.python_function import PythonFunction from common_constructs.ssm_parameter_utility import SSMParameterUtility from common_constructs.stack import Stack - from stacks import persistent_stack as ps from stacks.api_lambda_stack import ApiLambdaStack diff --git a/backend/compact-connect/stacks/api_stack/v1_api/attestations.py b/backend/compact-connect/stacks/api_stack/v1_api/attestations.py index dbc26fac8..73e090906 100644 --- a/backend/compact-connect/stacks/api_stack/v1_api/attestations.py +++ b/backend/compact-connect/stacks/api_stack/v1_api/attestations.py @@ -5,10 +5,10 @@ from aws_cdk import Duration from aws_cdk.aws_apigateway import LambdaIntegration, MethodResponse, Resource from cdk_nag import NagSuppressions + from common_constructs.cc_api import CCApi from common_constructs.python_function import PythonFunction from common_constructs.stack import Stack - from stacks import persistent_stack as ps from .api_model import ApiModel diff --git a/backend/compact-connect/stacks/api_stack/v1_api/bulk_upload_url.py b/backend/compact-connect/stacks/api_stack/v1_api/bulk_upload_url.py index 0b164ec0a..d795928f4 100644 --- a/backend/compact-connect/stacks/api_stack/v1_api/bulk_upload_url.py +++ b/backend/compact-connect/stacks/api_stack/v1_api/bulk_upload_url.py @@ -6,6 +6,7 @@ from aws_cdk.aws_apigateway import AuthorizationType, LambdaIntegration, MethodOptions, MethodResponse, Resource from aws_cdk.aws_iam import IRole from aws_cdk.aws_s3 import IBucket + from common_constructs.cc_api import CCApi from common_constructs.python_function import PythonFunction from common_constructs.stack import Stack diff --git a/backend/compact-connect/stacks/api_stack/v1_api/compact_configuration_api.py b/backend/compact-connect/stacks/api_stack/v1_api/compact_configuration_api.py index 7fc99a4a6..4e2604ede 100644 --- a/backend/compact-connect/stacks/api_stack/v1_api/compact_configuration_api.py +++ b/backend/compact-connect/stacks/api_stack/v1_api/compact_configuration_api.py @@ -5,10 +5,10 @@ from aws_cdk import Duration from aws_cdk.aws_apigateway import LambdaIntegration, MethodOptions, MethodResponse, Resource from cdk_nag import NagSuppressions + from common_constructs.cc_api import CCApi from common_constructs.python_function import PythonFunction from common_constructs.stack import Stack - from stacks import persistent_stack as ps from .api_model import ApiModel diff --git a/backend/compact-connect/stacks/api_stack/v1_api/credentials.py b/backend/compact-connect/stacks/api_stack/v1_api/credentials.py index 63bd86abf..41bdd820d 100644 --- a/backend/compact-connect/stacks/api_stack/v1_api/credentials.py +++ b/backend/compact-connect/stacks/api_stack/v1_api/credentials.py @@ -6,10 +6,10 @@ from aws_cdk.aws_apigateway import LambdaIntegration, MethodOptions, MethodResponse, Resource from aws_cdk.aws_iam import Effect, PolicyStatement from cdk_nag import NagSuppressions + from common_constructs.cc_api import CCApi from common_constructs.python_function import PythonFunction from common_constructs.stack import Stack - from stacks import persistent_stack as ps from .api_model import ApiModel diff --git a/backend/compact-connect/stacks/api_stack/v1_api/post_licenses.py b/backend/compact-connect/stacks/api_stack/v1_api/post_licenses.py index 2c14ce5b8..e40a9c80a 100644 --- a/backend/compact-connect/stacks/api_stack/v1_api/post_licenses.py +++ b/backend/compact-connect/stacks/api_stack/v1_api/post_licenses.py @@ -7,10 +7,10 @@ from aws_cdk.aws_dynamodb import ITable from aws_cdk.aws_iam import IRole from aws_cdk.aws_sqs import IQueue + from common_constructs.cc_api import CCApi from common_constructs.python_function import PythonFunction from common_constructs.stack import Stack - from stacks import persistent_stack as ps from .api_model import ApiModel diff --git a/backend/compact-connect/stacks/api_stack/v1_api/provider_management.py b/backend/compact-connect/stacks/api_stack/v1_api/provider_management.py index e2e16d1f8..8d6f31c76 100644 --- a/backend/compact-connect/stacks/api_stack/v1_api/provider_management.py +++ b/backend/compact-connect/stacks/api_stack/v1_api/provider_management.py @@ -17,11 +17,11 @@ from aws_cdk.aws_iam import Policy, PolicyStatement from aws_cdk.aws_kms import IKey from cdk_nag import NagSuppressions + from common_constructs.cc_api import CCApi from common_constructs.nodejs_function import NodejsFunction from common_constructs.python_function import PythonFunction from common_constructs.stack import Stack - from stacks import persistent_stack as ps from stacks.persistent_stack import ProviderTable, ProviderUsersBucket, RateLimitingTable, SSNTable, StaffUsers diff --git a/backend/compact-connect/stacks/api_stack/v1_api/provider_users.py b/backend/compact-connect/stacks/api_stack/v1_api/provider_users.py index 1cea1e63b..289d8ae46 100644 --- a/backend/compact-connect/stacks/api_stack/v1_api/provider_users.py +++ b/backend/compact-connect/stacks/api_stack/v1_api/provider_users.py @@ -3,10 +3,10 @@ from aws_cdk import Duration from aws_cdk.aws_apigateway import LambdaIntegration, MethodResponse, Resource from cdk_nag import NagSuppressions + from common_constructs.cc_api import CCApi from common_constructs.python_function import PythonFunction from common_constructs.stack import Stack - from stacks.api_lambda_stack import ApiLambdaStack from .api_model import ApiModel diff --git a/backend/compact-connect/stacks/api_stack/v1_api/public_lookup_api.py b/backend/compact-connect/stacks/api_stack/v1_api/public_lookup_api.py index 63443650e..2697859fa 100644 --- a/backend/compact-connect/stacks/api_stack/v1_api/public_lookup_api.py +++ b/backend/compact-connect/stacks/api_stack/v1_api/public_lookup_api.py @@ -5,10 +5,10 @@ from aws_cdk import Duration from aws_cdk.aws_apigateway import LambdaIntegration, MethodResponse, Resource from cdk_nag import NagSuppressions + from common_constructs.cc_api import CCApi from common_constructs.python_function import PythonFunction from common_constructs.stack import Stack - from stacks import persistent_stack as ps from .api_model import ApiModel diff --git a/backend/compact-connect/stacks/api_stack/v1_api/purchases.py b/backend/compact-connect/stacks/api_stack/v1_api/purchases.py index d212ff496..d5225c261 100644 --- a/backend/compact-connect/stacks/api_stack/v1_api/purchases.py +++ b/backend/compact-connect/stacks/api_stack/v1_api/purchases.py @@ -8,10 +8,10 @@ from aws_cdk.aws_iam import Effect, PolicyStatement from aws_cdk.aws_kms import IKey from cdk_nag import NagSuppressions + from common_constructs.cc_api import CCApi from common_constructs.python_function import PythonFunction from common_constructs.stack import Stack - from stacks.persistent_stack import CompactConfigurationTable, ProviderTable from .api_model import ApiModel diff --git a/backend/compact-connect/stacks/api_stack/v1_api/staff_users.py b/backend/compact-connect/stacks/api_stack/v1_api/staff_users.py index 64e31ad1a..e5f862e18 100644 --- a/backend/compact-connect/stacks/api_stack/v1_api/staff_users.py +++ b/backend/compact-connect/stacks/api_stack/v1_api/staff_users.py @@ -21,10 +21,10 @@ from aws_cdk.aws_dynamodb import ITable from aws_cdk.aws_kms import IKey from cdk_nag import NagSuppressions + from common_constructs.cc_api import CCApi from common_constructs.python_function import PythonFunction from common_constructs.user_pool import UserPool - from stacks import persistent_stack as ps from .api_model import ApiModel diff --git a/backend/compact-connect/stacks/disaster_recovery_stack/__init__.py b/backend/compact-connect/stacks/disaster_recovery_stack/__init__.py index 901e57bf8..9cdc369df 100644 --- a/backend/compact-connect/stacks/disaster_recovery_stack/__init__.py +++ b/backend/compact-connect/stacks/disaster_recovery_stack/__init__.py @@ -2,9 +2,9 @@ from aws_cdk.aws_dynamodb import Table from aws_cdk.aws_iam import PolicyStatement, ServicePrincipal from aws_cdk.aws_kms import Key -from common_constructs.stack import AppStack from constructs import Construct +from common_constructs.stack import AppStack from stacks import persistent_stack as ps from stacks.disaster_recovery_stack.restore_dynamo_db_table_step_function import ( RestoreDynamoDbTableStepFunctionConstruct, diff --git a/backend/compact-connect/stacks/disaster_recovery_stack/restore_dynamo_db_table_step_function.py b/backend/compact-connect/stacks/disaster_recovery_stack/restore_dynamo_db_table_step_function.py index eb108772a..5609a9696 100644 --- a/backend/compact-connect/stacks/disaster_recovery_stack/restore_dynamo_db_table_step_function.py +++ b/backend/compact-connect/stacks/disaster_recovery_stack/restore_dynamo_db_table_step_function.py @@ -20,9 +20,10 @@ WaitTime, ) from cdk_nag import NagSuppressions -from common_constructs.stack import Stack from constructs import Construct +from common_constructs.stack import Stack + class RestoreDynamoDbTableStepFunctionConstruct(Construct): def __init__( diff --git a/backend/compact-connect/stacks/disaster_recovery_stack/sync_table_step_function.py b/backend/compact-connect/stacks/disaster_recovery_stack/sync_table_step_function.py index 8a10706ed..8c6e0af77 100644 --- a/backend/compact-connect/stacks/disaster_recovery_stack/sync_table_step_function.py +++ b/backend/compact-connect/stacks/disaster_recovery_stack/sync_table_step_function.py @@ -19,9 +19,10 @@ ) from aws_cdk.aws_stepfunctions_tasks import LambdaInvoke from cdk_nag import NagSuppressions +from constructs import Construct + from common_constructs.python_function import PythonFunction from common_constructs.stack import Stack -from constructs import Construct class SyncTableDataStepFunctionConstruct(Construct): diff --git a/backend/compact-connect/stacks/event_listener_stack/__init__.py b/backend/compact-connect/stacks/event_listener_stack/__init__.py index 1177a1a84..5af836006 100644 --- a/backend/compact-connect/stacks/event_listener_stack/__init__.py +++ b/backend/compact-connect/stacks/event_listener_stack/__init__.py @@ -5,12 +5,12 @@ from aws_cdk import Duration from aws_cdk.aws_events import EventBus from cdk_nag import NagSuppressions +from constructs import Construct + from common_constructs.python_function import PythonFunction from common_constructs.queue_event_listener import QueueEventListener from common_constructs.ssm_parameter_utility import SSMParameterUtility from common_constructs.stack import AppStack -from constructs import Construct - from stacks import persistent_stack as ps diff --git a/backend/compact-connect/stacks/ingest_stack.py b/backend/compact-connect/stacks/ingest_stack.py index 5391597b1..e51ea3abd 100644 --- a/backend/compact-connect/stacks/ingest_stack.py +++ b/backend/compact-connect/stacks/ingest_stack.py @@ -8,12 +8,12 @@ from aws_cdk.aws_events import EventBus, EventPattern, Rule from aws_cdk.aws_events_targets import SqsQueue from cdk_nag import NagSuppressions +from constructs import Construct + from common_constructs.python_function import PythonFunction from common_constructs.queued_lambda_processor import QueuedLambdaProcessor from common_constructs.ssm_parameter_utility import SSMParameterUtility from common_constructs.stack import AppStack, Stack -from constructs import Construct - from stacks import persistent_stack as ps diff --git a/backend/compact-connect/stacks/managed_login_stack.py b/backend/compact-connect/stacks/managed_login_stack.py index 621d55dee..8672fc14b 100644 --- a/backend/compact-connect/stacks/managed_login_stack.py +++ b/backend/compact-connect/stacks/managed_login_stack.py @@ -1,9 +1,9 @@ import json from aws_cdk.aws_cognito import CfnManagedLoginBranding -from common_constructs.stack import AppStack from constructs import Construct +from common_constructs.stack import AppStack from stacks.persistent_stack import PersistentStack from stacks.provider_users import ProviderUsersStack diff --git a/backend/compact-connect/stacks/notification_stack.py b/backend/compact-connect/stacks/notification_stack.py index a95b3471e..457bde652 100644 --- a/backend/compact-connect/stacks/notification_stack.py +++ b/backend/compact-connect/stacks/notification_stack.py @@ -8,13 +8,13 @@ from aws_cdk.aws_events import EventBus, EventPattern, IEventBus, Rule from aws_cdk.aws_events_targets import SqsQueue from cdk_nag import NagSuppressions +from constructs import Construct + from common_constructs.python_function import PythonFunction from common_constructs.queue_event_listener import QueueEventListener from common_constructs.queued_lambda_processor import QueuedLambdaProcessor from common_constructs.ssm_parameter_utility import SSMParameterUtility from common_constructs.stack import AppStack -from constructs import Construct - from stacks import persistent_stack as ps diff --git a/backend/compact-connect/stacks/persistent_stack/__init__.py b/backend/compact-connect/stacks/persistent_stack/__init__.py index 2b1532b91..9e5dc8104 100644 --- a/backend/compact-connect/stacks/persistent_stack/__init__.py +++ b/backend/compact-connect/stacks/persistent_stack/__init__.py @@ -8,6 +8,8 @@ from aws_cdk.aws_lambda_python_alpha import PythonLayerVersion from aws_cdk.aws_logs import QueryDefinition, QueryString from cdk_nag import NagSuppressions +from constructs import Construct + from common_constructs.access_logs_bucket import AccessLogsBucket from common_constructs.alarm_topic import AlarmTopic from common_constructs.frontend_app_config_utility import PersistentStackFrontendAppConfigUtility @@ -16,8 +18,6 @@ from common_constructs.security_profile import SecurityProfile from common_constructs.ssm_parameter_utility import SSMParameterUtility from common_constructs.stack import AppStack -from constructs import Construct - from stacks.backup_infrastructure_stack import BackupInfrastructureStack from stacks.persistent_stack.bulk_uploads_bucket import BulkUploadsBucket from stacks.persistent_stack.compact_configuration_table import CompactConfigurationTable diff --git a/backend/compact-connect/stacks/persistent_stack/bulk_uploads_bucket.py b/backend/compact-connect/stacks/persistent_stack/bulk_uploads_bucket.py index fed7da066..a1ad7565d 100644 --- a/backend/compact-connect/stacks/persistent_stack/bulk_uploads_bucket.py +++ b/backend/compact-connect/stacks/persistent_stack/bulk_uploads_bucket.py @@ -13,13 +13,13 @@ from aws_cdk.aws_s3_notifications import LambdaDestination from aws_cdk.aws_sqs import IQueue from cdk_nag import NagSuppressions +from constructs import Construct + +import stacks.persistent_stack as ps from common_constructs.access_logs_bucket import AccessLogsBucket from common_constructs.bucket import Bucket from common_constructs.python_function import PythonFunction from common_constructs.stack import Stack -from constructs import Construct - -import stacks.persistent_stack as ps class BulkUploadsBucket(Bucket): diff --git a/backend/compact-connect/stacks/persistent_stack/compact_configuration_table.py b/backend/compact-connect/stacks/persistent_stack/compact_configuration_table.py index 7bc1d3684..d9beaf991 100644 --- a/backend/compact-connect/stacks/persistent_stack/compact_configuration_table.py +++ b/backend/compact-connect/stacks/persistent_stack/compact_configuration_table.py @@ -3,9 +3,9 @@ from aws_cdk.aws_dynamodb import Attribute, AttributeType, BillingMode, Table, TableEncryption from aws_cdk.aws_kms import IKey from cdk_nag import NagSuppressions -from common_constructs.backup_plan import CCBackupPlan from constructs import Construct +from common_constructs.backup_plan import CCBackupPlan from stacks.backup_infrastructure_stack import BackupInfrastructureStack diff --git a/backend/compact-connect/stacks/persistent_stack/compact_configuration_upload.py b/backend/compact-connect/stacks/persistent_stack/compact_configuration_upload.py index 10361a258..1df685d80 100644 --- a/backend/compact-connect/stacks/persistent_stack/compact_configuration_upload.py +++ b/backend/compact-connect/stacks/persistent_stack/compact_configuration_upload.py @@ -7,9 +7,10 @@ from aws_cdk.aws_logs import RetentionDays from aws_cdk.custom_resources import Provider from cdk_nag import NagSuppressions +from constructs import Construct + from common_constructs.python_function import PythonFunction from common_constructs.stack import Stack -from constructs import Construct from .compact_configuration_table import CompactConfigurationTable diff --git a/backend/compact-connect/stacks/persistent_stack/data_event_table.py b/backend/compact-connect/stacks/persistent_stack/data_event_table.py index d0e17ff41..1e92cb65e 100644 --- a/backend/compact-connect/stacks/persistent_stack/data_event_table.py +++ b/backend/compact-connect/stacks/persistent_stack/data_event_table.py @@ -12,11 +12,11 @@ from aws_cdk.aws_kms import IKey from aws_cdk.aws_sns import ITopic from cdk_nag import NagSuppressions +from constructs import Construct + from common_constructs.backup_plan import CCBackupPlan from common_constructs.python_function import PythonFunction from common_constructs.queued_lambda_processor import QueuedLambdaProcessor -from constructs import Construct - from stacks import persistent_stack as ps from stacks.backup_infrastructure_stack import BackupInfrastructureStack diff --git a/backend/compact-connect/stacks/persistent_stack/provider_table.py b/backend/compact-connect/stacks/persistent_stack/provider_table.py index 470a51a70..00d9c9e58 100644 --- a/backend/compact-connect/stacks/persistent_stack/provider_table.py +++ b/backend/compact-connect/stacks/persistent_stack/provider_table.py @@ -3,9 +3,9 @@ from aws_cdk.aws_dynamodb import Attribute, AttributeType, BillingMode, ProjectionType, Table, TableEncryption from aws_cdk.aws_kms import Key from cdk_nag import NagSuppressions -from common_constructs.backup_plan import CCBackupPlan from constructs import Construct +from common_constructs.backup_plan import CCBackupPlan from stacks.backup_infrastructure_stack import BackupInfrastructureStack diff --git a/backend/compact-connect/stacks/persistent_stack/provider_users_bucket.py b/backend/compact-connect/stacks/persistent_stack/provider_users_bucket.py index a30406afa..8f52df3e4 100644 --- a/backend/compact-connect/stacks/persistent_stack/provider_users_bucket.py +++ b/backend/compact-connect/stacks/persistent_stack/provider_users_bucket.py @@ -12,13 +12,13 @@ from aws_cdk.aws_s3 import BucketEncryption, CorsRule, EventType, HttpMethods from aws_cdk.aws_s3_notifications import LambdaDestination from cdk_nag import NagSuppressions +from constructs import Construct + +import stacks.persistent_stack as ps from common_constructs.access_logs_bucket import AccessLogsBucket from common_constructs.backup_plan import CCBackupPlan from common_constructs.bucket import Bucket from common_constructs.python_function import PythonFunction -from constructs import Construct - -import stacks.persistent_stack as ps from stacks.backup_infrastructure_stack import BackupInfrastructureStack diff --git a/backend/compact-connect/stacks/persistent_stack/ssn_table.py b/backend/compact-connect/stacks/persistent_stack/ssn_table.py index 7ef3d2ba0..b90d64b31 100644 --- a/backend/compact-connect/stacks/persistent_stack/ssn_table.py +++ b/backend/compact-connect/stacks/persistent_stack/ssn_table.py @@ -16,12 +16,12 @@ from aws_cdk.aws_kms import Key from aws_cdk.aws_sns import ITopic from cdk_nag import NagSuppressions +from constructs import Construct + from common_constructs.backup_plan import CCBackupPlan from common_constructs.python_function import PythonFunction from common_constructs.queued_lambda_processor import QueuedLambdaProcessor from common_constructs.stack import Stack -from constructs import Construct - from stacks.backup_infrastructure_stack import BackupInfrastructureStack diff --git a/backend/compact-connect/stacks/persistent_stack/staff_users.py b/backend/compact-connect/stacks/persistent_stack/staff_users.py index 82a51d863..09e37f502 100644 --- a/backend/compact-connect/stacks/persistent_stack/staff_users.py +++ b/backend/compact-connect/stacks/persistent_stack/staff_users.py @@ -14,13 +14,13 @@ ) from aws_cdk.aws_kms import IKey from cdk_nag import NagSuppressions +from constructs import Construct + from common_constructs.cognito_user_backup import CognitoUserBackup from common_constructs.nodejs_function import NodejsFunction from common_constructs.python_function import PythonFunction from common_constructs.resource_scope_mixin import ResourceScopeMixin from common_constructs.user_pool import UserPool -from constructs import Construct - from stacks import persistent_stack as ps from stacks.backup_infrastructure_stack import BackupInfrastructureStack from stacks.persistent_stack.users_table import UsersTable diff --git a/backend/compact-connect/stacks/persistent_stack/transaction_history_table.py b/backend/compact-connect/stacks/persistent_stack/transaction_history_table.py index 55d7c782f..7bb939115 100644 --- a/backend/compact-connect/stacks/persistent_stack/transaction_history_table.py +++ b/backend/compact-connect/stacks/persistent_stack/transaction_history_table.py @@ -3,9 +3,9 @@ from aws_cdk.aws_dynamodb import Attribute, AttributeType, BillingMode, Table, TableEncryption from aws_cdk.aws_kms import IKey from cdk_nag import NagSuppressions -from common_constructs.backup_plan import CCBackupPlan from constructs import Construct +from common_constructs.backup_plan import CCBackupPlan from stacks.backup_infrastructure_stack import BackupInfrastructureStack diff --git a/backend/compact-connect/stacks/persistent_stack/transaction_reports_bucket.py b/backend/compact-connect/stacks/persistent_stack/transaction_reports_bucket.py index 51d025b88..aa1c857a0 100644 --- a/backend/compact-connect/stacks/persistent_stack/transaction_reports_bucket.py +++ b/backend/compact-connect/stacks/persistent_stack/transaction_reports_bucket.py @@ -3,9 +3,10 @@ from aws_cdk.aws_kms import IKey from aws_cdk.aws_s3 import BucketEncryption from cdk_nag import NagSuppressions +from constructs import Construct + from common_constructs.access_logs_bucket import AccessLogsBucket from common_constructs.bucket import Bucket -from constructs import Construct class TransactionReportsBucket(Bucket): diff --git a/backend/compact-connect/stacks/persistent_stack/user_email_notifications.py b/backend/compact-connect/stacks/persistent_stack/user_email_notifications.py index cc84eea53..a5e94a6ce 100644 --- a/backend/compact-connect/stacks/persistent_stack/user_email_notifications.py +++ b/backend/compact-connect/stacks/persistent_stack/user_email_notifications.py @@ -9,9 +9,10 @@ from aws_cdk.aws_sns import Subscription, SubscriptionProtocol, Topic from aws_cdk.custom_resources import Provider from cdk_nag import NagSuppressions +from constructs import Construct + from common_constructs.python_function import PythonFunction from common_constructs.stack import Stack -from constructs import Construct class UserEmailNotifications(Construct): diff --git a/backend/compact-connect/stacks/persistent_stack/users_table.py b/backend/compact-connect/stacks/persistent_stack/users_table.py index 2b53fb3d7..e2a28bf17 100644 --- a/backend/compact-connect/stacks/persistent_stack/users_table.py +++ b/backend/compact-connect/stacks/persistent_stack/users_table.py @@ -3,9 +3,9 @@ from aws_cdk.aws_dynamodb import Attribute, AttributeType, BillingMode, ProjectionType, Table, TableEncryption from aws_cdk.aws_kms import IKey from cdk_nag import NagSuppressions -from common_constructs.backup_plan import CCBackupPlan from constructs import Construct +from common_constructs.backup_plan import CCBackupPlan from stacks.backup_infrastructure_stack import BackupInfrastructureStack diff --git a/backend/compact-connect/stacks/provider_users/__init__.py b/backend/compact-connect/stacks/provider_users/__init__.py index 180993679..f6c661c22 100644 --- a/backend/compact-connect/stacks/provider_users/__init__.py +++ b/backend/compact-connect/stacks/provider_users/__init__.py @@ -1,10 +1,10 @@ from aws_cdk import RemovalPolicy from aws_cdk.aws_cognito import SignInAliases, UserPoolEmail from aws_cdk.aws_logs import QueryDefinition, QueryString -from common_constructs.security_profile import SecurityProfile -from common_constructs.stack import AppStack from constructs import Construct +from common_constructs.security_profile import SecurityProfile +from common_constructs.stack import AppStack from stacks.persistent_stack import PersistentStack from stacks.provider_users.provider_users import ProviderUsers diff --git a/backend/compact-connect/stacks/provider_users/provider_users.py b/backend/compact-connect/stacks/provider_users/provider_users.py index d2c1603d0..8a107ab88 100644 --- a/backend/compact-connect/stacks/provider_users/provider_users.py +++ b/backend/compact-connect/stacks/provider_users/provider_users.py @@ -12,11 +12,11 @@ UserPoolOperation, ) from aws_cdk.aws_kms import IKey +from constructs import Construct + from common_constructs.cognito_user_backup import CognitoUserBackup from common_constructs.nodejs_function import NodejsFunction from common_constructs.user_pool import UserPool -from constructs import Construct - from stacks import persistent_stack as ps diff --git a/backend/compact-connect/stacks/reporting_stack.py b/backend/compact-connect/stacks/reporting_stack.py index 3eb42228e..c4a88fbe9 100644 --- a/backend/compact-connect/stacks/reporting_stack.py +++ b/backend/compact-connect/stacks/reporting_stack.py @@ -10,11 +10,11 @@ from aws_cdk.aws_events_targets import LambdaFunction from aws_cdk.aws_logs import QueryDefinition, QueryString from cdk_nag import NagSuppressions +from constructs import Construct + from common_constructs.nodejs_function import NodejsFunction from common_constructs.python_function import PythonFunction from common_constructs.stack import AppStack -from constructs import Construct - from stacks import persistent_stack as ps diff --git a/backend/compact-connect/stacks/state_api_stack/__init__.py b/backend/compact-connect/stacks/state_api_stack/__init__.py index 35a2a2c18..12192c225 100644 --- a/backend/compact-connect/stacks/state_api_stack/__init__.py +++ b/backend/compact-connect/stacks/state_api_stack/__init__.py @@ -1,9 +1,9 @@ from __future__ import annotations -from common_constructs.security_profile import SecurityProfile -from common_constructs.stack import AppStack from constructs import Construct +from common_constructs.security_profile import SecurityProfile +from common_constructs.stack import AppStack from stacks import persistent_stack as ps from stacks.state_auth import StateAuthStack diff --git a/backend/compact-connect/stacks/state_api_stack/api.py b/backend/compact-connect/stacks/state_api_stack/api.py index 55e85432c..967008398 100644 --- a/backend/compact-connect/stacks/state_api_stack/api.py +++ b/backend/compact-connect/stacks/state_api_stack/api.py @@ -2,9 +2,9 @@ from functools import cached_property -from common_constructs.cc_api import CCApi from constructs import Construct +from common_constructs.cc_api import CCApi from stacks import persistent_stack as ps from stacks.state_auth import StateAuthStack diff --git a/backend/compact-connect/stacks/state_api_stack/v1_api/bulk_upload_url.py b/backend/compact-connect/stacks/state_api_stack/v1_api/bulk_upload_url.py index 38731983d..47c11171a 100644 --- a/backend/compact-connect/stacks/state_api_stack/v1_api/bulk_upload_url.py +++ b/backend/compact-connect/stacks/state_api_stack/v1_api/bulk_upload_url.py @@ -5,10 +5,10 @@ from aws_cdk import Duration from aws_cdk.aws_apigateway import AuthorizationType, LambdaIntegration, MethodOptions, MethodResponse, Resource from aws_cdk.aws_iam import IRole + from common_constructs.cc_api import CCApi from common_constructs.python_function import PythonFunction from common_constructs.stack import Stack - from stacks import persistent_stack as ps from .api_model import ApiModel diff --git a/backend/compact-connect/stacks/state_api_stack/v1_api/post_licenses.py b/backend/compact-connect/stacks/state_api_stack/v1_api/post_licenses.py index ae1626d2f..4940c4c43 100644 --- a/backend/compact-connect/stacks/state_api_stack/v1_api/post_licenses.py +++ b/backend/compact-connect/stacks/state_api_stack/v1_api/post_licenses.py @@ -5,10 +5,10 @@ from aws_cdk import Duration from aws_cdk.aws_apigateway import LambdaIntegration, MethodOptions, MethodResponse, Resource from aws_cdk.aws_iam import IRole + from common_constructs.cc_api import CCApi from common_constructs.python_function import PythonFunction from common_constructs.stack import Stack - from stacks import persistent_stack as ps from .api_model import ApiModel diff --git a/backend/compact-connect/stacks/state_api_stack/v1_api/provider_management.py b/backend/compact-connect/stacks/state_api_stack/v1_api/provider_management.py index 3e2579fdf..15d79b091 100644 --- a/backend/compact-connect/stacks/state_api_stack/v1_api/provider_management.py +++ b/backend/compact-connect/stacks/state_api_stack/v1_api/provider_management.py @@ -7,10 +7,10 @@ from aws_cdk.aws_dynamodb import ITable from aws_cdk.aws_kms import IKey from cdk_nag import NagSuppressions + from common_constructs.cc_api import CCApi from common_constructs.python_function import PythonFunction from common_constructs.stack import Stack - from stacks import persistent_stack as ps from stacks.persistent_stack import ProviderTable diff --git a/backend/compact-connect/stacks/state_auth/__init__.py b/backend/compact-connect/stacks/state_auth/__init__.py index 65e8a5235..c7b64bf40 100644 --- a/backend/compact-connect/stacks/state_auth/__init__.py +++ b/backend/compact-connect/stacks/state_auth/__init__.py @@ -1,7 +1,7 @@ from aws_cdk import RemovalPolicy -from common_constructs.stack import AppStack from constructs import Construct +from common_constructs.stack import AppStack from stacks.persistent_stack import PersistentStack from stacks.state_auth.state_auth_users import StateAuthUsers diff --git a/backend/compact-connect/stacks/state_auth/state_auth_users.py b/backend/compact-connect/stacks/state_auth/state_auth_users.py index b035da210..58cc79d67 100644 --- a/backend/compact-connect/stacks/state_auth/state_auth_users.py +++ b/backend/compact-connect/stacks/state_auth/state_auth_users.py @@ -11,9 +11,9 @@ UserPool, ) from cdk_nag import NagSuppressions -from common_constructs.resource_scope_mixin import ResourceScopeMixin from constructs import Construct +from common_constructs.resource_scope_mixin import ResourceScopeMixin from stacks import persistent_stack as ps diff --git a/backend/compact-connect/stacks/transaction_monitoring_stack/__init__.py b/backend/compact-connect/stacks/transaction_monitoring_stack/__init__.py index 7acd12028..018056e71 100644 --- a/backend/compact-connect/stacks/transaction_monitoring_stack/__init__.py +++ b/backend/compact-connect/stacks/transaction_monitoring_stack/__init__.py @@ -2,9 +2,9 @@ import json -from common_constructs.stack import AppStack from constructs import Construct +from common_constructs.stack import AppStack from stacks import persistent_stack as ps from .transaction_history_processing_workflow import TransactionHistoryProcessingWorkflow diff --git a/backend/compact-connect/stacks/transaction_monitoring_stack/transaction_history_processing_workflow.py b/backend/compact-connect/stacks/transaction_monitoring_stack/transaction_history_processing_workflow.py index 7c2d1bfc2..695355539 100644 --- a/backend/compact-connect/stacks/transaction_monitoring_stack/transaction_history_processing_workflow.py +++ b/backend/compact-connect/stacks/transaction_monitoring_stack/transaction_history_processing_workflow.py @@ -25,10 +25,10 @@ ) from aws_cdk.aws_stepfunctions_tasks import LambdaInvoke from cdk_nag import NagSuppressions -from common_constructs.python_function import PythonFunction -from common_constructs.stack import Stack from constructs import Construct +from common_constructs.python_function import PythonFunction +from common_constructs.stack import Stack from stacks import persistent_stack as ps diff --git a/backend/compact-connect/tests/__init__.py b/backend/compact-connect/tests/__init__.py index e69de29bb..f56f03fdc 100644 --- a/backend/compact-connect/tests/__init__.py +++ b/backend/compact-connect/tests/__init__.py @@ -0,0 +1,5 @@ +import os +import sys + +# Make the `common_constructs` namespace package under `common-cdk` available to Python +sys.path.insert(0, os.path.abspath(os.path.join('..', 'common-cdk'))) diff --git a/backend/compact-connect/tests/app/base.py b/backend/compact-connect/tests/app/base.py index 00ee9e9ee..8d59e2e5b 100644 --- a/backend/compact-connect/tests/app/base.py +++ b/backend/compact-connect/tests/app/base.py @@ -5,22 +5,20 @@ from collections.abc import Mapping from unittest.mock import patch -from app import CompactConnectApp from aws_cdk.assertions import Annotations, Match, Template from aws_cdk.aws_apigateway import CfnGatewayResponse, CfnMethod -from aws_cdk.aws_cloudfront import CfnDistribution from aws_cdk.aws_cognito import CfnUserPool, CfnUserPoolClient, CfnUserPoolDomain, CfnUserPoolResourceServer from aws_cdk.aws_dynamodb import CfnTable from aws_cdk.aws_events import CfnRule from aws_cdk.aws_kms import CfnKey -from aws_cdk.aws_lambda import CfnEventSourceMapping, CfnFunction -from aws_cdk.aws_s3 import CfnBucket +from aws_cdk.aws_lambda import CfnEventSourceMapping from aws_cdk.aws_sqs import CfnQueue + +from app import CompactConnectApp from common_constructs.backup_plan import CCBackupPlan from common_constructs.stack import Stack -from pipeline import BackendStage, FrontendStage +from pipeline import BackendStage from stacks.api_stack import ApiStack -from stacks.frontend_deployment_stack import FrontendDeploymentStack from stacks.persistent_stack import PersistentStack from stacks.provider_users import ProviderUsersStack from stacks.state_auth import StateAuthStack @@ -97,29 +95,6 @@ def get_resource_properties_by_logical_id(logical_id: str, resources: Mapping[st except KeyError as exc: raise RuntimeError(f'{logical_id} not found in resources!') from exc - def _inspect_frontend_deployment_stack(self, ui_stack: FrontendDeploymentStack): - with self.subTest(ui_stack.stack_name): - ui_stack_template = Template.from_stack(ui_stack) - # Ensure we have a CloudFront distribution - ui_stack_template.resource_count_is('AWS::CloudFront::Distribution', 1) - # This stack is not anticipated to do much changing, so we'll just snapshot-test key resources - ui_bucket = ui_stack_template.find_resources(CfnBucket.CFN_RESOURCE_TYPE_NAME)[ - ui_stack.get_logical_id(ui_stack.ui_bucket.node.default_child) - ] - self.compare_snapshot(ui_bucket, snapshot_name=f'{ui_stack.stack_name}-UI_BUCKET', overwrite_snapshot=False) - distribution = ui_stack_template.find_resources(CfnDistribution.CFN_RESOURCE_TYPE_NAME) - self.assertEqual(len(distribution), 1) - self.compare_snapshot(distribution, f'{ui_stack.stack_name}-UI_DISTRIBUTION', overwrite_snapshot=False) - # take a snapshot of the lambda@edge code to ensure placeholder values are being injected - distribution_function = ui_stack_template.find_resources(CfnFunction.CFN_RESOURCE_TYPE_NAME)[ - ui_stack.get_logical_id(ui_stack.distribution.csp_function.node.default_child) - ] - self.compare_snapshot( - distribution_function, - f'{ui_stack.stack_name}-UI_DISTRIBUTION_LAMBDA_FUNCTION', - overwrite_snapshot=False, - ) - def _inspect_provider_users_stack( self, provider_users_stack: ProviderUsersStack, @@ -544,9 +519,6 @@ def _check_no_backend_stage_annotations(self, stage: BackendStage): if stage.persistent_stack.hosted_zone: self._check_no_stack_annotations(stage.reporting_stack) - def _check_no_frontend_stage_annotations(self, stage: FrontendStage): - self._check_no_stack_annotations(stage.frontend_deployment_stack) - def _count_stack_resources(self, stack: Stack) -> int: """ Count the number of resources in a CloudFormation stack. diff --git a/backend/compact-connect/tests/app/test_api/__init__.py b/backend/compact-connect/tests/app/test_api/__init__.py index 6155aa212..6cbf1a3e8 100644 --- a/backend/compact-connect/tests/app/test_api/__init__.py +++ b/backend/compact-connect/tests/app/test_api/__init__.py @@ -3,8 +3,8 @@ from aws_cdk.assertions import Template from aws_cdk.aws_lambda import IFunction -from stacks.api_lambda_stack import ApiLambdaStack +from stacks.api_lambda_stack import ApiLambdaStack from tests.app.base import TstAppABC diff --git a/backend/compact-connect/tests/app/test_cognito_backup.py b/backend/compact-connect/tests/app/test_cognito_backup.py index a3a77e97e..451bd1ee4 100644 --- a/backend/compact-connect/tests/app/test_cognito_backup.py +++ b/backend/compact-connect/tests/app/test_cognito_backup.py @@ -12,8 +12,8 @@ from aws_cdk.aws_cloudwatch import CfnAlarm from aws_cdk.aws_events import CfnRule from aws_cdk.aws_lambda import CfnFunction -from common_constructs.cognito_user_backup import CognitoUserBackup +from common_constructs.cognito_user_backup import CognitoUserBackup from tests.app.base import TstAppABC diff --git a/backend/compact-connect/tests/app/test_pipeline.py b/backend/compact-connect/tests/app/test_pipeline.py index 88655b791..6b6687d5d 100644 --- a/backend/compact-connect/tests/app/test_pipeline.py +++ b/backend/compact-connect/tests/app/test_pipeline.py @@ -3,7 +3,6 @@ from unittest import TestCase from unittest.mock import patch -from app import CompactConnectApp from aws_cdk.assertions import Match, Template from aws_cdk.aws_cognito import ( CfnUserPool, @@ -14,6 +13,7 @@ from aws_cdk.aws_lambda import CfnFunction, CfnLayerVersion from aws_cdk.aws_ssm import CfnParameter +from app import CompactConnectApp from tests.app.base import TstAppABC @@ -460,40 +460,3 @@ def test_app_refuses_to_synth_with_prod_vulnerable(self): with self.assertRaises(ValueError): CompactConnectApp(context=context) - - -class TestFrontendPipeline(TstAppABC, TestCase): - @classmethod - def get_context(cls): - with open('cdk.json') as f: - context = json.load(f)['context'] - # For pipeline deployments, the pipelines pull their CDK context values from SSM Parameter Store, rather - # than the cdk.context.json files used in local development. We can override the context values used in the - # tests by adding values here. - - # Suppresses lambda bundling for tests - context['aws:cdk:bundling-stacks'] = [] - - return context - - def test_synth_pipeline(self): - """ - Test infrastructure as deployed via the pipeline - """ - # Identify any findings from our AwsSolutions rule sets - self._check_no_stack_annotations(self.app.deployment_resources_stack) - self._check_no_stack_annotations(self.app.test_frontend_pipeline_stack) - self._check_no_stack_annotations(self.app.prod_frontend_pipeline_stack) - for stage in ( - self.app.test_frontend_pipeline_stack.pre_prod_frontend_stage, - self.app.beta_frontend_pipeline_stack.beta_frontend_stage, - self.app.prod_frontend_pipeline_stack.prod_frontend_stage, - ): - self._check_no_frontend_stage_annotations(stage) - - for frontend_deployment_stack in ( - self.app.test_frontend_pipeline_stack.pre_prod_frontend_stage.frontend_deployment_stack, - self.app.beta_frontend_pipeline_stack.beta_frontend_stage.frontend_deployment_stack, - self.app.prod_frontend_pipeline_stack.prod_frontend_stage.frontend_deployment_stack, - ): - self._inspect_frontend_deployment_stack(frontend_deployment_stack) diff --git a/backend/compact-connect/tests/app/test_sandbox.py b/backend/compact-connect/tests/app/test_sandbox.py index aca16f4ee..d24ea65a0 100644 --- a/backend/compact-connect/tests/app/test_sandbox.py +++ b/backend/compact-connect/tests/app/test_sandbox.py @@ -2,7 +2,6 @@ from unittest import TestCase from app import CompactConnectApp - from tests.app.base import TstAppABC diff --git a/backend/compact-connect/tests/common_constructs/test_cognito_user_backup.py b/backend/compact-connect/tests/common_constructs/test_cognito_user_backup.py index ab5cc511b..d2935d494 100644 --- a/backend/compact-connect/tests/common_constructs/test_cognito_user_backup.py +++ b/backend/compact-connect/tests/common_constructs/test_cognito_user_backup.py @@ -18,6 +18,7 @@ from aws_cdk.aws_lambda import CfnFunction from aws_cdk.aws_s3 import CfnBucket from aws_cdk.aws_sns import Topic + from common_constructs.access_logs_bucket import AccessLogsBucket from common_constructs.cognito_user_backup import CognitoUserBackup from stacks.backup_infrastructure_stack import BackupInfrastructureStack diff --git a/backend/compact-connect/tests/common_constructs/test_queue_event_listener.py b/backend/compact-connect/tests/common_constructs/test_queue_event_listener.py index 7eeffcb1c..72a7225c1 100644 --- a/backend/compact-connect/tests/common_constructs/test_queue_event_listener.py +++ b/backend/compact-connect/tests/common_constructs/test_queue_event_listener.py @@ -8,6 +8,7 @@ from aws_cdk.aws_lambda import CfnEventSourceMapping, Code, Function, Runtime from aws_cdk.aws_sns import Topic from aws_cdk.aws_sqs import CfnQueue + from common_constructs.queue_event_listener import QueueEventListener diff --git a/backend/compact-connect/tests/common_constructs/test_queued_lambda_processor.py b/backend/compact-connect/tests/common_constructs/test_queued_lambda_processor.py index 869e05735..9b91eff79 100644 --- a/backend/compact-connect/tests/common_constructs/test_queued_lambda_processor.py +++ b/backend/compact-connect/tests/common_constructs/test_queued_lambda_processor.py @@ -6,6 +6,7 @@ from aws_cdk.aws_lambda import CfnEventSourceMapping, Code, Function, Runtime from aws_cdk.aws_sns import Topic from aws_cdk.aws_sqs import CfnQueue + from common_constructs.queued_lambda_processor import QueuedLambdaProcessor diff --git a/backend/compact-connect/lambdas/python/common/.coveragerc b/backend/multi-account/.coveragerc similarity index 51% rename from backend/compact-connect/lambdas/python/common/.coveragerc rename to backend/multi-account/.coveragerc index 193e8ec8a..6e6498b9e 100644 --- a/backend/compact-connect/lambdas/python/common/.coveragerc +++ b/backend/multi-account/.coveragerc @@ -1,10 +1,17 @@ [run] -data_file = ../../../../.coverage +include = + ./**/* + +data_file = .coverage omit = + */bin/* */cdk.out/* */smoke-test/* */tests/* [report] skip_empty = true + +[html] +directory = coverage diff --git a/backend/multi-account/backups/requirements-dev.txt b/backend/multi-account/backups/requirements-dev.txt index 65ffcdb98..24b6c7067 100644 --- a/backend/multi-account/backups/requirements-dev.txt +++ b/backend/multi-account/backups/requirements-dev.txt @@ -2,7 +2,7 @@ # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# pip-compile --no-emit-index-url multi-account/backups/requirements-dev.in +# pip-compile --no-emit-index-url backups/requirements-dev.in # boto3==1.40.19 # via moto @@ -13,11 +13,11 @@ botocore==1.40.19 # s3transfer certifi==2025.8.3 # via requests -cffi==1.17.1 +cffi==2.0.0 # via cryptography charset-normalizer==3.4.3 # via requests -cryptography==45.0.6 +cryptography==46.0.1 # via moto idna==3.10 # via requests @@ -33,18 +33,18 @@ markupsafe==3.0.2 # via # jinja2 # werkzeug -moto==5.1.11 - # via -r multi-account/backups/requirements-dev.in +moto==5.1.12 + # via -r backups/requirements-dev.in packaging==25.0 # via pytest pluggy==1.6.0 # via pytest -pycparser==2.22 +pycparser==2.23 # via cffi pygments==2.19.2 # via pytest -pytest==8.4.1 - # via -r multi-account/backups/requirements-dev.in +pytest==8.4.2 + # via -r backups/requirements-dev.in python-dateutil==2.9.0.post0 # via # botocore @@ -57,7 +57,7 @@ requests==2.32.5 # responses responses==0.25.8 # via moto -s3transfer==0.13.1 +s3transfer==0.14.0 # via boto3 six==1.17.0 # via python-dateutil @@ -68,5 +68,5 @@ urllib3==2.5.0 # responses werkzeug==3.1.3 # via moto -xmltodict==0.14.2 +xmltodict==1.0.1 # via moto diff --git a/backend/multi-account/backups/requirements.txt b/backend/multi-account/backups/requirements.txt index 695dd53b7..60c4eac25 100644 --- a/backend/multi-account/backups/requirements.txt +++ b/backend/multi-account/backups/requirements.txt @@ -2,7 +2,7 @@ # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# pip-compile --no-emit-index-url multi-account/backups/requirements.in +# pip-compile --no-emit-index-url backups/requirements.in # attrs==25.3.0 # via @@ -20,11 +20,11 @@ cattrs==25.1.1 # via jsii constructs==10.4.2 # via - # -r multi-account/backups/requirements.in + # -r backups/requirements.in # aws-cdk-lib importlib-resources==6.5.2 # via jsii -jsii==1.113.0 +jsii==1.114.1 # via # aws-cdk-asset-awscli-v1 # aws-cdk-asset-node-proxy-agent-v6 diff --git a/backend/multi-account/bin/compile_requirements.sh b/backend/multi-account/bin/compile_requirements.sh new file mode 100755 index 000000000..513d6e514 --- /dev/null +++ b/backend/multi-account/bin/compile_requirements.sh @@ -0,0 +1,9 @@ +set -e + +pip-compile --no-emit-index-url --upgrade --no-strip-extras backups/requirements-dev.in +pip-compile --no-emit-index-url --upgrade --no-strip-extras backups/requirements.in +pip-compile --no-emit-index-url --upgrade --no-strip-extras control-tower/requirements-dev.in +pip-compile --no-emit-index-url --upgrade --no-strip-extras control-tower/requirements.in +pip-compile --no-emit-index-url --upgrade --no-strip-extras log-aggregation/requirements-dev.in +pip-compile --no-emit-index-url --upgrade --no-strip-extras log-aggregation/requirements.in +bin/sync_deps.sh diff --git a/backend/multi-account/bin/run_python_tests.py b/backend/multi-account/bin/run_python_tests.py new file mode 100755 index 000000000..1364f1a09 --- /dev/null +++ b/backend/multi-account/bin/run_python_tests.py @@ -0,0 +1,171 @@ +#!/usr/bin/env python3 +# ruff: noqa: I001 +""" +This script runs all Python tests in a unified pytest session, which allows coverage data collection to properly merge +across multiple test suites. + +Note: This should be run from the 'backend' directory. +""" + +import os +import sys +import webbrowser +from argparse import ArgumentParser +from pathlib import Path + +import pytest + +from coverage import Coverage + +# Get the absolute path to the backend directory +BACKEND_DIR = Path(__file__).parent.parent.absolute() + +# Define the test directories to include +TEST_DIRS = ( + 'control-tower', + 'log-aggregation', + 'backups', # Data retention backup infrastructure +) + + +def get_coverage(): + # Initialize coverage.py with the existing .coveragerc + return Coverage( + config_file=BACKEND_DIR / '.coveragerc', + # Coverage data in memory + data_file=None, + ) + + +def clean_modules(): + """ + Clean up modules between test runs to prevent cross-contamination. + This is especially important for modules like config that maintain state. + """ + # List of module prefixes to clean up + modules_to_clean = ['cc_common', 'common_test', 'handlers', 'tests'] + + for module_name in list(sys.modules.keys()): + for prefix in modules_to_clean: + if module_name == prefix or module_name.startswith(f'{prefix}.'): + del sys.modules[module_name] + + # Delete local modules after each run so we don't use cached modules and hit name clashes between tests + for local_dir in [*os.listdir()]: + # Remove the .py extension from the local file name + local_dir = local_dir.split('.py', 1)[0] + if local_dir.isidentifier(): + for m in sorted(sys.modules.keys()): + if m.startswith(local_dir): + del sys.modules[m] + + +def run_tests(cov: Coverage, args): + cov.start() + + # Track overall test result + exit_code = 0 + try: + # Prepare pytest arguments + pytest_args = ['tests', '--tb=short', '-W', 'ignore'] + if args.verbose > 0: + pytest_args.append(f'-{"v" * args.verbose}') + + # Run tests for each directory with the correct working directory + for test_dir in TEST_DIRS: + dir_path = BACKEND_DIR / test_dir + if not dir_path.exists(): + sys.stdout.write(f'Warning: Directory {dir_path} does not exist, skipping\n') + continue + + tests_dir = dir_path / 'tests' + if not tests_dir.exists(): + sys.stdout.write(f'Warning: Tests directory {tests_dir} does not exist, skipping\n') + continue + + sys.stdout.write('\n' + '=' * 80 + '\n') + sys.stdout.write(f'Running tests in {test_dir}\n') + sys.stdout.write('=' * 80 + '\n') + + # Save the original working directory and sys.path + original_dir = os.getcwd() + original_path = sys.path.copy() + original_env = os.environ.copy() + + try: + # Change to the test directory + os.chdir(dir_path) + + # Set up PYTHONPATH for common code if needed + if test_dir != 'compact-connect/lambdas/python/common' and 'python' in test_dir: + common_path = BACKEND_DIR / 'compact-connect/lambdas/python/common' + if str(common_path) not in sys.path: + sys.path.insert(0, str(common_path)) + + # Clean up modules before running tests + clean_modules() + + # Run pytest for this directory + test_result = pytest.main(pytest_args) + + # Update exit code if any tests fail + if test_result != 0 and exit_code == 0: + return test_result + + # Restore the original environment + os.environ = original_env # noqa: B003 + + # Thorough module cleanup after each test suite + clean_modules() + + finally: + # Restore the original working directory + os.chdir(original_dir) + + # Restore the original sys.path + sys.path = original_path + finally: + # Stop coverage measurement + cov.stop() + cov.save() + + return exit_code + + +def main(): + parser = ArgumentParser(description='Run all Python tests in a unified pytest session') + parser.add_argument('--report', action='store_true', help='Generate HTML coverage report') + parser.add_argument('--open-report', action='store_true', help='Open HTML coverage report after generation') + parser.add_argument('--fail-under', type=float, default=90, help='Fail if coverage is under this percentage') + parser.add_argument('--verbose', '-v', action='count', default=0, help='Increase verbosity') + args = parser.parse_args() + + cov = get_coverage() + exit_code = run_tests(cov, args) + + # Generate coverage report if requested + if args.report: + sys.stdout.write('\nGenerating coverage report...\n') + cov.html_report() + + if args.open_report: + # Open the coverage report + report_path = BACKEND_DIR / 'coverage' / 'index.html' + webbrowser.open(f'file://{report_path}') + + # Check coverage against threshold + sys.stdout.write('\nCalculating coverage...\n') + coverage_percent = cov.report() + if coverage_percent < args.fail_under: + sys.stdout.write( + f'Coverage {coverage_percent:.2f}% is below the required threshold of {args.fail_under:.2f}%\n' + ) + exit_code = 1 + + if exit_code > 0: + sys.stdout.write('\n================= TESTS FAILED =================\n') + return exit_code + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/backend/multi-account/bin/run_tests.sh b/backend/multi-account/bin/run_tests.sh new file mode 100755 index 000000000..3b3c267cc --- /dev/null +++ b/backend/multi-account/bin/run_tests.sh @@ -0,0 +1,115 @@ +#!/bin/bash +set -e + +# Default values +LANGUAGE="all" +REPORT=true +OPEN_REPORT=true +VERBOSE="" +FAIL_UNDER=90 + +# Print usage information +usage() { + echo "Usage: $0 [options]" + echo "Options:" + echo " -l, --language LANG Specify language to test (nodejs, python, all) [default: all]" + echo " -n, --no-report Don't generate coverage report" + echo " -o, --no-open Don't open coverage report in browser" + echo " -v, --verbose Increase python verbosity (can be used multiple times: -vv)" + echo " -f, --fail-under PCT Set minimum python coverage percentage [default: 90]" + echo " -h, --help Display this help message" + exit 1 +} + +# Parse command line arguments +while getopts "l:nov:f:h-:" opt; do + case $opt in + -) + case "${OPTARG}" in + language) + LANGUAGE="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 )) + ;; + no-report) + REPORT=false + ;; + no-open) + OPEN_REPORT=false + ;; + verbose) + VERBOSE="v${VERBOSE}" + ;; + fail-under) + FAIL_UNDER="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 )) + ;; + help) + usage + ;; + *) + echo "Invalid option: --${OPTARG}" >&2 + usage + ;; + esac + ;; + l) + LANGUAGE="$OPTARG" + ;; + n) + REPORT=false + ;; + o) + OPEN_REPORT=false + ;; + v) + VERBOSE="v${VERBOSE}" + ;; + f) + FAIL_UNDER="$OPTARG" + ;; + h) + usage + ;; + \?) + echo "Invalid option: -$OPTARG" >&2 + usage + ;; + esac +done + +# Validate language argument +if [[ "$LANGUAGE" != "python" && "$LANGUAGE" != "all" ]]; then + echo "Error: Language must be 'python', or 'all'" >&2 + usage +fi + +# Set this to 1 ahead of running tests, so this script will fail if neither node or python tests ran +EXIT=1 + +# Run Python tests if requested +if [[ "$LANGUAGE" == 'python' || "$LANGUAGE" == 'all' ]]; then + echo "Running Python tests..." + + # Build Python test arguments + PYTHON_ARGS=() + + # Add report flags + if [[ "$REPORT" == true ]]; then + PYTHON_ARGS+=("--report") + if [[ "$OPEN_REPORT" == true ]]; then + PYTHON_ARGS+=("--open-report") + fi + fi + + # Add verbosity flag + if [[ -n "$VERBOSE" ]]; then + PYTHON_ARGS+=("-${VERBOSE}") + fi + + # Add fail-under threshold + PYTHON_ARGS+=("--fail-under" "$FAIL_UNDER") + + # Run the Python tests + python3 bin/run_python_tests.py "${PYTHON_ARGS[@]}" || exit "$?" + EXIT=0 +fi + +exit "$EXIT" diff --git a/backend/multi-account/bin/sync_deps.sh b/backend/multi-account/bin/sync_deps.sh new file mode 100755 index 000000000..8daf1c0a2 --- /dev/null +++ b/backend/multi-account/bin/sync_deps.sh @@ -0,0 +1,7 @@ +pip-sync \ + backups/requirements-dev.txt \ + backups/requirements.txt \ + control-tower/requirements-dev.txt \ + control-tower/requirements.txt \ + log-aggregation/requirements-dev.txt \ + log-aggregation/requirements.txt diff --git a/backend/multi-account/control-tower/requirements-dev.in b/backend/multi-account/control-tower/requirements-dev.in index c63164ea3..34e2caeca 100644 --- a/backend/multi-account/control-tower/requirements-dev.in +++ b/backend/multi-account/control-tower/requirements-dev.in @@ -1 +1,6 @@ pytest>=6.2.5 +pytest-cov +coverage +ruff +pip-tools +pip-audit diff --git a/backend/multi-account/control-tower/requirements-dev.txt b/backend/multi-account/control-tower/requirements-dev.txt index 553af2b5a..21864a695 100644 --- a/backend/multi-account/control-tower/requirements-dev.txt +++ b/backend/multi-account/control-tower/requirements-dev.txt @@ -2,15 +2,101 @@ # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# pip-compile --no-emit-index-url multi-account/control-tower/requirements-dev.in +# pip-compile --no-emit-index-url control-tower/requirements-dev.in # +boolean-py==5.0 + # via license-expression +build==1.3.0 + # via pip-tools +cachecontrol[filecache]==0.14.3 + # via + # cachecontrol + # pip-audit +certifi==2025.8.3 + # via requests +charset-normalizer==3.4.3 + # via requests +click==8.2.1 + # via pip-tools +coverage[toml]==7.10.6 + # via + # -r control-tower/requirements-dev.in + # pytest-cov +cyclonedx-python-lib==9.1.0 + # via pip-audit +defusedxml==0.7.1 + # via py-serializable +filelock==3.19.1 + # via cachecontrol +idna==3.10 + # via requests iniconfig==2.1.0 # via pytest +license-expression==30.4.4 + # via cyclonedx-python-lib +markdown-it-py==4.0.0 + # via rich +mdurl==0.1.2 + # via markdown-it-py +msgpack==1.1.1 + # via cachecontrol +packageurl-python==0.17.5 + # via cyclonedx-python-lib packaging==25.0 - # via pytest + # via + # build + # pip-audit + # pip-requirements-parser + # pytest +pip-api==0.0.34 + # via pip-audit +pip-audit==2.9.0 + # via -r control-tower/requirements-dev.in +pip-requirements-parser==32.0.1 + # via pip-audit +pip-tools==7.5.0 + # via -r control-tower/requirements-dev.in +platformdirs==4.4.0 + # via pip-audit pluggy==1.6.0 - # via pytest + # via + # pytest + # pytest-cov +py-serializable==2.1.0 + # via cyclonedx-python-lib pygments==2.19.2 - # via pytest -pytest==8.4.1 - # via -r multi-account/control-tower/requirements-dev.in + # via + # pytest + # rich +pyparsing==3.2.4 + # via pip-requirements-parser +pyproject-hooks==1.2.0 + # via + # build + # pip-tools +pytest==8.4.2 + # via + # -r control-tower/requirements-dev.in + # pytest-cov +pytest-cov==7.0.0 + # via -r control-tower/requirements-dev.in +requests==2.32.5 + # via + # cachecontrol + # pip-audit +rich==14.1.0 + # via pip-audit +ruff==0.13.0 + # via -r control-tower/requirements-dev.in +sortedcontainers==2.4.0 + # via cyclonedx-python-lib +toml==0.10.2 + # via pip-audit +urllib3==2.5.0 + # via requests +wheel==0.45.1 + # via pip-tools + +# The following packages are considered to be unsafe in a requirements file: +# pip +# setuptools diff --git a/backend/multi-account/control-tower/requirements.txt b/backend/multi-account/control-tower/requirements.txt index 27e8cc844..d9f2a66a2 100644 --- a/backend/multi-account/control-tower/requirements.txt +++ b/backend/multi-account/control-tower/requirements.txt @@ -2,7 +2,7 @@ # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# pip-compile --no-emit-index-url multi-account/control-tower/requirements.in +# pip-compile --no-emit-index-url control-tower/requirements.in # attrs==25.3.0 # via @@ -20,11 +20,11 @@ cattrs==25.1.1 # via jsii constructs==10.4.2 # via - # -r multi-account/control-tower/requirements.in + # -r control-tower/requirements.in # aws-cdk-lib importlib-resources==6.5.2 # via jsii -jsii==1.113.0 +jsii==1.114.1 # via # aws-cdk-asset-awscli-v1 # aws-cdk-asset-node-proxy-agent-v6 diff --git a/backend/multi-account/log-aggregation/requirements-dev.txt b/backend/multi-account/log-aggregation/requirements-dev.txt index 6ba6860c1..e002df771 100644 --- a/backend/multi-account/log-aggregation/requirements-dev.txt +++ b/backend/multi-account/log-aggregation/requirements-dev.txt @@ -2,7 +2,7 @@ # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# pip-compile --no-emit-index-url multi-account/log-aggregation/requirements-dev.in +# pip-compile --no-emit-index-url log-aggregation/requirements-dev.in # iniconfig==2.1.0 # via pytest @@ -12,5 +12,5 @@ pluggy==1.6.0 # via pytest pygments==2.19.2 # via pytest -pytest==8.4.1 - # via -r multi-account/log-aggregation/requirements-dev.in +pytest==8.4.2 + # via -r log-aggregation/requirements-dev.in diff --git a/backend/multi-account/log-aggregation/requirements.txt b/backend/multi-account/log-aggregation/requirements.txt index 0e384ad3a..9aedc71a3 100644 --- a/backend/multi-account/log-aggregation/requirements.txt +++ b/backend/multi-account/log-aggregation/requirements.txt @@ -2,7 +2,7 @@ # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# pip-compile --no-emit-index-url multi-account/log-aggregation/requirements.in +# pip-compile --no-emit-index-url log-aggregation/requirements.in # attrs==25.3.0 # via @@ -20,11 +20,11 @@ cattrs==25.1.1 # via jsii constructs==10.4.2 # via - # -r multi-account/log-aggregation/requirements.in + # -r log-aggregation/requirements.in # aws-cdk-lib importlib-resources==6.5.2 # via jsii -jsii==1.113.0 +jsii==1.114.1 # via # aws-cdk-asset-awscli-v1 # aws-cdk-asset-node-proxy-agent-v6 diff --git a/backend/multi-account/ruff.toml b/backend/multi-account/ruff.toml new file mode 100644 index 000000000..66682be80 --- /dev/null +++ b/backend/multi-account/ruff.toml @@ -0,0 +1,112 @@ +# Exclude a variety of commonly ignored directories. +exclude = [ + ".bzr", + ".direnv", + ".eggs", + ".git", + ".git-rewrite", + ".hg", + ".ipynb_checkpoints", + ".mypy_cache", + ".nox", + ".pants.d", + ".pyenv", + ".pytest_cache", + ".pytype", + ".ruff_cache", + ".svn", + ".tox", + ".venv", + ".vscode", + "__pypackages__", + "_build", + "buck-out", + "build", + "dist", + "node_modules", + "site-packages", + "venv" +] + +line-length = 120 +indent-width = 4 + +# Assume Python 3.12 +target-version = "py312" + +[lint] +select = [ + "A", # flake8-builtins + "ARG", # flake8-unused-arguments + "B", # flake8-bugbear + "BLE", # flake8-blind-except + "DTZ", # flake8-datetimez + "E", # pycodestyle + "F", # Pyflakes + "FIX", # flake8-fixme + "FA", # flake8-future-annotations + "G", # flake8-logging-format + "I", # isort + "N", # pep8-naming + "PIE", # flake8-pie + "SLF", # flake8-self + "RET", # flake8-return + "RSE", # flake8-raise + "S", # flake8-bandit + "T", # flake8-print + "UP", # pyupgrade + "W", # warning +] +ignore = [ + "S311", # suspicious-non-cryptographic-random-usage + "N818", # error-suffix-on-exception-name + # the following are ignored by recommendation of ruff documentation + # due to conflicts with the ruff formatter see https://docs.astral.sh/ruff/formatter/#conflicting-lint-rules + "W191", # indentation contains tabs + "E111", # indentation-with-invalid-multiple + "E114", # indentation-with-invalid-multiple-comment + "E117", # over-indented + "D206", # indent-with-spaces + "D300", # triple-single-quotes + "Q000", # bad-quotes-inline-string + "Q001", # bad-quotes-multiline-string + "Q002", # bad-quotes-docstring + "Q003", # avoidable-escaped-quote + "COM812", # missing-trailing-comma + "COM819", # prohibited-trailing-comma + "ISC001", # single-line-implicit-string-concatenation + "ISC002", # multi-line-implicit-string-concatenation +] + +# Allow fix for all enabled rules (when `--fix`) is provided. +fixable = ["ALL"] +unfixable = [] + +# Allow unused variables when underscore-prefixed. +dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" + +[format] +quote-style = "single" + +# Like Black, indent with spaces, rather than tabs. +indent-style = "space" + +# Like Black, respect magic trailing commas. +skip-magic-trailing-comma = false + +# Like Black, automatically detect the appropriate line ending. +line-ending = "auto" + +# Enable auto-formatting of code examples in docstrings. Markdown, +# reStructuredText code/literal blocks and doctests are all supported. +# +# This is currently disabled by default, but it is planned for this +# to be opt-out in the future. +docstring-code-format = false + +# Set the line length limit used when formatting code snippets in +# docstrings. +# +# This only has an effect when the `docstring-code-format` setting is +# enabled. +docstring-code-line-length = "dynamic" diff --git a/backend/social-work-app/README.md b/backend/social-work-app/README.md new file mode 100644 index 000000000..ab73e902a --- /dev/null +++ b/backend/social-work-app/README.md @@ -0,0 +1,3 @@ +# Social Work Compact App + +This is the future home of the CompactConnect app version, customized for SocialWork From fa7ff531c81132a099d25d81bf57d63e1fdfd5ea Mon Sep 17 00:00:00 2001 From: Justin Frahm Date: Fri, 19 Sep 2025 09:53:23 -0600 Subject: [PATCH 13/32] Docs, common deploy resource stack --- .../deployment_resources_stack.py | 81 +++++++ backend/compact-connect-ui-app/README.md | 225 ++++-------------- backend/compact-connect-ui-app/app.py | 39 ++- .../pipeline/__init__.py | 74 +----- backend/compact-connect/README.md | 128 ++++++---- backend/compact-connect/app.py | 2 +- backend/compact-connect/docs/design/README.md | 5 + .../docs/design/pipeline-architecture.md} | 43 +++- .../docs}/design/pipeline-architecture.pdf | Bin backend/compact-connect/pipeline/__init__.py | 73 +----- .../compact-connect/pipeline/design/README.md | 96 -------- .../pipeline/design/pipeline-architecture.pdf | Bin 89240 -> 0 bytes 12 files changed, 270 insertions(+), 496 deletions(-) create mode 100644 backend/common-cdk/common_constructs/deployment_resources_stack.py rename backend/{compact-connect-ui-app/pipeline/design/README.md => compact-connect/docs/design/pipeline-architecture.md} (68%) rename backend/{compact-connect-ui-app/pipeline => compact-connect/docs}/design/pipeline-architecture.pdf (100%) delete mode 100644 backend/compact-connect/pipeline/design/README.md delete mode 100644 backend/compact-connect/pipeline/design/pipeline-architecture.pdf diff --git a/backend/common-cdk/common_constructs/deployment_resources_stack.py b/backend/common-cdk/common_constructs/deployment_resources_stack.py new file mode 100644 index 000000000..dff97e532 --- /dev/null +++ b/backend/common-cdk/common_constructs/deployment_resources_stack.py @@ -0,0 +1,81 @@ +import json + +from aws_cdk import RemovalPolicy +from aws_cdk.aws_kms import Key +from aws_cdk.aws_ssm import StringParameter +from cdk_nag import NagSuppressions +from constructs import Construct +from pipeline import DEPLOY_ENVIRONMENT_NAME + +from common_constructs.access_logs_bucket import AccessLogsBucket +from common_constructs.alarm_topic import AlarmTopic +from common_constructs.stack import Stack + + +class DeploymentResourcesStack(Stack): + """Stack that manages all shared resources for all pipeline stacks.""" + + def __init__( + self, + scope: Construct, + construct_id: str, + **kwargs, + ): + super().__init__(scope, construct_id, environment_name='deploy', **kwargs) + + pipeline_context_parameter_name = f'{DEPLOY_ENVIRONMENT_NAME}-compact-connect-context' + + # Fetch ssm_context if not provided locally + self.parameter = StringParameter.from_string_parameter_name( + self, + 'PipelineContext', + string_parameter_name=pipeline_context_parameter_name, + ) + value = StringParameter.value_from_lookup(self, self.parameter.parameter_name) + # When CDK runs for the first time, it synthesizes fully without actually retrieving the SSM Parameter + # value. It, instead, populates parameters and other look-ups with dummy values, synthesizes, collects all + # the look-ups together, populates them for real, then re-synthesizes with real values. + # To accommodate this pattern, we have to replace this dummy value with one that will actually + # let CDK complete its first round of synthesis, so that it can get to its second, real, synthesis. + if value != f'dummy-value-for-{pipeline_context_parameter_name}': + self.ssm_context = json.loads(value) + else: + with open(f'cdk.context.{DEPLOY_ENVIRONMENT_NAME}-example.json') as f: + self.ssm_context = json.load(f)['ssm_context'] + + self.deploy_environment_context = self.ssm_context['environments'][DEPLOY_ENVIRONMENT_NAME] + + self.pipeline_shared_encryption_key = Key( + self, + 'PipelineSharedEncryptionKey', + enable_key_rotation=True, + alias=f'{self.node.path}-shared-encryption-key', + removal_policy=RemovalPolicy.RETAIN, + ) + + notifications = self.deploy_environment_context.get('notifications', {}) + self.pipeline_alarm_topic = AlarmTopic( + self, + 'AlarmTopic', + master_key=self.pipeline_shared_encryption_key, + email_subscriptions=notifications.get('email', []), + slack_subscriptions=notifications.get('slack', []), + ) + + self.pipeline_access_logs_bucket = AccessLogsBucket( + self, + 'AccessLogsBucket', + removal_policy=RemovalPolicy.RETAIN, + auto_delete_objects=False, + ) + + NagSuppressions.add_resource_suppressions_by_path( + self, + f'{self.pipeline_access_logs_bucket.node.path}/Resource', + suppressions=[ + { + 'id': 'HIPAA.Security-S3BucketLoggingEnabled', + 'reason': 'This is the access logging bucket.', + }, + ], + ) diff --git a/backend/compact-connect-ui-app/README.md b/backend/compact-connect-ui-app/README.md index c577ba4f0..6bb0c6177 100644 --- a/backend/compact-connect-ui-app/README.md +++ b/backend/compact-connect-ui-app/README.md @@ -1,11 +1,15 @@ -# Compact Connect - Backend developer documentation +# Compact Connect UI - Backend developer documentation ## Looking for technical user documentation? -[Find it here](./docs/README.md) +[Find it here](../compact-connect/docs/README.md) + +## Looking for architectural design documentation? +[Find it here](../compact-connect/docs/design/README.md) ## Introduction -This is an [AWS-CDK](https://aws.amazon.com/cdk/) based project for the backend components of the licensure compact system. +This is an [AWS-CDK](https://aws.amazon.com/cdk/) based project for the frontend deploy and hosting infrastructure for +the licensure compact system. ## Table of Contents - **[Prerequisites](#prerequisites)** @@ -19,18 +23,19 @@ This is an [AWS-CDK](https://aws.amazon.com/cdk/) based project for the backend - **[More Info](#more-info)** ## Prerequisites -[Back to top](#compact-connect---backend-developer-documentation) +[Back to top](#compact-connect-ui---backend-developer-documentation) To deploy this app, you will need: 1) Access to an AWS account -2) Python>=3.12 installed on your machine, preferably through a virtual environment management tool like [pyenv](https://github.com/pyenv/pyenv), +2) Python>=3.12 installed on your machine, preferably through a virtual environment management tool like + [pyenv](https://github.com/pyenv/pyenv), for clean management of virtual environments across multiple Python versions. 3) Otherwise, follow the [Prerequisites section](https://cdkworkshop.com/15-prerequisites.html) of the CDK workshop to prepare your system to work with AWS-CDK, including a NodeJS install. 4) Follow the steps in the [Installing Dependencies](#installing-dependencies) section. ## Environment Config -[Back to top](#compact-connect---backend-developer-documentation) +[Back to top](#compact-connect-ui---backend-developer-documentation) The `cdk.json` file tells the CDK Toolkit how to execute your app, including configuration specific to any given target deployment. You can add local configuration that will be merged into the `cdk.json['context']` values with a @@ -42,7 +47,7 @@ using the tools of your choice (`pyenv` and `venv` are common). Once the virtualenv is activated, you can install the required dependencies. ## Installing Dependencies -[Back to top](#compact-connect---backend-developer-documentation) +[Back to top](#compact-connect-ui---backend-developer-documentation) Python requirements are pinned in [`requirements.txt`](requirements.txt). Install them using `pip`: @@ -50,7 +55,8 @@ Python requirements are pinned in [`requirements.txt`](requirements.txt). Instal $ pip install -r requirements.txt ``` -Node.js requirements (for some selected Lambda runtimes) are defined in [`package.json`](./lambdas/nodejs). Install them using `yarn`. +Node.js requirements (for some selected Lambda runtimes) are defined in [`package.json`](./lambdas/nodejs). Install +them using `yarn`. ```shell $ cd lambdas/nodejs @@ -70,7 +76,7 @@ To add additional dependencies, for example other CDK libraries, just add them t `pip-compile requirements.in`, then `pip install -r requirements.txt` command. ## Local Development -[Back to top](#compact-connect---backend-developer-documentation) +[Back to top](#compact-connect-ui---backend-developer-documentation) Local development can be done by editing the python code and `cdk.json`. For development purposes, this is simply a Python project that can be exercised with local tests. Be sure to install the development requirements: @@ -93,7 +99,7 @@ in human-readable format (for example, DynamoDB table names), run `bin/fetch_aws flags. ## Tests -[Back to top](#compact-connect---backend-developer-documentation) +[Back to top](#compact-connect-ui---backend-developer-documentation) Being a cloud project whose infrastructure is written in Python, establishing tests, using the python `unittest` library is critical to maintaining reliability and velocity. Be sure that any updates you add are covered @@ -101,172 +107,57 @@ by tests, so we don't introduce bugs or cost time identifying testable bugs afte unit/functional tests bundled with this app should be designed to execute with zero requirements for environmental setup (including environment variables) beyond simply installing the dependencies in `requirements*.txt` files. CDK tests are defined under the [tests](./tests) directory. Runtime code tests should be similarly bundled within the -lambda folders. Code that is common across all lambdas should be tested in the `common-python` directory, to reduce -duplication and ensure consistency across the app. +lambda folders. Code that is common across all lambdas should be abstracted to a common code asset and tested there, to +reduce duplication and ensure consistency across the app. To execute the tests, simply run `bin/sync_deps.sh` then `bin/run_tests.sh` from the `backend` directory. ## Documentation -[Back to top](#compact-connect---backend-developer-documentation) +[Back to top](#compact-connect-ui---backend-developer-documentation) -Keeping documentation current is an important part of feature development in this project. If the feature involves a non-trivial amount of architecture or other technical design, be sure that the design and considerations are captured in the [design documentation](./docs/design). If any updates are made to the API, be sure to follow these steps to keep the documentation current: -1) Export a fresh api specification (OAS 3.0) is exported from API Gateway and used to update [the Open API Specification JSON file](./docs/api-specification/latest-oas30.json). -2) Run `bin/trim_oas30.py` to organize and trim the API to include only supported API endpoints (and update the script itself, if needed). -3) If you exported the api specification from somewhere other than the CSG Test environment, be sure to set the `servers[0].url` entry back to the correct base URL for the CSG Test environment. -4) Use `bin/update_postman_collection.py` to update the [Postman Collection and Environment](./docs/postman), based on your new api spec, as appropriate. +Keeping documentation current is an important part of feature development in this project. If the feature involves a +non-trivial amount of architecture or other technical design, be sure that the design and considerations are captured +in the [design documentation](./docs/design). ## Deployment -[Back to top](#compact-connect---backend-developer-documentation) - -### AWS Service Quota Increases -Before deploying to any environment (sandbox, test, beta, or production), you'll need to request service quota increases for the following AWS services: - -#### 1. Resource Servers Per User Pool (Amazon Cognito) -The Staff Users pool in CompactConnect uses resource servers for every jurisdiction (50+ states/territories). It also has resource servers for each compact to implement granular permission scopes. As detailed in [User Architecture documentation](./docs/design/README.md#user-architecture), resource server scopes are defined at both the jurisdiction level (ie for state administrators) and the compact level (ie for compact administrators), allowing for fine-grained access control tailored to each entity's specific needs. - -**Required Steps:** -1. Visit the [AWS Service Quotas console](https://console.aws.amazon.com/servicequotas/home) in each AWS account you'll be deploying to -2. Search for "Amazon Cognito User Pools" -3. Find "Resource servers per user pool" (default value is 25) -4. Request an increase to at least 100 resource servers per user pool -5. Wait for AWS to approve the increase before attempting deployment - -This increase gives sufficient capacity for all jurisdictions (50+ states/territories) plus all compacts, with room for future expansion. - -#### 2. Concurrent Executions (AWS Lambda) -CompactConnect uses numerous Lambda functions to power its backend services. By default, new AWS accounts have a very low concurrent execution limit. - -**Required Steps:** -1. Visit the [AWS Service Quotas console](https://console.aws.amazon.com/servicequotas/home) in each AWS account you'll be deploying to -2. Search for "AWS Lambda" -3. Find "Concurrent executions" (default value is 10 for new accounts) -4. Request an increase to at least 1,000 concurrent executions -5. Wait for AWS to approve the increase before attempting deployment - -This increase ensures that your Lambda functions can scale appropriately during periods of high traffic without throttling. +[Back to top](#compact-connect-ui---backend-developer-documentation) ### First deploy to a Sandbox environment The very first deploy to a new environment (like your personal sandbox account) requires a few steps to fully set up its environment: -1) *Optional:* Create a new Route53 HostedZone in your AWS sandbox account for the DNS domain name you want to use for - your app. See [About Route53 Hosted Zones](#about-route53-hosted-zones) for more. Note: Without this step, you will - not be able to log in to the UI hosted in CloudFront. The Oauth2 authentication process requires a predictable - callback url to be pre-configured, which the domain name provides. You can still run a local UI against this app, - so long as you leave the `allow_local_ui` context value set to `true` and remove the `domain_name` param in your environment's context. -2) *Optional if testing SES email notifications with custom domain:* By default, AWS does not allow sending emails to unverified email - addresses. If you need to test SES email notifications and do not want to request AWS to remove your account from - the SES sandbox, you will need to set up a verified SES email identity for each address you want to send emails to. - See [Creating an email address identity](https://docs.aws.amazon.com/ses/latest/dg/creating-identities.html#verify-email-addresses-procedure). Alternatively, you can request AWS to remove your account - from the SES sandbox, which will allow you to send emails to addresses that are not verified. See [SES Sandbox](https://docs.aws.amazon.com/ses/latest/dg/request-production-access.html). - If you do not specify the `domain_name` field in your environment context, cognito will use its default email configuration. - See [Default User Pool Email Settings](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-email.html#user-pool-email-default) -3) Copy [cdk.context.sandbox-example.json](./cdk.context.sandbox-example.json) to `cdk.context.json`. -4) At the top level of the JSON structure update the `"environment_name"` field to your own name. -5) Update the environment entry under `ssm_context.environments` to your own name and your own AWS sandbox account id (which you can find by following these [these instructions](https://docs.aws.amazon.com/accounts/latest/reference/manage-acct-identifiers.html#FindAccountId)), - and domain name, if you set one up. **If you opted not to create a HostedZone, remove the `domain_name` field.** - The key under `environments` must match the value you put under `environment_name`. -6) Configure your aws cli to authenticate against your own account. There are several ways to do this based on the - type of authentication you use to login to your account. See the [AWS CLI Configuration Guide](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-quickstart.html). -7) Complete the [Google reCAPTCHA Setup](#google-recaptcha-setup) steps for your sandbox environment. -8) Run `cdk bootstrap` to add some base CDK support infrastructure to your AWS account. -9) Run `cdk deploy 'Sandbox/*'` to get the initial backend stack resources deployed. -10) *Optional:* If you have a domain name configured for your sandbox environment, once the backend stacks have successfully deployed, you can deploy the frontend UI by setting the 'deploy_sandbox_ui' context field to `true` and run `cdk deploy 'SandboxUI/*'`. If you do not have a domain name configured, you can still run the UI from local host (see the README.md under the webroot folder for more information about running the app on localhost). If you do have a domain name configured, the application will be accessible at the 'app' subdomain of the configured domain name (e.g., app.[configured_domain.com]). +1) Deploy the backend app. See the [backend app](../compact-connect/README.md) for details. You will need to configure + the 'optional' domain name, if you want to host your frontend in your sandbox. +2) Copy your sandbox context file from the backend app to this folder. +3) Once the backend stacks have successfully deployed with a domain name, you can deploy the frontend UI by setting the + running `cdk deploy 'SandboxUI/*'`. The application will then be accessible at the 'app' subdomain of the configured + domain name (e.g., `https://app.[configured_domain.com]`). ### Subsequent sandbox deploys: -For any future deploys, everything is set up so a simple `cdk deploy 'Sandbox/*'` should update all your infrastructure -to reflect the changes in your code. Full deployment steps are: +For any future deploys, everything is set up so a simple `cdk deploy 'SandboxUI/*'` should update all your frontend +infrastructure to reflect the changes in your code. Full deployment steps are: 1) Make sure your python environment is active. 2) Run `bin/sync_deps.sh` from `backend/` to ensure you have the latest requirements installed. 3) Configure your aws cli to authenticate against your own account. -4) Run `cdk deploy 'Sandbox/*'` to deploy the app to your AWS account. - -### Verifying SES configuration for Cognito User Notifications -If your account is in the SES sandbox, the simplest way to verify that SES is integrated with your cognito user pool is -to first go the AWS SES console and create an SES verified email identity for the email address you want to send a test -message to, See [Creating an email address identity](https://docs.aws.amazon.com/ses/latest/dg/creating-identities.html#verify-email-addresses-procedure). - -Once you have verified your email address, go to the AWS Cognito console and find your user pool. From there, you have -the option to create a new user using your verified email address, and select the option to send an email invite. Once -you create the user, you should receive an email notification from Cognito, and you can verify that -the FROM address is using your custom domain. The DMARC authentication will reject any emails from your domain that are -not properly configured using SPF and DKIM, so if you get the email notification from Cognito, you've verified that the -authentication is working as expected. +4) Run `cdk deploy 'SandboxUI/*'` to deploy the app to your AWS account. ### First deploy to the production environment The production environment requires a few steps to fully set up before deploys can be automated. Refer to the [README.md](../multi-account/README.md) for details on setting up a full multi-account architecture environment. Once that is done, perform the following steps to deploy the CI/CD pipelines into the appropriate AWS account: -- Complete the [Google reCAPTCHA Setup](#google-recaptcha-setup) steps for each environment you will be deploying to (test, beta, prod). Use the appropriate domain name for the environment (ie `app.test.compactconnect.org` for test environment, `app.beta.compactconnect.org` for beta environment, `app.compactconnect.org` for production). For the production environment, make sure to complete the billing setup steps as well. -- Have someone who has suitable permissions in the GitHub organization that hosts this code navigate to the AWS Console - for the Deploy account, go to the - [AWS CodeStar Connections](https://us-east-1.console.aws.amazon.com/codesuite/settings/connections) page and create a - connection that grants AWS permission to receive GitHub events. Note the ARN of the resulting connection for - the next step. -- Create a new Route53 hosted zone for the domain name you plan to use for the app in each of the production, beta, and test AWS - accounts. See [About Route53 hosted zones](#about-route53-hosted-zones) below for more detail. -- Request AWS to remove your account from the SES sandbox and wait for them to complete this request. - See [SES Sandbox](https://docs.aws.amazon.com/ses/latest/dg/request-production-access.html). -- With the [aws-cli](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html), set up your local machine to authenticate against the Deploy account as an administrator. -- For every environment, copy the appropriate example context file (`cdk.context.deploy-example.json` for the `Deploy` account, `cdk.context.test-example.json` for the `Test` account, `cdk.context.beta-example.json` for the `Beta` account, or `cdk.context.prod-example.json` for the `Prod` account) to `cdk.context.json` and update the values to match your respective accounts and other identifiers, including the code star connection you just created to match the identifiers for your actual accounts and resources. You will then need to run the `bin/put_ssm_context.sh ` script to push relevant content from your `cdk.context.json` script into an SSM Parameter in your Deploy account. Replace `` with the target environment. For example, to set up for the test environment: `bin/put_ssm_context.sh test`. - For example, to set up for the test environment: `bin/put_ssm_context.sh test`. -- Optional: If a Slack integration is desired for operational support, have someone with permission to install Slack - apps in your workspace and Admin access to each of the Test, Beta, Prod, and Deploy accounts log into each AWS account - and go to the Chatbot service. Select 'Slack' under the **Configure a chat client** box and click **Configure - client**, then follow the Slack authorization prompts. This will authorize AWS to integrate with the channels you - identify in your `cdk.context.json` file. For each Slack channel you want to integrate, be sure to invite your new AWS app to those channels. Update the - `notifications.slack` sections of the `cdk.context.json` file with the details for your Slack workspace and channels. - If you opt not to integrate with Slack, remove the `slack` fields from the file. -- Set cli-environment variables `CDK_DEFAULT_ACCOUNT` and `CDK_DEFAULT_REGION` to your deployment account id and `us-east-1`, respectively. -- For each environment (test, beta, prod), you need to deploy both the backend and frontend pipeline stacks: - -1. **Deploy the Backend Pipeline Stacks first (note: you will need to approve the permission change requests for each stack deployment in the terminal)**: - `cdk deploy --context action=bootstrapDeploy TestBackendPipelineStack BetaBackendPipelineStack ProdBackendPipelineStack` +1. **Deploy the Backend Pipeline Stacks and applications first. See [the backend app](../compact-connect/README.md) + for details. 2. **Then deploy the Frontend Pipeline Stacks (approve the permission change requests for each stack deployment)**: `cdk deploy --context action=bootstrapDeploy TestFrontendPipelineStack BetaFrontendPipelineStack ProdFrontendPipelineStack` -**Important**: When a pipeline stack is deployed, it will automatically trigger a deployment to its environment from the configured branch in your GitHub repo. The first time you deploy the backend pipeline, it should pass all the steps except the final trigger of the frontend pipeline, since the frontend pipeline will not exist until you deploy it. From there on, the pipelines should integrate as designed. - ### Subsequent production deploys Once the pipelines are established with the above steps, deployments will be automatically handled: -- Pushes to the `development` branch will trigger the test backend pipeline, which will then trigger the test frontend pipeline -- Pushes to the `main` branch will trigger both the beta and production backend pipelines, which will then trigger their respective frontend pipelines. - -## Google reCAPTCHA Setup -[Back to top](#compact-connect---backend-developer-documentation) - -The practitioner registration endpoint uses Google reCAPTCHA to prevent abuse. Follow these steps to set up reCAPTCHA for your environment: - -1. Visit https://www.google.com/recaptcha/ -2. Go to "v3 Admin Console" - - If needed, enter your Google account credentials -3. Create a site - - Recaptcha type is v3 (score based) - - Domain will be the frontend browser domain for the environment ('localhost' for local development) - - Google Cloud Platform may require a project name - - Submit -4. Open the Settings for the new site - - The Site Key (Public) will need to be set in the cdk.context.json for the appropriate environment under the field named `recaptcha_public_key` for deployments, or in your local `.env` file of the webroot folder (if running the app locally) - - The Secret Key (Private) will need to be manually stored in the AWS account in secrets manager, using the following secret name: - `compact-connect/env/{value of 'environment_name' in cdk.context.json}/recaptcha/token` - The value of the secret key should be in the following format: - ``` - { - "token": "" - } - ``` - You can run the following aws cli command to create the secret (make sure you are logged in to the same AWS account you want to store the secret in, under the us-east-1 region): - ``` - aws secretsmanager create-secret --name compact-connect/env/{value of 'environment_name' in cdk.context.json}/recaptcha/token --secret-string '{"token": ""}' - ``` - -For Production environments, additional billing setup is required: -1. In the Settings for a reCAPTCHA site, click "View in Cloud Console" -2. From the main nav, go to Billing -3. If you have an existing billing account, you may link it. Otherwise, you can create a New Billing account, where you will add payment information -4. More info on Google Recaptcha billing: https://cloud.google.com/recaptcha/docs/billing-information +- Pushes to the `development` branch will trigger the test backend pipeline, which will then trigger the test frontend + pipeline +- Pushes to the `main` branch will trigger both the beta and production backend pipelines, which will then trigger + their respective frontend pipelines. ### Useful commands @@ -277,42 +168,16 @@ For Production environments, additional billing setup is required: * `cdk docs` open CDK documentation ## Decommissioning -[Back to top](#compact-connect---backend-developer-documentation) +[Back to top](#compact-connect-ui---backend-developer-documentation) You can tear down resources associated with any of the CloudFormation stacks for this application with -`cdk destroy `. Most persistent resources with data remain in the Persistent stack, so you can freely -destroy the others without losing users or data. If you wish to destroy the Persistent stack as well, be aware that -some resources may be left behind as CloudFormation is designed to err on the side of orphaning resources over data -loss. You can identify any resources that weren't destroyed by watching the stack deletion from the AWS CloudFormation +`cdk destroy `. Most resources in frontend infrastructure can be deleted with the stack, however some data +storage resources, such as the access logs bucket, may not be configured to delete with the stack, depending on your. +You can identify any resources that weren't destroyed by watching the stack deletion from the AWS CloudFormation Console, then looking at the resources after its delete is complete, to look for any with a `Delete Skipped` status. -## About Route53 hosted zones - -A Hosted Zone in Route53 represents a collection of DNS records for a particular domain and its subdomains, managed -together. See the [Route53 FAQs for more](https://aws.amazon.com/route53/faqs/). When creating a hosted zone, you have -to also configure the domain name registrar (be it AWS or some other vendor) to point to the name servers associated -with your hosted zone, before the records in the zone will have any effect. When deploying this app, creating a hosted -zone in the AWS account for the UI and API domains is part of the environment setup. If you use the common approach -of having your test environments be a subdomain of your production environments (i.e. `compcatconnect.org` for prod -and `test.compcatconnect.org` for test), you need to delegate nameserver authority from your production hosted zone -(`compactconnect.org` in this example) to your test account's hosted zone (`test.compactconnect.org`). To do this, you -need to create your production hosted zone (`compactconnect.org`) in your production account first, then create your -test hosted zone (`test.compactconnect.org`) in your test account second, then delegate name server authority to your -test subdomain. To do this, find the NS record associated with your test hosted zone and copy its value, which should -look something like: -```text -ns-1.awsdns-19.co.uk. -ns-2.awsdns-18.com. -ns-5.awsdns-15.net. -ns-6.awsdns-16.org. -``` - -Copy those name server values and, back in your production hosted zone, create a new NS record that matches the test -one, with the same value (i.e. Record Name: `test.compcatconnect.org`, Type: `NS`, Value: ``). Once that -is done, your test hosted zone is ready for use by the app. You will need to perform this action for your beta environment as well, should you choose to deploy one. - ## More Info -[Back to top](#compact-connect---backend-developer-documentation) +[Back to top](#compact-connect-ui---backend-developer-documentation) -- [cdk-workshop](https://cdkworkshop.com/): If you are new to CDK, I highly recommend you go through the CDK Workshop for a quick - introduction to the technology and its concepts before getting too deep into any particular project. +- [cdk-workshop](https://cdkworkshop.com/): If you are new to CDK, I highly recommend you go through the CDK Workshop + for a quick introduction to the technology and its concepts before getting too deep into any particular project. diff --git a/backend/compact-connect-ui-app/app.py b/backend/compact-connect-ui-app/app.py index be006f967..dd18b8210 100644 --- a/backend/compact-connect-ui-app/app.py +++ b/backend/compact-connect-ui-app/app.py @@ -7,13 +7,14 @@ # Make the `common_constructs` namespace package under `common-cdk` available to Python sys.path.insert(0, os.path.abspath(os.path.join('..', 'common-cdk'))) +from common_constructs.deployment_resources_stack import DeploymentResourcesStack from common_constructs.stack import StandardTags + from pipeline import ( ACTION_CONTEXT_KEY, PIPELINE_STACK_CONTEXT_KEY, PIPELINE_SYNTH_ACTION, BetaFrontendPipelineStack, - DeploymentResourcesStack, ProdFrontendPipelineStack, TestFrontendPipelineStack, ) @@ -23,7 +24,7 @@ TEST_FRONTEND_PIPELINE_STACK = 'TestFrontendPipelineStack' BETA_FRONTEND_PIPELINE_STACK = 'BetaFrontendPipelineStack' PROD_FRONTEND_PIPELINE_STACK = 'ProdFrontendPipelineStack' -DEPLOYMENT_RESOURCES_STACK = 'DeploymentResourcesStack' +DEPLOYMENT_RESOURCES_STACK = 'FrontendDeploymentResourcesStack' # CDK path CDK_PATH = 'backend/compact-connect-ui-app' @@ -66,33 +67,24 @@ def __init__(self, *args, **kwargs): def _setup_sandbox_environment(self): """Set up sandbox environment stacks""" # ssm_context must be provided locally for a sandbox deploy - # TODO: review what context is still needed in this reduced app ssm_context = self.node.get_context('ssm_context') environment_name = self.node.get_context('environment_name') environment_context = ssm_context['environments'][environment_name] - # TODO: review this toggle flow for what can be simplified - # TODO: update pipeline docs - # NOTE: for first-time sandbox deployments, ensure you deploy the backend stage successfully first - # by running `cdk deploy 'Sandbox/*'`, then if you have a domain name configured and want to deploy the UI for - # your sandbox environment, set the 'deploy_sandbox_ui' field to true and deploy this stack by running - # `cdk deploy 'SandboxUI/*'. This ensures the user pool values are configured to be bundled with the UI build - # artifact. - if environment_context.get('deploy_sandbox_ui', False): - if not environment_context.get('domain_name'): - raise ValueError( - 'Cannot deploy sandbox UI if domain name is not configured for your environment. ' - 'You may still run the app from localhost. See README.md in the webroot folder for ' - 'more information about running the app from localhost.' - ) - - self.sandbox_frontend_stage = FrontendStage( - self, - 'SandboxUI', - environment_name=environment_name, - environment_context=environment_context, + if not environment_context.get('domain_name'): + raise ValueError( + 'Cannot deploy sandbox UI if domain name is not configured for your environment. ' + 'You may still run the app from localhost. See README.md in the webroot folder for ' + 'more information about running the app from localhost.' ) + self.sandbox_frontend_stage = FrontendStage( + self, + 'SandboxUI', + environment_name=environment_name, + environment_context=environment_context, + ) + def _setup_pipeline_environment(self): """ Set up pipeline environment stacks based on action and pipeline stack context @@ -131,7 +123,6 @@ def add_all_pipeline_stacks(self): stack resources in every environment. """ # This stack must be declared first, as all other pipeline stacks depend on it. - # TODO: decide how we will handle these common deployment resources after breaking up into multiple apps self.add_deployment_resources_stack() self.add_test_frontend_pipeline_stack() diff --git a/backend/compact-connect-ui-app/pipeline/__init__.py b/backend/compact-connect-ui-app/pipeline/__init__.py index eacc3d6dc..edf4c6b89 100644 --- a/backend/compact-connect-ui-app/pipeline/__init__.py +++ b/backend/compact-connect-ui-app/pipeline/__init__.py @@ -2,14 +2,11 @@ from aws_cdk import Environment, RemovalPolicy from aws_cdk.aws_iam import Effect, PolicyStatement -from aws_cdk.aws_kms import IKey, Key +from aws_cdk.aws_kms import IKey from aws_cdk.aws_s3 import IBucket from aws_cdk.aws_sns import ITopic from aws_cdk.aws_ssm import StringParameter from aws_cdk.pipelines import CodePipeline as CdkCodePipeline -from cdk_nag import NagSuppressions -from common_constructs.access_logs_bucket import AccessLogsBucket -from common_constructs.alarm_topic import AlarmTopic from common_constructs.stack import Stack from constructs import Construct @@ -32,75 +29,6 @@ BOOTSTRAP_DEPLOY_ACTION = 'bootstrapDeploy' -class DeploymentResourcesStack(Stack): - """Stack that manages all shared resources for all pipeline stacks.""" - - def __init__( - self, - scope: Construct, - construct_id: str, - **kwargs, - ): - super().__init__(scope, construct_id, environment_name='deploy', **kwargs) - - pipeline_context_parameter_name = f'{DEPLOY_ENVIRONMENT_NAME}-compact-connect-context' - - # Fetch ssm_context if not provided locally - self.parameter = StringParameter.from_string_parameter_name( - self, - 'PipelineContext', - string_parameter_name=pipeline_context_parameter_name, - ) - value = StringParameter.value_from_lookup(self, self.parameter.parameter_name) - # When CDK runs for the first time, it synthesizes fully without actually retrieving the SSM Parameter - # value. It, instead, populates parameters and other look-ups with dummy values, synthesizes, collects all - # the look-ups together, populates them for real, then re-synthesizes with real values. - # To accommodate this pattern, we have to replace this dummy value with one that will actually - # let CDK complete its first round of synthesis, so that it can get to its second, real, synthesis. - if value != f'dummy-value-for-{pipeline_context_parameter_name}': - self.ssm_context = json.loads(value) - else: - with open(f'cdk.context.{DEPLOY_ENVIRONMENT_NAME}-example.json') as f: - self.ssm_context = json.load(f)['ssm_context'] - - self.deploy_environment_context = self.ssm_context['environments'][DEPLOY_ENVIRONMENT_NAME] - - self.pipeline_shared_encryption_key = Key( - self, - 'PipelineSharedEncryptionKey', - enable_key_rotation=True, - alias='pipeline-shared-encryption-key', - removal_policy=RemovalPolicy.RETAIN, - ) - - notifications = self.deploy_environment_context.get('notifications', {}) - self.pipeline_alarm_topic = AlarmTopic( - self, - 'AlarmTopic', - master_key=self.pipeline_shared_encryption_key, - email_subscriptions=notifications.get('email', []), - slack_subscriptions=notifications.get('slack', []), - ) - - self.pipeline_access_logs_bucket = AccessLogsBucket( - self, - 'AccessLogsBucket', - removal_policy=RemovalPolicy.RETAIN, - auto_delete_objects=False, - ) - - NagSuppressions.add_resource_suppressions_by_path( - self, - f'{self.pipeline_access_logs_bucket.node.path}/Resource', - suppressions=[ - { - 'id': 'HIPAA.Security-S3BucketLoggingEnabled', - 'reason': 'This is the access logging bucket.', - }, - ], - ) - - class BasePipelineStack(Stack): """Base stack with common functionality for all pipeline stacks (both backend and frontend).""" diff --git a/backend/compact-connect/README.md b/backend/compact-connect/README.md index 7b4ace16f..0f44aa4d8 100644 --- a/backend/compact-connect/README.md +++ b/backend/compact-connect/README.md @@ -23,8 +23,9 @@ This is an [AWS-CDK](https://aws.amazon.com/cdk/) based project for the backend To deploy this app, you will need: 1) Access to an AWS account -2) Python>=3.12 installed on your machine, preferably through a virtual environment management tool like [pyenv](https://github.com/pyenv/pyenv), - for clean management of virtual environments across multiple Python versions. +2) Python>=3.12 installed on your machine, preferably through a virtual environment management tool like + [pyenv](https://github.com/pyenv/pyenv), for clean management of virtual environments across multiple Python + versions. 3) Otherwise, follow the [Prerequisites section](https://cdkworkshop.com/15-prerequisites.html) of the CDK workshop to prepare your system to work with AWS-CDK, including a NodeJS install. 4) Follow the steps in the [Installing Dependencies](#installing-dependencies) section. @@ -101,28 +102,40 @@ by tests, so we don't introduce bugs or cost time identifying testable bugs afte unit/functional tests bundled with this app should be designed to execute with zero requirements for environmental setup (including environment variables) beyond simply installing the dependencies in `requirements*.txt` files. CDK tests are defined under the [tests](./tests) directory. Runtime code tests should be similarly bundled within the -lambda folders. Code that is common across all lambdas should be tested in the `common-python` directory, to reduce -duplication and ensure consistency across the app. +lambda folders. Code that is common across all lambdas should be abstracted to a common code asset and tested there, to +reduce duplication and ensure consistency across the app. To execute the tests, simply run `bin/sync_deps.sh` then `bin/run_tests.sh` from the `backend` directory. ## Documentation [Back to top](#compact-connect---backend-developer-documentation) -Keeping documentation current is an important part of feature development in this project. If the feature involves a non-trivial amount of architecture or other technical design, be sure that the design and considerations are captured in the [design documentation](./docs/design). If any updates are made to the API, be sure to follow these steps to keep the documentation current: -1) Export a fresh api specification (OAS 3.0) is exported from API Gateway and used to update [the Open API Specification JSON file](./docs/api-specification/latest-oas30.json). -2) Run `bin/trim_oas30.py` to organize and trim the API to include only supported API endpoints (and update the script itself, if needed). -3) If you exported the api specification from somewhere other than the CSG Test environment, be sure to set the `servers[0].url` entry back to the correct base URL for the CSG Test environment. -4) Use `bin/update_postman_collection.py` to update the [Postman Collection and Environment](./docs/postman), based on your new api spec, as appropriate. +Keeping documentation current is an important part of feature development in this project. If the feature involves a +non-trivial amount of architecture or other technical design, be sure that the design and considerations are captured +in the [design documentation](./docs/design). If any updates are made to the API, be sure to follow these steps to keep +the documentation current: +1) Export a fresh api specification (OAS 3.0) is exported from API Gateway and used to update + [the Open API Specification JSON file](./docs/api-specification/latest-oas30.json). +2) Run `bin/trim_oas30.py` to organize and trim the API to include only supported API endpoints (and update the script + itself, if needed). +3) If you exported the api specification from somewhere other than the CSG Test environment, be sure to set the + `servers[0].url` entry back to the correct base URL for the CSG Test environment. +4) Use `bin/update_postman_collection.py` to update the [Postman Collection and Environment](./docs/postman), based on + your new api spec, as appropriate. ## Deployment [Back to top](#compact-connect---backend-developer-documentation) ### AWS Service Quota Increases -Before deploying to any environment (sandbox, test, beta, or production), you'll need to request service quota increases for the following AWS services: +Before deploying to any environment (sandbox, test, beta, or production), you'll need to request service quota +increases for the following AWS services: #### 1. Resource Servers Per User Pool (Amazon Cognito) -The Staff Users pool in CompactConnect uses resource servers for every jurisdiction (50+ states/territories). It also has resource servers for each compact to implement granular permission scopes. As detailed in [User Architecture documentation](./docs/design/README.md#user-architecture), resource server scopes are defined at both the jurisdiction level (ie for state administrators) and the compact level (ie for compact administrators), allowing for fine-grained access control tailored to each entity's specific needs. +The Staff Users pool in CompactConnect uses resource servers for every jurisdiction (50+ states/territories). It also +has resource servers for each compact to implement granular permission scopes. As detailed in +[User Architecture documentation](./docs/design/README.md#user-architecture), resource server scopes are defined at +both the jurisdiction level (ie for state administrators) and the compact level (ie for compact administrators), +allowing for fine-grained access control tailored to each entity's specific needs. **Required Steps:** 1. Visit the [AWS Service Quotas console](https://console.aws.amazon.com/servicequotas/home) in each AWS account you'll be deploying to @@ -131,10 +144,12 @@ The Staff Users pool in CompactConnect uses resource servers for every jurisdict 4. Request an increase to at least 100 resource servers per user pool 5. Wait for AWS to approve the increase before attempting deployment -This increase gives sufficient capacity for all jurisdictions (50+ states/territories) plus all compacts, with room for future expansion. +This increase gives sufficient capacity for all jurisdictions (50+ states/territories) plus all compacts, with room for +future expansion. #### 2. Concurrent Executions (AWS Lambda) -CompactConnect uses numerous Lambda functions to power its backend services. By default, new AWS accounts have a very low concurrent execution limit. +CompactConnect uses numerous Lambda functions to power its backend services. By default, new AWS accounts have a very +low concurrent execution limit. **Required Steps:** 1. Visit the [AWS Service Quotas console](https://console.aws.amazon.com/servicequotas/home) in each AWS account you'll be deploying to @@ -152,17 +167,23 @@ its environment: your app. See [About Route53 Hosted Zones](#about-route53-hosted-zones) for more. Note: Without this step, you will not be able to log in to the UI hosted in CloudFront. The Oauth2 authentication process requires a predictable callback url to be pre-configured, which the domain name provides. You can still run a local UI against this app, - so long as you leave the `allow_local_ui` context value set to `true` and remove the `domain_name` param in your environment's context. -2) *Optional if testing SES email notifications with custom domain:* By default, AWS does not allow sending emails to unverified email + so long as you leave the `allow_local_ui` context value set to `true` and remove the `domain_name` param in your + environment's context. +2) *Optional if testing SES email notifications with custom domain:* By default, AWS does not allow sending emails to + unverified email addresses. If you need to test SES email notifications and do not want to request AWS to remove your account from the SES sandbox, you will need to set up a verified SES email identity for each address you want to send emails to. See [Creating an email address identity](https://docs.aws.amazon.com/ses/latest/dg/creating-identities.html#verify-email-addresses-procedure). Alternatively, you can request AWS to remove your account - from the SES sandbox, which will allow you to send emails to addresses that are not verified. See [SES Sandbox](https://docs.aws.amazon.com/ses/latest/dg/request-production-access.html). - If you do not specify the `domain_name` field in your environment context, cognito will use its default email configuration. + from the SES sandbox, which will allow you to send emails to addresses that are not verified. See + [SES Sandbox](https://docs.aws.amazon.com/ses/latest/dg/request-production-access.html). + If you do not specify the `domain_name` field in your environment context, cognito will use its default email + configuration. See [Default User Pool Email Settings](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-email.html#user-pool-email-default) 3) Copy [cdk.context.sandbox-example.json](./cdk.context.sandbox-example.json) to `cdk.context.json`. 4) At the top level of the JSON structure update the `"environment_name"` field to your own name. -5) Update the environment entry under `ssm_context.environments` to your own name and your own AWS sandbox account id (which you can find by following these [these instructions](https://docs.aws.amazon.com/accounts/latest/reference/manage-acct-identifiers.html#FindAccountId)), +5) Update the environment entry under `ssm_context.environments` to your own name and your own AWS sandbox account id + (which you can find by following + [these instructions](https://docs.aws.amazon.com/accounts/latest/reference/manage-acct-identifiers.html#FindAccountId)), and domain name, if you set one up. **If you opted not to create a HostedZone, remove the `domain_name` field.** The key under `environments` must match the value you put under `environment_name`. 6) Configure your aws cli to authenticate against your own account. There are several ways to do this based on the @@ -171,7 +192,9 @@ its environment: 8) Run `cdk bootstrap` to add some base CDK support infrastructure to your AWS account. See [Custom bootstrap stack](#custom-bootstrap-stack) below for optional custom stack deployment. 9) Run `cdk deploy 'Sandbox/*'` to get the initial backend stack resources deployed. -10) *Optional:* If you have a domain name configured for your sandbox environment, once the backend stacks have successfully deployed, you can deploy the frontend UI by setting the 'deploy_sandbox_ui' context field to `true` and run `cdk deploy 'SandboxUI/*'`. If you do not have a domain name configured, you can still run the UI from local host (see the README.md under the webroot folder for more information about running the app on localhost). If you do have a domain name configured, the application will be accessible at the 'app' subdomain of the configured domain name (e.g., app.[configured_domain.com]). +10) *Optional:* If you have a domain name configured for your sandbox environment, once the backend stacks have + successfully deployed, you can deploy the frontend UI app as well. See the + [UI app for details](../compact-connect-ui-app/README.md). ### Subsequent sandbox deploys: For any future deploys, everything is set up so a simple `cdk deploy 'Sandbox/*'` should update all your infrastructure @@ -207,48 +230,67 @@ authentication is working as expected. The production environment requires a few steps to fully set up before deploys can be automated. Refer to the [README.md](../multi-account/README.md) for details on setting up a full multi-account architecture environment. Once that is done, perform the following steps to deploy the CI/CD pipelines into the appropriate AWS account: -- Complete the [Google reCAPTCHA Setup](#google-recaptcha-setup) steps for each environment you will be deploying to (test, beta, prod). Use the appropriate domain name for the environment (ie `app.test.compactconnect.org` for test environment, `app.beta.compactconnect.org` for beta environment, `app.compactconnect.org` for production). For the production environment, make sure to complete the billing setup steps as well. -- Have someone who has suitable permissions in the GitHub organization that hosts this code navigate to the AWS Console +- Complete the [Google reCAPTCHA Setup](#google-recaptcha-setup) steps for each environment you will be deploying to (test, beta, prod). + Use the appropriate domain name for the environment (ie `app.test.compactconnect.org` for test environment, + `app.beta.compactconnect.org` for beta environment, `app.compactconnect.org` for production). For the production + environment, make sure to complete the billing setup steps as well. +- Have someone with suitable permissions in the GitHub organization that hosts this code navigate to the AWS Console for the Deploy account, go to the [AWS CodeStar Connections](https://us-east-1.console.aws.amazon.com/codesuite/settings/connections) page and create a connection that grants AWS permission to receive GitHub events. Note the ARN of the resulting connection for the next step. -- Create a new Route53 hosted zone for the domain name you plan to use for the app in each of the production, beta, and test AWS - accounts. See [About Route53 hosted zones](#about-route53-hosted-zones) below for more detail. +- Create a new Route53 hosted zone for the domain name you plan to use for the app in each of the production, beta, and + test AWS accounts. See [About Route53 hosted zones](#about-route53-hosted-zones) below for more detail. - Request AWS to remove your account from the SES sandbox and wait for them to complete this request. See [SES Sandbox](https://docs.aws.amazon.com/ses/latest/dg/request-production-access.html). - With the [aws-cli](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html), set up your local machine to authenticate against the Deploy account as an administrator. -- For every environment, copy the appropriate example context file (`cdk.context.deploy-example.json` for the `Deploy` account, `cdk.context.test-example.json` for the `Test` account, `cdk.context.beta-example.json` for the `Beta` account, or `cdk.context.prod-example.json` for the `Prod` account) to `cdk.context.json` and update the values to match your respective accounts and other identifiers, including the code star connection you just created to match the identifiers for your actual accounts and resources. You will then need to run the `bin/put_ssm_context.sh ` script to push relevant content from your `cdk.context.json` script into an SSM Parameter in your Deploy account. Replace `` with the target environment. For example, to set up for the test environment: `bin/put_ssm_context.sh test`. - For example, to set up for the test environment: `bin/put_ssm_context.sh test`. +- For every environment, copy the appropriate example context file (`cdk.context.deploy-example.json` for the `Deploy` + account, `cdk.context.test-example.json` for the `Test` account, `cdk.context.beta-example.json` for the `Beta` + account, or `cdk.context.prod-example.json` for the `Prod` account) to `cdk.context.json` and update the values to + match your respective accounts and other identifiers, including the code star connection you just created to match + the identifiers for your actual accounts and resources. You will then need to run the + `bin/put_ssm_context.sh ` script to push relevant content from your `cdk.context.json` script into an + SSM Parameter in your Deploy account. Replace `` with the target environment. For example, to set up for + the test environment: `bin/put_ssm_context.sh test`. + For example, to set up for the test environment: `bin/put_ssm_context.sh test`. - Optional: If a Slack integration is desired for operational support, have someone with permission to install Slack apps in your workspace and Admin access to each of the Test, Beta, Prod, and Deploy accounts log into each AWS account and go to the Chatbot service. Select 'Slack' under the **Configure a chat client** box and click **Configure client**, then follow the Slack authorization prompts. This will authorize AWS to integrate with the channels you - identify in your `cdk.context.json` file. For each Slack channel you want to integrate, be sure to invite your new AWS app to those channels. Update the - `notifications.slack` sections of the `cdk.context.json` file with the details for your Slack workspace and channels. + identify in your `cdk.context.json` file. For each Slack channel you want to integrate, be sure to invite your new + AWS app to those channels. Update the `notifications.slack` sections of the `cdk.context.json` file with the details + for your Slack workspace and channels. If you opt not to integrate with Slack, remove the `slack` fields from the file. -- Set cli-environment variables `CDK_DEFAULT_ACCOUNT` and `CDK_DEFAULT_REGION` to your deployment account id and `us-east-1`, respectively. +- Set cli-environment variables `CDK_DEFAULT_ACCOUNT` and `CDK_DEFAULT_REGION` to your deployment account id and + `us-east-1`, respectively. - For each environment (test, beta, prod), you need to deploy both the backend and frontend pipeline stacks: -1. **Deploy the Backend Pipeline Stacks first (note: you will need to approve the permission change requests for each stack deployment in the terminal)**: +1. **Deploy the Backend Pipeline Stacks first (note: you will need to approve the permission change requests for each + stack deployment in the terminal)**: `cdk deploy --context action=bootstrapDeploy TestBackendPipelineStack BetaBackendPipelineStack ProdBackendPipelineStack` -2. **Then deploy the Frontend Pipeline Stacks (approve the permission change requests for each stack deployment)**: - `cdk deploy --context action=bootstrapDeploy TestFrontendPipelineStack BetaFrontendPipelineStack ProdFrontendPipelineStack` +2. **Then deploy the Frontend App**: + See the [UI app for details](../compact-connect-ui-app/README.md). -**Important**: When a pipeline stack is deployed, it will automatically trigger a deployment to its environment from the configured branch in your GitHub repo. The first time you deploy the backend pipeline, it should pass all the steps except the final trigger of the frontend pipeline, since the frontend pipeline will not exist until you deploy it. From there on, the pipelines should integrate as designed. +**Important**: When a pipeline stack is deployed, it will automatically trigger a deployment to its environment from +the configured branch in your GitHub repo. The first time you deploy the backend pipeline, it should pass all the steps +except the final trigger of the frontend pipeline, since the frontend pipeline will not exist until you deploy it. From +there on, the pipelines should integrate as designed. ### Subsequent production deploys Once the pipelines are established with the above steps, deployments will be automatically handled: -- Pushes to the `development` branch will trigger the test backend pipeline, which will then trigger the test frontend pipeline -- Pushes to the `main` branch will trigger both the beta and production backend pipelines, which will then trigger their respective frontend pipelines. +- Pushes to the `development` branch will trigger the test backend pipeline, which will then trigger the test frontend + pipeline +- Pushes to the `main` branch will trigger both the beta and production backend pipelines, which will then trigger + their respective frontend pipelines. ## Google reCAPTCHA Setup [Back to top](#compact-connect---backend-developer-documentation) -The practitioner registration endpoint uses Google reCAPTCHA to prevent abuse. Follow these steps to set up reCAPTCHA for your environment: +The practitioner registration endpoint uses Google reCAPTCHA to prevent abuse. Follow these steps to set up reCAPTCHA +for your environment: 1. Visit https://www.google.com/recaptcha/ 2. Go to "v3 Admin Console" @@ -259,8 +301,11 @@ The practitioner registration endpoint uses Google reCAPTCHA to prevent abuse. F - Google Cloud Platform may require a project name - Submit 4. Open the Settings for the new site - - The Site Key (Public) will need to be set in the cdk.context.json for the appropriate environment under the field named `recaptcha_public_key` for deployments, or in your local `.env` file of the webroot folder (if running the app locally) - - The Secret Key (Private) will need to be manually stored in the AWS account in secrets manager, using the following secret name: + - The Site Key (Public) will need to be set in the cdk.context.json for the appropriate environment under the field + named `recaptcha_public_key` for deployments, or in your local `.env` file of the webroot folder (if running the + app locally) + - The Secret Key (Private) will need to be manually stored in the AWS account in secrets manager, using the + following secret name: `compact-connect/env/{value of 'environment_name' in cdk.context.json}/recaptcha/token` The value of the secret key should be in the following format: ``` @@ -268,7 +313,8 @@ The practitioner registration endpoint uses Google reCAPTCHA to prevent abuse. F "token": "" } ``` - You can run the following aws cli command to create the secret (make sure you are logged in to the same AWS account you want to store the secret in, under the us-east-1 region): + You can run the following aws cli command to create the secret (make sure you are logged in to the same AWS account + you want to store the secret in, under the us-east-1 region): ``` aws secretsmanager create-secret --name compact-connect/env/{value of 'environment_name' in cdk.context.json}/recaptcha/token --secret-string '{"token": ""}' ``` @@ -276,7 +322,8 @@ The practitioner registration endpoint uses Google reCAPTCHA to prevent abuse. F For Production environments, additional billing setup is required: 1. In the Settings for a reCAPTCHA site, click "View in Cloud Console" 2. From the main nav, go to Billing -3. If you have an existing billing account, you may link it. Otherwise, you can create a New Billing account, where you will add payment information +3. If you have an existing billing account, you may link it. Otherwise, you can create a New Billing account, where you + will add payment information 4. More info on Google Recaptcha billing: https://cloud.google.com/recaptcha/docs/billing-information ### Useful commands @@ -320,7 +367,8 @@ ns-6.awsdns-16.org. Copy those name server values and, back in your production hosted zone, create a new NS record that matches the test one, with the same value (i.e. Record Name: `test.compcatconnect.org`, Type: `NS`, Value: ``). Once that -is done, your test hosted zone is ready for use by the app. You will need to perform this action for your beta environment as well, should you choose to deploy one. +is done, your test hosted zone is ready for use by the app. You will need to perform this action for your beta +environment as well, should you choose to deploy one. ## More Info [Back to top](#compact-connect---backend-developer-documentation) diff --git a/backend/compact-connect/app.py b/backend/compact-connect/app.py index fa7468b5e..c8036ca5e 100644 --- a/backend/compact-connect/app.py +++ b/backend/compact-connect/app.py @@ -7,13 +7,13 @@ # Make the `common_constructs` namespace package under `common-cdk` available to Python sys.path.insert(0, os.path.abspath(os.path.join('..', 'common-cdk'))) +from common_constructs.deployment_resources_stack import DeploymentResourcesStack from common_constructs.stack import StandardTags from pipeline import ( ACTION_CONTEXT_KEY, PIPELINE_STACK_CONTEXT_KEY, PIPELINE_SYNTH_ACTION, BetaBackendPipelineStack, - DeploymentResourcesStack, ProdBackendPipelineStack, TestBackendPipelineStack, ) diff --git a/backend/compact-connect/docs/design/README.md b/backend/compact-connect/docs/design/README.md index ac73766ae..84d89add1 100644 --- a/backend/compact-connect/docs/design/README.md +++ b/backend/compact-connect/docs/design/README.md @@ -675,6 +675,11 @@ For this reason, we use the batch settlement time as the timestamp for the trans transaction history table. This ensures that any transactions that are in a batch which fails to settle will eventually be processed and stored in the transaction history table. +## CI/CD Pipelines + +This project leverages AWS CodePipeline to deploy the backend and frontend infrastructure. See the +[pipeline architecture docs](./pipeline-architecture.md) for detailed discussion. + ## Audit Logging [Back to top](#backend-design) diff --git a/backend/compact-connect-ui-app/pipeline/design/README.md b/backend/compact-connect/docs/design/pipeline-architecture.md similarity index 68% rename from backend/compact-connect-ui-app/pipeline/design/README.md rename to backend/compact-connect/docs/design/pipeline-architecture.md index 26d8ad82d..b432588ea 100644 --- a/backend/compact-connect-ui-app/pipeline/design/README.md +++ b/backend/compact-connect/docs/design/pipeline-architecture.md @@ -4,12 +4,28 @@ ## Overview -The CompactConnect CI/CD pipeline architecture implements an optimized deployment strategy built around AWS CDK Pipelines (see https://docs.aws.amazon.com/cdk/v2/guide/cdk_pipeline.html). It follows a multi-pipeline approach with separate backend and frontend pipelines to improve deployment speed, reliability, and security. +The CompactConnect CI/CD pipeline architecture implements an optimized deployment strategy built around AWS CDK +Pipelines (see https://docs.aws.amazon.com/cdk/v2/guide/cdk_pipeline.html). It follows a multi-pipeline approach with +separate backend and frontend pipelines to improve deployment speed, reliability, and security. ## Key Components -- **Backend Pipelines**: Deploy infrastructure resources and backend components first -- **Frontend Pipelines**: Deploy frontend applications with backend configuration values +### Backend Pipelines + +There are different backend pipelines for each environment, defined as part of this CDK app. Those pipelines deploy +infrastructure resources and backend components to environment-specific application AWS accounts. + +### Frontend Pipelines + +There are also different frontend pipelines for each environment. These pipelines are defined as part of the separate +[CompactConnect UI App](../../../compact-connect-ui-app/README.md). The frontend pipelines deploy application hosting +infrastructure to the environment-specific application AWS accounts, based on backend configuration values, provide +by the backend deploy process. + +### Deployment Resources Stack + + + - **Deployment Resources Stack**: Shared resources used by all pipeline stacks across all environments - **Environments**: Test, Beta, and Production environments @@ -19,13 +35,15 @@ The CompactConnect CI/CD pipeline architecture implements an optimized deploymen 2. Backend Pipeline successful completion → Trigger Frontend Pipeline 3. Frontend Pipeline deploys web application using configuration values from Backend -Commits pushed to the 'development' branch trigger the test pipelines. Commits pushed to the 'main' branch trigger the beta and prod pipelines. +Commits pushed to the 'development' branch trigger the test pipelines. Commits pushed to the 'main' branch trigger the +beta and prod pipelines. ## Self-Mutation Feature and Optimization ### Understanding CDK Pipeline Self-Mutation -AWS CDK Pipelines include a powerful "self-mutation" feature that allows the pipeline to update itself. When code changes affecting the pipeline's structure are pushed, the pipeline: +AWS CDK Pipelines include a powerful "self-mutation" feature that allows the pipeline to update itself. When code +changes affecting the pipeline's structure are pushed, the pipeline: 1. Executes with its current configuration 2. Synthesizes CloudFormation templates for all stacks in the app @@ -34,19 +52,24 @@ AWS CDK Pipelines include a powerful "self-mutation" feature that allows the pip While powerful, this feature presents challenges: -1. **Performance Impact**: By default, CDK synthesizes all stacks in the application even when only one pipeline needs to be updated. This can be extremely slow, especially for complex applications. +1. **Performance Impact**: By default, CDK synthesizes all stacks in the application even when only one pipeline needs + to be updated. This can be extremely slow, especially for complex applications. -2. **Unnecessary Processing**: Every pipeline synthesis includes bundling operations (like frontend builds) even when those components aren't changing. +2. **Unnecessary Processing**: Every pipeline synthesis includes bundling operations (like frontend builds) even when + those components aren't changing. ### The SynthSubstituteStage and SynthSubstituteStack Solution -To address these challenges, we've implemented the `SynthSubstituteStage` and `SynthSubstituteStack` classes that act as lightweight placeholders during the synthesis process: +To address these challenges, we've implemented the `SynthSubstituteStage` and `SynthSubstituteStack` classes that act +as lightweight placeholders during the synthesis process: #### How It Works -1. During pipeline synthesis, we pass in cdk context variables to determine which specific pipeline is being synthesized. +1. During pipeline synthesis, we pass in cdk context variables to determine which specific pipeline is being + synthesized. -2. For any stage that isn't part of the current pipeline being synthesized, we replace it with a `SynthSubstituteStage` containing a minimal `SynthSubstituteStack`. +2. For any stage that isn't part of the current pipeline being synthesized, we replace it with a `SynthSubstituteStage` + containing a minimal `SynthSubstituteStack`. 3. The substitute stack synths a single SSM parameter resource, dramatically reducing synthesis time compared to full application stacks. diff --git a/backend/compact-connect-ui-app/pipeline/design/pipeline-architecture.pdf b/backend/compact-connect/docs/design/pipeline-architecture.pdf similarity index 100% rename from backend/compact-connect-ui-app/pipeline/design/pipeline-architecture.pdf rename to backend/compact-connect/docs/design/pipeline-architecture.pdf diff --git a/backend/compact-connect/pipeline/__init__.py b/backend/compact-connect/pipeline/__init__.py index 59a46dfa6..0ea18692a 100644 --- a/backend/compact-connect/pipeline/__init__.py +++ b/backend/compact-connect/pipeline/__init__.py @@ -2,7 +2,7 @@ from aws_cdk import Environment, RemovalPolicy from aws_cdk.aws_iam import Effect, PolicyDocument, PolicyStatement, Role, ServicePrincipal -from aws_cdk.aws_kms import IKey, Key +from aws_cdk.aws_kms import IKey from aws_cdk.aws_s3 import IBucket from aws_cdk.aws_sns import ITopic from aws_cdk.aws_ssm import StringParameter @@ -11,8 +11,6 @@ from cdk_nag import NagSuppressions from constructs import Construct -from common_constructs.access_logs_bucket import AccessLogsBucket -from common_constructs.alarm_topic import AlarmTopic from common_constructs.stack import Stack from pipeline.backend_pipeline import BackendPipeline from pipeline.backend_stage import BackendStage @@ -33,75 +31,6 @@ BOOTSTRAP_DEPLOY_ACTION = 'bootstrapDeploy' -class DeploymentResourcesStack(Stack): - """Stack that manages all shared resources for all pipeline stacks.""" - - def __init__( - self, - scope: Construct, - construct_id: str, - **kwargs, - ): - super().__init__(scope, construct_id, environment_name='deploy', **kwargs) - - pipeline_context_parameter_name = f'{DEPLOY_ENVIRONMENT_NAME}-compact-connect-context' - - # Fetch ssm_context if not provided locally - self.parameter = StringParameter.from_string_parameter_name( - self, - 'PipelineContext', - string_parameter_name=pipeline_context_parameter_name, - ) - value = StringParameter.value_from_lookup(self, self.parameter.parameter_name) - # When CDK runs for the first time, it synthesizes fully without actually retrieving the SSM Parameter - # value. It, instead, populates parameters and other look-ups with dummy values, synthesizes, collects all - # the look-ups together, populates them for real, then re-synthesizes with real values. - # To accommodate this pattern, we have to replace this dummy value with one that will actually - # let CDK complete its first round of synthesis, so that it can get to its second, real, synthesis. - if value != f'dummy-value-for-{pipeline_context_parameter_name}': - self.ssm_context = json.loads(value) - else: - with open(f'cdk.context.{DEPLOY_ENVIRONMENT_NAME}-example.json') as f: - self.ssm_context = json.load(f)['ssm_context'] - - self.deploy_environment_context = self.ssm_context['environments'][DEPLOY_ENVIRONMENT_NAME] - - self.pipeline_shared_encryption_key = Key( - self, - 'PipelineSharedEncryptionKey', - enable_key_rotation=True, - alias='pipeline-shared-encryption-key', - removal_policy=RemovalPolicy.RETAIN, - ) - - notifications = self.deploy_environment_context.get('notifications', {}) - self.pipeline_alarm_topic = AlarmTopic( - self, - 'AlarmTopic', - master_key=self.pipeline_shared_encryption_key, - email_subscriptions=notifications.get('email', []), - slack_subscriptions=notifications.get('slack', []), - ) - - self.pipeline_access_logs_bucket = AccessLogsBucket( - self, - 'AccessLogsBucket', - removal_policy=RemovalPolicy.RETAIN, - auto_delete_objects=False, - ) - - NagSuppressions.add_resource_suppressions_by_path( - self, - f'{self.pipeline_access_logs_bucket.node.path}/Resource', - suppressions=[ - { - 'id': 'HIPAA.Security-S3BucketLoggingEnabled', - 'reason': 'This is the access logging bucket.', - }, - ], - ) - - class BasePipelineStack(Stack): """Base stack with common functionality for all pipeline stacks (both backend and frontend).""" diff --git a/backend/compact-connect/pipeline/design/README.md b/backend/compact-connect/pipeline/design/README.md deleted file mode 100644 index b959ec06f..000000000 --- a/backend/compact-connect/pipeline/design/README.md +++ /dev/null @@ -1,96 +0,0 @@ -# CDK Pipeline Architecture Design - -[View Pipeline Architecture (PDF)](./pipeline-architecture.pdf) - -## Overview - -The CompactConnect CI/CD pipeline architecture implements an optimized deployment strategy built around AWS CDK Pipelines (see https://docs.aws.amazon.com/cdk/v2/guide/cdk_pipeline.html). It follows a multi-pipeline approach with separate backend and frontend pipelines to improve deployment speed, reliability, and security. - -## Key Components - -- **Backend Pipelines**: Deploy infrastructure resources and backend components first -- **Frontend Pipelines**: Deploy frontend applications with backend configuration values -- **Deployment Resources Stack**: Shared resources used by all pipeline stacks across all environments -- **Environments**: Test, Beta, and Production environments - -## Pipeline Flow - -1. GitHub push → Backend Pipeline -2. Backend Pipeline successful completion → Trigger Frontend Pipeline -3. Frontend Pipeline deploys web application using configuration values from Backend - -Commits pushed to the 'development' branch trigger the test pipelines. Commits pushed to the 'main' branch trigger the beta and prod pipelines. - -## Self-Mutation Feature and Optimization - -### Understanding CDK Pipeline Self-Mutation - -AWS CDK Pipelines include a powerful "self-mutation" feature that allows the pipeline to update itself. When code changes affecting the pipeline's structure are pushed, the pipeline: - -1. Executes with its current configuration -2. Synthesizes CloudFormation templates for all stacks in the app -3. Deploys a "self-mutation" step that updates the pipeline's own definition -4. Continues deployment with the updated pipeline definition - -While powerful, this feature presents challenges: - -1. **Performance Impact**: By default, CDK synthesizes all stacks in the application even when only one pipeline needs to be updated. This can be extremely slow, especially for complex applications. - -2. **Unnecessary Processing**: Every pipeline synthesis includes bundling operations (like frontend builds) even when those components aren't changing. - -### The SynthSubstituteStage and SynthSubstituteStack Solution - -To address these challenges, we've implemented the `SynthSubstituteStage` and `SynthSubstituteStack` classes that act as lightweight placeholders during the synthesis process: - -#### How It Works - -1. During pipeline synthesis, we pass in cdk context variables to determine which specific pipeline is being synthesized. - -2. For any stage that isn't part of the current pipeline being synthesized, we replace it with a `SynthSubstituteStage` containing a minimal `SynthSubstituteStack`. - -3. The substitute stack synths a single SSM parameter resource, dramatically reducing synthesis time compared to full application stacks. - -## Implementation Details - -The substitution mechanism relies on CDK context values which we pass in during the CDK synth step of the pipeline definition (see the [BackendPipeline](../backend_pipeline.py) and [FrontendPipeline](../frontend_pipeline.py) class constructors, specifically the `synth.commands` property): - -```python -commands=[ - ... other commands - # Only synthesize the specific pipeline stack needed - f'cdk synth --context pipelineStack={pipeline_stack_name} --context action=pipelineSynth', -], -``` -The following context values are used to determine which pipeline to fully synthesize: - -- `action`: Specifies the current action (e.g., `pipelineSynth`, `bootstrapDeploy`) -- `pipelineStack`: The specific pipeline stack being synthesized - -In the pipeline stack classes, the `_determine_backend_stage` and `_determine_frontend_stage` methods handle the stage substitution logic: - -```python -def _determine_backend_stage(self, construct_id, app_name, environment_name, environment_context): - # Check if we're in pipeline synthesis mode and if we're synthesizing this specific pipeline - action = self.node.try_get_context('action') - pipeline_stack_name = self.node.try_get_context('pipelineStack') - - # Use substitute stage if not synthesizing this specific pipeline or during bootstrap - if (action == PIPELINE_SYNTH_ACTION and pipeline_stack_name != self.stack_name) or action == BOOTSTRAP_DEPLOY_ACTION: - return SynthSubstituteStage( - self, - 'SubstituteBackendStage', - environment_context=environment_context, - ) - - # Otherwise, use the real stage - return BackendStage( - self, - construct_id, - app_name=app_name, - environment_name=environment_name, - environment_context=environment_context, - ) -``` - -# Bootstrapping the piplines -See this [README.md](../../README.md) for details on performing a bootstrap deployment of the pipelines. diff --git a/backend/compact-connect/pipeline/design/pipeline-architecture.pdf b/backend/compact-connect/pipeline/design/pipeline-architecture.pdf deleted file mode 100644 index c5146c2e2864567e8077cee448b9ef6524bb8bc7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 89240 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%0A=wxbf58988*?(rcE z&4^8kEW6cw0@H)>befpEs}EejINXw$*FG}&D_~BO%~Zr8Jw+1&-xssX&7I9l%q5PpN;M4MnK&OuhrYtZOGl#SzAO{j?pf{*5?`y#Dep-%JVGL(t*TC)tt%s|V7N2dDemjYw!e5a{V$ypbTT9eAaEosjm8aaNd$=&ldvK8)vsdo>*dn?wtxS#5@jdL;AM`U>?R)J} z*5;6DySkl0MuhI$BZ!W&OyZn{b|<5?tsDk<2M+M}szql(vD?&7wJA17f9d#*jq5hq ztrDSteNXS!1L8RsE#rLD;$(!^`d$~RgChL1P5y^B5>MLY-JJ^ekh3iPHj^(KV5b=b zrfnJ|$c%6>TGsu99=fm5p+~8?sMy_o;26Ep^5D+IOeZkfu2yh6W7)RNa^R%6j2~%g zWFbG;8*`>Z5I*<*JlCud?}K(4YX{U*DcfJ{Sobj)od)P`mgDn4lcVO`AvrC{LxC%; zPA;@bw?!JUzwyxY3!HBq=x_NcVvZVfK0KH8m3ZDnQgZdK=j)&5`8&U_`<;b-oqRtZ zUwK}GJF$QDzI!Gk92AUEU642%&7!h{FE*qPO-c{GgdKO%I_6?a-e-fY#8nKbzngd@$O4AiF6`USM!cuXR;(l+PpeMtx{lu^@aCTao<77=V4SVKGxXS zEuPaJRGpsPv89XKdMescmr&MkKevq@>KUHqXnjdd18nK_^Lz&IkRJ(%QE|DdmJE;T z`fJe3+GonS3uuCdO~p136klqyDfKSe*u(u!bxN4(K@h22E1Pt4@_e{5x&Dt`Mb9JT2YRXZ zYr6{HG?s?gX(rQOuC&MZfCrCwH(SLo_DLa#IsZM%!3zCFiQdI#_GJe8O~r2r$EQFs zrBNuYX8{QY7SGQ}WiL0tgTHx;o{&P8oXmLzPF3hqL!uWIXLC+^s(NDHc+<8h9LdLv zE$&94c*AoRBp9w7`kSMpQ7i5&mxoGgA8$Kw#-*v13{Un}k1H-o7BdazrU&L23YZ9Y z1a{cOQS#-=?aU+?z$%S-X!BXK~06Ki#OE3`JG-vj_mhMh){$R789f^GDNur_Ls z1nZ(6`;P_!OBwexwh2F2ZAsgcY1wn|t0HqZ3yVB13ww39PK|PchHn*wYZL2~N9d<7 z_ZzI{QET!RqjRrrCE~lQLC9x~%?sr^#0Bl$*Iz??Tt*{(K8R5SHjp9CXTG%t;CjL{ zNd$cz!g)9N@WeN#G2@EZBA>wz?KmNcI|A;X9k;iek{IBq!lxM@I4N?jX&;=)RroC0 zIHNnN+uQdn3Mn1yVWTYBk9}-u0vW0HM$Fw{Q;Oxz^I=fU*f&@@qu>_YQQMwjm$&lc zXTTZI?c7hu=MhLnqket@RVOo&n!D5Ulf%M@4+-hH&!~;zi1%(_3ASeBB~G29Z@7h9 zjeGj0t-|4fsvF~TsG`@Bo3oo`{%r77ug&9e-nLHyYRN%Qjw<5X)l6CBRfF%$7|R_! zmp`=>@Dyom*$CPuOv6N!zZIQ6-&-3JF{Sv{F!Y;u!5IHB`|T~x05+_h3d8T)q{Oni zja!$`LbVaeZyT0UuhW0Yn}$%|H-If^fkxBHZHPO_7{D4g|7<)VsO#FuxEeTq6T=vU z*D$%S6`4U~%u%dzzSOq(ZmZ1B#OfUF8T0!y0xvYCjiLQ#4mt1dnHWTcxct=@WQ}l! z-T5ogH%d+O#hx14Rd7}4>0Ux)H&5A+zy_u7`?9+o8+{8+VwZKg$1dwzn+s1;j$d44 z41Ly5^+a6m`DC1LRmITI6ui`S$4QD*&*WNTUOF6{e)0)|LrG*ipqVfB;)_I>`qzEK zlvmg5mSei1A%w_)??sQ0{>B2n?lzf>W3gF4Q-Vv3Lo;ZCBn2wj3Y(LI=Iol57qi74^vl zC{?M^==FO@*_g)=9B?zC1$y`*znnn%DfAX@LvnacW;780syVLY*o}wknK?0+P@)Bz zUGXOkjX80J_s?GS#_bq<2+p(@R?Mi8|2v@e8$v=64?CDlS2s(oBbzLw;kR{K7kxrv zr~%~)F4$=(WhW6p3R8_l6+XChcH) z)G;U8cZNbr!Ec*24Q7^xTrM-}h7qlD#FFsapZ+W; z`Cm2LV(327KydDgXsS zPXWQ{5NsP++3?reAs~a`$Q%rpS+D%PTdPDJuzrrNk}2~ z_E-GDR=VvBW%TTwuld#({ENg$0lXgU0QYisDrH5dr7EijpwB|r!zoD5m!s>|aksz5 zude~TufzMqK%$lWvEua1mKq?hw|=B?NT8X#0N_&4=MP#t`0mzyFi zXQ1eX^Zqc(mZOs$^utvRknHZvOT*a3&`tk>;VdyE1@F_qJM23z$}nKAMyh z(3Gpqc5iQQ=l{B;&y166Gjy>t$TF36mBXCn^d>2M)=lB-cJ(H`WJ0o@VlGW9ZwCEY z6MT%*q+EiRisVr<4yAO2hu25vfsVR;W$gQzf6lyLCe=1*ChWr%3pdfzt8f}4%$ z6Xo0sR@IuT_Vs+^?HrmcasCYQ=>z!F1>Ip+Q38{+=m%cD4DxfKPZu<;g)&3Z@_BBP zXSJy{IZ?cKlK>7!5JT8DmuCtwn*GiBRkj3{U=d8|=G%a8jQPSf10~6L{<8vPYafgS zGVr)Q$<6Yw4|5`h?xj3Jo?V>d-Z?T@BXhs-eUV44n6>f7jw~V} z57NTE1~ZK6i1?rUStT<%bNrg*@yL*misZM?$sQUF7j%3?!6tnY^i37^Yo^LFr>t2* znyjE;zcNYY`uz6+PTrqpJ^2}?%iuBwknCpr@$poXmNJ)D@@?jTUmlm+Ec{B$v9|?% zmc)%aKRf@9Y9}nj z`FvYIBf_;$FJ)wj00pIco1QMjAdgq?(IO?wcfOW6VLBDXd44|t*%_9s&&4A_YTG3q zlHx)*pc-+TG`U`NP@!jO`m{{+?SOU?AXeRPyAjc&4tjh3BKo!@u@Npa5XnKK#VT=W zY7A%~y_;BJI@$*GjOwh|u1@pIAA@GMgRGLowqtGwq*v6Tl?OT%BJ%G>zF0Vc$ykP*vdbuXd9D|-7t}#`; zxFxwVsE&}mxu9Z{O=v;YX`HeW{`MH0TiFZmSYsD?eFH%wZx#=bCTXoNKsEwS(cD`P zKr&0B+X-CE#Gu!$_YU-6z?EjR6gz+^Giq<66dOb^{9ApJ=@uxppegft`mL|+V@B6` zo7;B+9!``(3s#QF`ak91b!AM4v4a?gV60<O|>mdSvqYvP(Jxv(tEr2qgQ|K3;L>+v>||cu2dQ zO8T-_wII}AlhU!=uYf#yHPosZ9GfSI!yc{f{6?%3h2@@~T-?1j^mfD%V;49-ONlh+ zB+^Vppt4k<#nx*@TEfreou~ha=Gji;N}HxbL*!w~CPJ~QB^KzF&{(7cqlSJtc}ZrH ze)3U1CrwY@0)&NK{d*(Q()~M5jw+Por3Pib2e22u5pMY#cMI>Cw43FN*yTqxqR{eS z@Z~tVMTFY+MZ(k0Ks#@EM5lU}Xd=oW7YCJByo6rA_1GQ;SNIz_G9^am^GrDHt?4W^ zQo}g`qI%Db&S%{yWPHb24LDo?qEzzdG4k6-s`o~v#%S>53XdTyQB-;H%vF>xQ7~M6 zIcLFH8sztB)UfhW%bny#ZMOx%RXh|2D|$D4@i5JW&TUkZAsFd&BM`!|27l z_^pj!y`R6$TFAb-Uvh@fZ3(;(aIWqV06FICs~_w|_W5Ba*o#CLnEfT~W%Ptsipw(` z-DdVNm?^inGRSv=#3+s`Se)+u=3I6mduv8D*g5`sdH%GDr;odC;7~h?LFif6$p{-( zYtt9ll)J{Y-xq)h&RC54h3;?yxt-8CWgZ#nQT}*GmZoib

lVHA>R}}g8ztm5Bg=(YkiTeF1 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 From c759a4065a27d02b59b05c84d8df60831f97cb23 Mon Sep 17 00:00:00 2001 From: Justin Frahm Date: Fri, 19 Sep 2025 10:47:46 -0600 Subject: [PATCH 14/32] Move base pipeline stack to common --- .../common_constructs/base_pipeline_stack.py | 129 ++++++++++++++++ .../deployment_resources_stack.py | 2 +- .../cdk.context.sandbox-example.json | 1 - .../pipeline/__init__.py | 102 +------------ .../pipeline/frontend_pipeline.py | 5 +- .../cdk.context.sandbox-example.json | 1 - backend/compact-connect/pipeline/__init__.py | 144 ++---------------- .../pipeline/backend_pipeline.py | 2 +- 8 files changed, 152 insertions(+), 234 deletions(-) create mode 100644 backend/common-cdk/common_constructs/base_pipeline_stack.py diff --git a/backend/common-cdk/common_constructs/base_pipeline_stack.py b/backend/common-cdk/common_constructs/base_pipeline_stack.py new file mode 100644 index 000000000..3ae09d1dc --- /dev/null +++ b/backend/common-cdk/common_constructs/base_pipeline_stack.py @@ -0,0 +1,129 @@ +import json + +from aws_cdk import Environment, RemovalPolicy +from aws_cdk.aws_iam import Effect, PolicyStatement, Role, ServicePrincipal +from aws_cdk.aws_s3 import IBucket +from aws_cdk.aws_ssm import StringParameter +from aws_cdk.pipelines import CodePipeline as CdkCodePipeline +from constructs import Construct + +from common_constructs.stack import Stack + +TEST_ENVIRONMENT_NAME = 'test' +BETA_ENVIRONMENT_NAME = 'beta' +PROD_ENVIRONMENT_NAME = 'prod' +ALLOWED_ENVIRONMENT_NAMES = [TEST_ENVIRONMENT_NAME, BETA_ENVIRONMENT_NAME, PROD_ENVIRONMENT_NAME] + +class BasePipelineStack(Stack): + """Base stack with common functionality for all pipeline stacks (both backend and frontend).""" + + def __init__( + self, + scope: Construct, + construct_id: str, + environment_name: str, + env: Environment, + removal_policy: RemovalPolicy, + pipeline_access_logs_bucket: IBucket, + **kwargs, + ): + super().__init__(scope, construct_id, environment_name='pipeline', env=env, **kwargs) + + self.env = env + self.environment_name = environment_name + self.removal_policy = removal_policy + self.access_logs_bucket = pipeline_access_logs_bucket + + pipeline_context_parameter_name = f'{self.environment_name}-compact-connect-context' + + # Fetch ssm_context if not provided locally + self.parameter = StringParameter.from_string_parameter_name( + self, + 'PipelineContext', + string_parameter_name=pipeline_context_parameter_name, + ) + value = StringParameter.value_from_lookup(self, self.parameter.parameter_name) + # When CDK runs for the first time, it synthesizes fully without actually retrieving the SSM Parameter + # value. It, instead, populates parameters and other look-ups with dummy values, synthesizes, collects all + # the look-ups together, populates them for real, then re-synthesizes with real values. + # To accommodate this pattern, we have to replace this dummy value with one that will actually + # let CDK complete its first round of synthesis, so that it can get to its second, real, synthesis. + if value != f'dummy-value-for-{pipeline_context_parameter_name}': + self.ssm_context = json.loads(value) + else: + with open(f'cdk.context.{environment_name}-example.json') as f: + self.ssm_context = json.load(f)['ssm_context'] + + self.pipeline_environment_context = self.ssm_context['environments']['pipeline'] + self.connection_arn = self.pipeline_environment_context['connection_arn'] + self.github_repo_string = self.ssm_context['github_repo_string'] + self.backup_config = self.ssm_context.get('backup_config', {}) + self.app_name = self.ssm_context['app_name'] + + def _get_predictable_role_name(self, pipeline_type: str, role_type: str) -> str: + """Generate predictable role name for bootstrap template integration. + + :param pipeline_type: 'Backend' or 'Frontend' + :param role_type: 'Synth', 'SelfMutation', 'AssetPublishing', 'Deploy' + :return: Predictable role name following pattern: CompactConnect-{env}-{pipeline}-{role}Role + """ + if self.environment_name not in ALLOWED_ENVIRONMENT_NAMES: + raise ValueError(f'Environment name must be one of {ALLOWED_ENVIRONMENT_NAMES}') + + return f'CompactConnect-{self.environment_name}-{pipeline_type}-{role_type}Role' + + def create_predictable_pipeline_role(self, pipeline_type: str) -> Role: + """Create a predictable cross-account role that will be trusted by bootstrap roles. + + :param pipeline_type: 'Backend' or 'Frontend' + :return: The cross-account role with predictable name for bootstrap trust policies + """ + # Create environment and pipeline-type specific cross-account roles + cross_account_role_name = f'CompactConnect-{self.environment_name}-{pipeline_type}-CrossAccountRole' + + return Role( + self, + f'{pipeline_type}CrossAccountRole', + role_name=cross_account_role_name, + assumed_by=ServicePrincipal('codepipeline.amazonaws.com'), + description=f'Cross-account role for {self.environment_name} {pipeline_type.lower()}' + 'pipeline bootstrap trust policies', + ) + + def _add_pipeline_cdk_assume_role_policy(self, pipeline: CdkCodePipeline): + pipeline.synth_project.add_to_role_policy( + PolicyStatement( + effect=Effect.ALLOW, + actions=['sts:AssumeRole'], + resources=[ + self.format_arn( + partition=self.partition, + service='iam', + region='', + account='*', + resource='role', + resource_name='cdk-hnb659fds-lookup-role-*', + ), + ], + ), + ) + + def _get_frontend_pipeline_name(self): + if self.environment_name not in ALLOWED_ENVIRONMENT_NAMES: + raise ValueError(f'Environment name must be one of {ALLOWED_ENVIRONMENT_NAMES}') + + return f'{self.environment_name}-compactConnect-frontendPipeline' + + def _get_frontend_pipeline_arn(self): + pipeline_name = self._get_frontend_pipeline_name() + + return self.format_arn( + partition=self.partition, + service='codepipeline', + region=self.env.region, + account=self.env.account, + resource=pipeline_name, + ) + + +DEPLOY_ENVIRONMENT_NAME = 'deploy' diff --git a/backend/common-cdk/common_constructs/deployment_resources_stack.py b/backend/common-cdk/common_constructs/deployment_resources_stack.py index dff97e532..cb1a95640 100644 --- a/backend/common-cdk/common_constructs/deployment_resources_stack.py +++ b/backend/common-cdk/common_constructs/deployment_resources_stack.py @@ -5,10 +5,10 @@ from aws_cdk.aws_ssm import StringParameter from cdk_nag import NagSuppressions from constructs import Construct -from pipeline import DEPLOY_ENVIRONMENT_NAME from common_constructs.access_logs_bucket import AccessLogsBucket from common_constructs.alarm_topic import AlarmTopic +from common_constructs.base_pipeline_stack import DEPLOY_ENVIRONMENT_NAME from common_constructs.stack import Stack diff --git a/backend/compact-connect-ui-app/cdk.context.sandbox-example.json b/backend/compact-connect-ui-app/cdk.context.sandbox-example.json index bea66dcb8..593eae5c2 100644 --- a/backend/compact-connect-ui-app/cdk.context.sandbox-example.json +++ b/backend/compact-connect-ui-app/cdk.context.sandbox-example.json @@ -16,7 +16,6 @@ "domain_name": "justin.compactconnect.org", "backup_enabled": false, "allow_local_ui": true, - "deploy_sandbox_ui": false, "security_profile": "VULNERABLE", "recaptcha_public_key": "123-KFEUsjehfuejILDVUKkRnAF9SSzb8o9uv5lY7Ih", "robots_meta": "noindex,nofollow", diff --git a/backend/compact-connect-ui-app/pipeline/__init__.py b/backend/compact-connect-ui-app/pipeline/__init__.py index edf4c6b89..9d649ba30 100644 --- a/backend/compact-connect-ui-app/pipeline/__init__.py +++ b/backend/compact-connect-ui-app/pipeline/__init__.py @@ -1,27 +1,19 @@ -import json - from aws_cdk import Environment, RemovalPolicy -from aws_cdk.aws_iam import Effect, PolicyStatement from aws_cdk.aws_kms import IKey from aws_cdk.aws_s3 import IBucket from aws_cdk.aws_sns import ITopic -from aws_cdk.aws_ssm import StringParameter -from aws_cdk.pipelines import CodePipeline as CdkCodePipeline -from common_constructs.stack import Stack +from common_constructs.base_pipeline_stack import ( + BETA_ENVIRONMENT_NAME, + PROD_ENVIRONMENT_NAME, + TEST_ENVIRONMENT_NAME, + BasePipelineStack, +) from constructs import Construct from pipeline.frontend_pipeline import FrontendPipeline from pipeline.frontend_stage import FrontendStage from pipeline.synth_substitute_stage import SynthSubstituteStage -TEST_ENVIRONMENT_NAME = 'test' -BETA_ENVIRONMENT_NAME = 'beta' -PROD_ENVIRONMENT_NAME = 'prod' - -ALLOWED_ENVIRONMENT_NAMES = [TEST_ENVIRONMENT_NAME, BETA_ENVIRONMENT_NAME, PROD_ENVIRONMENT_NAME] - -DEPLOY_ENVIRONMENT_NAME = 'deploy' - # Action constants ACTION_CONTEXT_KEY = 'action' PIPELINE_STACK_CONTEXT_KEY = 'pipelineStack' @@ -29,88 +21,6 @@ BOOTSTRAP_DEPLOY_ACTION = 'bootstrapDeploy' -class BasePipelineStack(Stack): - """Base stack with common functionality for all pipeline stacks (both backend and frontend).""" - - def __init__( - self, - scope: Construct, - construct_id: str, - environment_name: str, - env: Environment, - removal_policy: RemovalPolicy, - pipeline_access_logs_bucket: IBucket, - **kwargs, - ): - super().__init__(scope, construct_id, environment_name='pipeline', env=env, **kwargs) - - self.env = env - self.environment_name = environment_name - self.removal_policy = removal_policy - self.access_logs_bucket = pipeline_access_logs_bucket - - pipeline_context_parameter_name = f'{self.environment_name}-compact-connect-context' - - # Fetch ssm_context if not provided locally - self.parameter = StringParameter.from_string_parameter_name( - self, - 'PipelineContext', - string_parameter_name=pipeline_context_parameter_name, - ) - value = StringParameter.value_from_lookup(self, self.parameter.parameter_name) - # When CDK runs for the first time, it synthesizes fully without actually retrieving the SSM Parameter - # value. It, instead, populates parameters and other look-ups with dummy values, synthesizes, collects all - # the look-ups together, populates them for real, then re-synthesizes with real values. - # To accommodate this pattern, we have to replace this dummy value with one that will actually - # let CDK complete its first round of synthesis, so that it can get to its second, real, synthesis. - if value != f'dummy-value-for-{pipeline_context_parameter_name}': - self.ssm_context = json.loads(value) - else: - with open(f'cdk.context.{environment_name}-example.json') as f: - self.ssm_context = json.load(f)['ssm_context'] - - self.pipeline_environment_context = self.ssm_context['environments']['pipeline'] - self.connection_arn = self.pipeline_environment_context['connection_arn'] - self.github_repo_string = self.ssm_context['github_repo_string'] - self.backup_config = self.ssm_context.get('backup_config', {}) - self.app_name = self.ssm_context['app_name'] - - def _add_pipeline_cdk_assume_role_policy(self, pipeline: CdkCodePipeline): - pipeline.synth_project.add_to_role_policy( - PolicyStatement( - effect=Effect.ALLOW, - actions=['sts:AssumeRole'], - resources=[ - self.format_arn( - partition=self.partition, - service='iam', - region='', - account='*', - resource='role', - resource_name='cdk-hnb659fds-lookup-role-*', - ), - ], - ), - ) - - def _get_frontend_pipeline_name(self): - if self.environment_name not in ALLOWED_ENVIRONMENT_NAMES: - raise ValueError(f'Environment name must be one of {ALLOWED_ENVIRONMENT_NAMES}') - - return f'{self.environment_name}-compactConnect-frontendPipeline' - - def _get_frontend_pipeline_arn(self): - pipeline_name = self._get_frontend_pipeline_name() - - return self.format_arn( - partition=self.partition, - service='codepipeline', - region=self.env.region, - account=self.env.account, - resource=pipeline_name, - ) - - class BaseFrontendPipelineStack(BasePipelineStack): """ Base class for frontend pipeline stacks. diff --git a/backend/compact-connect-ui-app/pipeline/frontend_pipeline.py b/backend/compact-connect-ui-app/pipeline/frontend_pipeline.py index ac73e28ba..a991c21f4 100644 --- a/backend/compact-connect-ui-app/pipeline/frontend_pipeline.py +++ b/backend/compact-connect-ui-app/pipeline/frontend_pipeline.py @@ -2,6 +2,7 @@ import os +import common_constructs.base_pipeline_stack from aws_cdk import RemovalPolicy, Stack from aws_cdk.aws_codebuild import BuildSpec from aws_cdk.aws_codestarnotifications import NotificationRule @@ -15,8 +16,6 @@ from cdk_nag import NagSuppressions from common_constructs.bucket import Bucket -import pipeline - class FrontendPipeline(CdkCodePipeline): """ @@ -36,7 +35,7 @@ class FrontendPipeline(CdkCodePipeline): def __init__( self, - scope: pipeline.BasePipelineStack, + scope: common_constructs.base_pipeline_stack.BasePipelineStack, construct_id: str, *, pipeline_name: str, diff --git a/backend/compact-connect/cdk.context.sandbox-example.json b/backend/compact-connect/cdk.context.sandbox-example.json index bea66dcb8..593eae5c2 100644 --- a/backend/compact-connect/cdk.context.sandbox-example.json +++ b/backend/compact-connect/cdk.context.sandbox-example.json @@ -16,7 +16,6 @@ "domain_name": "justin.compactconnect.org", "backup_enabled": false, "allow_local_ui": true, - "deploy_sandbox_ui": false, "security_profile": "VULNERABLE", "recaptcha_public_key": "123-KFEUsjehfuejILDVUKkRnAF9SSzb8o9uv5lY7Ih", "robots_meta": "noindex,nofollow", diff --git a/backend/compact-connect/pipeline/__init__.py b/backend/compact-connect/pipeline/__init__.py index 0ea18692a..e962b1da0 100644 --- a/backend/compact-connect/pipeline/__init__.py +++ b/backend/compact-connect/pipeline/__init__.py @@ -1,29 +1,23 @@ -import json - from aws_cdk import Environment, RemovalPolicy from aws_cdk.aws_iam import Effect, PolicyDocument, PolicyStatement, Role, ServicePrincipal from aws_cdk.aws_kms import IKey from aws_cdk.aws_s3 import IBucket from aws_cdk.aws_sns import ITopic -from aws_cdk.aws_ssm import StringParameter from aws_cdk.pipelines import CodeBuildStep -from aws_cdk.pipelines import CodePipeline as CdkCodePipeline from cdk_nag import NagSuppressions from constructs import Construct -from common_constructs.stack import Stack +from common_constructs.base_pipeline_stack import ( + ALLOWED_ENVIRONMENT_NAMES, + BETA_ENVIRONMENT_NAME, + PROD_ENVIRONMENT_NAME, + TEST_ENVIRONMENT_NAME, + BasePipelineStack, +) from pipeline.backend_pipeline import BackendPipeline from pipeline.backend_stage import BackendStage from pipeline.synth_substitute_stage import SynthSubstituteStage -TEST_ENVIRONMENT_NAME = 'test' -BETA_ENVIRONMENT_NAME = 'beta' -PROD_ENVIRONMENT_NAME = 'prod' - -ALLOWED_ENVIRONMENT_NAMES = [TEST_ENVIRONMENT_NAME, BETA_ENVIRONMENT_NAME, PROD_ENVIRONMENT_NAME] - -DEPLOY_ENVIRONMENT_NAME = 'deploy' - # Action constants ACTION_CONTEXT_KEY = 'action' PIPELINE_STACK_CONTEXT_KEY = 'pipelineStack' @@ -31,124 +25,6 @@ BOOTSTRAP_DEPLOY_ACTION = 'bootstrapDeploy' -class BasePipelineStack(Stack): - """Base stack with common functionality for all pipeline stacks (both backend and frontend).""" - - def __init__( - self, - scope: Construct, - construct_id: str, - environment_name: str, - env: Environment, - removal_policy: RemovalPolicy, - pipeline_access_logs_bucket: IBucket, - **kwargs, - ): - super().__init__(scope, construct_id, environment_name='pipeline', env=env, **kwargs) - - self.env = env - self.environment_name = environment_name - self.removal_policy = removal_policy - self.access_logs_bucket = pipeline_access_logs_bucket - - pipeline_context_parameter_name = f'{self.environment_name}-compact-connect-context' - - # Fetch ssm_context if not provided locally - self.parameter = StringParameter.from_string_parameter_name( - self, - 'PipelineContext', - string_parameter_name=pipeline_context_parameter_name, - ) - value = StringParameter.value_from_lookup(self, self.parameter.parameter_name) - # When CDK runs for the first time, it synthesizes fully without actually retrieving the SSM Parameter - # value. It, instead, populates parameters and other look-ups with dummy values, synthesizes, collects all - # the look-ups together, populates them for real, then re-synthesizes with real values. - # To accommodate this pattern, we have to replace this dummy value with one that will actually - # let CDK complete its first round of synthesis, so that it can get to its second, real, synthesis. - if value != f'dummy-value-for-{pipeline_context_parameter_name}': - self.ssm_context = json.loads(value) - else: - with open(f'cdk.context.{environment_name}-example.json') as f: - self.ssm_context = json.load(f)['ssm_context'] - - self.pipeline_environment_context = self.ssm_context['environments']['pipeline'] - self.connection_arn = self.pipeline_environment_context['connection_arn'] - self.github_repo_string = self.ssm_context['github_repo_string'] - self.backup_config = self.ssm_context.get('backup_config', {}) - self.app_name = self.ssm_context['app_name'] - - def _get_predictable_role_name(self, pipeline_type: str, role_type: str) -> str: - """Generate predictable role name for bootstrap template integration. - - :param pipeline_type: 'Backend' or 'Frontend' - :param role_type: 'Synth', 'SelfMutation', 'AssetPublishing', 'Deploy' - :return: Predictable role name following pattern: CompactConnect-{env}-{pipeline}-{role}Role - """ - if self.environment_name not in ALLOWED_ENVIRONMENT_NAMES: - raise ValueError(f'Environment name must be one of {ALLOWED_ENVIRONMENT_NAMES}') - - return f'CompactConnect-{self.environment_name}-{pipeline_type}-{role_type}Role' - - def create_predictable_pipeline_role(self, pipeline_type: str) -> Role: - """Create a predictable cross-account role that will be trusted by bootstrap roles. - - :param pipeline_type: 'Backend' or 'Frontend' - :return: The cross-account role with predictable name for bootstrap trust policies - """ - # Create environment and pipeline-type specific cross-account roles - cross_account_role_name = f'CompactConnect-{self.environment_name}-{pipeline_type}-CrossAccountRole' - - return Role( - self, - f'{pipeline_type}CrossAccountRole', - role_name=cross_account_role_name, - assumed_by=ServicePrincipal('codepipeline.amazonaws.com'), - description=f'Cross-account role for {self.environment_name} {pipeline_type.lower()}' - 'pipeline bootstrap trust policies', - ) - - def _add_pipeline_cdk_assume_role_policy(self, pipeline: CdkCodePipeline): - pipeline.synth_project.add_to_role_policy( - PolicyStatement( - effect=Effect.ALLOW, - actions=['sts:AssumeRole'], - resources=[ - self.format_arn( - partition=self.partition, - service='iam', - region='', - account='*', - resource='role', - resource_name='cdk-hnb659fds-lookup-role-*', - ), - ], - ), - ) - - def _get_backend_pipeline_name(self): - if self.environment_name not in ALLOWED_ENVIRONMENT_NAMES: - raise ValueError(f'Environment name must be one of {ALLOWED_ENVIRONMENT_NAMES}') - - return f'{self.environment_name}-compactConnect-backendPipeline' - - def _get_frontend_pipeline_name(self): - if self.environment_name not in ALLOWED_ENVIRONMENT_NAMES: - raise ValueError(f'Environment name must be one of {ALLOWED_ENVIRONMENT_NAMES}') - - return f'{self.environment_name}-compactConnect-frontendPipeline' - - def _get_frontend_pipeline_arn(self): - pipeline_name = self._get_frontend_pipeline_name() - - return self.format_arn( - partition=self.partition, - service='codepipeline', - region=self.env.region, - account=self.env.account, - resource=pipeline_name, - ) - - class BaseBackendPipelineStack(BasePipelineStack): """ Base class for backend pipeline stacks. @@ -175,6 +51,12 @@ def __init__( **kwargs, ) + def _get_backend_pipeline_name(self): + if self.environment_name not in ALLOWED_ENVIRONMENT_NAMES: + raise ValueError(f'Environment name must be one of {ALLOWED_ENVIRONMENT_NAMES}') + + return f'{self.environment_name}-compactConnect-backendPipeline' + def _determine_backend_stage(self, construct_id, app_name, environment_name, environment_context): """ Return either a real BackendStage or a SynthSubstituteStage depending on pipeline synthesis context. diff --git a/backend/compact-connect/pipeline/backend_pipeline.py b/backend/compact-connect/pipeline/backend_pipeline.py index 1e1c7f775..7955d34ec 100644 --- a/backend/compact-connect/pipeline/backend_pipeline.py +++ b/backend/compact-connect/pipeline/backend_pipeline.py @@ -35,7 +35,7 @@ class BackendPipeline(CdkCodePipeline): def __init__( self, - scope: pipeline.BasePipelineStack, + scope: common_constructs.base_pipeline_stack.BasePipelineStack, construct_id: str, *, pipeline_name: str, From e88f23747ddca7454aa67f3a1e2a4b30dd0ae853 Mon Sep 17 00:00:00 2001 From: Justin Frahm Date: Mon, 22 Sep 2025 09:38:04 -0600 Subject: [PATCH 15/32] Update compact-connect deps --- backend/compact-connect/bin/sync_deps.sh | 2 +- .../cognito-backup/requirements-dev.txt | 32 +++++++++---------- .../python/cognito-backup/requirements.txt | 16 +++++----- .../lambdas/python/common/requirements-dev.in | 1 + .../python/common/requirements-dev.txt | 32 ++++++++++++------- .../lambdas/python/common/requirements.in | 2 +- .../lambdas/python/common/requirements.txt | 28 ++++++++-------- .../requirements-dev.txt | 20 ++++++------ .../compact-configuration/requirements.txt | 2 +- .../custom-resources/requirements-dev.txt | 21 ++++++------ .../python/custom-resources/requirements.txt | 2 +- .../python/data-events/requirements-dev.txt | 20 ++++++------ .../python/data-events/requirements.txt | 2 +- .../disaster-recovery/requirements-dev.txt | 20 ++++++------ .../python/disaster-recovery/requirements.txt | 2 +- .../provider-data-v1/requirements-dev.txt | 22 ++++++------- .../python/provider-data-v1/requirements.txt | 2 +- .../python/purchases/requirements-dev.txt | 20 ++++++------ .../lambdas/python/purchases/requirements.txt | 4 +-- .../staff-user-pre-token/requirements-dev.txt | 20 ++++++------ .../staff-user-pre-token/requirements.txt | 2 +- .../python/staff-users/requirements-dev.txt | 24 +++++++------- .../python/staff-users/requirements.txt | 2 +- backend/compact-connect/requirements-dev.txt | 28 ++++++++-------- backend/compact-connect/requirements.txt | 24 +++++++------- 25 files changed, 180 insertions(+), 170 deletions(-) diff --git a/backend/compact-connect/bin/sync_deps.sh b/backend/compact-connect/bin/sync_deps.sh index 9a9d80983..46835a8cd 100755 --- a/backend/compact-connect/bin/sync_deps.sh +++ b/backend/compact-connect/bin/sync_deps.sh @@ -1,5 +1,5 @@ ( - cd compact-connect/lambdas/nodejs + cd lambdas/nodejs yarn install ) diff --git a/backend/compact-connect/lambdas/python/cognito-backup/requirements-dev.txt b/backend/compact-connect/lambdas/python/cognito-backup/requirements-dev.txt index c94233d79..b9e90ad65 100644 --- a/backend/compact-connect/lambdas/python/cognito-backup/requirements-dev.txt +++ b/backend/compact-connect/lambdas/python/cognito-backup/requirements-dev.txt @@ -2,27 +2,27 @@ # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# pip-compile --no-emit-index-url compact-connect/lambdas/python/cognito-backup/requirements-dev.in +# pip-compile --cert=None --client-cert=None --index-url=None --no-emit-index-url --pip-args=None lambdas/python/cognito-backup/requirements-dev.in # -aws-lambda-powertools==3.19.0 - # via -r compact-connect/lambdas/python/cognito-backup/requirements-dev.in -boto3==1.40.19 +aws-lambda-powertools==3.20.0 + # via -r lambdas/python/cognito-backup/requirements-dev.in +boto3==1.40.35 # via - # -r compact-connect/lambdas/python/cognito-backup/requirements-dev.in + # -r lambdas/python/cognito-backup/requirements-dev.in # moto -botocore==1.40.19 +botocore==1.40.35 # via - # -r compact-connect/lambdas/python/cognito-backup/requirements-dev.in + # -r lambdas/python/cognito-backup/requirements-dev.in # boto3 # moto # s3transfer certifi==2025.8.3 # via requests -cffi==1.17.1 +cffi==2.0.0 # via cryptography charset-normalizer==3.4.3 # via requests -cryptography==45.0.6 +cryptography==46.0.1 # via moto idna==3.10 # via requests @@ -39,20 +39,20 @@ markupsafe==3.0.2 # via # jinja2 # werkzeug -moto[cognito-idp,s3]==5.1.11 - # via -r compact-connect/lambdas/python/cognito-backup/requirements-dev.in +moto[cognito-idp,s3]==5.1.12 + # via -r lambdas/python/cognito-backup/requirements-dev.in packaging==25.0 # via pytest pluggy==1.6.0 # via pytest py-partiql-parser==0.6.1 # via moto -pycparser==2.22 +pycparser==2.23 # via cffi pygments==2.19.2 # via pytest -pytest==8.4.1 - # via -r compact-connect/lambdas/python/cognito-backup/requirements-dev.in +pytest==8.4.2 + # via -r lambdas/python/cognito-backup/requirements-dev.in python-dateutil==2.9.0.post0 # via # botocore @@ -67,7 +67,7 @@ requests==2.32.5 # responses responses==0.25.8 # via moto -s3transfer==0.13.1 +s3transfer==0.14.0 # via boto3 six==1.17.0 # via python-dateutil @@ -80,5 +80,5 @@ urllib3==2.5.0 # responses werkzeug==3.1.3 # via moto -xmltodict==0.14.2 +xmltodict==1.0.2 # via moto diff --git a/backend/compact-connect/lambdas/python/cognito-backup/requirements.txt b/backend/compact-connect/lambdas/python/cognito-backup/requirements.txt index 98af73d6a..8d02a8f2d 100644 --- a/backend/compact-connect/lambdas/python/cognito-backup/requirements.txt +++ b/backend/compact-connect/lambdas/python/cognito-backup/requirements.txt @@ -2,15 +2,15 @@ # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# pip-compile --no-emit-index-url compact-connect/lambdas/python/cognito-backup/requirements.in +# pip-compile --cert=None --client-cert=None --index-url=None --no-emit-index-url --pip-args=None lambdas/python/cognito-backup/requirements.in # -aws-lambda-powertools==3.19.0 - # via -r compact-connect/lambdas/python/cognito-backup/requirements.in -boto3==1.40.19 - # via -r compact-connect/lambdas/python/cognito-backup/requirements.in -botocore==1.40.19 +aws-lambda-powertools==3.20.0 + # via -r lambdas/python/cognito-backup/requirements.in +boto3==1.40.35 + # via -r lambdas/python/cognito-backup/requirements.in +botocore==1.40.35 # via - # -r compact-connect/lambdas/python/cognito-backup/requirements.in + # -r lambdas/python/cognito-backup/requirements.in # boto3 # s3transfer jmespath==1.0.1 @@ -20,7 +20,7 @@ jmespath==1.0.1 # botocore python-dateutil==2.9.0.post0 # via botocore -s3transfer==0.13.1 +s3transfer==0.14.0 # via boto3 six==1.17.0 # via python-dateutil diff --git a/backend/compact-connect/lambdas/python/common/requirements-dev.in b/backend/compact-connect/lambdas/python/common/requirements-dev.in index 804353503..b737d5527 100644 --- a/backend/compact-connect/lambdas/python/common/requirements-dev.in +++ b/backend/compact-connect/lambdas/python/common/requirements-dev.in @@ -1,2 +1,3 @@ moto[dynamodb, s3]>=5.0.12, <6 +boto3-stubs[full] Faker>=28,<29 diff --git a/backend/compact-connect/lambdas/python/common/requirements-dev.txt b/backend/compact-connect/lambdas/python/common/requirements-dev.txt index 607d4ef63..d79ddd986 100644 --- a/backend/compact-connect/lambdas/python/common/requirements-dev.txt +++ b/backend/compact-connect/lambdas/python/common/requirements-dev.txt @@ -2,27 +2,33 @@ # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# pip-compile --no-emit-index-url compact-connect/lambdas/python/common/requirements-dev.in +# pip-compile --cert=None --client-cert=None --index-url=None --no-emit-index-url --pip-args=None lambdas/python/common/requirements-dev.in # -boto3==1.40.19 +boto3==1.40.35 # via moto -botocore==1.40.19 +boto3-stubs[full]==1.40.35 + # via -r lambdas/python/common/requirements-dev.in +boto3-stubs-full==1.40.34 + # via boto3-stubs +botocore==1.40.35 # via # boto3 # moto # s3transfer +botocore-stubs==1.40.33 + # via boto3-stubs certifi==2025.8.3 # via requests -cffi==1.17.1 +cffi==2.0.0 # via cryptography charset-normalizer==3.4.3 # via requests -cryptography==45.0.6 +cryptography==46.0.1 # via moto docker==7.1.0 # via moto faker==28.4.1 - # via -r compact-connect/lambdas/python/common/requirements-dev.in + # via -r lambdas/python/common/requirements-dev.in idna==3.10 # via requests jinja2==3.1.6 @@ -35,11 +41,11 @@ markupsafe==3.0.2 # via # jinja2 # werkzeug -moto[dynamodb,s3]==5.1.11 - # via -r compact-connect/lambdas/python/common/requirements-dev.in +moto[dynamodb,s3]==5.1.12 + # via -r lambdas/python/common/requirements-dev.in py-partiql-parser==0.6.1 # via moto -pycparser==2.22 +pycparser==2.23 # via cffi python-dateutil==2.9.0.post0 # via @@ -57,10 +63,14 @@ requests==2.32.5 # responses responses==0.25.8 # via moto -s3transfer==0.13.1 +s3transfer==0.14.0 # via boto3 six==1.17.0 # via python-dateutil +types-awscrt==0.27.6 + # via botocore-stubs +types-s3transfer==0.13.1 + # via boto3-stubs urllib3==2.5.0 # via # botocore @@ -69,5 +79,5 @@ urllib3==2.5.0 # responses werkzeug==3.1.3 # via moto -xmltodict==0.14.2 +xmltodict==1.0.2 # via moto diff --git a/backend/compact-connect/lambdas/python/common/requirements.in b/backend/compact-connect/lambdas/python/common/requirements.in index bd28fbdc1..0cb9f207b 100644 --- a/backend/compact-connect/lambdas/python/common/requirements.in +++ b/backend/compact-connect/lambdas/python/common/requirements.in @@ -1,6 +1,6 @@ argon2-cffi>=25.1.0, <26.0.0 aws-lambda-powertools>=3.5.0, <4 boto3>=1.34.33, <2 -cryptography>=45.0.5, <46 +cryptography>=46, <47 marshmallow>=3.21.3, <4.0.0 requests>=2.31.0, <3.0.0 diff --git a/backend/compact-connect/lambdas/python/common/requirements.txt b/backend/compact-connect/lambdas/python/common/requirements.txt index 1a11fba53..828f0ab9d 100644 --- a/backend/compact-connect/lambdas/python/common/requirements.txt +++ b/backend/compact-connect/lambdas/python/common/requirements.txt @@ -2,30 +2,30 @@ # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# pip-compile --no-emit-index-url compact-connect/lambdas/python/common/requirements.in +# pip-compile --cert=None --client-cert=None --index-url=None --no-emit-index-url --pip-args=None lambdas/python/common/requirements.in # argon2-cffi==25.1.0 - # via -r compact-connect/lambdas/python/common/requirements.in + # via -r lambdas/python/common/requirements.in argon2-cffi-bindings==25.1.0 # via argon2-cffi -aws-lambda-powertools==3.19.0 - # via -r compact-connect/lambdas/python/common/requirements.in -boto3==1.40.19 - # via -r compact-connect/lambdas/python/common/requirements.in -botocore==1.40.19 +aws-lambda-powertools==3.20.0 + # via -r lambdas/python/common/requirements.in +boto3==1.40.35 + # via -r lambdas/python/common/requirements.in +botocore==1.40.35 # via # boto3 # s3transfer certifi==2025.8.3 # via requests -cffi==1.17.1 +cffi==2.0.0 # via # argon2-cffi-bindings # cryptography charset-normalizer==3.4.3 # via requests -cryptography==45.0.6 - # via -r compact-connect/lambdas/python/common/requirements.in +cryptography==46.0.1 + # via -r lambdas/python/common/requirements.in idna==3.10 # via requests jmespath==1.0.1 @@ -34,16 +34,16 @@ jmespath==1.0.1 # boto3 # botocore marshmallow==3.26.1 - # via -r compact-connect/lambdas/python/common/requirements.in + # via -r lambdas/python/common/requirements.in packaging==25.0 # via marshmallow -pycparser==2.22 +pycparser==2.23 # via cffi python-dateutil==2.9.0.post0 # via botocore requests==2.32.5 - # via -r compact-connect/lambdas/python/common/requirements.in -s3transfer==0.13.1 + # via -r lambdas/python/common/requirements.in +s3transfer==0.14.0 # via boto3 six==1.17.0 # via python-dateutil diff --git a/backend/compact-connect/lambdas/python/compact-configuration/requirements-dev.txt b/backend/compact-connect/lambdas/python/compact-configuration/requirements-dev.txt index 81e902ad3..e30bfbb2a 100644 --- a/backend/compact-connect/lambdas/python/compact-configuration/requirements-dev.txt +++ b/backend/compact-connect/lambdas/python/compact-configuration/requirements-dev.txt @@ -2,22 +2,22 @@ # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# pip-compile --no-emit-index-url compact-connect/lambdas/python/compact-configuration/requirements-dev.in +# pip-compile --cert=None --client-cert=None --index-url=None --no-emit-index-url --pip-args=None lambdas/python/compact-configuration/requirements-dev.in # -boto3==1.40.19 +boto3==1.40.35 # via moto -botocore==1.40.19 +botocore==1.40.35 # via # boto3 # moto # s3transfer certifi==2025.8.3 # via requests -cffi==1.17.1 +cffi==2.0.0 # via cryptography charset-normalizer==3.4.3 # via requests -cryptography==45.0.6 +cryptography==46.0.1 # via moto docker==7.1.0 # via moto @@ -33,11 +33,11 @@ markupsafe==3.0.2 # via # jinja2 # werkzeug -moto[dynamodb,s3]==5.1.11 - # via -r compact-connect/lambdas/python/compact-configuration/requirements-dev.in +moto[dynamodb,s3]==5.1.12 + # via -r lambdas/python/compact-configuration/requirements-dev.in py-partiql-parser==0.6.1 # via moto -pycparser==2.22 +pycparser==2.23 # via cffi python-dateutil==2.9.0.post0 # via @@ -54,7 +54,7 @@ requests==2.32.5 # responses responses==0.25.8 # via moto -s3transfer==0.13.1 +s3transfer==0.14.0 # via boto3 six==1.17.0 # via python-dateutil @@ -66,5 +66,5 @@ urllib3==2.5.0 # responses werkzeug==3.1.3 # via moto -xmltodict==0.14.2 +xmltodict==1.0.2 # via moto diff --git a/backend/compact-connect/lambdas/python/compact-configuration/requirements.txt b/backend/compact-connect/lambdas/python/compact-configuration/requirements.txt index 87680f5d5..f56b86777 100644 --- a/backend/compact-connect/lambdas/python/compact-configuration/requirements.txt +++ b/backend/compact-connect/lambdas/python/compact-configuration/requirements.txt @@ -2,5 +2,5 @@ # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# pip-compile --no-emit-index-url compact-connect/lambdas/python/compact-configuration/requirements.in +# pip-compile --cert=None --client-cert=None --index-url=None --no-emit-index-url --pip-args=None lambdas/python/compact-configuration/requirements.in # diff --git a/backend/compact-connect/lambdas/python/custom-resources/requirements-dev.txt b/backend/compact-connect/lambdas/python/custom-resources/requirements-dev.txt index 982de0c34..82c17d527 100644 --- a/backend/compact-connect/lambdas/python/custom-resources/requirements-dev.txt +++ b/backend/compact-connect/lambdas/python/custom-resources/requirements-dev.txt @@ -2,23 +2,22 @@ # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# pip-compile --no-emit-index-url compact-connect/lambdas/python/custom-resources/requirements-dev.in +# pip-compile --cert=None --client-cert=None --index-url=None --no-emit-index-url --pip-args=None lambdas/python/custom-resources/requirements-dev.in # - -boto3==1.40.19 +boto3==1.40.35 # via moto -botocore==1.40.19 +botocore==1.40.35 # via # boto3 # moto # s3transfer certifi==2025.8.3 # via requests -cffi==1.17.1 +cffi==2.0.0 # via cryptography charset-normalizer==3.4.3 # via requests -cryptography==45.0.6 +cryptography==46.0.1 # via moto docker==7.1.0 # via moto @@ -34,11 +33,11 @@ markupsafe==3.0.2 # via # jinja2 # werkzeug -moto[dynamodb,s3]==5.1.11 - # via -r compact-connect/lambdas/python/custom-resources/requirements-dev.in +moto[dynamodb,s3]==5.1.12 + # via -r lambdas/python/custom-resources/requirements-dev.in py-partiql-parser==0.6.1 # via moto -pycparser==2.22 +pycparser==2.23 # via cffi python-dateutil==2.9.0.post0 # via @@ -55,7 +54,7 @@ requests==2.32.5 # responses responses==0.25.8 # via moto -s3transfer==0.13.1 +s3transfer==0.14.0 # via boto3 six==1.17.0 # via python-dateutil @@ -67,5 +66,5 @@ urllib3==2.5.0 # responses werkzeug==3.1.3 # via moto -xmltodict==0.14.2 +xmltodict==1.0.2 # via moto diff --git a/backend/compact-connect/lambdas/python/custom-resources/requirements.txt b/backend/compact-connect/lambdas/python/custom-resources/requirements.txt index 40f4dd194..6c98adb26 100644 --- a/backend/compact-connect/lambdas/python/custom-resources/requirements.txt +++ b/backend/compact-connect/lambdas/python/custom-resources/requirements.txt @@ -2,5 +2,5 @@ # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# pip-compile --no-emit-index-url compact-connect/lambdas/python/custom-resources/requirements.in +# pip-compile --cert=None --client-cert=None --index-url=None --no-emit-index-url --pip-args=None lambdas/python/custom-resources/requirements.in # diff --git a/backend/compact-connect/lambdas/python/data-events/requirements-dev.txt b/backend/compact-connect/lambdas/python/data-events/requirements-dev.txt index bfc33a1da..c92146177 100644 --- a/backend/compact-connect/lambdas/python/data-events/requirements-dev.txt +++ b/backend/compact-connect/lambdas/python/data-events/requirements-dev.txt @@ -2,22 +2,22 @@ # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# pip-compile --no-emit-index-url compact-connect/lambdas/python/data-events/requirements-dev.in +# pip-compile --cert=None --client-cert=None --index-url=None --no-emit-index-url --pip-args=None lambdas/python/data-events/requirements-dev.in # -boto3==1.40.19 +boto3==1.40.35 # via moto -botocore==1.40.19 +botocore==1.40.35 # via # boto3 # moto # s3transfer certifi==2025.8.3 # via requests -cffi==1.17.1 +cffi==2.0.0 # via cryptography charset-normalizer==3.4.3 # via requests -cryptography==45.0.6 +cryptography==46.0.1 # via moto docker==7.1.0 # via moto @@ -33,11 +33,11 @@ markupsafe==3.0.2 # via # jinja2 # werkzeug -moto[dynamodb,s3]==5.1.11 - # via -r compact-connect/lambdas/python/data-events/requirements-dev.in +moto[dynamodb,s3]==5.1.12 + # via -r lambdas/python/data-events/requirements-dev.in py-partiql-parser==0.6.1 # via moto -pycparser==2.22 +pycparser==2.23 # via cffi python-dateutil==2.9.0.post0 # via @@ -54,7 +54,7 @@ requests==2.32.5 # responses responses==0.25.8 # via moto -s3transfer==0.13.1 +s3transfer==0.14.0 # via boto3 six==1.17.0 # via python-dateutil @@ -66,5 +66,5 @@ urllib3==2.5.0 # responses werkzeug==3.1.3 # via moto -xmltodict==0.14.2 +xmltodict==1.0.2 # via moto diff --git a/backend/compact-connect/lambdas/python/data-events/requirements.txt b/backend/compact-connect/lambdas/python/data-events/requirements.txt index bd5f9271f..da006c453 100644 --- a/backend/compact-connect/lambdas/python/data-events/requirements.txt +++ b/backend/compact-connect/lambdas/python/data-events/requirements.txt @@ -2,5 +2,5 @@ # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# pip-compile --no-emit-index-url compact-connect/lambdas/python/data-events/requirements.in +# pip-compile --cert=None --client-cert=None --index-url=None --no-emit-index-url --pip-args=None lambdas/python/data-events/requirements.in # diff --git a/backend/compact-connect/lambdas/python/disaster-recovery/requirements-dev.txt b/backend/compact-connect/lambdas/python/disaster-recovery/requirements-dev.txt index 1c844d20c..8c2dca592 100644 --- a/backend/compact-connect/lambdas/python/disaster-recovery/requirements-dev.txt +++ b/backend/compact-connect/lambdas/python/disaster-recovery/requirements-dev.txt @@ -2,22 +2,22 @@ # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# pip-compile --no-emit-index-url compact-connect/lambdas/python/disaster-recovery/requirements-dev.in +# pip-compile --cert=None --client-cert=None --index-url=None --no-emit-index-url --pip-args=None lambdas/python/disaster-recovery/requirements-dev.in # -boto3==1.40.19 +boto3==1.40.35 # via moto -botocore==1.40.19 +botocore==1.40.35 # via # boto3 # moto # s3transfer certifi==2025.8.3 # via requests -cffi==1.17.1 +cffi==2.0.0 # via cryptography charset-normalizer==3.4.3 # via requests -cryptography==45.0.6 +cryptography==46.0.1 # via moto docker==7.1.0 # via moto @@ -33,11 +33,11 @@ markupsafe==3.0.2 # via # jinja2 # werkzeug -moto[dynamodb,s3]==5.1.11 - # via -r compact-connect/lambdas/python/disaster-recovery/requirements-dev.in +moto[dynamodb,s3]==5.1.12 + # via -r lambdas/python/disaster-recovery/requirements-dev.in py-partiql-parser==0.6.1 # via moto -pycparser==2.22 +pycparser==2.23 # via cffi python-dateutil==2.9.0.post0 # via @@ -54,7 +54,7 @@ requests==2.32.5 # responses responses==0.25.8 # via moto -s3transfer==0.13.1 +s3transfer==0.14.0 # via boto3 six==1.17.0 # via python-dateutil @@ -66,5 +66,5 @@ urllib3==2.5.0 # responses werkzeug==3.1.3 # via moto -xmltodict==0.14.2 +xmltodict==1.0.2 # via moto diff --git a/backend/compact-connect/lambdas/python/disaster-recovery/requirements.txt b/backend/compact-connect/lambdas/python/disaster-recovery/requirements.txt index 24e02f5ca..800fc58fc 100644 --- a/backend/compact-connect/lambdas/python/disaster-recovery/requirements.txt +++ b/backend/compact-connect/lambdas/python/disaster-recovery/requirements.txt @@ -2,5 +2,5 @@ # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# pip-compile --no-emit-index-url compact-connect/lambdas/python/disaster-recovery/requirements.in +# pip-compile --cert=None --client-cert=None --index-url=None --no-emit-index-url --pip-args=None lambdas/python/disaster-recovery/requirements.in # diff --git a/backend/compact-connect/lambdas/python/provider-data-v1/requirements-dev.txt b/backend/compact-connect/lambdas/python/provider-data-v1/requirements-dev.txt index d1063406e..4c01da874 100644 --- a/backend/compact-connect/lambdas/python/provider-data-v1/requirements-dev.txt +++ b/backend/compact-connect/lambdas/python/provider-data-v1/requirements-dev.txt @@ -2,27 +2,27 @@ # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# pip-compile --no-emit-index-url compact-connect/lambdas/python/provider-data-v1/requirements-dev.in +# pip-compile --cert=None --client-cert=None --index-url=None --no-emit-index-url --pip-args=None lambdas/python/provider-data-v1/requirements-dev.in # -boto3==1.40.19 +boto3==1.40.35 # via moto -botocore==1.40.19 +botocore==1.40.35 # via # boto3 # moto # s3transfer certifi==2025.8.3 # via requests -cffi==1.17.1 +cffi==2.0.0 # via cryptography charset-normalizer==3.4.3 # via requests -cryptography==45.0.6 +cryptography==46.0.1 # via moto docker==7.1.0 # via moto faker==28.4.1 - # via -r compact-connect/lambdas/python/provider-data-v1/requirements-dev.in + # via -r lambdas/python/provider-data-v1/requirements-dev.in idna==3.10 # via requests jinja2==3.1.6 @@ -35,11 +35,11 @@ markupsafe==3.0.2 # via # jinja2 # werkzeug -moto[dynamodb,s3]==5.1.11 - # via -r compact-connect/lambdas/python/provider-data-v1/requirements-dev.in +moto[dynamodb,s3]==5.1.12 + # via -r lambdas/python/provider-data-v1/requirements-dev.in py-partiql-parser==0.6.1 # via moto -pycparser==2.22 +pycparser==2.23 # via cffi python-dateutil==2.9.0.post0 # via @@ -57,7 +57,7 @@ requests==2.32.5 # responses responses==0.25.8 # via moto -s3transfer==0.13.1 +s3transfer==0.14.0 # via boto3 six==1.17.0 # via python-dateutil @@ -69,5 +69,5 @@ urllib3==2.5.0 # responses werkzeug==3.1.3 # via moto -xmltodict==0.14.2 +xmltodict==1.0.2 # via moto diff --git a/backend/compact-connect/lambdas/python/provider-data-v1/requirements.txt b/backend/compact-connect/lambdas/python/provider-data-v1/requirements.txt index 26d50642e..15e52fd4e 100644 --- a/backend/compact-connect/lambdas/python/provider-data-v1/requirements.txt +++ b/backend/compact-connect/lambdas/python/provider-data-v1/requirements.txt @@ -2,5 +2,5 @@ # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# pip-compile --no-emit-index-url compact-connect/lambdas/python/provider-data-v1/requirements.in +# pip-compile --cert=None --client-cert=None --index-url=None --no-emit-index-url --pip-args=None lambdas/python/provider-data-v1/requirements.in # diff --git a/backend/compact-connect/lambdas/python/purchases/requirements-dev.txt b/backend/compact-connect/lambdas/python/purchases/requirements-dev.txt index c122cfe73..2a1ee6b0a 100644 --- a/backend/compact-connect/lambdas/python/purchases/requirements-dev.txt +++ b/backend/compact-connect/lambdas/python/purchases/requirements-dev.txt @@ -2,22 +2,22 @@ # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# pip-compile --no-emit-index-url compact-connect/lambdas/python/purchases/requirements-dev.in +# pip-compile --cert=None --client-cert=None --index-url=None --no-emit-index-url --pip-args=None lambdas/python/purchases/requirements-dev.in # -boto3==1.40.19 +boto3==1.40.35 # via moto -botocore==1.40.19 +botocore==1.40.35 # via # boto3 # moto # s3transfer certifi==2025.8.3 # via requests -cffi==1.17.1 +cffi==2.0.0 # via cryptography charset-normalizer==3.4.3 # via requests -cryptography==45.0.6 +cryptography==46.0.1 # via moto docker==7.1.0 # via moto @@ -33,11 +33,11 @@ markupsafe==3.0.2 # via # jinja2 # werkzeug -moto[dynamodb,s3]==5.1.11 - # via -r compact-connect/lambdas/python/purchases/requirements-dev.in +moto[dynamodb,s3]==5.1.12 + # via -r lambdas/python/purchases/requirements-dev.in py-partiql-parser==0.6.1 # via moto -pycparser==2.22 +pycparser==2.23 # via cffi python-dateutil==2.9.0.post0 # via @@ -54,7 +54,7 @@ requests==2.32.5 # responses responses==0.25.8 # via moto -s3transfer==0.13.1 +s3transfer==0.14.0 # via boto3 six==1.17.0 # via python-dateutil @@ -66,5 +66,5 @@ urllib3==2.5.0 # responses werkzeug==3.1.3 # via moto -xmltodict==0.14.2 +xmltodict==1.0.2 # via moto diff --git a/backend/compact-connect/lambdas/python/purchases/requirements.txt b/backend/compact-connect/lambdas/python/purchases/requirements.txt index a7a8fd65e..3b8e3df89 100644 --- a/backend/compact-connect/lambdas/python/purchases/requirements.txt +++ b/backend/compact-connect/lambdas/python/purchases/requirements.txt @@ -2,10 +2,10 @@ # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# pip-compile --no-emit-index-url compact-connect/lambdas/python/purchases/requirements.in +# pip-compile --cert=None --client-cert=None --index-url=None --no-emit-index-url --pip-args=None lambdas/python/purchases/requirements.in # authorizenet==1.1.6 - # via -r compact-connect/lambdas/python/purchases/requirements.in + # via -r lambdas/python/purchases/requirements.in certifi==2025.8.3 # via requests charset-normalizer==3.4.3 diff --git a/backend/compact-connect/lambdas/python/staff-user-pre-token/requirements-dev.txt b/backend/compact-connect/lambdas/python/staff-user-pre-token/requirements-dev.txt index 711acbc28..5ea9fc177 100644 --- a/backend/compact-connect/lambdas/python/staff-user-pre-token/requirements-dev.txt +++ b/backend/compact-connect/lambdas/python/staff-user-pre-token/requirements-dev.txt @@ -2,22 +2,22 @@ # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# pip-compile --no-emit-index-url compact-connect/lambdas/python/staff-user-pre-token/requirements-dev.in +# pip-compile --cert=None --client-cert=None --index-url=None --no-emit-index-url --pip-args=None lambdas/python/staff-user-pre-token/requirements-dev.in # -boto3==1.40.19 +boto3==1.40.35 # via moto -botocore==1.40.19 +botocore==1.40.35 # via # boto3 # moto # s3transfer certifi==2025.8.3 # via requests -cffi==1.17.1 +cffi==2.0.0 # via cryptography charset-normalizer==3.4.3 # via requests -cryptography==45.0.6 +cryptography==46.0.1 # via moto docker==7.1.0 # via moto @@ -33,11 +33,11 @@ markupsafe==3.0.2 # via # jinja2 # werkzeug -moto[dynamodb,s3]==5.1.11 - # via -r compact-connect/lambdas/python/staff-user-pre-token/requirements-dev.in +moto[dynamodb,s3]==5.1.12 + # via -r lambdas/python/staff-user-pre-token/requirements-dev.in py-partiql-parser==0.6.1 # via moto -pycparser==2.22 +pycparser==2.23 # via cffi python-dateutil==2.9.0.post0 # via @@ -54,7 +54,7 @@ requests==2.32.5 # responses responses==0.25.8 # via moto -s3transfer==0.13.1 +s3transfer==0.14.0 # via boto3 six==1.17.0 # via python-dateutil @@ -66,5 +66,5 @@ urllib3==2.5.0 # responses werkzeug==3.1.3 # via moto -xmltodict==0.14.2 +xmltodict==1.0.2 # via moto diff --git a/backend/compact-connect/lambdas/python/staff-user-pre-token/requirements.txt b/backend/compact-connect/lambdas/python/staff-user-pre-token/requirements.txt index 346c6642c..23d37d92a 100644 --- a/backend/compact-connect/lambdas/python/staff-user-pre-token/requirements.txt +++ b/backend/compact-connect/lambdas/python/staff-user-pre-token/requirements.txt @@ -2,5 +2,5 @@ # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# pip-compile --no-emit-index-url compact-connect/lambdas/python/staff-user-pre-token/requirements.in +# pip-compile --cert=None --client-cert=None --index-url=None --no-emit-index-url --pip-args=None lambdas/python/staff-user-pre-token/requirements.in # diff --git a/backend/compact-connect/lambdas/python/staff-users/requirements-dev.txt b/backend/compact-connect/lambdas/python/staff-users/requirements-dev.txt index fb2a72460..6e72a0e88 100644 --- a/backend/compact-connect/lambdas/python/staff-users/requirements-dev.txt +++ b/backend/compact-connect/lambdas/python/staff-users/requirements-dev.txt @@ -2,29 +2,29 @@ # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# pip-compile --no-emit-index-url compact-connect/lambdas/python/staff-users/requirements-dev.in +# pip-compile --cert=None --client-cert=None --index-url=None --no-emit-index-url --pip-args=None lambdas/python/staff-users/requirements-dev.in # -boto3==1.40.19 +boto3==1.40.35 # via moto -botocore==1.40.19 +botocore==1.40.35 # via # boto3 # moto # s3transfer certifi==2025.8.3 # via requests -cffi==1.17.1 +cffi==2.0.0 # via cryptography charset-normalizer==3.4.3 # via requests -cryptography==45.0.6 +cryptography==46.0.1 # via # joserfc # moto docker==7.1.0 # via moto faker==28.4.1 - # via -r compact-connect/lambdas/python/staff-users/requirements-dev.in + # via -r lambdas/python/staff-users/requirements-dev.in idna==3.10 # via requests jinja2==3.1.6 @@ -33,17 +33,17 @@ jmespath==1.0.1 # via # boto3 # botocore -joserfc==1.3.1 +joserfc==1.3.3 # via moto markupsafe==3.0.2 # via # jinja2 # werkzeug -moto[cognitoidp,dynamodb,s3]==5.1.11 - # via -r compact-connect/lambdas/python/staff-users/requirements-dev.in +moto[cognitoidp,dynamodb,s3]==5.1.12 + # via -r lambdas/python/staff-users/requirements-dev.in py-partiql-parser==0.6.1 # via moto -pycparser==2.22 +pycparser==2.23 # via cffi python-dateutil==2.9.0.post0 # via @@ -61,7 +61,7 @@ requests==2.32.5 # responses responses==0.25.8 # via moto -s3transfer==0.13.1 +s3transfer==0.14.0 # via boto3 six==1.17.0 # via python-dateutil @@ -73,5 +73,5 @@ urllib3==2.5.0 # responses werkzeug==3.1.3 # via moto -xmltodict==0.14.2 +xmltodict==1.0.2 # via moto diff --git a/backend/compact-connect/lambdas/python/staff-users/requirements.txt b/backend/compact-connect/lambdas/python/staff-users/requirements.txt index 49d8e7e5d..3830b77fd 100644 --- a/backend/compact-connect/lambdas/python/staff-users/requirements.txt +++ b/backend/compact-connect/lambdas/python/staff-users/requirements.txt @@ -2,5 +2,5 @@ # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# pip-compile --no-emit-index-url compact-connect/lambdas/python/staff-users/requirements.in +# pip-compile --cert=None --client-cert=None --index-url=None --no-emit-index-url --pip-args=None lambdas/python/staff-users/requirements.in # diff --git a/backend/compact-connect/requirements-dev.txt b/backend/compact-connect/requirements-dev.txt index 1f4d4e0c2..82a9ca638 100644 --- a/backend/compact-connect/requirements-dev.txt +++ b/backend/compact-connect/requirements-dev.txt @@ -2,7 +2,7 @@ # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# pip-compile --no-emit-index-url compact-connect/requirements-dev.in +# pip-compile --cert=None --client-cert=None --index-url=None --no-emit-index-url --pip-args=None requirements-dev.in # boolean-py==5.0 # via license-expression @@ -16,18 +16,18 @@ certifi==2025.8.3 # via requests charset-normalizer==3.4.3 # via requests -click==8.2.1 +click==8.3.0 # via pip-tools -coverage[toml]==7.10.5 +coverage[toml]==7.10.6 # via - # -r compact-connect/requirements-dev.in + # -r requirements-dev.in # pytest-cov cyclonedx-python-lib==9.1.0 # via pip-audit defusedxml==0.7.1 # via py-serializable faker==28.4.1 - # via -r compact-connect/requirements-dev.in + # via -r requirements-dev.in filelock==3.19.1 # via cachecontrol idna==3.10 @@ -53,11 +53,11 @@ packaging==25.0 pip-api==0.0.34 # via pip-audit pip-audit==2.9.0 - # via -r compact-connect/requirements-dev.in + # via -r requirements-dev.in pip-requirements-parser==32.0.1 # via pip-audit pip-tools==7.5.0 - # via -r compact-connect/requirements-dev.in + # via -r requirements-dev.in platformdirs==4.4.0 # via pip-audit pluggy==1.6.0 @@ -70,18 +70,18 @@ pygments==2.19.2 # via # pytest # rich -pyparsing==3.2.3 +pyparsing==3.2.4 # via pip-requirements-parser pyproject-hooks==1.2.0 # via # build # pip-tools -pytest==8.4.1 +pytest==8.4.2 # via - # -r compact-connect/requirements-dev.in + # -r requirements-dev.in # pytest-cov -pytest-cov==6.2.1 - # via -r compact-connect/requirements-dev.in +pytest-cov==7.0.0 + # via -r requirements-dev.in python-dateutil==2.9.0.post0 # via faker requests==2.32.5 @@ -90,8 +90,8 @@ requests==2.32.5 # pip-audit rich==14.1.0 # via pip-audit -ruff==0.12.11 - # via -r compact-connect/requirements-dev.in +ruff==0.13.1 + # via -r requirements-dev.in six==1.17.0 # via python-dateutil sortedcontainers==2.4.0 diff --git a/backend/compact-connect/requirements.txt b/backend/compact-connect/requirements.txt index 9fd7b5812..97e6c37ff 100644 --- a/backend/compact-connect/requirements.txt +++ b/backend/compact-connect/requirements.txt @@ -2,7 +2,7 @@ # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# pip-compile --no-emit-index-url compact-connect/requirements.in +# pip-compile --cert=None --client-cert=None --index-url=None --no-emit-index-url --pip-args=None requirements.in # attrs==25.3.0 # via @@ -12,28 +12,28 @@ aws-cdk-asset-awscli-v1==2.2.242 # via aws-cdk-lib aws-cdk-asset-node-proxy-agent-v6==2.1.0 # via aws-cdk-lib -aws-cdk-aws-lambda-python-alpha==2.213.0a0 - # via -r compact-connect/requirements.in -aws-cdk-cloud-assembly-schema==48.6.0 +aws-cdk-aws-lambda-python-alpha==2.215.0a0 + # via -r requirements.in +aws-cdk-cloud-assembly-schema==48.10.0 # via aws-cdk-lib -aws-cdk-lib==2.213.0 +aws-cdk-lib==2.215.0 # via - # -r compact-connect/requirements.in + # -r requirements.in # aws-cdk-aws-lambda-python-alpha # cdk-nag -cattrs==25.1.1 +cattrs==25.2.0 # via jsii -cdk-nag==2.37.11 - # via -r compact-connect/requirements.in +cdk-nag==2.37.31 + # via -r requirements.in constructs==10.4.2 # via - # -r compact-connect/requirements.in + # -r requirements.in # aws-cdk-aws-lambda-python-alpha # aws-cdk-lib # cdk-nag importlib-resources==6.5.2 # via jsii -jsii==1.113.0 +jsii==1.114.1 # via # aws-cdk-asset-awscli-v1 # aws-cdk-asset-node-proxy-agent-v6 @@ -55,7 +55,7 @@ publication==0.0.3 python-dateutil==2.9.0.post0 # via jsii pyyaml==6.0.2 - # via -r compact-connect/requirements.in + # via -r requirements.in six==1.17.0 # via python-dateutil typeguard==2.13.3 From 7887af29ffe6435a1d202d151155e2123425ccbc Mon Sep 17 00:00:00 2001 From: Justin Frahm Date: Wed, 24 Sep 2025 13:17:41 -0600 Subject: [PATCH 16/32] Move new frontend tests to frontend app, linting --- .github/workflows/check-common-cdk.yml | 2 +- .../tests/app/test_pipeline.py | 71 +++++++++++++++++++ backend/compact-connect/app.py | 1 + .../common_constructs/cc_api.py | 4 +- .../common_constructs/cognito_user_backup.py | 6 +- .../common_constructs/user_pool.py | 3 +- backend/compact-connect/pipeline/__init__.py | 4 +- .../pipeline/backend_pipeline.py | 1 + .../compact-connect/pipeline/backend_stage.py | 2 +- .../stacks/api_lambda_stack/__init__.py | 2 +- .../stacks/api_lambda_stack/provider_users.py | 2 +- .../stacks/api_stack/__init__.py | 4 +- .../compact-connect/stacks/api_stack/api.py | 2 +- .../stacks/api_stack/v1_api/api.py | 2 +- .../stacks/api_stack/v1_api/api_model.py | 2 +- .../stacks/api_stack/v1_api/attestations.py | 2 +- .../api_stack/v1_api/bulk_upload_url.py | 2 +- .../v1_api/compact_configuration_api.py | 2 +- .../stacks/api_stack/v1_api/credentials.py | 2 +- .../stacks/api_stack/v1_api/post_licenses.py | 2 +- .../api_stack/v1_api/provider_management.py | 2 +- .../stacks/api_stack/v1_api/provider_users.py | 2 +- .../api_stack/v1_api/public_lookup_api.py | 2 +- .../stacks/api_stack/v1_api/purchases.py | 2 +- .../disaster_recovery_stack/__init__.py | 2 +- .../restore_dynamo_db_table_step_function.py | 3 +- .../sync_table_step_function.py | 2 +- .../stacks/event_listener_stack/__init__.py | 2 +- .../compact-connect/stacks/ingest_stack.py | 2 +- .../stacks/managed_login_stack.py | 2 +- .../stacks/notification_stack.py | 2 +- .../stacks/persistent_stack/__init__.py | 8 +-- .../persistent_stack/bulk_uploads_bucket.py | 6 +- .../compact_configuration_upload.py | 2 +- .../persistent_stack/provider_users_bucket.py | 4 +- .../stacks/persistent_stack/ssn_table.py | 2 +- .../transaction_reports_bucket.py | 3 +- .../user_email_notifications.py | 2 +- .../stacks/provider_users/__init__.py | 4 +- .../compact-connect/stacks/reporting_stack.py | 2 +- .../stacks/state_api_stack/__init__.py | 4 +- .../state_api_stack/v1_api/api_model.py | 2 +- .../state_api_stack/v1_api/bulk_upload_url.py | 2 +- .../state_api_stack/v1_api/post_licenses.py | 2 +- .../v1_api/provider_management.py | 2 +- .../stacks/state_auth/__init__.py | 2 +- .../transaction_monitoring_stack/__init__.py | 2 +- ...transaction_history_processing_workflow.py | 2 +- backend/compact-connect/tests/app/base.py | 2 +- .../tests/app/test_pipeline.py | 23 ------ .../test_cognito_user_backup.py | 2 +- 51 files changed, 133 insertions(+), 86 deletions(-) diff --git a/.github/workflows/check-common-cdk.yml b/.github/workflows/check-common-cdk.yml index 80d381f9b..576b5203a 100644 --- a/.github/workflows/check-common-cdk.yml +++ b/.github/workflows/check-common-cdk.yml @@ -53,4 +53,4 @@ jobs: - name: Test backend # Start at 14%, because that's what our 'unit' coverage is, while we convert this to more stand-alone # We will raise this up to 90 over time, to support these constructs more like a library - run: "cd backend/common-cdk; pytest tests --cov=common_constructs --cov-fail-under 14" + run: "cd backend/common-cdk; pytest tests --cov=common_constructs --cov-fail-under 11" diff --git a/backend/compact-connect-ui-app/tests/app/test_pipeline.py b/backend/compact-connect-ui-app/tests/app/test_pipeline.py index 08b20dfa0..74796477e 100644 --- a/backend/compact-connect-ui-app/tests/app/test_pipeline.py +++ b/backend/compact-connect-ui-app/tests/app/test_pipeline.py @@ -1,6 +1,8 @@ import json from unittest import TestCase +from aws_cdk.assertions import Match, Template + from tests.app.base import TstAppABC @@ -39,3 +41,72 @@ def test_synth_pipeline(self): self.app.prod_frontend_pipeline_stack.prod_frontend_stage.frontend_deployment_stack, ): self._inspect_frontend_deployment_stack(frontend_deployment_stack) + + def test_predictable_pipeline_role_names_created(self): + """Test that pipelines create roles with predictable names for bootstrap trust policies.""" + # Test frontend pipeline role naming - roles are environment-specific + frontend_test_cases = [ + (self.app.test_frontend_pipeline_stack, 'CompactConnect-test-Frontend-CrossAccountRole'), + (self.app.beta_frontend_pipeline_stack, 'CompactConnect-beta-Frontend-CrossAccountRole'), + (self.app.prod_frontend_pipeline_stack, 'CompactConnect-prod-Frontend-CrossAccountRole'), + ] + + for frontend_stack, expected_role_name in frontend_test_cases: + with self.subTest(role=expected_role_name): + frontend_template = Template.from_stack(frontend_stack) + + # Look for the predictable frontend pipeline role + frontend_roles = frontend_template.find_resources( + 'AWS::IAM::Role', props={'Properties': {'RoleName': expected_role_name}} + ) + self.assertEqual( + len(frontend_roles), + 1, + f'Should have exactly one frontend pipeline role with name {expected_role_name}', + ) + + def test_pipeline_role_trust_policies(self): + """Test that pipeline roles have correct trust policies for CodePipeline service.""" + # Test that all pipeline roles trust the CodePipeline service + # Note: Roles are environment-specific to avoid naming conflicts + test_cases = [ + (self.app.test_frontend_pipeline_stack, 'CompactConnect-test-Frontend-CrossAccountRole'), + (self.app.beta_frontend_pipeline_stack, 'CompactConnect-beta-Frontend-CrossAccountRole'), + (self.app.prod_frontend_pipeline_stack, 'CompactConnect-prod-Frontend-CrossAccountRole'), + ] + + for stack, expected_role_name in test_cases: + with self.subTest(role=expected_role_name): + template = Template.from_stack(stack) + + # Verify pipeline role trusts CodePipeline service + template.has_resource_properties( + 'AWS::IAM::Role', + { + 'RoleName': expected_role_name, + 'AssumeRolePolicyDocument': { + 'Statement': Match.array_with( + [ + { + 'Effect': 'Allow', + 'Principal': {'Service': 'codepipeline.amazonaws.com'}, + 'Action': 'sts:AssumeRole', + } + ] + ) + }, + }, + ) + + def test_pipeline_uses_predictable_roles_for_actions(self): + """Test that pipelines are configured to use predictable roles for all actions.""" + # Test that pipelines use the role parameter and use_pipeline_role_for_actions=True + frontend_pipeline_stack = self.app.test_frontend_pipeline_stack + frontend_template = Template.from_stack(frontend_pipeline_stack) + + # The pipeline should reference the predictable cross-account role + # This validates that our role parameter is being used + frontend_template.has_resource_properties( + 'AWS::CodePipeline::Pipeline', + {'RoleArn': {'Fn::GetAtt': [Match.string_like_regexp('.*FrontendCrossAccountRole.*'), 'Arn']}}, + ) diff --git a/backend/compact-connect/app.py b/backend/compact-connect/app.py index c8036ca5e..7d43f4ff1 100644 --- a/backend/compact-connect/app.py +++ b/backend/compact-connect/app.py @@ -9,6 +9,7 @@ from common_constructs.deployment_resources_stack import DeploymentResourcesStack from common_constructs.stack import StandardTags + from pipeline import ( ACTION_CONTEXT_KEY, PIPELINE_STACK_CONTEXT_KEY, diff --git a/backend/compact-connect/common_constructs/cc_api.py b/backend/compact-connect/common_constructs/cc_api.py index 49d1d1fe6..2291ff22d 100644 --- a/backend/compact-connect/common_constructs/cc_api.py +++ b/backend/compact-connect/common_constructs/cc_api.py @@ -26,11 +26,11 @@ from aws_cdk.aws_route53 import ARecord, IHostedZone, RecordTarget from aws_cdk.aws_route53_targets import ApiGateway from cdk_nag import NagSuppressions -from constructs import IConstruct - from common_constructs.security_profile import SecurityProfile from common_constructs.stack import AppStack from common_constructs.webacl import WebACL, WebACLScope +from constructs import IConstruct + from stacks import persistent_stack as ps MD_FORMAT = r'^[01]{1}[0-9]{1}-[0-3]{1}[0-9]{1}$' diff --git a/backend/compact-connect/common_constructs/cognito_user_backup.py b/backend/compact-connect/common_constructs/cognito_user_backup.py index b821bd273..374b3b92c 100644 --- a/backend/compact-connect/common_constructs/cognito_user_backup.py +++ b/backend/compact-connect/common_constructs/cognito_user_backup.py @@ -21,13 +21,13 @@ from aws_cdk.aws_s3 import BucketEncryption, LifecycleRule from aws_cdk.aws_sns import ITopic from cdk_nag import NagSuppressions +from common_constructs.access_logs_bucket import AccessLogsBucket +from common_constructs.bucket import Bucket +from common_constructs.stack import Stack from constructs import Construct -from common_constructs.access_logs_bucket import AccessLogsBucket from common_constructs.backup_plan import CCBackupPlan -from common_constructs.bucket import Bucket from common_constructs.python_function import PythonFunction -from common_constructs.stack import Stack from stacks.backup_infrastructure_stack import BackupInfrastructureStack diff --git a/backend/compact-connect/common_constructs/user_pool.py b/backend/compact-connect/common_constructs/user_pool.py index 7e39a42d7..bdd172377 100644 --- a/backend/compact-connect/common_constructs/user_pool.py +++ b/backend/compact-connect/common_constructs/user_pool.py @@ -30,9 +30,8 @@ from aws_cdk.aws_cognito import UserPool as CdkUserPool from aws_cdk.aws_kms import IKey from cdk_nag import NagSuppressions -from constructs import Construct - from common_constructs.security_profile import SecurityProfile +from constructs import Construct class UserPool(CdkUserPool): diff --git a/backend/compact-connect/pipeline/__init__.py b/backend/compact-connect/pipeline/__init__.py index e962b1da0..a6a04aa8e 100644 --- a/backend/compact-connect/pipeline/__init__.py +++ b/backend/compact-connect/pipeline/__init__.py @@ -5,8 +5,6 @@ from aws_cdk.aws_sns import ITopic from aws_cdk.pipelines import CodeBuildStep from cdk_nag import NagSuppressions -from constructs import Construct - from common_constructs.base_pipeline_stack import ( ALLOWED_ENVIRONMENT_NAMES, BETA_ENVIRONMENT_NAME, @@ -14,6 +12,8 @@ TEST_ENVIRONMENT_NAME, BasePipelineStack, ) +from constructs import Construct + from pipeline.backend_pipeline import BackendPipeline from pipeline.backend_stage import BackendStage from pipeline.synth_substitute_stage import SynthSubstituteStage diff --git a/backend/compact-connect/pipeline/backend_pipeline.py b/backend/compact-connect/pipeline/backend_pipeline.py index 7955d34ec..87b2296f1 100644 --- a/backend/compact-connect/pipeline/backend_pipeline.py +++ b/backend/compact-connect/pipeline/backend_pipeline.py @@ -2,6 +2,7 @@ import os +import common_constructs.base_pipeline_stack from aws_cdk import RemovalPolicy, Stack from aws_cdk.aws_codebuild import BuildSpec from aws_cdk.aws_codestarnotifications import NotificationRule diff --git a/backend/compact-connect/pipeline/backend_stage.py b/backend/compact-connect/pipeline/backend_stage.py index ca8782998..3dd131188 100644 --- a/backend/compact-connect/pipeline/backend_stage.py +++ b/backend/compact-connect/pipeline/backend_stage.py @@ -1,7 +1,7 @@ from aws_cdk import Environment, Stage +from common_constructs.stack import StandardTags from constructs import Construct -from common_constructs.stack import StandardTags from stacks.api_lambda_stack import ApiLambdaStack from stacks.api_stack import ApiStack from stacks.disaster_recovery_stack import DisasterRecoveryStack diff --git a/backend/compact-connect/stacks/api_lambda_stack/__init__.py b/backend/compact-connect/stacks/api_lambda_stack/__init__.py index ddd92358c..bf68bad61 100644 --- a/backend/compact-connect/stacks/api_lambda_stack/__init__.py +++ b/backend/compact-connect/stacks/api_lambda_stack/__init__.py @@ -1,8 +1,8 @@ from __future__ import annotations +from common_constructs.stack import AppStack from constructs import Construct -from common_constructs.stack import AppStack from stacks import persistent_stack as ps from stacks.provider_users import ProviderUsersStack diff --git a/backend/compact-connect/stacks/api_lambda_stack/provider_users.py b/backend/compact-connect/stacks/api_lambda_stack/provider_users.py index 809321727..025ff4f35 100644 --- a/backend/compact-connect/stacks/api_lambda_stack/provider_users.py +++ b/backend/compact-connect/stacks/api_lambda_stack/provider_users.py @@ -7,9 +7,9 @@ from aws_cdk.aws_cloudwatch_actions import SnsAction from aws_cdk.aws_secretsmanager import Secret from cdk_nag import NagSuppressions +from common_constructs.stack import Stack from common_constructs.python_function import PythonFunction -from common_constructs.stack import Stack from stacks import persistent_stack as ps from stacks.provider_users import ProviderUsersStack diff --git a/backend/compact-connect/stacks/api_stack/__init__.py b/backend/compact-connect/stacks/api_stack/__init__.py index fdb5d4ead..4d2578047 100644 --- a/backend/compact-connect/stacks/api_stack/__init__.py +++ b/backend/compact-connect/stacks/api_stack/__init__.py @@ -1,9 +1,9 @@ from __future__ import annotations -from constructs import Construct - from common_constructs.security_profile import SecurityProfile from common_constructs.stack import AppStack +from constructs import Construct + from stacks import persistent_stack as ps from stacks.api_lambda_stack import ApiLambdaStack from stacks.provider_users import ProviderUsersStack diff --git a/backend/compact-connect/stacks/api_stack/api.py b/backend/compact-connect/stacks/api_stack/api.py index 351993d16..1301f3441 100644 --- a/backend/compact-connect/stacks/api_stack/api.py +++ b/backend/compact-connect/stacks/api_stack/api.py @@ -4,10 +4,10 @@ from functools import cached_property from aws_cdk import ArnFormat +from common_constructs.stack import Stack from constructs import Construct from common_constructs.cc_api import CCApi -from common_constructs.stack import Stack from stacks import persistent_stack as ps from stacks.api_lambda_stack import ApiLambdaStack from stacks.provider_users import ProviderUsersStack diff --git a/backend/compact-connect/stacks/api_stack/v1_api/api.py b/backend/compact-connect/stacks/api_stack/v1_api/api.py index 4331277c9..262d7369e 100644 --- a/backend/compact-connect/stacks/api_stack/v1_api/api.py +++ b/backend/compact-connect/stacks/api_stack/v1_api/api.py @@ -4,10 +4,10 @@ from aws_cdk.aws_apigateway import AuthorizationType, IResource, MethodOptions from cdk_nag import NagSuppressions +from common_constructs.stack import Stack from common_constructs.python_function import PythonFunction from common_constructs.ssm_parameter_utility import SSMParameterUtility -from common_constructs.stack import Stack from stacks import persistent_stack as ps from stacks.api_lambda_stack import ApiLambdaStack diff --git a/backend/compact-connect/stacks/api_stack/v1_api/api_model.py b/backend/compact-connect/stacks/api_stack/v1_api/api_model.py index e03b7198b..a5f53c9b8 100644 --- a/backend/compact-connect/stacks/api_stack/v1_api/api_model.py +++ b/backend/compact-connect/stacks/api_stack/v1_api/api_model.py @@ -4,10 +4,10 @@ from __future__ import annotations from aws_cdk.aws_apigateway import JsonSchema, JsonSchemaType, Model +from common_constructs.stack import AppStack # Importing module level to allow lazy loading for typing from common_constructs import cc_api -from common_constructs.stack import AppStack class ApiModel: diff --git a/backend/compact-connect/stacks/api_stack/v1_api/attestations.py b/backend/compact-connect/stacks/api_stack/v1_api/attestations.py index 73e090906..dde18f992 100644 --- a/backend/compact-connect/stacks/api_stack/v1_api/attestations.py +++ b/backend/compact-connect/stacks/api_stack/v1_api/attestations.py @@ -5,10 +5,10 @@ from aws_cdk import Duration from aws_cdk.aws_apigateway import LambdaIntegration, MethodResponse, Resource from cdk_nag import NagSuppressions +from common_constructs.stack import Stack from common_constructs.cc_api import CCApi from common_constructs.python_function import PythonFunction -from common_constructs.stack import Stack from stacks import persistent_stack as ps from .api_model import ApiModel diff --git a/backend/compact-connect/stacks/api_stack/v1_api/bulk_upload_url.py b/backend/compact-connect/stacks/api_stack/v1_api/bulk_upload_url.py index d795928f4..09dc242b1 100644 --- a/backend/compact-connect/stacks/api_stack/v1_api/bulk_upload_url.py +++ b/backend/compact-connect/stacks/api_stack/v1_api/bulk_upload_url.py @@ -6,10 +6,10 @@ from aws_cdk.aws_apigateway import AuthorizationType, LambdaIntegration, MethodOptions, MethodResponse, Resource from aws_cdk.aws_iam import IRole from aws_cdk.aws_s3 import IBucket +from common_constructs.stack import Stack from common_constructs.cc_api import CCApi from common_constructs.python_function import PythonFunction -from common_constructs.stack import Stack from .api_model import ApiModel diff --git a/backend/compact-connect/stacks/api_stack/v1_api/compact_configuration_api.py b/backend/compact-connect/stacks/api_stack/v1_api/compact_configuration_api.py index 4e2604ede..667f9b086 100644 --- a/backend/compact-connect/stacks/api_stack/v1_api/compact_configuration_api.py +++ b/backend/compact-connect/stacks/api_stack/v1_api/compact_configuration_api.py @@ -5,10 +5,10 @@ from aws_cdk import Duration from aws_cdk.aws_apigateway import LambdaIntegration, MethodOptions, MethodResponse, Resource from cdk_nag import NagSuppressions +from common_constructs.stack import Stack from common_constructs.cc_api import CCApi from common_constructs.python_function import PythonFunction -from common_constructs.stack import Stack from stacks import persistent_stack as ps from .api_model import ApiModel diff --git a/backend/compact-connect/stacks/api_stack/v1_api/credentials.py b/backend/compact-connect/stacks/api_stack/v1_api/credentials.py index 41bdd820d..e101ea81d 100644 --- a/backend/compact-connect/stacks/api_stack/v1_api/credentials.py +++ b/backend/compact-connect/stacks/api_stack/v1_api/credentials.py @@ -6,10 +6,10 @@ from aws_cdk.aws_apigateway import LambdaIntegration, MethodOptions, MethodResponse, Resource from aws_cdk.aws_iam import Effect, PolicyStatement from cdk_nag import NagSuppressions +from common_constructs.stack import Stack from common_constructs.cc_api import CCApi from common_constructs.python_function import PythonFunction -from common_constructs.stack import Stack from stacks import persistent_stack as ps from .api_model import ApiModel diff --git a/backend/compact-connect/stacks/api_stack/v1_api/post_licenses.py b/backend/compact-connect/stacks/api_stack/v1_api/post_licenses.py index e40a9c80a..a73656b11 100644 --- a/backend/compact-connect/stacks/api_stack/v1_api/post_licenses.py +++ b/backend/compact-connect/stacks/api_stack/v1_api/post_licenses.py @@ -7,10 +7,10 @@ from aws_cdk.aws_dynamodb import ITable from aws_cdk.aws_iam import IRole from aws_cdk.aws_sqs import IQueue +from common_constructs.stack import Stack from common_constructs.cc_api import CCApi from common_constructs.python_function import PythonFunction -from common_constructs.stack import Stack from stacks import persistent_stack as ps from .api_model import ApiModel diff --git a/backend/compact-connect/stacks/api_stack/v1_api/provider_management.py b/backend/compact-connect/stacks/api_stack/v1_api/provider_management.py index 8d6f31c76..f119a4447 100644 --- a/backend/compact-connect/stacks/api_stack/v1_api/provider_management.py +++ b/backend/compact-connect/stacks/api_stack/v1_api/provider_management.py @@ -17,11 +17,11 @@ from aws_cdk.aws_iam import Policy, PolicyStatement from aws_cdk.aws_kms import IKey from cdk_nag import NagSuppressions +from common_constructs.stack import Stack from common_constructs.cc_api import CCApi from common_constructs.nodejs_function import NodejsFunction from common_constructs.python_function import PythonFunction -from common_constructs.stack import Stack from stacks import persistent_stack as ps from stacks.persistent_stack import ProviderTable, ProviderUsersBucket, RateLimitingTable, SSNTable, StaffUsers diff --git a/backend/compact-connect/stacks/api_stack/v1_api/provider_users.py b/backend/compact-connect/stacks/api_stack/v1_api/provider_users.py index 289d8ae46..57e5df608 100644 --- a/backend/compact-connect/stacks/api_stack/v1_api/provider_users.py +++ b/backend/compact-connect/stacks/api_stack/v1_api/provider_users.py @@ -3,10 +3,10 @@ from aws_cdk import Duration from aws_cdk.aws_apigateway import LambdaIntegration, MethodResponse, Resource from cdk_nag import NagSuppressions +from common_constructs.stack import Stack from common_constructs.cc_api import CCApi from common_constructs.python_function import PythonFunction -from common_constructs.stack import Stack from stacks.api_lambda_stack import ApiLambdaStack from .api_model import ApiModel diff --git a/backend/compact-connect/stacks/api_stack/v1_api/public_lookup_api.py b/backend/compact-connect/stacks/api_stack/v1_api/public_lookup_api.py index 2697859fa..feb74d125 100644 --- a/backend/compact-connect/stacks/api_stack/v1_api/public_lookup_api.py +++ b/backend/compact-connect/stacks/api_stack/v1_api/public_lookup_api.py @@ -5,10 +5,10 @@ from aws_cdk import Duration from aws_cdk.aws_apigateway import LambdaIntegration, MethodResponse, Resource from cdk_nag import NagSuppressions +from common_constructs.stack import Stack from common_constructs.cc_api import CCApi from common_constructs.python_function import PythonFunction -from common_constructs.stack import Stack from stacks import persistent_stack as ps from .api_model import ApiModel diff --git a/backend/compact-connect/stacks/api_stack/v1_api/purchases.py b/backend/compact-connect/stacks/api_stack/v1_api/purchases.py index d5225c261..f0aea11e7 100644 --- a/backend/compact-connect/stacks/api_stack/v1_api/purchases.py +++ b/backend/compact-connect/stacks/api_stack/v1_api/purchases.py @@ -8,10 +8,10 @@ from aws_cdk.aws_iam import Effect, PolicyStatement from aws_cdk.aws_kms import IKey from cdk_nag import NagSuppressions +from common_constructs.stack import Stack from common_constructs.cc_api import CCApi from common_constructs.python_function import PythonFunction -from common_constructs.stack import Stack from stacks.persistent_stack import CompactConfigurationTable, ProviderTable from .api_model import ApiModel diff --git a/backend/compact-connect/stacks/disaster_recovery_stack/__init__.py b/backend/compact-connect/stacks/disaster_recovery_stack/__init__.py index 9cdc369df..901e57bf8 100644 --- a/backend/compact-connect/stacks/disaster_recovery_stack/__init__.py +++ b/backend/compact-connect/stacks/disaster_recovery_stack/__init__.py @@ -2,9 +2,9 @@ from aws_cdk.aws_dynamodb import Table from aws_cdk.aws_iam import PolicyStatement, ServicePrincipal from aws_cdk.aws_kms import Key +from common_constructs.stack import AppStack from constructs import Construct -from common_constructs.stack import AppStack from stacks import persistent_stack as ps from stacks.disaster_recovery_stack.restore_dynamo_db_table_step_function import ( RestoreDynamoDbTableStepFunctionConstruct, diff --git a/backend/compact-connect/stacks/disaster_recovery_stack/restore_dynamo_db_table_step_function.py b/backend/compact-connect/stacks/disaster_recovery_stack/restore_dynamo_db_table_step_function.py index 5609a9696..eb108772a 100644 --- a/backend/compact-connect/stacks/disaster_recovery_stack/restore_dynamo_db_table_step_function.py +++ b/backend/compact-connect/stacks/disaster_recovery_stack/restore_dynamo_db_table_step_function.py @@ -20,9 +20,8 @@ WaitTime, ) from cdk_nag import NagSuppressions -from constructs import Construct - from common_constructs.stack import Stack +from constructs import Construct class RestoreDynamoDbTableStepFunctionConstruct(Construct): diff --git a/backend/compact-connect/stacks/disaster_recovery_stack/sync_table_step_function.py b/backend/compact-connect/stacks/disaster_recovery_stack/sync_table_step_function.py index 8c6e0af77..c10cfd058 100644 --- a/backend/compact-connect/stacks/disaster_recovery_stack/sync_table_step_function.py +++ b/backend/compact-connect/stacks/disaster_recovery_stack/sync_table_step_function.py @@ -19,10 +19,10 @@ ) from aws_cdk.aws_stepfunctions_tasks import LambdaInvoke from cdk_nag import NagSuppressions +from common_constructs.stack import Stack from constructs import Construct from common_constructs.python_function import PythonFunction -from common_constructs.stack import Stack class SyncTableDataStepFunctionConstruct(Construct): diff --git a/backend/compact-connect/stacks/event_listener_stack/__init__.py b/backend/compact-connect/stacks/event_listener_stack/__init__.py index 5af836006..1fb112adb 100644 --- a/backend/compact-connect/stacks/event_listener_stack/__init__.py +++ b/backend/compact-connect/stacks/event_listener_stack/__init__.py @@ -5,12 +5,12 @@ from aws_cdk import Duration from aws_cdk.aws_events import EventBus from cdk_nag import NagSuppressions +from common_constructs.stack import AppStack from constructs import Construct from common_constructs.python_function import PythonFunction from common_constructs.queue_event_listener import QueueEventListener from common_constructs.ssm_parameter_utility import SSMParameterUtility -from common_constructs.stack import AppStack from stacks import persistent_stack as ps diff --git a/backend/compact-connect/stacks/ingest_stack.py b/backend/compact-connect/stacks/ingest_stack.py index e51ea3abd..cb8e0c4ef 100644 --- a/backend/compact-connect/stacks/ingest_stack.py +++ b/backend/compact-connect/stacks/ingest_stack.py @@ -8,12 +8,12 @@ from aws_cdk.aws_events import EventBus, EventPattern, Rule from aws_cdk.aws_events_targets import SqsQueue from cdk_nag import NagSuppressions +from common_constructs.stack import AppStack, Stack from constructs import Construct from common_constructs.python_function import PythonFunction from common_constructs.queued_lambda_processor import QueuedLambdaProcessor from common_constructs.ssm_parameter_utility import SSMParameterUtility -from common_constructs.stack import AppStack, Stack from stacks import persistent_stack as ps diff --git a/backend/compact-connect/stacks/managed_login_stack.py b/backend/compact-connect/stacks/managed_login_stack.py index 8672fc14b..621d55dee 100644 --- a/backend/compact-connect/stacks/managed_login_stack.py +++ b/backend/compact-connect/stacks/managed_login_stack.py @@ -1,9 +1,9 @@ import json from aws_cdk.aws_cognito import CfnManagedLoginBranding +from common_constructs.stack import AppStack from constructs import Construct -from common_constructs.stack import AppStack from stacks.persistent_stack import PersistentStack from stacks.provider_users import ProviderUsersStack diff --git a/backend/compact-connect/stacks/notification_stack.py b/backend/compact-connect/stacks/notification_stack.py index 457bde652..0e61513ae 100644 --- a/backend/compact-connect/stacks/notification_stack.py +++ b/backend/compact-connect/stacks/notification_stack.py @@ -8,13 +8,13 @@ from aws_cdk.aws_events import EventBus, EventPattern, IEventBus, Rule from aws_cdk.aws_events_targets import SqsQueue from cdk_nag import NagSuppressions +from common_constructs.stack import AppStack from constructs import Construct from common_constructs.python_function import PythonFunction from common_constructs.queue_event_listener import QueueEventListener from common_constructs.queued_lambda_processor import QueuedLambdaProcessor from common_constructs.ssm_parameter_utility import SSMParameterUtility -from common_constructs.stack import AppStack from stacks import persistent_stack as ps diff --git a/backend/compact-connect/stacks/persistent_stack/__init__.py b/backend/compact-connect/stacks/persistent_stack/__init__.py index 9e5dc8104..e6563dac2 100644 --- a/backend/compact-connect/stacks/persistent_stack/__init__.py +++ b/backend/compact-connect/stacks/persistent_stack/__init__.py @@ -8,16 +8,16 @@ from aws_cdk.aws_lambda_python_alpha import PythonLayerVersion from aws_cdk.aws_logs import QueryDefinition, QueryString from cdk_nag import NagSuppressions -from constructs import Construct - from common_constructs.access_logs_bucket import AccessLogsBucket from common_constructs.alarm_topic import AlarmTopic from common_constructs.frontend_app_config_utility import PersistentStackFrontendAppConfigUtility +from common_constructs.security_profile import SecurityProfile +from common_constructs.stack import AppStack +from constructs import Construct + from common_constructs.nodejs_function import NodejsFunction from common_constructs.python_function import COMMON_PYTHON_LAMBDA_LAYER_SSM_PARAMETER_NAME -from common_constructs.security_profile import SecurityProfile from common_constructs.ssm_parameter_utility import SSMParameterUtility -from common_constructs.stack import AppStack from stacks.backup_infrastructure_stack import BackupInfrastructureStack from stacks.persistent_stack.bulk_uploads_bucket import BulkUploadsBucket from stacks.persistent_stack.compact_configuration_table import CompactConfigurationTable diff --git a/backend/compact-connect/stacks/persistent_stack/bulk_uploads_bucket.py b/backend/compact-connect/stacks/persistent_stack/bulk_uploads_bucket.py index a1ad7565d..5f39f145e 100644 --- a/backend/compact-connect/stacks/persistent_stack/bulk_uploads_bucket.py +++ b/backend/compact-connect/stacks/persistent_stack/bulk_uploads_bucket.py @@ -13,13 +13,13 @@ from aws_cdk.aws_s3_notifications import LambdaDestination from aws_cdk.aws_sqs import IQueue from cdk_nag import NagSuppressions +from common_constructs.access_logs_bucket import AccessLogsBucket +from common_constructs.bucket import Bucket +from common_constructs.stack import Stack from constructs import Construct import stacks.persistent_stack as ps -from common_constructs.access_logs_bucket import AccessLogsBucket -from common_constructs.bucket import Bucket from common_constructs.python_function import PythonFunction -from common_constructs.stack import Stack class BulkUploadsBucket(Bucket): diff --git a/backend/compact-connect/stacks/persistent_stack/compact_configuration_upload.py b/backend/compact-connect/stacks/persistent_stack/compact_configuration_upload.py index 1df685d80..8816e5ca6 100644 --- a/backend/compact-connect/stacks/persistent_stack/compact_configuration_upload.py +++ b/backend/compact-connect/stacks/persistent_stack/compact_configuration_upload.py @@ -7,10 +7,10 @@ from aws_cdk.aws_logs import RetentionDays from aws_cdk.custom_resources import Provider from cdk_nag import NagSuppressions +from common_constructs.stack import Stack from constructs import Construct from common_constructs.python_function import PythonFunction -from common_constructs.stack import Stack from .compact_configuration_table import CompactConfigurationTable diff --git a/backend/compact-connect/stacks/persistent_stack/provider_users_bucket.py b/backend/compact-connect/stacks/persistent_stack/provider_users_bucket.py index 8f52df3e4..415b35856 100644 --- a/backend/compact-connect/stacks/persistent_stack/provider_users_bucket.py +++ b/backend/compact-connect/stacks/persistent_stack/provider_users_bucket.py @@ -12,12 +12,12 @@ from aws_cdk.aws_s3 import BucketEncryption, CorsRule, EventType, HttpMethods from aws_cdk.aws_s3_notifications import LambdaDestination from cdk_nag import NagSuppressions +from common_constructs.access_logs_bucket import AccessLogsBucket +from common_constructs.bucket import Bucket from constructs import Construct import stacks.persistent_stack as ps -from common_constructs.access_logs_bucket import AccessLogsBucket from common_constructs.backup_plan import CCBackupPlan -from common_constructs.bucket import Bucket from common_constructs.python_function import PythonFunction from stacks.backup_infrastructure_stack import BackupInfrastructureStack diff --git a/backend/compact-connect/stacks/persistent_stack/ssn_table.py b/backend/compact-connect/stacks/persistent_stack/ssn_table.py index b90d64b31..6e2e2bceb 100644 --- a/backend/compact-connect/stacks/persistent_stack/ssn_table.py +++ b/backend/compact-connect/stacks/persistent_stack/ssn_table.py @@ -16,12 +16,12 @@ from aws_cdk.aws_kms import Key from aws_cdk.aws_sns import ITopic from cdk_nag import NagSuppressions +from common_constructs.stack import Stack from constructs import Construct from common_constructs.backup_plan import CCBackupPlan from common_constructs.python_function import PythonFunction from common_constructs.queued_lambda_processor import QueuedLambdaProcessor -from common_constructs.stack import Stack from stacks.backup_infrastructure_stack import BackupInfrastructureStack diff --git a/backend/compact-connect/stacks/persistent_stack/transaction_reports_bucket.py b/backend/compact-connect/stacks/persistent_stack/transaction_reports_bucket.py index aa1c857a0..51d025b88 100644 --- a/backend/compact-connect/stacks/persistent_stack/transaction_reports_bucket.py +++ b/backend/compact-connect/stacks/persistent_stack/transaction_reports_bucket.py @@ -3,10 +3,9 @@ from aws_cdk.aws_kms import IKey from aws_cdk.aws_s3 import BucketEncryption from cdk_nag import NagSuppressions -from constructs import Construct - from common_constructs.access_logs_bucket import AccessLogsBucket from common_constructs.bucket import Bucket +from constructs import Construct class TransactionReportsBucket(Bucket): diff --git a/backend/compact-connect/stacks/persistent_stack/user_email_notifications.py b/backend/compact-connect/stacks/persistent_stack/user_email_notifications.py index a5e94a6ce..9a69b1116 100644 --- a/backend/compact-connect/stacks/persistent_stack/user_email_notifications.py +++ b/backend/compact-connect/stacks/persistent_stack/user_email_notifications.py @@ -9,10 +9,10 @@ from aws_cdk.aws_sns import Subscription, SubscriptionProtocol, Topic from aws_cdk.custom_resources import Provider from cdk_nag import NagSuppressions +from common_constructs.stack import Stack from constructs import Construct from common_constructs.python_function import PythonFunction -from common_constructs.stack import Stack class UserEmailNotifications(Construct): diff --git a/backend/compact-connect/stacks/provider_users/__init__.py b/backend/compact-connect/stacks/provider_users/__init__.py index f6c661c22..180993679 100644 --- a/backend/compact-connect/stacks/provider_users/__init__.py +++ b/backend/compact-connect/stacks/provider_users/__init__.py @@ -1,10 +1,10 @@ from aws_cdk import RemovalPolicy from aws_cdk.aws_cognito import SignInAliases, UserPoolEmail from aws_cdk.aws_logs import QueryDefinition, QueryString -from constructs import Construct - from common_constructs.security_profile import SecurityProfile from common_constructs.stack import AppStack +from constructs import Construct + from stacks.persistent_stack import PersistentStack from stacks.provider_users.provider_users import ProviderUsers diff --git a/backend/compact-connect/stacks/reporting_stack.py b/backend/compact-connect/stacks/reporting_stack.py index c4a88fbe9..7e81ee32b 100644 --- a/backend/compact-connect/stacks/reporting_stack.py +++ b/backend/compact-connect/stacks/reporting_stack.py @@ -10,11 +10,11 @@ from aws_cdk.aws_events_targets import LambdaFunction from aws_cdk.aws_logs import QueryDefinition, QueryString from cdk_nag import NagSuppressions +from common_constructs.stack import AppStack from constructs import Construct from common_constructs.nodejs_function import NodejsFunction from common_constructs.python_function import PythonFunction -from common_constructs.stack import AppStack from stacks import persistent_stack as ps diff --git a/backend/compact-connect/stacks/state_api_stack/__init__.py b/backend/compact-connect/stacks/state_api_stack/__init__.py index 12192c225..35a2a2c18 100644 --- a/backend/compact-connect/stacks/state_api_stack/__init__.py +++ b/backend/compact-connect/stacks/state_api_stack/__init__.py @@ -1,9 +1,9 @@ from __future__ import annotations -from constructs import Construct - from common_constructs.security_profile import SecurityProfile from common_constructs.stack import AppStack +from constructs import Construct + from stacks import persistent_stack as ps from stacks.state_auth import StateAuthStack diff --git a/backend/compact-connect/stacks/state_api_stack/v1_api/api_model.py b/backend/compact-connect/stacks/state_api_stack/v1_api/api_model.py index b3153e8b1..d0ca9992f 100644 --- a/backend/compact-connect/stacks/state_api_stack/v1_api/api_model.py +++ b/backend/compact-connect/stacks/state_api_stack/v1_api/api_model.py @@ -4,10 +4,10 @@ from __future__ import annotations from aws_cdk.aws_apigateway import JsonSchema, JsonSchemaType, Model +from common_constructs.stack import AppStack # Importing module level to allow lazy loading for typing from common_constructs import cc_api -from common_constructs.stack import AppStack class ApiModel: diff --git a/backend/compact-connect/stacks/state_api_stack/v1_api/bulk_upload_url.py b/backend/compact-connect/stacks/state_api_stack/v1_api/bulk_upload_url.py index 47c11171a..8499d2c8a 100644 --- a/backend/compact-connect/stacks/state_api_stack/v1_api/bulk_upload_url.py +++ b/backend/compact-connect/stacks/state_api_stack/v1_api/bulk_upload_url.py @@ -5,10 +5,10 @@ from aws_cdk import Duration from aws_cdk.aws_apigateway import AuthorizationType, LambdaIntegration, MethodOptions, MethodResponse, Resource from aws_cdk.aws_iam import IRole +from common_constructs.stack import Stack from common_constructs.cc_api import CCApi from common_constructs.python_function import PythonFunction -from common_constructs.stack import Stack from stacks import persistent_stack as ps from .api_model import ApiModel diff --git a/backend/compact-connect/stacks/state_api_stack/v1_api/post_licenses.py b/backend/compact-connect/stacks/state_api_stack/v1_api/post_licenses.py index 4940c4c43..8e9bf0840 100644 --- a/backend/compact-connect/stacks/state_api_stack/v1_api/post_licenses.py +++ b/backend/compact-connect/stacks/state_api_stack/v1_api/post_licenses.py @@ -5,10 +5,10 @@ from aws_cdk import Duration from aws_cdk.aws_apigateway import LambdaIntegration, MethodOptions, MethodResponse, Resource from aws_cdk.aws_iam import IRole +from common_constructs.stack import Stack from common_constructs.cc_api import CCApi from common_constructs.python_function import PythonFunction -from common_constructs.stack import Stack from stacks import persistent_stack as ps from .api_model import ApiModel diff --git a/backend/compact-connect/stacks/state_api_stack/v1_api/provider_management.py b/backend/compact-connect/stacks/state_api_stack/v1_api/provider_management.py index 15d79b091..e40456f45 100644 --- a/backend/compact-connect/stacks/state_api_stack/v1_api/provider_management.py +++ b/backend/compact-connect/stacks/state_api_stack/v1_api/provider_management.py @@ -7,10 +7,10 @@ from aws_cdk.aws_dynamodb import ITable from aws_cdk.aws_kms import IKey from cdk_nag import NagSuppressions +from common_constructs.stack import Stack from common_constructs.cc_api import CCApi from common_constructs.python_function import PythonFunction -from common_constructs.stack import Stack from stacks import persistent_stack as ps from stacks.persistent_stack import ProviderTable diff --git a/backend/compact-connect/stacks/state_auth/__init__.py b/backend/compact-connect/stacks/state_auth/__init__.py index c7b64bf40..65e8a5235 100644 --- a/backend/compact-connect/stacks/state_auth/__init__.py +++ b/backend/compact-connect/stacks/state_auth/__init__.py @@ -1,7 +1,7 @@ from aws_cdk import RemovalPolicy +from common_constructs.stack import AppStack from constructs import Construct -from common_constructs.stack import AppStack from stacks.persistent_stack import PersistentStack from stacks.state_auth.state_auth_users import StateAuthUsers diff --git a/backend/compact-connect/stacks/transaction_monitoring_stack/__init__.py b/backend/compact-connect/stacks/transaction_monitoring_stack/__init__.py index 018056e71..7acd12028 100644 --- a/backend/compact-connect/stacks/transaction_monitoring_stack/__init__.py +++ b/backend/compact-connect/stacks/transaction_monitoring_stack/__init__.py @@ -2,9 +2,9 @@ import json +from common_constructs.stack import AppStack from constructs import Construct -from common_constructs.stack import AppStack from stacks import persistent_stack as ps from .transaction_history_processing_workflow import TransactionHistoryProcessingWorkflow diff --git a/backend/compact-connect/stacks/transaction_monitoring_stack/transaction_history_processing_workflow.py b/backend/compact-connect/stacks/transaction_monitoring_stack/transaction_history_processing_workflow.py index 695355539..117f15071 100644 --- a/backend/compact-connect/stacks/transaction_monitoring_stack/transaction_history_processing_workflow.py +++ b/backend/compact-connect/stacks/transaction_monitoring_stack/transaction_history_processing_workflow.py @@ -25,10 +25,10 @@ ) from aws_cdk.aws_stepfunctions_tasks import LambdaInvoke from cdk_nag import NagSuppressions +from common_constructs.stack import Stack from constructs import Construct from common_constructs.python_function import PythonFunction -from common_constructs.stack import Stack from stacks import persistent_stack as ps diff --git a/backend/compact-connect/tests/app/base.py b/backend/compact-connect/tests/app/base.py index 8d59e2e5b..bcd7467fe 100644 --- a/backend/compact-connect/tests/app/base.py +++ b/backend/compact-connect/tests/app/base.py @@ -13,10 +13,10 @@ from aws_cdk.aws_kms import CfnKey from aws_cdk.aws_lambda import CfnEventSourceMapping from aws_cdk.aws_sqs import CfnQueue +from common_constructs.stack import Stack from app import CompactConnectApp from common_constructs.backup_plan import CCBackupPlan -from common_constructs.stack import Stack from pipeline import BackendStage from stacks.api_stack import ApiStack from stacks.persistent_stack import PersistentStack diff --git a/backend/compact-connect/tests/app/test_pipeline.py b/backend/compact-connect/tests/app/test_pipeline.py index 6b6687d5d..8fffc1ac6 100644 --- a/backend/compact-connect/tests/app/test_pipeline.py +++ b/backend/compact-connect/tests/app/test_pipeline.py @@ -363,26 +363,6 @@ def test_predictable_pipeline_role_names_created(self): f'Should have exactly one backend pipeline role with name {expected_role_name}', ) - # Test frontend pipeline role naming - roles are environment-specific - frontend_test_cases = [ - (self.app.test_frontend_pipeline_stack, 'CompactConnect-test-Frontend-CrossAccountRole'), - (self.app.beta_frontend_pipeline_stack, 'CompactConnect-beta-Frontend-CrossAccountRole'), - (self.app.prod_frontend_pipeline_stack, 'CompactConnect-prod-Frontend-CrossAccountRole'), - ] - - for frontend_stack, expected_role_name in frontend_test_cases: - with self.subTest(role=expected_role_name): - frontend_template = Template.from_stack(frontend_stack) - - # Look for the predictable frontend pipeline role - frontend_roles = frontend_template.find_resources( - 'AWS::IAM::Role', props={'Properties': {'RoleName': expected_role_name}} - ) - self.assertEqual( - len(frontend_roles), - 1, - f'Should have exactly one frontend pipeline role with name {expected_role_name}', - ) def test_pipeline_role_trust_policies(self): """Test that pipeline roles have correct trust policies for CodePipeline service.""" @@ -392,9 +372,6 @@ def test_pipeline_role_trust_policies(self): (self.app.test_backend_pipeline_stack, 'CompactConnect-test-Backend-CrossAccountRole'), (self.app.beta_backend_pipeline_stack, 'CompactConnect-beta-Backend-CrossAccountRole'), (self.app.prod_backend_pipeline_stack, 'CompactConnect-prod-Backend-CrossAccountRole'), - (self.app.test_frontend_pipeline_stack, 'CompactConnect-test-Frontend-CrossAccountRole'), - (self.app.beta_frontend_pipeline_stack, 'CompactConnect-beta-Frontend-CrossAccountRole'), - (self.app.prod_frontend_pipeline_stack, 'CompactConnect-prod-Frontend-CrossAccountRole'), ] for stack, expected_role_name in test_cases: diff --git a/backend/compact-connect/tests/common_constructs/test_cognito_user_backup.py b/backend/compact-connect/tests/common_constructs/test_cognito_user_backup.py index d2935d494..7beb210f0 100644 --- a/backend/compact-connect/tests/common_constructs/test_cognito_user_backup.py +++ b/backend/compact-connect/tests/common_constructs/test_cognito_user_backup.py @@ -18,8 +18,8 @@ from aws_cdk.aws_lambda import CfnFunction from aws_cdk.aws_s3 import CfnBucket from aws_cdk.aws_sns import Topic - from common_constructs.access_logs_bucket import AccessLogsBucket + from common_constructs.cognito_user_backup import CognitoUserBackup from stacks.backup_infrastructure_stack import BackupInfrastructureStack From b2882b54e8dd91f98f027e741b3ac9ab52584bc6 Mon Sep 17 00:00:00 2001 From: Justin Frahm Date: Wed, 24 Sep 2025 14:10:41 -0600 Subject: [PATCH 17/32] Update pipelines to v2 --- backend/compact-connect-ui-app/pipeline/frontend_pipeline.py | 2 ++ backend/compact-connect/pipeline/backend_pipeline.py | 2 ++ 2 files changed, 4 insertions(+) diff --git a/backend/compact-connect-ui-app/pipeline/frontend_pipeline.py b/backend/compact-connect-ui-app/pipeline/frontend_pipeline.py index a991c21f4..1af11d671 100644 --- a/backend/compact-connect-ui-app/pipeline/frontend_pipeline.py +++ b/backend/compact-connect-ui-app/pipeline/frontend_pipeline.py @@ -5,6 +5,7 @@ import common_constructs.base_pipeline_stack from aws_cdk import RemovalPolicy, Stack from aws_cdk.aws_codebuild import BuildSpec +from aws_cdk.aws_codepipeline import PipelineType from aws_cdk.aws_codestarnotifications import NotificationRule from aws_cdk.aws_iam import ServicePrincipal from aws_cdk.aws_kms import IKey @@ -80,6 +81,7 @@ def __init__( scope, construct_id, pipeline_name=pipeline_name, + pipeline_type=PipelineType.V2, artifact_bucket=artifact_bucket, role=pipeline_role, use_pipeline_role_for_actions=True, diff --git a/backend/compact-connect/pipeline/backend_pipeline.py b/backend/compact-connect/pipeline/backend_pipeline.py index 87b2296f1..527450f71 100644 --- a/backend/compact-connect/pipeline/backend_pipeline.py +++ b/backend/compact-connect/pipeline/backend_pipeline.py @@ -5,6 +5,7 @@ import common_constructs.base_pipeline_stack from aws_cdk import RemovalPolicy, Stack from aws_cdk.aws_codebuild import BuildSpec +from aws_cdk.aws_codepipeline import PipelineType from aws_cdk.aws_codestarnotifications import NotificationRule from aws_cdk.aws_iam import ServicePrincipal from aws_cdk.aws_kms import IKey @@ -81,6 +82,7 @@ def __init__( scope, construct_id, pipeline_name=pipeline_name, + pipeline_type=PipelineType.V2, artifact_bucket=artifact_bucket, role=pipeline_role, use_pipeline_role_for_actions=True, From f40792e87d1ab43026240fde7837cf2fb48acee7 Mon Sep 17 00:00:00 2001 From: Justin Frahm Date: Sat, 27 Sep 2025 13:34:53 -0600 Subject: [PATCH 18/32] Remove unsupported commit hook example --- backend/bin/pre-commit | 123 ----------------------------------------- 1 file changed, 123 deletions(-) delete mode 100755 backend/bin/pre-commit diff --git a/backend/bin/pre-commit b/backend/bin/pre-commit deleted file mode 100755 index e0a6859f5..000000000 --- a/backend/bin/pre-commit +++ /dev/null @@ -1,123 +0,0 @@ -#!/usr/bin/env bash -# -# An example hook script to verify what is about to be committed. -# Called by "git commit" with no arguments. The hook should -# exit with non-zero status after issuing an appropriate message if -# it wants to stop the commit. -# -# To enable this hook, move this file to ".git/hooks/pre-commit". - -# TODO: Investigate a reasonable pre-commit testing approach with multi-service - -if git rev-parse --verify HEAD >/dev/null 2>&1 -then - against=HEAD -else - # Initial commit: diff against an empty tree object - against=$(git hash-object -t tree /dev/null) -fi - -# If you want to allow non-ASCII filenames set this variable to true. -allownonascii=$(git config --type=bool hooks.allownonascii) - -# Redirect output to stderr. -exec 1>&2 - - -# Cross platform projects tend to avoid non-ASCII filenames; prevent -# them from being added to the repository. We exploit the fact that the -# printable range starts at the space character and ends with tilde. -if [ "$allownonascii" != "true" ] && - # Note that the use of brackets around a tr range is ok here, (it's - # even required, for portability to Solaris 10's /usr/bin/tr), since - # the square bracket bytes happen to fall in the designated range. - test $(git diff --cached --name-only --diff-filter=A -z $against | - LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0 -then - cat <<\EOF -Error: Attempt to add a non-ASCII file name. - -This can cause problems if you want to work with people on other platforms. - -To be portable it is advisable to rename the file. - -If you know what you are doing you can disable this check using: - - git config hooks.allownonascii true -EOF - exit 1 -fi - -# If there are whitespace errors, print the offending file names and fail. -git diff-index --check --cached $against -- || exit 1 - -# Check individual files (not deleted) in the index -files=$(git diff-index --cached --name-only --diff-filter=d "$against") -result=0 # track exit code -if [ -n "$files" ]; then - for f in $files; do - # Only report known text files... - if [ -z "$(git diff-index --cached --stat=1 "$against" "$f" | grep -is '| Bin')" ]; then - # Only match regular files (e.g. no symlinks) - # See: https://stackoverflow.com/a/8347325 - if [ "$(git ls-files --stage "$f" | awk '{print $1}' | head -c2)" = "10" ]; then - # Checking if the last line is just a newline - # Using staged version of file instead of working dir - if [ -n "$(git cat-file blob "$(git ls-files --stage "$f" | awk '{print $2}')" | tail -c1)" ]; then - # Report error - if [ "$result" -lt "1" ]; then - echo "Error: The following files have no trailing newline:" 1>&2 - fi - echo -en "\t" 1>&2 - echo "$f" 1>&2 - result=1 - fi - fi - fi - done -fi -[ "$result" -lt "1" ] || exit "$result" - - -# Backend checks -dir=$(pwd) -cd backend - -# Lint all python files -files=$(git diff-index \ - --cached \ - --name-only \ - $against \ - --relative \ - --no-renames \ - --diff-filter=dr \ - -- '*.py') -if [ -n "$files" ]; then - ruff check $files || exit "$?" -fi - - -# Check dependencies for known vulnerabilities -pip-audit || exit "$?" - -# Lint all typescript files - -files=$(git diff-index \ - --cached \ - --name-only \ - $against \ - --relative \ - --no-renames \ - --diff-filter=dr \ - -- '*.ts') -if [ -n "$files" ]; then - ( - cd compact-connect/lambdas/nodejs/ - yarn lint:ingest || exit "$?" - ) || exit "$?" -fi - -# Run the back-end tests -bin/run_tests.sh -no || exit "$?" - -cd "$dir" From 7df48203f4b89d11dd32fcf9bb9fba807fc0fac6 Mon Sep 17 00:00:00 2001 From: Justin Frahm Date: Sat, 27 Sep 2025 15:11:01 -0600 Subject: [PATCH 19/32] Use pipeline role in synth steps --- backend/compact-connect-ui-app/pipeline/frontend_pipeline.py | 5 +++-- backend/compact-connect/pipeline/backend_pipeline.py | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/backend/compact-connect-ui-app/pipeline/frontend_pipeline.py b/backend/compact-connect-ui-app/pipeline/frontend_pipeline.py index 1af11d671..c060dabfb 100644 --- a/backend/compact-connect-ui-app/pipeline/frontend_pipeline.py +++ b/backend/compact-connect-ui-app/pipeline/frontend_pipeline.py @@ -12,7 +12,7 @@ from aws_cdk.aws_s3 import BucketEncryption, IBucket from aws_cdk.aws_sns import ITopic from aws_cdk.aws_ssm import IParameter -from aws_cdk.pipelines import CodeBuildOptions, CodePipelineSource, ShellStep +from aws_cdk.pipelines import CodeBuildOptions, CodeBuildStep, CodePipelineSource from aws_cdk.pipelines import CodePipeline as CdkCodePipeline from cdk_nag import NagSuppressions from common_constructs.bucket import Bucket @@ -85,7 +85,7 @@ def __init__( artifact_bucket=artifact_bucket, role=pipeline_role, use_pipeline_role_for_actions=True, - synth=ShellStep( + synth=CodeBuildStep( 'Synth', input=CodePipelineSource.connection( repo_string=github_repo_string, @@ -111,6 +111,7 @@ def __init__( # Only synthesize the specific pipeline stack needed f'cdk synth --context pipelineStack={pipeline_stack_name} --context action=pipelineSynth', ], + role=pipeline_role, ), synth_code_build_defaults=CodeBuildOptions( partial_build_spec=BuildSpec.from_object( diff --git a/backend/compact-connect/pipeline/backend_pipeline.py b/backend/compact-connect/pipeline/backend_pipeline.py index 527450f71..4c331752a 100644 --- a/backend/compact-connect/pipeline/backend_pipeline.py +++ b/backend/compact-connect/pipeline/backend_pipeline.py @@ -12,7 +12,7 @@ from aws_cdk.aws_s3 import BucketEncryption, IBucket from aws_cdk.aws_sns import ITopic from aws_cdk.aws_ssm import IParameter -from aws_cdk.pipelines import CodeBuildOptions, CodePipelineSource, ShellStep +from aws_cdk.pipelines import CodeBuildOptions, CodeBuildStep, CodePipelineSource from aws_cdk.pipelines import CodePipeline as CdkCodePipeline from cdk_nag import NagSuppressions from common_constructs.bucket import Bucket @@ -86,7 +86,7 @@ def __init__( artifact_bucket=artifact_bucket, role=pipeline_role, use_pipeline_role_for_actions=True, - synth=ShellStep( + synth=CodeBuildStep( 'Synth', input=CodePipelineSource.connection( repo_string=github_repo_string, @@ -109,6 +109,7 @@ def __init__( # Only synthesize the specific pipeline stack needed f'cdk synth --context pipelineStack={pipeline_stack_name} --context action=pipelineSynth', ], + role=pipeline_role, ), synth_code_build_defaults=CodeBuildOptions( partial_build_spec=BuildSpec.from_object( From 280307f5de324a5bd53ebc5267cc68d31caf89bd Mon Sep 17 00:00:00 2001 From: Justin Frahm Date: Mon, 29 Sep 2025 22:45:03 -0600 Subject: [PATCH 20/32] Use cross-account role for CodeBuild steps --- .../common_constructs/base_pipeline_stack.py | 7 +- .../pipeline/frontend_pipeline.py | 109 ++++++++++++++++- .../tests/app/test_pipeline.py | 5 +- .../pipeline/backend_pipeline.py | 110 +++++++++++++++++- .../tests/app/test_pipeline.py | 5 +- 5 files changed, 226 insertions(+), 10 deletions(-) diff --git a/backend/common-cdk/common_constructs/base_pipeline_stack.py b/backend/common-cdk/common_constructs/base_pipeline_stack.py index 3ae09d1dc..116e54520 100644 --- a/backend/common-cdk/common_constructs/base_pipeline_stack.py +++ b/backend/common-cdk/common_constructs/base_pipeline_stack.py @@ -1,7 +1,7 @@ import json from aws_cdk import Environment, RemovalPolicy -from aws_cdk.aws_iam import Effect, PolicyStatement, Role, ServicePrincipal +from aws_cdk.aws_iam import CompositePrincipal, Effect, PolicyStatement, Role, ServicePrincipal from aws_cdk.aws_s3 import IBucket from aws_cdk.aws_ssm import StringParameter from aws_cdk.pipelines import CodePipeline as CdkCodePipeline @@ -85,7 +85,10 @@ def create_predictable_pipeline_role(self, pipeline_type: str) -> Role: self, f'{pipeline_type}CrossAccountRole', role_name=cross_account_role_name, - assumed_by=ServicePrincipal('codepipeline.amazonaws.com'), + assumed_by=CompositePrincipal( + ServicePrincipal('codepipeline.amazonaws.com'), + ServicePrincipal('codebuild.amazonaws.com'), + ), description=f'Cross-account role for {self.environment_name} {pipeline_type.lower()}' 'pipeline bootstrap trust policies', ) diff --git a/backend/compact-connect-ui-app/pipeline/frontend_pipeline.py b/backend/compact-connect-ui-app/pipeline/frontend_pipeline.py index c060dabfb..7778a3d2f 100644 --- a/backend/compact-connect-ui-app/pipeline/frontend_pipeline.py +++ b/backend/compact-connect-ui-app/pipeline/frontend_pipeline.py @@ -3,11 +3,11 @@ import os import common_constructs.base_pipeline_stack -from aws_cdk import RemovalPolicy, Stack -from aws_cdk.aws_codebuild import BuildSpec +from aws_cdk import ArnFormat, Fn, RemovalPolicy, Stack +from aws_cdk.aws_codebuild import BuildSpec, CfnProject from aws_cdk.aws_codepipeline import PipelineType from aws_cdk.aws_codestarnotifications import NotificationRule -from aws_cdk.aws_iam import ServicePrincipal +from aws_cdk.aws_iam import Effect, PolicyStatement, Role, ServicePrincipal from aws_cdk.aws_kms import IKey from aws_cdk.aws_s3 import BucketEncryption, IBucket from aws_cdk.aws_sns import ITopic @@ -174,6 +174,7 @@ def build_pipeline(self) -> None: ) self._add_alarms() + self._add_codebuild_pipeline_role_override() def _add_alarms(self): NotificationRule( @@ -192,3 +193,105 @@ def _add_alarms(self): # Grant CodeStar permission to use the key that encrypts the alarm topic code_star_principal = ServicePrincipal('codestar-notifications.amazonaws.com') self._encryption_key.grant_encrypt_decrypt(code_star_principal) + + def _add_codebuild_pipeline_role_override(self): + """ + CodePipeline does not support automatically using the pipeline role for the CodeBuild steps it generates. + To allow the Assets step to assume roles into the environment accounts, we need to force it to use the + CodePipeline role for the Assets step. + + This is done by overriding the CodeBuild role with the pipeline role for the Assets step. + """ + assets_node = self.node.try_find_child('Assets') + # The pipeline won't _always_ build an Assets step (like for the substitution stack), so we need to handle + # it not existing + if assets_node is not None: + # Override the role used + stack = Stack.of(self) + pipeline_role: Role = self.pipeline.role + file_asset_node: CfnProject = assets_node.node.try_find_child('FileAsset').node.default_child + file_asset_node.add_property_override('ServiceRole', pipeline_role.role_arn) + + # Add the permissions this role will need for the Assets step + # Note: many of the permissions needed for this step are already granted by virtue of being + # passed into the Synth CodeBuildStep, which automatically configures it with permissions. + # We don't duplicate those here. + pipeline_role.add_to_principal_policy( + PolicyStatement( + effect=Effect.ALLOW, + actions=[ + 'logs:CreateLogGroup', + 'logs:CreateLogStream', + 'logs:PutLogEvents', + ], + resources=[ + stack.format_arn( + partition=stack.partition, + service='logs', + region=stack.region, + account=stack.account, + resource='log-group', + resource_name=Fn.join( + '', + [ + '/aws/codebuild/', + Fn.ref(stack.get_logical_id(file_asset_node)), + ':*' + ] + ) , + arn_format=ArnFormat.COLON_RESOURCE_NAME, + ), + ], + ) + ) + pipeline_role.add_to_principal_policy( + PolicyStatement( + effect=Effect.ALLOW, + actions=[ + 'sts:AssumeRole', + ], + resources=[ + stack.format_arn( + partition=stack.partition, + service='iam', + region='', + account='*', + resource='role', + resource_name=f'cdk-hnb659fds-file-publishing-role-*-{stack.region}', + ), + ], + ) + + ) + pipeline_role.add_to_principal_policy( + PolicyStatement( + effect=Effect.ALLOW, + actions=[ + "codebuild:BatchPutCodeCoverages", + "codebuild:BatchPutTestCases", + "codebuild:CreateReport", + "codebuild:CreateReportGroup", + "codebuild:UpdateReport" + ], + resources=[ + Fn.join( + '', + [ + stack.format_arn( + partition=stack.partition, + service='codebuild', + region=stack.region, + account=stack.account, + resource='report-group', + resource_name='', + ), + Fn.ref(stack.get_logical_id(file_asset_node)), + '-*', + ] + ), + ] + ) + ) + + # Now, remove the unused role and default policy + assets_node.node.try_remove_child('FileRole') diff --git a/backend/compact-connect-ui-app/tests/app/test_pipeline.py b/backend/compact-connect-ui-app/tests/app/test_pipeline.py index 74796477e..824cd317f 100644 --- a/backend/compact-connect-ui-app/tests/app/test_pipeline.py +++ b/backend/compact-connect-ui-app/tests/app/test_pipeline.py @@ -89,7 +89,10 @@ def test_pipeline_role_trust_policies(self): [ { 'Effect': 'Allow', - 'Principal': {'Service': 'codepipeline.amazonaws.com'}, + 'Principal': {'Service': [ + 'codebuild.amazonaws.com', + 'codepipeline.amazonaws.com', + ]}, 'Action': 'sts:AssumeRole', } ] diff --git a/backend/compact-connect/pipeline/backend_pipeline.py b/backend/compact-connect/pipeline/backend_pipeline.py index 4c331752a..172c8f25c 100644 --- a/backend/compact-connect/pipeline/backend_pipeline.py +++ b/backend/compact-connect/pipeline/backend_pipeline.py @@ -3,11 +3,11 @@ import os import common_constructs.base_pipeline_stack -from aws_cdk import RemovalPolicy, Stack -from aws_cdk.aws_codebuild import BuildSpec +from aws_cdk import ArnFormat, Fn, RemovalPolicy, Stack +from aws_cdk.aws_codebuild import BuildSpec, CfnProject from aws_cdk.aws_codepipeline import PipelineType from aws_cdk.aws_codestarnotifications import NotificationRule -from aws_cdk.aws_iam import ServicePrincipal +from aws_cdk.aws_iam import Effect, PolicyStatement, Role, ServicePrincipal from aws_cdk.aws_kms import IKey from aws_cdk.aws_s3 import BucketEncryption, IBucket from aws_cdk.aws_sns import ITopic @@ -77,6 +77,7 @@ def __init__( # Create predictable pipeline role before initializing the pipeline pipeline_role = scope.create_predictable_pipeline_role('Backend') + artifact_bucket.grant_read(pipeline_role) super().__init__( scope, @@ -172,6 +173,7 @@ def build_pipeline(self) -> None: ) self._add_alarms() + self._add_codebuild_pipeline_role_override() def _add_alarms(self): NotificationRule( @@ -190,3 +192,105 @@ def _add_alarms(self): # Grant CodeStar permission to use the key that encrypts the alarm topic code_star_principal = ServicePrincipal('codestar-notifications.amazonaws.com') self._encryption_key.grant_encrypt_decrypt(code_star_principal) + + def _add_codebuild_pipeline_role_override(self): + """ + CodePipeline does not support automatically using the pipeline role for the CodeBuild steps it generates. + To allow the Assets step to assume roles into the environment accounts, we need to force it to use the + CodePipeline role for the Assets step. + + This is done by overriding the CodeBuild role with the pipeline role for the Assets step. + """ + assets_node = self.node.try_find_child('Assets') + # The pipeline won't _always_ build an Assets step (like for the substitution stack), so we need to handle + # it not existing + if assets_node is not None: + # Override the role used + stack = Stack.of(self) + pipeline_role: Role = self.pipeline.role + file_asset_node: CfnProject = assets_node.node.try_find_child('FileAsset').node.default_child + file_asset_node.add_property_override('ServiceRole', pipeline_role.role_arn) + + # Add the permissions this role will need for the Assets step + # Note: many of the permissions needed for this step are already granted by virtue of being + # passed into the Synth CodeBuildStep, which automatically configures it with permissions. + # We don't duplicate those here. + pipeline_role.add_to_principal_policy( + PolicyStatement( + effect=Effect.ALLOW, + actions=[ + 'logs:CreateLogGroup', + 'logs:CreateLogStream', + 'logs:PutLogEvents', + ], + resources=[ + stack.format_arn( + partition=stack.partition, + service='logs', + region=stack.region, + account=stack.account, + resource='log-group', + resource_name=Fn.join( + '', + [ + '/aws/codebuild/', + Fn.ref(stack.get_logical_id(file_asset_node)), + ':*' + ] + ) , + arn_format=ArnFormat.COLON_RESOURCE_NAME, + ), + ], + ) + ) + pipeline_role.add_to_principal_policy( + PolicyStatement( + effect=Effect.ALLOW, + actions=[ + 'sts:AssumeRole', + ], + resources=[ + stack.format_arn( + partition=stack.partition, + service='iam', + region='', + account='*', + resource='role', + resource_name=f'cdk-hnb659fds-file-publishing-role-*-{stack.region}', + ), + ], + ) + + ) + pipeline_role.add_to_principal_policy( + PolicyStatement( + effect=Effect.ALLOW, + actions=[ + "codebuild:BatchPutCodeCoverages", + "codebuild:BatchPutTestCases", + "codebuild:CreateReport", + "codebuild:CreateReportGroup", + "codebuild:UpdateReport" + ], + resources=[ + Fn.join( + '', + [ + stack.format_arn( + partition=stack.partition, + service='codebuild', + region=stack.region, + account=stack.account, + resource='report-group', + resource_name='', + ), + Fn.ref(stack.get_logical_id(file_asset_node)), + '-*', + ] + ), + ] + ) + ) + + # Now, remove the unused role and default policy + assets_node.node.try_remove_child('FileRole') diff --git a/backend/compact-connect/tests/app/test_pipeline.py b/backend/compact-connect/tests/app/test_pipeline.py index 8fffc1ac6..5a927272d 100644 --- a/backend/compact-connect/tests/app/test_pipeline.py +++ b/backend/compact-connect/tests/app/test_pipeline.py @@ -388,7 +388,10 @@ def test_pipeline_role_trust_policies(self): [ { 'Effect': 'Allow', - 'Principal': {'Service': 'codepipeline.amazonaws.com'}, + 'Principal': {'Service': [ + 'codebuild.amazonaws.com', + 'codepipeline.amazonaws.com', + ]}, 'Action': 'sts:AssumeRole', } ] From 9ead10219032ce5b678c0a6fa713ef296e82d275 Mon Sep 17 00:00:00 2001 From: Justin Frahm Date: Tue, 30 Sep 2025 13:36:37 -0600 Subject: [PATCH 21/32] Remove duplicate app_client files --- .../common_constructs/base_pipeline_stack.py | 14 +- .../deployment_resources_stack.py | 12 +- backend/compact-connect-ui-app/app.py | 2 + .../app_clients/README.md | 272 ------------ .../app_clients/bin/create_app_client.py | 341 --------------- .../app_clients/bin/manage_signature_keys.py | 390 ------------------ .../README.md | 1 - .../pipeline/frontend_pipeline.py | 3 +- backend/compact-connect/app.py | 2 + .../pipeline/backend_pipeline.py | 3 +- 10 files changed, 28 insertions(+), 1012 deletions(-) delete mode 100644 backend/compact-connect-ui-app/app_clients/README.md delete mode 100755 backend/compact-connect-ui-app/app_clients/bin/create_app_client.py delete mode 100755 backend/compact-connect-ui-app/app_clients/bin/manage_signature_keys.py delete mode 100644 backend/compact-connect-ui-app/app_clients/it_staff_onboarding_instructions/README.md diff --git a/backend/common-cdk/common_constructs/base_pipeline_stack.py b/backend/common-cdk/common_constructs/base_pipeline_stack.py index 116e54520..fc7c5856d 100644 --- a/backend/common-cdk/common_constructs/base_pipeline_stack.py +++ b/backend/common-cdk/common_constructs/base_pipeline_stack.py @@ -1,4 +1,5 @@ import json +from enum import StrEnum from aws_cdk import Environment, RemovalPolicy from aws_cdk.aws_iam import CompositePrincipal, Effect, PolicyStatement, Role, ServicePrincipal @@ -12,8 +13,16 @@ TEST_ENVIRONMENT_NAME = 'test' BETA_ENVIRONMENT_NAME = 'beta' PROD_ENVIRONMENT_NAME = 'prod' +DEPLOY_ENVIRONMENT_NAME = 'deploy' + ALLOWED_ENVIRONMENT_NAMES = [TEST_ENVIRONMENT_NAME, BETA_ENVIRONMENT_NAME, PROD_ENVIRONMENT_NAME] + +class CCPipelineType(StrEnum): + BACKEND = 'Backend' + FRONTEND = 'Frontend' + + class BasePipelineStack(Stack): """Base stack with common functionality for all pipeline stacks (both backend and frontend).""" @@ -72,7 +81,7 @@ def _get_predictable_role_name(self, pipeline_type: str, role_type: str) -> str: return f'CompactConnect-{self.environment_name}-{pipeline_type}-{role_type}Role' - def create_predictable_pipeline_role(self, pipeline_type: str) -> Role: + def create_predictable_pipeline_role(self, pipeline_type: CCPipelineType) -> Role: """Create a predictable cross-account role that will be trusted by bootstrap roles. :param pipeline_type: 'Backend' or 'Frontend' @@ -127,6 +136,3 @@ def _get_frontend_pipeline_arn(self): account=self.env.account, resource=pipeline_name, ) - - -DEPLOY_ENVIRONMENT_NAME = 'deploy' diff --git a/backend/common-cdk/common_constructs/deployment_resources_stack.py b/backend/common-cdk/common_constructs/deployment_resources_stack.py index cb1a95640..cb7f07a0d 100644 --- a/backend/common-cdk/common_constructs/deployment_resources_stack.py +++ b/backend/common-cdk/common_constructs/deployment_resources_stack.py @@ -8,7 +8,7 @@ from common_constructs.access_logs_bucket import AccessLogsBucket from common_constructs.alarm_topic import AlarmTopic -from common_constructs.base_pipeline_stack import DEPLOY_ENVIRONMENT_NAME +from common_constructs.base_pipeline_stack import DEPLOY_ENVIRONMENT_NAME, CCPipelineType from common_constructs.stack import Stack @@ -19,6 +19,7 @@ def __init__( self, scope: Construct, construct_id: str, + pipeline_type: CCPipelineType, **kwargs, ): super().__init__(scope, construct_id, environment_name='deploy', **kwargs) @@ -53,7 +54,14 @@ def __init__( removal_policy=RemovalPolicy.RETAIN, ) - notifications = self.deploy_environment_context.get('notifications', {}) + match pipeline_type: + case CCPipelineType.BACKEND: + notifications = self.deploy_environment_context.get('back_end_notifications', {}) + case CCPipelineType.FRONTEND: + notifications = self.deploy_environment_context.get('front_end_notifications', {}) + case _: + raise ValueError(f'Invalid pipeline type: {pipeline_type}') + self.pipeline_alarm_topic = AlarmTopic( self, 'AlarmTopic', diff --git a/backend/compact-connect-ui-app/app.py b/backend/compact-connect-ui-app/app.py index dd18b8210..522444d97 100644 --- a/backend/compact-connect-ui-app/app.py +++ b/backend/compact-connect-ui-app/app.py @@ -7,6 +7,7 @@ # Make the `common_constructs` namespace package under `common-cdk` available to Python sys.path.insert(0, os.path.abspath(os.path.join('..', 'common-cdk'))) +from common_constructs.base_pipeline_stack import CCPipelineType from common_constructs.deployment_resources_stack import DeploymentResourcesStack from common_constructs.stack import StandardTags @@ -134,6 +135,7 @@ def add_deployment_resources_stack(self): self.deployment_resources_stack = DeploymentResourcesStack( self, DEPLOYMENT_RESOURCES_STACK, + pipeline_type=CCPipelineType.FRONTEND, env=self.environment, standard_tags=StandardTags(**self.tags, environment='deploy'), ) diff --git a/backend/compact-connect-ui-app/app_clients/README.md b/backend/compact-connect-ui-app/app_clients/README.md deleted file mode 100644 index a80c8e51a..000000000 --- a/backend/compact-connect-ui-app/app_clients/README.md +++ /dev/null @@ -1,272 +0,0 @@ -# App Client Management for Staff Users - -## Overview - -This document is a guide for technical staff for managing Cognito app clients for machine-to-machine authentication in -the State API. All app clients must be documented in the external 'Compact Connect App Client Registry' Google Sheet -(If you do not have access to said registry, contact a maintainer of the project and request access). - -## Creating a New App Client - -### 1. Prerequisites - -Before creating a new app client, ensure you have: -- Jurisdiction requirements documented (compact and state) -- Contact information for the consuming team -- Approval to grant the app client with the requested scopes -- AWS credentials configured with permissions to create app clients for the State Auth user pool in the needed AWS - accounts -- Python 3.10+ installed with boto3 dependency (`pip install boto3`) - -### 2. Update Registry - -Add the new app client information to the external Google Sheet registry for tracking and disaster recovery purposes -(ie a deployment error or AWS region outage causes app client data to be lost so it must be recreated). - -#### **Scope Configuration** - -Scopes are the permissions that the app client will have. There are two tiers of scopes: - -##### **Compact-Level Scopes:** - -These are the scopes that are scoped to a specific compact. Granting these scopes will allow the app client to perform -actions across all jurisdictions within that compact. Generally, the only scope that should be granted at the compact -level is the `{compact}/readGeneral` scope if needed. - -The following scopes are available at the compact level: -``` -{compact}/admin -{compact}/readGeneral -{compact}/readSSN -{compact}/write -``` - -##### **Jurisdiction-Level Scopes:** - -These are the scopes that are scoped to a specific jurisdiction/compact combination. Granting these scopes will allow -the app client to perform actions within a specific jurisdiction/compact combination. You should only grant these -scopes if the consuming team has a specific need for a jurisdiction/compact combination. - -The following scopes are available at the jurisdiction level: -```text -{jurisdiction}/{compact}.admin -{jurisdiction}/{compact}.write -{jurisdiction}/{compact}.readPrivate -{jurisdiction}/{compact}.readSSN -``` - -Currently, the most common scope needed by app clients is `{jurisdiction}/{compact}.write`, which allows uploading -license data for a jurisdiction/compact combination. Scopes that expose PII (e.g., `.readSSN`, `.readPrivate`) should -be granted sparingly and will require valid request signatures once a signing public key is configured for the -jurisdiction. - -### 3. Create App Client Using Interactive Python Script - -**Use the provided Python script in the bin directory for streamlined app client creation:** - - -```bash -python3 bin/create_app_client.py -e -u -``` - -**Interactive Process:** -The script will prompt you for: -- App client name (e.g., "example-ky-app-client-v1") -- Compact (aslp, octp, coun) -- State postal abbreviation (e.g., "ky", "la") -- Additional scopes (optional) - -**Automatic Scope Generation:** -The script automatically creates these standard scopes: -- `{compact}/readGeneral` - General read access for the compact -- `{state}/{compact}.write` - Write access for the specific state/compact combination - -### 4. **Send Credentials to Consuming Team** - -**When using the Python script (recommended):** -The script will output two separate sections: - -**A. Credentials JSON (for one-time link service):** -```json -{ - "clientId": "6g34example89j", - "clientSecret": "1234example567890" -} -``` -**Important:** These credentials should be securely transmitted to the consuming team via an encrypted channel -(i.e., a one-time use link). Copy this JSON and use it with your one-time secret link generator. Once you have sent -the credentials over to the IT staff, ensure you remove all remnants of the credentials from your device. - -**B. Email Template:** -The script will also generate an email template with contextual information (compact name, state, auth URL, license -upload URL) that you can copy/paste into your email client. This template includes a placeholder for the one-time -link that you'll generate separately. - - -#### Email Instructions for consuming team - -As part of the email message sent to the consuming team, be sure to include the onboarding instructions document from -the `it_staff_onboarding_instructions/` directory. - -## Managing API Signing Public Keys - -### Overview - -Signature-based authentication provides an additional layer of security for API access to sensitive licensure data. Each -compact/state combination can have multiple SIGNATURE public keys configured to support key rotation and zero-downtime -deployments. - -### Authorization Requirements - -**⚠️ CRITICAL SECURITY NOTICE:** Due to the sensitivity of the data protected by SIGNATURE authentication (including -partial Social Security Numbers, personal addresses, and professional license details), configuration of new SIGNATURE -public keys in production environments **MUST** include explicit authorization from the state board executive director. - - -### Creating SIGNATURE Public Keys - -Once a state configures a public key, they will be able to access the SIGNATURE-required API endpoints. API endpoints with -_optional_ SIGNATURE support will also begin to enforce SIGNATURE signatures for that combination of compact and state. **This -means that, once a compact/state has a public key configured, they will be denied access to SIGNATURE-Optional endpoints, -such as the `POST license` endpoint, unless they have also implemented SIGNATURE signatures there as well.** Be sure that -the representative is advised that they should begin signing those requests _before_ CompactConnect has a configured -public key. - -#### 1. Prerequisites - -Before creating a new SIGNATURE public key, ensure you have: -- **Production Authorization**: Explicit approval from the state board executive director for production environments -- Validated the identity of the individual providing the public key to you -- Jurisdiction and compact information confirmed -- Contact information for the state IT representative -- The public key file (`.pub` format) from the state IT representative -- AWS credentials configured with permissions to write to the compact configuration table -- Python 3.10+ installed with boto3 dependency (`pip install boto3`) - -#### 2. Key ID Naming Convention - -The state IT department should provide an identifier; however, you can recommend a descriptive key ID that includes: -- Environment indicator (if applicable) -- Version or date suffix - -Examples: -- `prod-key-001` -- `beta-key-2024-01` - -#### 3. Create SIGNATURE Public Key Using Interactive Python Script - -**Use the provided Python script in the bin directory for streamlined SIGNATURE key management:** - -```bash -python3 bin/manage_signature_keys.py create -t -``` - -**Interactive Process:** -The script will prompt you for: -- Compact (aslp, octp, coun) -- State postal abbreviation (e.g., "ky", "la") -- Key ID (e.g., "client-org-prod-key-001") - -**File Reading:** -The script will: -- Notify you that it will read the public key from `.pub` -- Validate the PEM format of the public key -- Check for existing keys with the same ID -- Write the key to the compact configuration database - -#### 4. Database Schema - -SIGNATURE keys are stored in the compact configuration table with the following schema: -- **Primary Key (pk)**: `{compact}#SIGNATURE_KEYS#{state}` -- **Sort Key (sk)**: `{compact}#JURISDICTION#{jurisdiction}#{key_id}` -- **Additional Fields**: - - `publicKey`: PEM-encoded public key content - - `compact`: Compact abbreviation - - `jurisdiction`: Jurisdiction abbreviation - - `keyId`: Key identifier - - `createdAt`: Creation timestamp - -### Deleting SIGNATURE Public Keys - -#### 1. Prerequisites - -Before deleting a SIGNATURE public key, ensure you have: -- Confirmation that the key is no longer in use by the state IT department -- Confirmation of the key id to be deleted -- Understanding of the impact on API access for the compact/state combination - -#### 2. Delete SIGNATURE Public Key Using Interactive Python Script - -```bash -python3 bin/manage_signature_keys.py delete -t -``` - -**Interactive Process:** -The script will: -- Prompt for compact and state -- List all existing keys for the compact/state combination -- Allow you to select the specific key ID to delete -- Require typing "DELETE" to confirm the deletion -- Remove the key from the compact configuration database - -### Key Rotation Best Practices - -#### 1. Planning - -- Coordinate with the State IT representative well in advance -- Plan for zero-downtime deployment - -#### 2. Implementation - -- Create new keys before removing old ones -- Allow both keys to be active during the transition period -- Monitor API access and authentication success rates -- Remove old keys only after confirming new keys are working correctly - -#### 3. Documentation - -- Document key rotation dates and reasons -- Maintain audit trail of all key management activities - -### Security Considerations - -#### 1. Key Storage - -- Public keys are stored in DynamoDB with appropriate access controls -- Private keys should never be stored in CompactConnect systems -- State IT departments are responsible for secure private key management - -#### 2. Access Control - -- Only authorized technical staff should have access to key management resources -- All key management activities should be logged and audited -- Production key creation requires executive director approval - -## Rotating App Client Credentials - -Unfortunately, AWS Cognito does not support rotating app client credentials for an existing app client. The only way -to rotate credentials is to create a new app client with a new clientId and clientSecret and then delete the old one. -The following process should be performed if credentials are accidentally exposed or in the event of a security breach -where the old credentials are compromised. - -### 1. Pre-rotation Tasks - -- Contact consuming team to schedule rotation -- Follow "Creating a New App Client" steps above using either the Python script (recommended) or AWS CLI, you will -increment clientName version suffix by 1 (e.g. "example-ky-app-client-v1" -> "example-ky-app-client-v2") -- Follow “Creating a New App Client” using the Python script (recommended) or AWS CLI. Increment the client name’s - version suffix by 1 (e.g., “example-ky-app-client-v1” -> “example-ky-app-client-v2”). -- Update the external Google Sheet registry with new client information - -### 2. Migration - -- Provide new client id and client secret to consuming team -- Consuming team will need to confirm that the new credentials are deployed in their systems, the old app client is -not in use, and their systems are working as expected. - -### 3. Cleanup - -- Delete old app client from Cognito using the following cli command: -``` -aws cognito-idp delete-user-pool-client --user-pool-id '' --client-id '' -``` diff --git a/backend/compact-connect-ui-app/app_clients/bin/create_app_client.py b/backend/compact-connect-ui-app/app_clients/bin/create_app_client.py deleted file mode 100755 index bb99339b2..000000000 --- a/backend/compact-connect-ui-app/app_clients/bin/create_app_client.py +++ /dev/null @@ -1,341 +0,0 @@ -#!/usr/bin/env python3 -# ruff: noqa: T201 we use print statements for scripts run locally - -""" -Script to create AWS Cognito app clients interactively. - -This script prompts users for the necessary information to create app clients -in different environments (test, beta, prod) and automatically generates -the standard scopes based on compact and state inputs. -""" - -import argparse -import json -import re -import sys -from pathlib import Path - -import boto3 -from botocore.exceptions import ClientError, NoCredentialsError - - -def load_cdk_config(): - """Load configuration from cdk.json file.""" - # Find cdk.json file - look in parent directories - current_dir = Path(__file__).parent - cdk_json_path = None - - # Look up the directory tree for cdk.json - for parent in [current_dir] + list(current_dir.parents): - potential_path = parent / 'cdk.json' - if potential_path.exists(): - cdk_json_path = potential_path - break - - if not cdk_json_path: - raise FileNotFoundError('Could not find cdk.json file in current directory or parent directories') - - with open(cdk_json_path) as f: - cdk_config = json.load(f) - - context = cdk_config.get('context', {}) - - return { - 'compacts': context.get('compacts', []), - 'active_compact_member_jurisdictions': context.get('active_compact_member_jurisdictions', {}), - } - - -# Load configuration from cdk.json -CDK_CONFIG = load_cdk_config() -VALID_COMPACTS = CDK_CONFIG['compacts'] -ACTIVE_COMPACT_JURISDICTIONS = CDK_CONFIG['active_compact_member_jurisdictions'] - - -# Valid scope patterns for validation -VALID_SCOPE_PATTERNS = [ - r'^[a-z]+/readGeneral$', - r'^[a-z]+/readSSN$', - r'^[a-z]+/write$', - r'^[a-z]+/admin$', - r'^[a-z]{2}/[a-z]+\.write$', - r'^[a-z]{2}/[a-z]+\.readPrivate$', - r'^[a-z]{2}/[a-z]+\.readSSN$', - r'^[a-z]{2}/[a-z]+\.admin$', -] - - -def validate_compact(compact): - """Validate compact input.""" - compact = compact.lower().strip() - if compact not in VALID_COMPACTS: - raise ValueError(f'Invalid compact: {compact}. Valid compacts are: {", ".join(VALID_COMPACTS)}') - return compact - - -def validate_state(state, compact): - """Validate state postal abbreviation for the given compact.""" - state = state.lower().strip() - - # Get valid states for this compact - valid_states = ACTIVE_COMPACT_JURISDICTIONS.get(compact, []) - - if state not in valid_states: - raise ValueError( - f'Invalid state: {state}. Valid states for {compact.upper()} compact are: {", ".join(sorted(valid_states))}' - ) - return state - - -def validate_scope(scope): - """Validate a single scope against known patterns.""" - scope = scope.strip() - for pattern in VALID_SCOPE_PATTERNS: - if re.match(pattern, scope): - return True - return False - - -def validate_additional_scopes(scopes_input): - """Validate additional scopes input.""" - if not scopes_input.strip(): - return [] - - scopes = [scope.strip() for scope in scopes_input.split(',')] - invalid_scopes = [] - - for scope in scopes: - if not validate_scope(scope): - invalid_scopes.append(scope) - - if invalid_scopes: - print(f'\nInvalid scopes detected: {", ".join(invalid_scopes)}') - print('Valid scope patterns:') - print(' Compact-level: {compact}/readGeneral, {compact}/readSSN, {compact}/write, {compact}/admin') - print( - 'Jurisdiction-level: {state}/{compact}.write, {state}/{compact}.readPrivate, {state}/{compact}.readSSN, ' - '{state}/{compact}.admin' - ) - raise ValueError('Invalid scopes provided') - - return scopes - - -def get_user_input(): - """Get user input for app client configuration.""" - print('=== App Client Configuration ===\n') - - # Get client name - client_name = input("Enter the app client name (e.g., 'example-ky-app-client-v1'): ").strip() - if not client_name: - raise ValueError('Client name is required') - - # Get compact - while True: - try: - print(f'\nValid compacts: {", ".join(VALID_COMPACTS)}') - compact = input('Enter the compact: ').strip() - compact = validate_compact(compact) - break - except ValueError as e: - print(f'Error: {e}') - - # Get state - while True: - try: - valid_states = ACTIVE_COMPACT_JURISDICTIONS.get(compact, []) - print(f'\nValid states for {compact.upper()} compact: {", ".join(sorted(valid_states))}') - state = input("Enter the state postal abbreviation (e.g., 'ky', 'la'): ").strip() - state = validate_state(state, compact) - break - except ValueError as e: - print(f'Error: {e}') - - # Get additional scopes (optional) - print('\nThe following scope will be automatically included:') - print(f' - {state}/{compact}.write') - - additional_scopes = [] - while True: - try: - scopes_input = input('\nEnter any additional scopes (comma-separated, or press Enter for none): ').strip() - additional_scopes = validate_additional_scopes(scopes_input) - break - except ValueError as e: - print(f'Error: {e}') - continue - - # Generate final scope list - scopes = [f'{state}/{compact}.write'] - scopes.extend(additional_scopes) - - # Remove duplicates - deduped_scopes = list(set(scopes)) - - print('\nFinal configuration:') - print(f' Client Name: {client_name}') - print(f' Compact: {compact}') - print(f' State: {state}') - print(f' Scopes: {", ".join(deduped_scopes)}') - - confirm = input('\nProceed with this configuration? (y/N): ').strip().lower() - if confirm != 'y': - print('Configuration cancelled.') - sys.exit(0) - - return {'clientName': client_name, 'compact': compact, 'state': state, 'scopes': deduped_scopes} - - -def create_app_client(user_pool_id, config): - """Create the app client using boto3 Cognito client.""" - client_name = config['clientName'] - scopes = config['scopes'] - - print(f'\nCreating app client: {client_name}') - print(f'With scopes: {", ".join(scopes)}') - - try: - # Create boto3 Cognito IDP client - cognito_client = boto3.client('cognito-idp', region_name='us-east-1') - - # Create the user pool client - return cognito_client.create_user_pool_client( - UserPoolId=user_pool_id, - ClientName=client_name, - PreventUserExistenceErrors='ENABLED', - GenerateSecret=True, - TokenValidityUnits={'AccessToken': 'minutes'}, - AccessTokenValidity=15, - AllowedOAuthFlowsUserPoolClient=True, - AllowedOAuthFlows=['client_credentials'], - AllowedOAuthScopes=scopes, - ) - - except NoCredentialsError: - print('Error: AWS credentials not found. Please configure your AWS credentials.') - print("You can use 'aws configure' or set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment variables.") - sys.exit(1) - except ClientError as e: - error_code = e.response['Error']['Code'] - error_message = e.response['Error']['Message'] - print(f'Error creating app client: {error_code} - {error_message}') - sys.exit(1) - - -def print_credentials(client_id, client_secret): - """Print only the sensitive credentials in JSON format for secure copy/paste.""" - credentials = { - 'clientId': client_id, - 'clientSecret': client_secret, - } - - print('\n' + '=' * 60) - print('APP CLIENT CREDENTIALS (FOR ONE-TIME LINK SERVICE)') - print('=' * 60) - print(json.dumps(credentials, indent=2)) - print('=' * 60) - print('Please copy the JSON above and use it with your one-time secret link generator.') - print('Do not leave these credentials in terminal history or logs.') - print('=' * 60) - - -def print_email_template(environment, compact, state): - """Print an email template with contextual information for the consuming team.""" - # Get environment-specific URLs - auth_urls = { - 'beta': 'https://compact-connect-state-auth-beta.auth.us-east-1.amazoncognito.com/oauth2/token', - 'prod': 'https://compact-connect-state-auth.auth.us-east-1.amazoncognito.com/oauth2/token', - 'test': 'https://compact-connect-state-auth-test.auth.us-east-1.amazoncognito.com/oauth2/token', - } - - api_base_urls = { - 'beta': 'https://state-api.beta.compactconnect.org', - 'prod': 'https://state-api.compactconnect.org', - 'test': 'https://state-api.test.compactconnect.org', - } - - # Compact name mapping - compact_names = { - 'aslp': 'Audiology and Speech Language Pathology', - 'octp': 'Occupational Therapy', - 'coun': 'Counseling', - } - - compact_name = compact_names.get(compact, compact.upper()) - auth_url = auth_urls.get(environment) - license_upload_url = f'{api_base_urls.get(environment)}/v1/compacts/{compact}/jurisdictions/{state}/licenses' - - email_template = f""" -Thank you for integrating with Compact Connect! You have been designated as the IT professional who is able to handle -credentials for secure machine-to-machine authentication between your state and CompactConnect. - -Details for these credentials are: -Compact: {compact_name} -State: {state.upper()} -Auth URL: {auth_url} -License Upload URL: {license_upload_url} - -Follow this link to your API credentials as soon as you are ready to securely store them. They will only be viewable -once: - - -For more information on CompactConnect and how to integrate your state IT system with ours, see the documentation -here: -https://github.com/csg-org/CompactConnect/blob/development/backend/compact-connect/docs/it_staff_onboarding_instructions.md -""" - - print('\n' + '=' * 60) - print('EMAIL TEMPLATE (COPY/PASTE INTO EMAIL CLIENT)') - print('=' * 60) - print(email_template.strip()) - print('=' * 60) - - -def main(): - parser = argparse.ArgumentParser(description='Create AWS Cognito app client interactively') - parser.add_argument( - '-e', '--environment', required=True, choices=['test', 'beta', 'prod'], help='Environment (test, beta, or prod)' - ) - parser.add_argument('-u', '--user-pool-id', required=True, help='AWS Cognito User Pool ID') - - args = parser.parse_args() - - try: - print(f'Creating app client for {args.environment} environment...') - print(f'User Pool ID: {args.user_pool_id}\n') - - # Get configuration from user input - config = get_user_input() - - # Create the app client - response = create_app_client(args.user_pool_id, config) - - # Extract credentials from response - user_pool_client = response.get('UserPoolClient', {}) - client_id = user_pool_client.get('ClientId') - client_secret = user_pool_client.get('ClientSecret') - client_name = user_pool_client.get('ClientName') - - if not client_id or not client_secret: - print('Error: Could not extract client ID or secret from AWS response') - sys.exit(1) - - print('\n✅ App client created successfully!') - print(f'Client Name: {client_name}') - print(f'Client ID: {client_id}') - - # Print credentials for secure copy/paste - print_credentials(client_id, client_secret) - - # Print email template - print_email_template(args.environment, config['compact'], config['state']) - - print('\n📝 Remember to add this app client to your external registry!') - - except Exception as e: # noqa: BLE001 - print(f'Error: {e}') - sys.exit(1) - - -if __name__ == '__main__': - main() diff --git a/backend/compact-connect-ui-app/app_clients/bin/manage_signature_keys.py b/backend/compact-connect-ui-app/app_clients/bin/manage_signature_keys.py deleted file mode 100755 index 50b4696cf..000000000 --- a/backend/compact-connect-ui-app/app_clients/bin/manage_signature_keys.py +++ /dev/null @@ -1,390 +0,0 @@ -#!/usr/bin/env python3 -# ruff: noqa: T201 we use print statements for scripts run locally - -""" -Script to manage SIGNATURE public keys in the compact configuration database. - -This script allows users to create and delete SIGNATURE public keys for different -compact/jurisdiction combinations. It follows the same interactive style as -the create_app_client.py script. -""" - -import argparse -import json -import sys -from datetime import UTC, datetime -from pathlib import Path - -import boto3 -from botocore.exceptions import ClientError, NoCredentialsError - - -def load_cdk_config(): - """Load configuration from cdk.json file.""" - # Find cdk.json file - look in parent directories - current_dir = Path(__file__).parent - cdk_json_path = None - - # Look up the directory tree for cdk.json - for parent in [current_dir] + list(current_dir.parents): - potential_path = parent / 'cdk.json' - if potential_path.exists(): - cdk_json_path = potential_path - break - - if not cdk_json_path: - raise FileNotFoundError('Could not find cdk.json file in current directory or parent directories') - - with open(cdk_json_path) as f: - cdk_config = json.load(f) - - context = cdk_config.get('context', {}) - - return { - 'compacts': context.get('compacts', []), - 'active_compact_member_jurisdictions': context.get('active_compact_member_jurisdictions', {}), - } - - -# Load configuration from cdk.json -CDK_CONFIG = load_cdk_config() -VALID_COMPACTS = CDK_CONFIG['compacts'] -ACTIVE_COMPACT_JURISDICTIONS = CDK_CONFIG['active_compact_member_jurisdictions'] - - -def validate_compact(compact): - """Validate compact input.""" - compact = compact.lower().strip() - if compact not in VALID_COMPACTS: - raise ValueError(f'Invalid compact: {compact}. Valid compacts are: {", ".join(VALID_COMPACTS)}') - return compact - - -def validate_state(state, compact): - """Validate state postal abbreviation for the given compact.""" - state = state.lower().strip() - - # Get valid states for this compact - valid_states = ACTIVE_COMPACT_JURISDICTIONS.get(compact, []) - - if state not in valid_states: - raise ValueError( - f'Invalid state: {state}. Valid states for {compact.upper()} compact are: {", ".join(sorted(valid_states))}' - ) - return state - - -def validate_key_id(key_id): - """Validate key ID input.""" - key_id = key_id.strip() - if not key_id: - raise ValueError('Key ID cannot be empty') - if len(key_id) > 100: # Reasonable limit for key ID - raise ValueError('Key ID is too long (max 100 characters)') - if not key_id.replace('-', '').replace('_', '').isalnum(): - raise ValueError('Key ID can only contain alphanumeric characters, hyphens, and underscores') - return key_id - - -def get_user_input_for_create(): - """Get user input for creating a SIGNATURE public key.""" - print('=== SIGNATURE Public Key Creation ===\n') - - # Get compact - while True: - try: - print(f'Valid compacts: {", ".join(VALID_COMPACTS)}') - compact = input('Enter the compact: ').strip() - compact = validate_compact(compact) - break - except ValueError as e: - print(f'Error: {e}') - - # Get state - while True: - try: - valid_states = ACTIVE_COMPACT_JURISDICTIONS.get(compact, []) - print(f'\nValid states for {compact.upper()} compact: {", ".join(sorted(valid_states))}') - state = input("Enter the state postal abbreviation (e.g., 'ky', 'la'): ").strip() - state = validate_state(state, compact) - break - except ValueError as e: - print(f'Error: {e}') - - # Get key ID - while True: - try: - key_id = input('\nEnter the key ID (e.g., "client-key-001"): ').strip() - key_id = validate_key_id(key_id) - break - except ValueError as e: - print(f'Error: {e}') - - print('\nConfiguration:') - print(f' Compact: {compact}') - print(f' State: {state}') - print(f' Key ID: {key_id}') - print(f' Public key file: {key_id}.pub') - - confirm = input('\nProceed with this configuration? (y/N): ').strip().lower() - if confirm != 'y': - print('Configuration cancelled.') - sys.exit(0) - - return {'compact': compact, 'state': state, 'key_id': key_id} - - -def read_public_key_file(key_id): - """Read the public key from the specified file.""" - file_path = Path(f'{key_id}.pub') - - if not file_path.exists(): - raise FileNotFoundError( - f'Public key file "{file_path}" not found. Please ensure the file exists in the current directory.' - ) - - try: - with open(file_path) as f: - public_key_content = f.read().strip() - - if not public_key_content: - raise ValueError('Public key file is empty') - - # Basic validation that this looks like a PEM public key - if not public_key_content.startswith('-----BEGIN PUBLIC KEY-----'): - raise ValueError( - 'Public key file does not appear to be in PEM format (should start with "-----BEGIN PUBLIC KEY-----")' - ) - - if not public_key_content.endswith('-----END PUBLIC KEY-----'): - raise ValueError( - 'Public key file does not appear to be in PEM format (should end with "-----END PUBLIC KEY-----")' - ) - - return public_key_content - - except (FileNotFoundError, ValueError) as e: - raise ValueError(f'Error reading public key file: {e}') from e - - -def create_signature_key(table_name, config): - """Create the SIGNATURE public key in DynamoDB.""" - compact = config['compact'] - state = config['state'] - key_id = config['key_id'] - - print(f'\nCreating SIGNATURE public key: {key_id}') - print(f'For compact: {compact}, state: {state}') - - try: - # Create boto3 DynamoDB client - dynamodb = boto3.resource('dynamodb') - table = dynamodb.Table(table_name) - - # Check if key already exists - pk = f'{compact}#SIGNATURE_KEYS#{state}' - sk = f'{compact}#JURISDICTION#{state}#{key_id}' - - response = table.get_item(Key={'pk': pk, 'sk': sk}) - - if 'Item' in response: - print(f'\n⚠️ Warning: A key with ID "{key_id}" already exists for {compact}/{state}') - overwrite = input('Do you want to overwrite it? (y/N): ').strip().lower() - if overwrite != 'y': - print('Operation cancelled.') - sys.exit(0) - - # Read the public key file - print(f'\nReading public key from {key_id}.pub...') - public_key_pem = read_public_key_file(key_id) - - # Create the item - item = { - 'pk': pk, - 'sk': sk, - 'publicKey': public_key_pem, - 'compact': compact, - 'jurisdiction': state, - 'keyId': key_id, - 'createdAt': datetime.now(tz=UTC).isoformat(), - } - - # Write to DynamoDB - table.put_item(Item=item) - - print('\n✅ SIGNATURE public key created successfully!') - print(f'Key ID: {key_id}') - print(f'Compact: {compact}') - print(f'State: {state}') - - except NoCredentialsError: - print('Error: AWS credentials not found. Please configure your AWS credentials.') - print("You can use 'aws configure' or set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment variables.") - raise - except ClientError as e: - error_code = e.response['Error']['Code'] - error_message = e.response['Error']['Message'] - print(f'Error creating SIGNATURE key: {error_code} - {error_message}') - raise - - -def get_user_input_for_delete(): - """Get user input for deleting a SIGNATURE public key.""" - print('=== SIGNATURE Public Key Deletion ===\n') - - # Get compact - while True: - try: - print(f'Valid compacts: {", ".join(VALID_COMPACTS)}') - compact = input('Enter the compact: ').strip() - compact = validate_compact(compact) - break - except ValueError as e: - print(f'Error: {e}') - - # Get state - while True: - try: - valid_states = ACTIVE_COMPACT_JURISDICTIONS.get(compact, []) - print(f'\nValid states for {compact.upper()} compact: {", ".join(sorted(valid_states))}') - state = input("Enter the state postal abbreviation (e.g., 'ky', 'la'): ").strip() - state = validate_state(state, compact) - break - except ValueError as e: - print(f'Error: {e}') - - return {'compact': compact, 'state': state} - - -def list_existing_keys(table_name, config): - """List existing SIGNATURE keys for the given compact/state combination.""" - compact = config['compact'] - state = config['state'] - - try: - # Create boto3 DynamoDB client - dynamodb = boto3.resource('dynamodb') - table = dynamodb.Table(table_name) - - # Query for existing keys - pk = f'{compact}#SIGNATURE_KEYS#{state}' - sk_prefix = f'{compact}#JURISDICTION#{state}#' - - response = table.query( - KeyConditionExpression='pk = :pk AND begins_with(sk, :sk_prefix)', - ExpressionAttributeValues={':pk': pk, ':sk_prefix': sk_prefix}, - ) - - items = response.get('Items', []) - - if not items: - print(f'\nNo SIGNATURE keys found for {compact}/{state}') - return [] - - print(f'\nExisting SIGNATURE keys for {compact}/{state}:') - for i, item in enumerate(items, 1): - key_id = item['sk'].split('#')[-1] - created_at = item.get('createdAt', 'Unknown') - print(f' {i}. Key ID: {key_id} (Created: {created_at})') - - return items - - except NoCredentialsError: - print('Error: AWS credentials not found. Please configure your AWS credentials.') - print("You can use 'aws configure' or set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment variables.") - raise - except ClientError as e: - error_code = e.response['Error']['Code'] - error_message = e.response['Error']['Message'] - print(f'Error listing SIGNATURE keys: {error_code} - {error_message}') - raise - - -def delete_signature_key(table_name, config, key_id): - """Delete the specified SIGNATURE public key from DynamoDB.""" - compact = config['compact'] - state = config['state'] - - print(f'\nDeleting SIGNATURE public key: {key_id}') - print(f'For compact: {compact}, state: {state}') - - try: - # Create boto3 DynamoDB client - dynamodb = boto3.resource('dynamodb') - table = dynamodb.Table(table_name) - - # Delete the item - pk = f'{compact}#SIGNATURE_KEYS#{state}' - sk = f'{compact}#JURISDICTION#{state}#{key_id}' - - table.delete_item(Key={'pk': pk, 'sk': sk}) - - print('\n✅ SIGNATURE public key deleted successfully!') - print(f'Key ID: {key_id}') - print(f'Compact: {compact}') - print(f'State: {state}') - - except NoCredentialsError: - print('Error: AWS credentials not found. Please configure your AWS credentials.') - print("You can use 'aws configure' or set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment variables.") - raise - except ClientError as e: - error_code = e.response['Error']['Code'] - error_message = e.response['Error']['Message'] - print(f'Error deleting SIGNATURE key: {error_code} - {error_message}') - raise - - -def main(): - parser = argparse.ArgumentParser(description='Manage SIGNATURE public keys in the compact configuration database') - parser.add_argument('action', choices=['create', 'delete'], help='Action to perform (create or delete)') - parser.add_argument('-t', '--table-name', required=True, help='DynamoDB table name for compact configuration') - - args = parser.parse_args() - - print(f'Managing SIGNATURE keys for {args.table_name} table...\n') - - if args.action == 'create': - # Create flow - config = get_user_input_for_create() - - create_signature_key(args.table_name, config) - - elif args.action == 'delete': - # Delete flow - config = get_user_input_for_delete() - - # List existing keys - existing_keys = list_existing_keys(args.table_name, config) - - if not existing_keys: - print('\nNo keys to delete.') - sys.exit(0) - - # Get key ID to delete - while True: - key_id = input('\nEnter the exact key ID to delete: ').strip() - key_id = validate_key_id(key_id) - - # Check if key exists - key_exists = any(item['sk'].split('#')[-1] == key_id for item in existing_keys) - if not key_exists: - print(f'Error: Key ID "{key_id}" not found in the list above') - continue - - break - - # Final confirmation - print(f'\n⚠️ You are about to delete SIGNATURE key "{key_id}" for {config["compact"]}/{config["state"]}') - print('This action cannot be undone.') - - confirm = input('\nAre you sure you want to delete this key? Type "DELETE" to confirm: ').strip() - if confirm != 'DELETE': - print('Deletion cancelled.') - sys.exit(0) - - delete_signature_key(args.table_name, config, key_id) - - -if __name__ == '__main__': - main() diff --git a/backend/compact-connect-ui-app/app_clients/it_staff_onboarding_instructions/README.md b/backend/compact-connect-ui-app/app_clients/it_staff_onboarding_instructions/README.md deleted file mode 100644 index f28dc34c4..000000000 --- a/backend/compact-connect-ui-app/app_clients/it_staff_onboarding_instructions/README.md +++ /dev/null @@ -1 +0,0 @@ -[Moved Here](../../docs/it_staff_onboarding_instructions.md) diff --git a/backend/compact-connect-ui-app/pipeline/frontend_pipeline.py b/backend/compact-connect-ui-app/pipeline/frontend_pipeline.py index 7778a3d2f..e87917a0c 100644 --- a/backend/compact-connect-ui-app/pipeline/frontend_pipeline.py +++ b/backend/compact-connect-ui-app/pipeline/frontend_pipeline.py @@ -15,6 +15,7 @@ from aws_cdk.pipelines import CodeBuildOptions, CodeBuildStep, CodePipelineSource from aws_cdk.pipelines import CodePipeline as CdkCodePipeline from cdk_nag import NagSuppressions +from common_constructs.base_pipeline_stack import CCPipelineType from common_constructs.bucket import Bucket @@ -75,7 +76,7 @@ def __init__( ) # Create predictable pipeline role before initializing the pipeline - pipeline_role = scope.create_predictable_pipeline_role('Frontend') + pipeline_role = scope.create_predictable_pipeline_role(CCPipelineType.FRONTEND) super().__init__( scope, diff --git a/backend/compact-connect/app.py b/backend/compact-connect/app.py index 7d43f4ff1..2d48aa457 100644 --- a/backend/compact-connect/app.py +++ b/backend/compact-connect/app.py @@ -7,6 +7,7 @@ # Make the `common_constructs` namespace package under `common-cdk` available to Python sys.path.insert(0, os.path.abspath(os.path.join('..', 'common-cdk'))) +from common_constructs.base_pipeline_stack import CCPipelineType from common_constructs.deployment_resources_stack import DeploymentResourcesStack from common_constructs.stack import StandardTags @@ -135,6 +136,7 @@ def add_deployment_resources_stack(self): self.deployment_resources_stack = DeploymentResourcesStack( self, DEPLOYMENT_RESOURCES_STACK, + pipeline_type=CCPipelineType.BACKEND, env=self.environment, standard_tags=StandardTags(**self.tags, environment='deploy'), ) diff --git a/backend/compact-connect/pipeline/backend_pipeline.py b/backend/compact-connect/pipeline/backend_pipeline.py index 172c8f25c..56a0fd776 100644 --- a/backend/compact-connect/pipeline/backend_pipeline.py +++ b/backend/compact-connect/pipeline/backend_pipeline.py @@ -15,6 +15,7 @@ from aws_cdk.pipelines import CodeBuildOptions, CodeBuildStep, CodePipelineSource from aws_cdk.pipelines import CodePipeline as CdkCodePipeline from cdk_nag import NagSuppressions +from common_constructs.base_pipeline_stack import CCPipelineType from common_constructs.bucket import Bucket import pipeline @@ -76,7 +77,7 @@ def __init__( ) # Create predictable pipeline role before initializing the pipeline - pipeline_role = scope.create_predictable_pipeline_role('Backend') + pipeline_role = scope.create_predictable_pipeline_role(CCPipelineType.BACKEND) artifact_bucket.grant_read(pipeline_role) super().__init__( From 64c80a10e18334f39360f89d660bca1293c623c9 Mon Sep 17 00:00:00 2001 From: Justin Frahm Date: Wed, 1 Oct 2025 11:48:21 -0600 Subject: [PATCH 22/32] Clean up split nodejs lambda configs --- .../lambdas/nodejs/Gruntfile.js | 64 - .../lambdas/nodejs/README.md | 2 +- .../lambdas/nodejs/cloudfront-csp/README.md | 2 +- .../lambdas/nodejs/eslint.config.mjs | 22 +- .../lambdas/nodejs/jest.config.mjs | 20 - .../lambdas/nodejs/package.json | 34 +- .../lambdas/nodejs/yarn.lock | 1213 +---------------- .../lambdas/nodejs/Gruntfile.js | 64 - .../lambdas/nodejs/package.json | 15 +- .../compact-connect/lambdas/nodejs/yarn.lock | 769 +---------- 10 files changed, 57 insertions(+), 2148 deletions(-) delete mode 100644 backend/compact-connect-ui-app/lambdas/nodejs/Gruntfile.js delete mode 100644 backend/compact-connect-ui-app/lambdas/nodejs/jest.config.mjs delete mode 100644 backend/compact-connect/lambdas/nodejs/Gruntfile.js diff --git a/backend/compact-connect-ui-app/lambdas/nodejs/Gruntfile.js b/backend/compact-connect-ui-app/lambdas/nodejs/Gruntfile.js deleted file mode 100644 index 92efc316e..000000000 --- a/backend/compact-connect-ui-app/lambdas/nodejs/Gruntfile.js +++ /dev/null @@ -1,64 +0,0 @@ -// -// Gruntfile.js -// CompactConnect -// -// Created by InspiringApps on 7/22/2024. -// - -module.exports = function (grunt) { - require('jit-grunt')(grunt); - - grunt.initConfig({ - //==================== - //= Directory config = - //==================== - lambdaFiles: [ - 'cloudfront-csp/**/**.js', - '!**/node_modules/**/*.js', - '!**/dist/**/*.js', - '!Gruntfile.js', - ], - changedFiles: ['<%= lambdaFiles %>'], - - //=========== - //= ES Lint = - //=========== - eslint: { - initial: { - src: ['<%= lambdaFiles %>'], - }, - watch: { - src: '<%= changedFiles %>', - } - }, - - //================ - //= Watch config = - //================ - watch: { - lambdaFiles: { - files: ['<%= lambdaFiles %>'], - tasks: ['eslint:watch'], - options: { spawn: false } - }, - } - - }); - - // Update the `changedFiles` value with just the files that changed. - // Tasks that operate on <%= changedFiles %> will then operate faster. - var changedFiles = Object.create(null); - var onChange = grunt.util._.debounce(function() { - grunt.config('changedFiles', Object.keys(changedFiles)); - changedFiles = Object.create(null); - }, 200); - - grunt.event.on('watch', function(action, filepath) { - changedFiles[filepath] = action; - onChange(); - }); - - // Task definition - grunt.registerTask('default', ['eslint:initial', 'watch']); - grunt.registerTask('check', ['eslint:initial']); -}; diff --git a/backend/compact-connect-ui-app/lambdas/nodejs/README.md b/backend/compact-connect-ui-app/lambdas/nodejs/README.md index 2d27942eb..89e7d6036 100644 --- a/backend/compact-connect-ui-app/lambdas/nodejs/README.md +++ b/backend/compact-connect-ui-app/lambdas/nodejs/README.md @@ -1,6 +1,6 @@ # NodeJS Lambdas -This folder contains all lambda runtimes that are written with NodeJS/TypeScript. Because these lambdas are each bundled through CDK with ESBuild, we can pull common code and tests together, leaving only the entrypoints in a lambda-specific folder, leaving ESBuild to pull in only needed lib code. +This folder contains all lambda runtimes that are written with NodeJS/JavaScript. Because these lambdas are each bundled through CDK with ESBuild, we can pull common code and tests together, leaving only the entrypoints in a lambda-specific folder, leaving ESBuild to pull in only needed lib code. ## Prerequisites diff --git a/backend/compact-connect-ui-app/lambdas/nodejs/cloudfront-csp/README.md b/backend/compact-connect-ui-app/lambdas/nodejs/cloudfront-csp/README.md index 128eb4a47..38238c536 100644 --- a/backend/compact-connect-ui-app/lambdas/nodejs/cloudfront-csp/README.md +++ b/backend/compact-connect-ui-app/lambdas/nodejs/cloudfront-csp/README.md @@ -28,7 +28,7 @@ _[back to top](#cloudfront-csp-header-edge-lambda)_ --- ## Local development - **Linting** - - `yarn run grunt` + - `yarn run lint` - This lints all code in all the Lambda function + watches locally for changes - **Running an individual Lambda** - The easiest way to execute the Lambda is to run the tests ([see below](#tests)) diff --git a/backend/compact-connect-ui-app/lambdas/nodejs/eslint.config.mjs b/backend/compact-connect-ui-app/lambdas/nodejs/eslint.config.mjs index bea30f936..73f5ae325 100644 --- a/backend/compact-connect-ui-app/lambdas/nodejs/eslint.config.mjs +++ b/backend/compact-connect-ui-app/lambdas/nodejs/eslint.config.mjs @@ -1,5 +1,4 @@ -import typescriptParser from '@typescript-eslint/parser'; -import typescriptPlugin from '@typescript-eslint/eslint-plugin'; +// No TypeScript dependencies needed for JavaScript-only project const OFF = 0; const WARNING = 1; @@ -10,19 +9,15 @@ export default [ ignores: ['cdk.out/**/*'], }, { - files: ['**/*.ts', '**/*.js'], + files: ['**/*.js'], languageOptions: { ecmaVersion: 2022, - sourceType: 'module', - parser: typescriptParser, + sourceType: 'commonjs', globals: { es2022: true, node: true, }, }, - plugins: { - '@typescript-eslint': typescriptPlugin, - }, rules: { indent: [ERROR, 4], 'linebreak-style': [ERROR, 'unix'], @@ -55,7 +50,6 @@ export default [ 'lines-between-class-members': [ERROR, 'always', { exceptAfterSingleLine: true }], 'implicit-arrow-linebreak': OFF, 'class-methods-use-this': OFF, - '@typescript-eslint/no-explicit-any': OFF, 'padding-line-between-statements': [ ERROR, { blankLine: 'always', prev: ['const', 'let', 'var'], next: '*' }, @@ -64,19 +58,11 @@ export default [ } }, { - files: ['**/*.js'], - rules: { - '@typescript-eslint/no-var-requires': OFF, - '@typescript-eslint/camelcase': OFF, - }, - }, - { - files: ['**/__tests__/*.{j,t}s?(x)'], + files: ['**/__tests__/*.js'], rules: { 'no-unused-expressions': OFF, 'quote-props': OFF, 'import/no-extraneous-dependencies': OFF, - '@typescript-eslint/no-var-requires': OFF, }, } ]; diff --git a/backend/compact-connect-ui-app/lambdas/nodejs/jest.config.mjs b/backend/compact-connect-ui-app/lambdas/nodejs/jest.config.mjs deleted file mode 100644 index 023b7de50..000000000 --- a/backend/compact-connect-ui-app/lambdas/nodejs/jest.config.mjs +++ /dev/null @@ -1,20 +0,0 @@ -export default { - preset: 'ts-jest', - transform: {}, // Disables all transformations to commonJS - testEnvironment: 'node', - testMatch: ['**/tests/**/*.test.[jt]s?(x)'], - moduleFileExtensions: ['ts', 'js'], - verbose: true, - testPathIgnorePatterns: [ - '/node_modules/', - ], - passWithNoTests: true, // Allow Jest to pass when no tests are found - coverageThreshold: { - global: { - branches: 90, - functions: 90, - lines: 90, - statements: 90, - } - } -}; diff --git a/backend/compact-connect-ui-app/lambdas/nodejs/package.json b/backend/compact-connect-ui-app/lambdas/nodejs/package.json index 10867f024..a14a73afb 100644 --- a/backend/compact-connect-ui-app/lambdas/nodejs/package.json +++ b/backend/compact-connect-ui-app/lambdas/nodejs/package.json @@ -4,47 +4,23 @@ "type": "commonjs", "description": "NodeJS lambdas for Compact Connect", "scripts": { - "build": "tsc", - "watch": "tsc -w", - "test:ingest": "NODE_OPTIONS=\"$NODE_OPTIONS --experimental-vm-modules\" jest --coverage", - "lint:ingest": "eslint '**/*.ts' --no-error-on-unmatched-pattern", - "lint:ingest:fix": "eslint --fix '**/*.ts' --no-error-on-unmatched-pattern", + "build": "echo 'No build step needed for JavaScript-only project'", + "watch": "echo 'No watch step needed for JavaScript-only project'", "test:csp": "mocha cloudfront-csp/test", - "lint:csp": "grunt check", - "lint": "eslint '**/*.ts' --no-error-on-unmatched-pattern && grunt check", - "test": "NODE_OPTIONS=\"$NODE_OPTIONS --experimental-vm-modules\" jest --coverage && mocha cloudfront-csp/test", - "audit:dependencies": "yarn audit --groups dependencies --level moderate", - "grunt": "grunt" + "lint": "eslint '**/*.js' --no-error-on-unmatched-pattern", + "test": "mocha cloudfront-csp/test", + "audit:dependencies": "yarn audit --groups dependencies --level moderate" }, "author": "Inspiring Apps", "license": "UNLICENSED", "devDependencies": { - "@types/aws-lambda": "8.10.145", - "@types/jest": "^29.5.12", - "@types/node": "22.5.4", - "@types/nodemailer": "^6.4.14", - "@types/react": "^18.3.12", - "@typescript-eslint/eslint-plugin": "^8.12.2", - "@typescript-eslint/parser": "^8.12.2", "aws-sdk-client-mock": "^4.1.0", "aws-sdk-client-mock-jest": "^4.1.0", "esbuild": "0.24.0", "eslint": "^9.13.0", - "jest": "^29.7.0", - "react": "^18.3.1", - "react-dom": "^18.3.1", - "ts-jest": "^29.2.5", - "ts-node": "^10.9.2", - "tslib": "^2.8.0", - "tsx": "^4.19.2", - "typescript": "5.6.3", "chai": "^4.1.2", "chai-match-pattern": "^1.1.0", "chalk": "^4.1.2", - "grunt": "^1.6.1", - "grunt-contrib-watch": "^1.1.0", - "grunt-eslint": "^25.0.0", - "jit-grunt": "^0.10.0", "lambda-local": "^2.2.0", "mocha": "^11.0.0" }, diff --git a/backend/compact-connect-ui-app/lambdas/nodejs/yarn.lock b/backend/compact-connect-ui-app/lambdas/nodejs/yarn.lock index d902386c0..e72e6ada6 100644 --- a/backend/compact-connect-ui-app/lambdas/nodejs/yarn.lock +++ b/backend/compact-connect-ui-app/lambdas/nodejs/yarn.lock @@ -1019,13 +1019,6 @@ resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.6.0.tgz#ec6cd237440700bc23ca23087f513c75508958b0" integrity sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA== -"@cspotcode/source-map-support@^0.8.0": - version "0.8.1" - resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" - integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw== - dependencies: - "@jridgewell/trace-mapping" "0.3.9" - "@dabh/diagnostics@^2.0.2": version "2.0.3" resolved "https://registry.yarnpkg.com/@dabh/diagnostics/-/diagnostics-2.0.3.tgz#7f7e97ee9a725dffc7808d93668cc984e1dc477a" @@ -1035,254 +1028,134 @@ enabled "2.0.x" kuler "^2.0.0" -"@esbuild/aix-ppc64@0.23.1": - version "0.23.1" - resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.23.1.tgz#51299374de171dbd80bb7d838e1cfce9af36f353" - integrity sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ== - "@esbuild/aix-ppc64@0.24.0": version "0.24.0" resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.24.0.tgz#b57697945b50e99007b4c2521507dc613d4a648c" integrity sha512-WtKdFM7ls47zkKHFVzMz8opM7LkcsIp9amDUBIAWirg70RM71WRSjdILPsY5Uv1D42ZpUfaPILDlfactHgsRkw== -"@esbuild/android-arm64@0.23.1": - version "0.23.1" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.23.1.tgz#58565291a1fe548638adb9c584237449e5e14018" - integrity sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw== - "@esbuild/android-arm64@0.24.0": version "0.24.0" resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.24.0.tgz#1add7e0af67acefd556e407f8497e81fddad79c0" integrity sha512-Vsm497xFM7tTIPYK9bNTYJyF/lsP590Qc1WxJdlB6ljCbdZKU9SY8i7+Iin4kyhV/KV5J2rOKsBQbB77Ab7L/w== -"@esbuild/android-arm@0.23.1": - version "0.23.1" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.23.1.tgz#5eb8c652d4c82a2421e3395b808e6d9c42c862ee" - integrity sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ== - "@esbuild/android-arm@0.24.0": version "0.24.0" resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.24.0.tgz#ab7263045fa8e090833a8e3c393b60d59a789810" integrity sha512-arAtTPo76fJ/ICkXWetLCc9EwEHKaeya4vMrReVlEIUCAUncH7M4bhMQ+M9Vf+FFOZJdTNMXNBrWwW+OXWpSew== -"@esbuild/android-x64@0.23.1": - version "0.23.1" - resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.23.1.tgz#ae19d665d2f06f0f48a6ac9a224b3f672e65d517" - integrity sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg== - "@esbuild/android-x64@0.24.0": version "0.24.0" resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.24.0.tgz#e8f8b196cfdfdd5aeaebbdb0110983460440e705" integrity sha512-t8GrvnFkiIY7pa7mMgJd7p8p8qqYIz1NYiAoKc75Zyv73L3DZW++oYMSHPRarcotTKuSs6m3hTOa5CKHaS02TQ== -"@esbuild/darwin-arm64@0.23.1": - version "0.23.1" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.23.1.tgz#05b17f91a87e557b468a9c75e9d85ab10c121b16" - integrity sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q== - "@esbuild/darwin-arm64@0.24.0": version "0.24.0" resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.24.0.tgz#2d0d9414f2acbffd2d86e98253914fca603a53dd" integrity sha512-CKyDpRbK1hXwv79soeTJNHb5EiG6ct3efd/FTPdzOWdbZZfGhpbcqIpiD0+vwmpu0wTIL97ZRPZu8vUt46nBSw== -"@esbuild/darwin-x64@0.23.1": - version "0.23.1" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.23.1.tgz#c58353b982f4e04f0d022284b8ba2733f5ff0931" - integrity sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw== - "@esbuild/darwin-x64@0.24.0": version "0.24.0" resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.24.0.tgz#33087aab31a1eb64c89daf3d2cf8ce1775656107" integrity sha512-rgtz6flkVkh58od4PwTRqxbKH9cOjaXCMZgWD905JOzjFKW+7EiUObfd/Kav+A6Gyud6WZk9w+xu6QLytdi2OA== -"@esbuild/freebsd-arm64@0.23.1": - version "0.23.1" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.23.1.tgz#f9220dc65f80f03635e1ef96cfad5da1f446f3bc" - integrity sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA== - "@esbuild/freebsd-arm64@0.24.0": version "0.24.0" resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.0.tgz#bb76e5ea9e97fa3c753472f19421075d3a33e8a7" integrity sha512-6Mtdq5nHggwfDNLAHkPlyLBpE5L6hwsuXZX8XNmHno9JuL2+bg2BX5tRkwjyfn6sKbxZTq68suOjgWqCicvPXA== -"@esbuild/freebsd-x64@0.23.1": - version "0.23.1" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.23.1.tgz#69bd8511fa013b59f0226d1609ac43f7ce489730" - integrity sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g== - "@esbuild/freebsd-x64@0.24.0": version "0.24.0" resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.24.0.tgz#e0e2ce9249fdf6ee29e5dc3d420c7007fa579b93" integrity sha512-D3H+xh3/zphoX8ck4S2RxKR6gHlHDXXzOf6f/9dbFt/NRBDIE33+cVa49Kil4WUjxMGW0ZIYBYtaGCa2+OsQwQ== -"@esbuild/linux-arm64@0.23.1": - version "0.23.1" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.23.1.tgz#8050af6d51ddb388c75653ef9871f5ccd8f12383" - integrity sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g== - "@esbuild/linux-arm64@0.24.0": version "0.24.0" resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.24.0.tgz#d1b2aa58085f73ecf45533c07c82d81235388e75" integrity sha512-TDijPXTOeE3eaMkRYpcy3LarIg13dS9wWHRdwYRnzlwlA370rNdZqbcp0WTyyV/k2zSxfko52+C7jU5F9Tfj1g== -"@esbuild/linux-arm@0.23.1": - version "0.23.1" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.23.1.tgz#ecaabd1c23b701070484990db9a82f382f99e771" - integrity sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ== - "@esbuild/linux-arm@0.24.0": version "0.24.0" resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.24.0.tgz#8e4915df8ea3e12b690a057e77a47b1d5935ef6d" integrity sha512-gJKIi2IjRo5G6Glxb8d3DzYXlxdEj2NlkixPsqePSZMhLudqPhtZ4BUrpIuTjJYXxvF9njql+vRjB2oaC9XpBw== -"@esbuild/linux-ia32@0.23.1": - version "0.23.1" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.23.1.tgz#3ed2273214178109741c09bd0687098a0243b333" - integrity sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ== - "@esbuild/linux-ia32@0.24.0": version "0.24.0" resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.24.0.tgz#8200b1110666c39ab316572324b7af63d82013fb" integrity sha512-K40ip1LAcA0byL05TbCQ4yJ4swvnbzHscRmUilrmP9Am7//0UjPreh4lpYzvThT2Quw66MhjG//20mrufm40mA== -"@esbuild/linux-loong64@0.23.1": - version "0.23.1" - resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.23.1.tgz#a0fdf440b5485c81b0fbb316b08933d217f5d3ac" - integrity sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw== - "@esbuild/linux-loong64@0.24.0": version "0.24.0" resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.24.0.tgz#6ff0c99cf647504df321d0640f0d32e557da745c" integrity sha512-0mswrYP/9ai+CU0BzBfPMZ8RVm3RGAN/lmOMgW4aFUSOQBjA31UP8Mr6DDhWSuMwj7jaWOT0p0WoZ6jeHhrD7g== -"@esbuild/linux-mips64el@0.23.1": - version "0.23.1" - resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.23.1.tgz#e11a2806346db8375b18f5e104c5a9d4e81807f6" - integrity sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q== - "@esbuild/linux-mips64el@0.24.0": version "0.24.0" resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.24.0.tgz#3f720ccd4d59bfeb4c2ce276a46b77ad380fa1f3" integrity sha512-hIKvXm0/3w/5+RDtCJeXqMZGkI2s4oMUGj3/jM0QzhgIASWrGO5/RlzAzm5nNh/awHE0A19h/CvHQe6FaBNrRA== -"@esbuild/linux-ppc64@0.23.1": - version "0.23.1" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.23.1.tgz#06a2744c5eaf562b1a90937855b4d6cf7c75ec96" - integrity sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw== - "@esbuild/linux-ppc64@0.24.0": version "0.24.0" resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.24.0.tgz#9d6b188b15c25afd2e213474bf5f31e42e3aa09e" integrity sha512-HcZh5BNq0aC52UoocJxaKORfFODWXZxtBaaZNuN3PUX3MoDsChsZqopzi5UupRhPHSEHotoiptqikjN/B77mYQ== -"@esbuild/linux-riscv64@0.23.1": - version "0.23.1" - resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.23.1.tgz#65b46a2892fc0d1af4ba342af3fe0fa4a8fe08e7" - integrity sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA== - "@esbuild/linux-riscv64@0.24.0": version "0.24.0" resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.24.0.tgz#f989fdc9752dfda286c9cd87c46248e4dfecbc25" integrity sha512-bEh7dMn/h3QxeR2KTy1DUszQjUrIHPZKyO6aN1X4BCnhfYhuQqedHaa5MxSQA/06j3GpiIlFGSsy1c7Gf9padw== -"@esbuild/linux-s390x@0.23.1": - version "0.23.1" - resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.23.1.tgz#e71ea18c70c3f604e241d16e4e5ab193a9785d6f" - integrity sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw== - "@esbuild/linux-s390x@0.24.0": version "0.24.0" resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.24.0.tgz#29ebf87e4132ea659c1489fce63cd8509d1c7319" integrity sha512-ZcQ6+qRkw1UcZGPyrCiHHkmBaj9SiCD8Oqd556HldP+QlpUIe2Wgn3ehQGVoPOvZvtHm8HPx+bH20c9pvbkX3g== -"@esbuild/linux-x64@0.23.1": - version "0.23.1" - resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.23.1.tgz#d47f97391e80690d4dfe811a2e7d6927ad9eed24" - integrity sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ== - "@esbuild/linux-x64@0.24.0": version "0.24.0" resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.24.0.tgz#4af48c5c0479569b1f359ffbce22d15f261c0cef" integrity sha512-vbutsFqQ+foy3wSSbmjBXXIJ6PL3scghJoM8zCL142cGaZKAdCZHyf+Bpu/MmX9zT9Q0zFBVKb36Ma5Fzfa8xA== -"@esbuild/netbsd-x64@0.23.1": - version "0.23.1" - resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.23.1.tgz#44e743c9778d57a8ace4b72f3c6b839a3b74a653" - integrity sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA== - "@esbuild/netbsd-x64@0.24.0": version "0.24.0" resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.24.0.tgz#1ae73d23cc044a0ebd4f198334416fb26c31366c" integrity sha512-hjQ0R/ulkO8fCYFsG0FZoH+pWgTTDreqpqY7UnQntnaKv95uP5iW3+dChxnx7C3trQQU40S+OgWhUVwCjVFLvg== -"@esbuild/openbsd-arm64@0.23.1": - version "0.23.1" - resolved "https://registry.yarnpkg.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.23.1.tgz#05c5a1faf67b9881834758c69f3e51b7dee015d7" - integrity sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q== - "@esbuild/openbsd-arm64@0.24.0": version "0.24.0" resolved "https://registry.yarnpkg.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.0.tgz#5d904a4f5158c89859fd902c427f96d6a9e632e2" integrity sha512-MD9uzzkPQbYehwcN583yx3Tu5M8EIoTD+tUgKF982WYL9Pf5rKy9ltgD0eUgs8pvKnmizxjXZyLt0z6DC3rRXg== -"@esbuild/openbsd-x64@0.23.1": - version "0.23.1" - resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.23.1.tgz#2e58ae511bacf67d19f9f2dcd9e8c5a93f00c273" - integrity sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA== - "@esbuild/openbsd-x64@0.24.0": version "0.24.0" resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.24.0.tgz#4c8aa88c49187c601bae2971e71c6dc5e0ad1cdf" integrity sha512-4ir0aY1NGUhIC1hdoCzr1+5b43mw99uNwVzhIq1OY3QcEwPDO3B7WNXBzaKY5Nsf1+N11i1eOfFcq+D/gOS15Q== -"@esbuild/sunos-x64@0.23.1": - version "0.23.1" - resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.23.1.tgz#adb022b959d18d3389ac70769cef5a03d3abd403" - integrity sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA== - "@esbuild/sunos-x64@0.24.0": version "0.24.0" resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.24.0.tgz#8ddc35a0ea38575fa44eda30a5ee01ae2fa54dd4" integrity sha512-jVzdzsbM5xrotH+W5f1s+JtUy1UWgjU0Cf4wMvffTB8m6wP5/kx0KiaLHlbJO+dMgtxKV8RQ/JvtlFcdZ1zCPA== -"@esbuild/win32-arm64@0.23.1": - version "0.23.1" - resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.23.1.tgz#84906f50c212b72ec360f48461d43202f4c8b9a2" - integrity sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A== - "@esbuild/win32-arm64@0.24.0": version "0.24.0" resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.24.0.tgz#6e79c8543f282c4539db684a207ae0e174a9007b" integrity sha512-iKc8GAslzRpBytO2/aN3d2yb2z8XTVfNV0PjGlCxKo5SgWmNXx82I/Q3aG1tFfS+A2igVCY97TJ8tnYwpUWLCA== -"@esbuild/win32-ia32@0.23.1": - version "0.23.1" - resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.23.1.tgz#5e3eacc515820ff729e90d0cb463183128e82fac" - integrity sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ== - "@esbuild/win32-ia32@0.24.0": version "0.24.0" resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.24.0.tgz#057af345da256b7192d18b676a02e95d0fa39103" integrity sha512-vQW36KZolfIudCcTnaTpmLQ24Ha1RjygBo39/aLkM2kmjkWmZGEJ5Gn9l5/7tzXA42QGIoWbICfg6KLLkIw6yw== -"@esbuild/win32-x64@0.23.1": - version "0.23.1" - resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.23.1.tgz#81fd50d11e2c32b2d6241470e3185b70c7b30699" - integrity sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg== - "@esbuild/win32-x64@0.24.0": version "0.24.0" resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.24.0.tgz#168ab1c7e1c318b922637fad8f339d48b01e1244" integrity sha512-7IAFPrjSQIJrGsK6flwg7NFmwBoSTyF3rl7If0hNUFQU4ilTsEPL6GuMuU9BfIWVVGuRnuIidkSMC+c0Otu8IA== -"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": +"@eslint-community/eslint-utils@^4.2.0": version "4.4.1" resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz#d1145bf2c20132d6400495d6df4bf59362fd9d56" integrity sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA== dependencies: eslint-visitor-keys "^3.4.3" -"@eslint-community/regexpp@^4.10.0", "@eslint-community/regexpp@^4.12.1": +"@eslint-community/regexpp@^4.12.1": version "4.12.1" resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.1.tgz#cfc6cffe39df390a3841cde2abccf92eaa7ae0e0" integrity sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ== @@ -1592,7 +1465,7 @@ "@jridgewell/sourcemap-codec" "^1.4.10" "@jridgewell/trace-mapping" "^0.3.24" -"@jridgewell/resolve-uri@^3.0.3", "@jridgewell/resolve-uri@^3.1.0": +"@jridgewell/resolve-uri@^3.1.0": version "3.1.2" resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== @@ -1607,14 +1480,6 @@ resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz#3188bcb273a414b0d215fd22a58540b989b9409a" integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ== -"@jridgewell/trace-mapping@0.3.9": - version "0.3.9" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9" - integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ== - dependencies: - "@jridgewell/resolve-uri" "^3.0.3" - "@jridgewell/sourcemap-codec" "^1.4.10" - "@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.18", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": version "0.3.25" resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz#15f190e98895f3fc23276ee14bc76b675c2e50f0" @@ -1623,27 +1488,6 @@ "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" -"@nodelib/fs.scandir@2.1.5": - version "2.1.5" - resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" - integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== - dependencies: - "@nodelib/fs.stat" "2.0.5" - run-parallel "^1.1.9" - -"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": - version "2.0.5" - resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" - integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== - -"@nodelib/fs.walk@^1.2.3": - version "1.2.8" - resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" - integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== - dependencies: - "@nodelib/fs.scandir" "2.1.5" - fastq "^1.6.0" - "@pkgjs/parseargs@^0.11.0": version "0.11.0" resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" @@ -2186,31 +2030,6 @@ "@smithy/types" "^3.7.2" tslib "^2.6.2" -"@tsconfig/node10@^1.0.7": - version "1.0.11" - resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.11.tgz#6ee46400685f130e278128c7b38b7e031ff5b2f2" - integrity sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw== - -"@tsconfig/node12@^1.0.7": - version "1.0.11" - resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.11.tgz#ee3def1f27d9ed66dac6e46a295cffb0152e058d" - integrity sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag== - -"@tsconfig/node14@^1.0.0": - version "1.0.3" - resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.3.tgz#e4386316284f00b98435bf40f72f75a09dabf6c1" - integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow== - -"@tsconfig/node16@^1.0.2": - version "1.0.4" - resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.4.tgz#0b92dcc0cc1c81f6f306a381f28e31b1a56536e9" - integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA== - -"@types/aws-lambda@8.10.145": - version "8.10.145" - resolved "https://registry.yarnpkg.com/@types/aws-lambda/-/aws-lambda-8.10.145.tgz#b2d31a987f4888e5553ff1819f57cafa475594d9" - integrity sha512-dtByW6WiFk5W5Jfgz1VM+YPA21xMXTuSFoLYIDY0L44jDLLflVPtZkYuu3/YxpGcvjzKFBZLU+GyKjR0HOYtyw== - "@types/babel__core@^7.1.14": version "7.20.5" resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.5.tgz#3df15f27ba85319caa07ba08d0721889bb39c017" @@ -2275,14 +2094,6 @@ dependencies: "@types/istanbul-lib-report" "*" -"@types/jest@^29.5.12": - version "29.5.14" - resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.14.tgz#2b910912fa1d6856cadcd0c1f95af7df1d6049e5" - integrity sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ== - dependencies: - expect "^29.0.0" - pretty-format "^29.0.0" - "@types/json-schema@^7.0.15": version "7.0.15" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" @@ -2295,33 +2106,6 @@ dependencies: undici-types "~6.20.0" -"@types/node@22.5.4": - version "22.5.4" - resolved "https://registry.yarnpkg.com/@types/node/-/node-22.5.4.tgz#83f7d1f65bc2ed223bdbf57c7884f1d5a4fa84e8" - integrity sha512-FDuKUJQm/ju9fT/SeX/6+gBzoPzlVCzfzmGkwKvRHQVxi4BntVbyIwf6a4Xn62mrvndLiml6z/UBXIdEVjQLXg== - dependencies: - undici-types "~6.19.2" - -"@types/nodemailer@^6.4.14": - version "6.4.17" - resolved "https://registry.yarnpkg.com/@types/nodemailer/-/nodemailer-6.4.17.tgz#5c82a42aee16a3dd6ea31446a1bd6a447f1ac1a4" - integrity sha512-I9CCaIp6DTldEg7vyUTZi8+9Vo0hi1/T8gv3C89yk1rSAAzoKQ8H8ki/jBYJSFoH/BisgLP8tkZMlQ91CIquww== - dependencies: - "@types/node" "*" - -"@types/prop-types@*": - version "15.7.14" - resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.14.tgz#1433419d73b2a7ebfc6918dcefd2ec0d5cd698f2" - integrity sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ== - -"@types/react@^18.3.12": - version "18.3.14" - resolved "https://registry.yarnpkg.com/@types/react/-/react-18.3.14.tgz#7ce43bbca0e15e1c4f67ad33ea3f83d75aa6756b" - integrity sha512-NzahNKvjNhVjuPBQ+2G7WlxstQ+47kXZNHlUvFakDViuIEfGY926GqhMueQFZ7woG+sPiQKlF36XfrIUVSUfFg== - dependencies: - "@types/prop-types" "*" - csstype "^3.0.2" - "@types/sinon@^17.0.3": version "17.0.3" resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-17.0.3.tgz#9aa7e62f0a323b9ead177ed23a36ea757141a5fa" @@ -2361,87 +2145,6 @@ dependencies: "@types/yargs-parser" "*" -"@typescript-eslint/eslint-plugin@^8.12.2": - version "8.18.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.18.0.tgz#0901933326aea4443b81df3f740ca7dfc45c7bea" - integrity sha512-NR2yS7qUqCL7AIxdJUQf2MKKNDVNaig/dEB0GBLU7D+ZdHgK1NoH/3wsgO3OnPVipn51tG3MAwaODEGil70WEw== - dependencies: - "@eslint-community/regexpp" "^4.10.0" - "@typescript-eslint/scope-manager" "8.18.0" - "@typescript-eslint/type-utils" "8.18.0" - "@typescript-eslint/utils" "8.18.0" - "@typescript-eslint/visitor-keys" "8.18.0" - graphemer "^1.4.0" - ignore "^5.3.1" - natural-compare "^1.4.0" - ts-api-utils "^1.3.0" - -"@typescript-eslint/parser@^8.12.2": - version "8.18.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.18.0.tgz#a1c9456cbb6a089730bf1d3fc47946c5fb5fe67b" - integrity sha512-hgUZ3kTEpVzKaK3uNibExUYm6SKKOmTU2BOxBSvOYwtJEPdVQ70kZJpPjstlnhCHcuc2WGfSbpKlb/69ttyN5Q== - dependencies: - "@typescript-eslint/scope-manager" "8.18.0" - "@typescript-eslint/types" "8.18.0" - "@typescript-eslint/typescript-estree" "8.18.0" - "@typescript-eslint/visitor-keys" "8.18.0" - debug "^4.3.4" - -"@typescript-eslint/scope-manager@8.18.0": - version "8.18.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.18.0.tgz#30b040cb4557804a7e2bcc65cf8fdb630c96546f" - integrity sha512-PNGcHop0jkK2WVYGotk/hxj+UFLhXtGPiGtiaWgVBVP1jhMoMCHlTyJA+hEj4rszoSdLTK3fN4oOatrL0Cp+Xw== - dependencies: - "@typescript-eslint/types" "8.18.0" - "@typescript-eslint/visitor-keys" "8.18.0" - -"@typescript-eslint/type-utils@8.18.0": - version "8.18.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.18.0.tgz#6f0d12cf923b6fd95ae4d877708c0adaad93c471" - integrity sha512-er224jRepVAVLnMF2Q7MZJCq5CsdH2oqjP4dT7K6ij09Kyd+R21r7UVJrF0buMVdZS5QRhDzpvzAxHxabQadow== - dependencies: - "@typescript-eslint/typescript-estree" "8.18.0" - "@typescript-eslint/utils" "8.18.0" - debug "^4.3.4" - ts-api-utils "^1.3.0" - -"@typescript-eslint/types@8.18.0": - version "8.18.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.18.0.tgz#3afcd30def8756bc78541268ea819a043221d5f3" - integrity sha512-FNYxgyTCAnFwTrzpBGq+zrnoTO4x0c1CKYY5MuUTzpScqmY5fmsh2o3+57lqdI3NZucBDCzDgdEbIaNfAjAHQA== - -"@typescript-eslint/typescript-estree@8.18.0": - version "8.18.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.18.0.tgz#d8ca785799fbb9c700cdff1a79c046c3e633c7f9" - integrity sha512-rqQgFRu6yPkauz+ms3nQpohwejS8bvgbPyIDq13cgEDbkXt4LH4OkDMT0/fN1RUtzG8e8AKJyDBoocuQh8qNeg== - dependencies: - "@typescript-eslint/types" "8.18.0" - "@typescript-eslint/visitor-keys" "8.18.0" - debug "^4.3.4" - fast-glob "^3.3.2" - is-glob "^4.0.3" - minimatch "^9.0.4" - semver "^7.6.0" - ts-api-utils "^1.3.0" - -"@typescript-eslint/utils@8.18.0": - version "8.18.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.18.0.tgz#48f67205d42b65d895797bb7349d1be5c39a62f7" - integrity sha512-p6GLdY383i7h5b0Qrfbix3Vc3+J2k6QWw6UMUeY5JGfm3C5LbZ4QIZzJNoNOfgyRe0uuYKjvVOsO/jD4SJO+xg== - dependencies: - "@eslint-community/eslint-utils" "^4.4.0" - "@typescript-eslint/scope-manager" "8.18.0" - "@typescript-eslint/types" "8.18.0" - "@typescript-eslint/typescript-estree" "8.18.0" - -"@typescript-eslint/visitor-keys@8.18.0": - version "8.18.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.18.0.tgz#7b6d33534fa808e33a19951907231ad2ea5c36dd" - integrity sha512-pCh/qEA8Lb1wVIqNvBke8UaRjJ6wrAWkJO5yyIbs8Yx6TNGYyfNjOo61tLv+WwLvoLPp4BQ8B7AHKijl8NGUfw== - dependencies: - "@typescript-eslint/types" "8.18.0" - eslint-visitor-keys "^4.2.0" - "@usewaypoint/block-avatar@^0.0.3": version "0.0.3" resolved "https://registry.yarnpkg.com/@usewaypoint/block-avatar/-/block-avatar-0.0.3.tgz#f63510ed82690a2b4b68ead62a191d80b20c2957" @@ -2549,24 +2252,12 @@ loupe "^3.1.2" tinyrainbow "^1.2.0" -abbrev@1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" - integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== - acorn-jsx@^5.3.2: version "5.3.2" resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== -acorn-walk@^8.1.1: - version "8.3.4" - resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.4.tgz#794dd169c3977edf4ba4ea47583587c5866236b7" - integrity sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g== - dependencies: - acorn "^8.11.0" - -acorn@^8.11.0, acorn@^8.14.0, acorn@^8.4.1: +acorn@^8.14.0: version "8.14.0" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.14.0.tgz#063e2c70cac5fb4f6467f0b11152e04c682795b0" integrity sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA== @@ -2628,11 +2319,6 @@ anymatch@^3.0.3, anymatch@~3.1.2: normalize-path "^3.0.0" picomatch "^2.0.4" -arg@^4.1.0: - version "4.1.3" - resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" - integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== - argparse@^1.0.7: version "1.0.10" resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" @@ -2645,16 +2331,6 @@ argparse@^2.0.1: resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== -array-each@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/array-each/-/array-each-1.0.1.tgz#a794af0c05ab1752846ee753a1f211a05ba0c44f" - integrity sha512-zHjL5SZa68hkKHBFBK6DJCTtr9sfTCPCaph/L7tMSLcTFgy+zX7E+6q5UArbtOtMBCtxdICpfTCspRse+ywyXA== - -array-slice@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/array-slice/-/array-slice-1.1.0.tgz#e368ea15f89bc7069f7ffb89aec3a6c7d4ac22d4" - integrity sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w== - assertion-error@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b" @@ -2665,14 +2341,7 @@ assertion-error@^2.0.1: resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-2.0.1.tgz#f641a196b335690b1070bf00b6e7593fec190bf7" integrity sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA== -async@^2.6.0: - version "2.6.4" - resolved "https://registry.yarnpkg.com/async/-/async-2.6.4.tgz#706b7ff6084664cd7eae713f6f965433b5504221" - integrity sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA== - dependencies: - lodash "^4.17.14" - -async@^3.2.3, async@~3.2.0: +async@^3.2.3: version "3.2.6" resolved "https://registry.yarnpkg.com/async/-/async-3.2.6.tgz#1b0728e14929d51b85b449b7f06e27c1145e38ce" integrity sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA== @@ -2806,16 +2475,6 @@ binary-extensions@^2.0.0: resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522" integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw== -body@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/body/-/body-5.1.0.tgz#e4ba0ce410a46936323367609ecb4e6553125069" - integrity sha512-chUsBxGRtuElD6fmw1gHLpvnKdVLK302peeFa9ZqAEk8TyzZ3fygLyUEDDPTJvL9+Bor0dIwn6ePOsRM2y0zQQ== - dependencies: - continuable-cache "^0.3.1" - error "^7.0.0" - raw-body "~1.1.0" - safe-json-parse "~1.0.1" - bowser@^2.11.0: version "2.11.0" resolved "https://registry.yarnpkg.com/bowser/-/bowser-2.11.0.tgz#5ca3c35757a7aa5771500c70a73a9f91ef420a8f" @@ -2858,13 +2517,6 @@ browserslist@^4.24.0: node-releases "^2.0.18" update-browserslist-db "^1.1.1" -bs-logger@^0.2.6: - version "0.2.6" - resolved "https://registry.yarnpkg.com/bs-logger/-/bs-logger-0.2.6.tgz#eb7d365307a72cf974cc6cda76b68354ad336bd8" - integrity sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog== - dependencies: - fast-json-stable-stringify "2.x" - bser@2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" @@ -2886,11 +2538,6 @@ buffer@4.9.2: ieee754 "^1.1.4" isarray "^1.0.0" -bytes@1: - version "1.0.0" - resolved "https://registry.yarnpkg.com/bytes/-/bytes-1.0.0.tgz#3569ede8ba34315fab99c3e92cb04c7220de1fa8" - integrity sha512-/x68VkHLeTl3/Ll8IvxdwzhrT+IyKc52e/oyHhA2RwqPqswSnjVbSddfPRwAsJtbilMAPSRWwAlpxdYsSWOTKQ== - call-bind-apply-helpers@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz#32e5892e6361b29b0b545ba6f7763378daca2840" @@ -2960,7 +2607,7 @@ chai@^5.1.2: loupe "^3.1.0" pathval "^2.0.0" -chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.0, chalk@^4.1.2, chalk@~4.1.0: +chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== @@ -3086,11 +2733,6 @@ color@^3.1.3: color-convert "^1.9.3" color-string "^1.6.0" -colors@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/colors/-/colors-1.1.2.tgz#168a4701756b6a7f51a12ce0c97bfa28c084ed63" - integrity sha512-ENwblkFQpqqia6b++zLD/KUWafYlVY/UNnAp7oz7LY7E924wmpye416wBOmvv/HMWzl8gL1kJlfvId/1Dg176w== - colorspace@1.1.x: version "1.1.4" resolved "https://registry.yarnpkg.com/colorspace/-/colorspace-1.1.4.tgz#8d442d1186152f60453bf8070cd66eb364e59243" @@ -3114,11 +2756,6 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== -continuable-cache@^0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/continuable-cache/-/continuable-cache-0.3.1.tgz#bd727a7faed77e71ff3985ac93351a912733ad0f" - integrity sha512-TF30kpKhTH8AGCG3dut0rdd/19B7Z+qCnrMoBLpyQu/2drZdNrrpcjPEoJeSVsQM+8KmWG5O56oPDjSSUsuTyA== - convert-source-map@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" @@ -3137,11 +2774,6 @@ create-jest@^29.7.0: jest-util "^29.7.0" prompts "^2.0.1" -create-require@^1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" - integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== - cross-spawn@^7.0.0, cross-spawn@^7.0.3, cross-spawn@^7.0.5: version "7.0.6" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" @@ -3151,24 +2783,7 @@ cross-spawn@^7.0.0, cross-spawn@^7.0.3, cross-spawn@^7.0.5: shebang-command "^2.0.0" which "^2.0.1" -csstype@^3.0.2: - version "3.1.3" - resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81" - integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw== - -dateformat@~4.6.2: - version "4.6.3" - resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-4.6.3.tgz#556fa6497e5217fedb78821424f8a1c22fa3f4b5" - integrity sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA== - -debug@^3.1.0: - version "3.2.7" - resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" - integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== - dependencies: - ms "^2.1.1" - -debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@^4.3.5: +debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.5: version "4.4.0" resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.0.tgz#2b3f2aea2ffeb776477460267377dc8710faba8a" integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA== @@ -3216,11 +2831,6 @@ define-data-property@^1.1.4: es-errors "^1.3.0" gopd "^1.0.1" -detect-file@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/detect-file/-/detect-file-1.0.0.tgz#f0d66d03672a825cb1b73bdb3fe62310c8e552b7" - integrity sha512-DtCOLG98P007x7wiiOmfI0fi3eIKyWiLTGJ2MDnVi/E04lWGbf+JzrRHMm0rgIIZJGtHpKpbVgLWHrv8xXpc3Q== - detect-newline@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" @@ -3231,11 +2841,6 @@ diff-sequences@^29.6.3: resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.6.3.tgz#4deaf894d11407c51efc8418012f9e70b84ea921" integrity sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q== -diff@^4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" - integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== - diff@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/diff/-/diff-5.2.0.tgz#26ded047cd1179b78b9537d5ef725503ce1ae531" @@ -3260,13 +2865,6 @@ eastasianwidth@^0.2.0: resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== -ejs@^3.1.10: - version "3.1.10" - resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.10.tgz#69ab8358b14e896f80cc39e62087b88500c3ac3b" - integrity sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA== - dependencies: - jake "^10.8.5" - electron-to-chromium@^1.5.41: version "1.5.71" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.71.tgz#d8b5dba1e55b320f2f4e9b1ca80738f53fcfec2b" @@ -3299,13 +2897,6 @@ error-ex@^1.3.1: dependencies: is-arrayish "^0.2.1" -error@^7.0.0: - version "7.2.1" - resolved "https://registry.yarnpkg.com/error/-/error-7.2.1.tgz#eab21a4689b5f684fc83da84a0e390de82d94894" - integrity sha512-fo9HBvWnx3NGUKMvMwB/CBCMMrfEJgbDTVDEkPygA3Bdd3lM1OyCd+rbQ8BwnpF6GdVeOLDNmyL4N5Bg80ZvdA== - dependencies: - string-template "~0.2.1" - es-define-property@^1.0.0, es-define-property@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa" @@ -3346,36 +2937,6 @@ esbuild@0.24.0: "@esbuild/win32-ia32" "0.24.0" "@esbuild/win32-x64" "0.24.0" -esbuild@~0.23.0: - version "0.23.1" - resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.23.1.tgz#40fdc3f9265ec0beae6f59824ade1bd3d3d2dab8" - integrity sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg== - optionalDependencies: - "@esbuild/aix-ppc64" "0.23.1" - "@esbuild/android-arm" "0.23.1" - "@esbuild/android-arm64" "0.23.1" - "@esbuild/android-x64" "0.23.1" - "@esbuild/darwin-arm64" "0.23.1" - "@esbuild/darwin-x64" "0.23.1" - "@esbuild/freebsd-arm64" "0.23.1" - "@esbuild/freebsd-x64" "0.23.1" - "@esbuild/linux-arm" "0.23.1" - "@esbuild/linux-arm64" "0.23.1" - "@esbuild/linux-ia32" "0.23.1" - "@esbuild/linux-loong64" "0.23.1" - "@esbuild/linux-mips64el" "0.23.1" - "@esbuild/linux-ppc64" "0.23.1" - "@esbuild/linux-riscv64" "0.23.1" - "@esbuild/linux-s390x" "0.23.1" - "@esbuild/linux-x64" "0.23.1" - "@esbuild/netbsd-x64" "0.23.1" - "@esbuild/openbsd-arm64" "0.23.1" - "@esbuild/openbsd-x64" "0.23.1" - "@esbuild/sunos-x64" "0.23.1" - "@esbuild/win32-arm64" "0.23.1" - "@esbuild/win32-ia32" "0.23.1" - "@esbuild/win32-x64" "0.23.1" - escalade@^3.1.1, escalade@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" @@ -3409,7 +2970,7 @@ eslint-visitor-keys@^4.2.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz#687bacb2af884fcdda8a6e7d65c606f46a14cd45" integrity sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw== -eslint@^9.0.0, eslint@^9.13.0: +eslint@^9.13.0: version "9.16.0" resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.16.0.tgz#66832e66258922ac0a626f803a9273e37747f2a6" integrity sha512-whp8mSQI4C8VXd+fLgSM0lh3UlmcFtVwUQjyKCFfsp+2ItAIYhlq/hqGahGqHE6cv9unM41VlqKk2VtKYR2TaA== @@ -3487,11 +3048,6 @@ esutils@^2.0.2: resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== -eventemitter2@~0.4.13: - version "0.4.14" - resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-0.4.14.tgz#8f61b75cde012b2e9eb284d4545583b5643b61ab" - integrity sha512-K7J4xq5xAD5jHsGM5ReWXRTFa3JRGofHiMcVgQ8PRwgWxzjHpMWCIzsmyf60+mh8KLsqYPcjUMa0AC4hd6lPyQ== - events@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924" @@ -3512,19 +3068,12 @@ execa@^5.0.0: signal-exit "^3.0.3" strip-final-newline "^2.0.0" -exit@^0.1.2, exit@~0.1.2: +exit@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ== -expand-tilde@^2.0.0, expand-tilde@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/expand-tilde/-/expand-tilde-2.0.2.tgz#97e801aa052df02454de46b02bf621642cdc8502" - integrity sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw== - dependencies: - homedir-polyfill "^1.0.1" - -expect@>28.1.3, expect@^29.0.0, expect@^29.7.0: +expect@>28.1.3, expect@^29.7.0: version "29.7.0" resolved "https://registry.yarnpkg.com/expect/-/expect-29.7.0.tgz#578874590dcb3214514084c08115d8aee61e11bc" integrity sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw== @@ -3535,28 +3084,12 @@ expect@>28.1.3, expect@^29.0.0, expect@^29.7.0: jest-message-util "^29.7.0" jest-util "^29.7.0" -extend@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" - integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== - fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== -fast-glob@^3.3.2: - version "3.3.2" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129" - integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow== - dependencies: - "@nodelib/fs.stat" "^2.0.2" - "@nodelib/fs.walk" "^1.2.3" - glob-parent "^5.1.2" - merge2 "^1.3.0" - micromatch "^4.0.4" - -fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@^2.1.0: +fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== @@ -3573,20 +3106,6 @@ fast-xml-parser@4.4.1: dependencies: strnum "^1.0.5" -fastq@^1.6.0: - version "1.17.1" - resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.17.1.tgz#2a523f07a4e7b1e81a42b91b8bf2254107753b47" - integrity sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w== - dependencies: - reusify "^1.0.4" - -faye-websocket@~0.10.0: - version "0.10.0" - resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.10.0.tgz#4e492f8d04dfb6f89003507f6edbf2d501e7c6f4" - integrity sha512-Xhj93RXbMSq8urNCUq4p9l0P6hnySJ/7YNRhYNug0bLOuii7pKO7xQFb5mx9xZXWCar88pLPb805PvUkwrLZpQ== - dependencies: - websocket-driver ">=0.5.1" - fb-watchman@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.2.tgz#e9524ee6b5c77e9e5001af0f85f3adbb8623255c" @@ -3606,13 +3125,6 @@ file-entry-cache@^8.0.0: dependencies: flat-cache "^4.0.0" -filelist@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/filelist/-/filelist-1.0.4.tgz#f78978a1e944775ff9e62e744424f215e58352b5" - integrity sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q== - dependencies: - minimatch "^5.0.1" - fill-range@^7.1.1: version "7.1.1" resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" @@ -3636,42 +3148,6 @@ find-up@^5.0.0: locate-path "^6.0.0" path-exists "^4.0.0" -findup-sync@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-4.0.0.tgz#956c9cdde804052b881b428512905c4a5f2cdef0" - integrity sha512-6jvvn/12IC4quLBL1KNokxC7wWTvYncaVUYSoxWw7YykPLuRrnv4qdHcSOywOI5RpkOVGeQRtWM8/q+G6W6qfQ== - dependencies: - detect-file "^1.0.0" - is-glob "^4.0.0" - micromatch "^4.0.2" - resolve-dir "^1.0.1" - -findup-sync@~5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-5.0.0.tgz#54380ad965a7edca00cc8f63113559aadc541bd2" - integrity sha512-MzwXju70AuyflbgeOhzvQWAvvQdo1XL0A9bVvlXsYcFEBM87WR4OakL4OfZq+QRmr+duJubio+UtNQCPsVESzQ== - dependencies: - detect-file "^1.0.0" - is-glob "^4.0.3" - micromatch "^4.0.4" - resolve-dir "^1.0.1" - -fined@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/fined/-/fined-1.2.0.tgz#d00beccf1aa2b475d16d423b0238b713a2c4a37b" - integrity sha512-ZYDqPLGxDkDhDZBjZBb+oD1+j0rA4E0pXY50eplAAOPg2N/gUBSSk5IM1/QhPfyVo19lJ+CvXpqfvk+b2p/8Ng== - dependencies: - expand-tilde "^2.0.2" - is-plain-object "^2.0.3" - object.defaults "^1.1.0" - object.pick "^1.2.0" - parse-filepath "^1.0.1" - -flagged-respawn@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/flagged-respawn/-/flagged-respawn-1.0.1.tgz#e7de6f1279ddd9ca9aac8a5971d618606b3aab41" - integrity sha512-lNaHNVymajmk0OJMBn8fVUAU1BtDeKIqKoVhk4xAALB57aALg6b4W0MfJ/cUE0g9YBXy5XhSlPIpYIJ7HaY/3Q== - flat-cache@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-4.0.1.tgz#0ece39fcb14ee012f4b0410bd33dd9c1f011127c" @@ -3702,18 +3178,6 @@ for-each@^0.3.3: dependencies: is-callable "^1.1.3" -for-in@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" - integrity sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ== - -for-own@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/for-own/-/for-own-1.0.0.tgz#c63332f415cedc4b04dbfe70cf836494c53cb44b" - integrity sha512-0OABksIGrxKK8K4kynWkQ7y1zounQxP+CWnyclVwj81KW3vlLlGUx57DKGcP/LH216GzqnstnPocF16Nxs0Ycg== - dependencies: - for-in "^1.0.1" - foreground-child@^3.1.0: version "3.3.0" resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.3.0.tgz#0ac8644c06e431439f8561db8ecf29a7b5519c77" @@ -3727,7 +3191,7 @@ fs.realpath@^1.0.0: resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== -fsevents@^2.3.2, fsevents@~2.3.2, fsevents@~2.3.3: +fsevents@^2.3.2, fsevents@~2.3.2: version "2.3.3" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== @@ -3737,13 +3201,6 @@ function-bind@^1.1.2: resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== -gaze@^1.1.0: - version "1.1.3" - resolved "https://registry.yarnpkg.com/gaze/-/gaze-1.1.3.tgz#c441733e13b927ac8c0ff0b4c3b033f28812924a" - integrity sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g== - dependencies: - globule "^1.0.0" - gensync@^1.0.0-beta.2: version "1.0.0-beta.2" resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" @@ -3783,32 +3240,20 @@ get-stream@^6.0.0: resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== -get-tsconfig@^4.7.5: - version "4.8.1" - resolved "https://registry.yarnpkg.com/get-tsconfig/-/get-tsconfig-4.8.1.tgz#8995eb391ae6e1638d251118c7b56de7eb425471" - integrity sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg== +glob-parent@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" + integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== dependencies: - resolve-pkg-maps "^1.0.0" - -getobject@~1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/getobject/-/getobject-1.0.2.tgz#25ec87a50370f6dcc3c6ba7ef43c4c16215c4c89" - integrity sha512-2zblDBaFcb3rB4rF77XVnuINOE2h2k/OnqXAiy0IrTxUfV1iFp3la33oAQVY9pCpWU268WFYVt2t71hlMuLsOg== + is-glob "^4.0.3" -glob-parent@^5.1.2, glob-parent@~5.1.2: +glob-parent@~5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== dependencies: is-glob "^4.0.1" -glob-parent@^6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" - integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== - dependencies: - is-glob "^4.0.3" - glob-to-regexp@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" @@ -3838,38 +3283,6 @@ glob@^7.1.3, glob@^7.1.4: once "^1.3.0" path-is-absolute "^1.0.0" -glob@~7.1.1, glob@~7.1.6: - version "7.1.7" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90" - integrity sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.4" - once "^1.3.0" - path-is-absolute "^1.0.0" - -global-modules@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-1.0.0.tgz#6d770f0eb523ac78164d72b5e71a8877265cc3ea" - integrity sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg== - dependencies: - global-prefix "^1.0.1" - is-windows "^1.0.1" - resolve-dir "^1.0.0" - -global-prefix@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-1.0.2.tgz#dbf743c6c14992593c655568cb66ed32c0122ebe" - integrity sha512-5lsx1NUDHtSjfg0eHlmYvZKv8/nVqX4ckFbM+FrGcQ+04KWcWFo9P5MxPZYSzUvyzmdTbI7Eix8Q4IbELDqzKg== - dependencies: - expand-tilde "^2.0.2" - homedir-polyfill "^1.0.1" - ini "^1.3.4" - is-windows "^1.0.1" - which "^1.2.14" - globals@^11.1.0: version "11.12.0" resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" @@ -3880,15 +3293,6 @@ globals@^14.0.0: resolved "https://registry.yarnpkg.com/globals/-/globals-14.0.0.tgz#898d7413c29babcf6bafe56fcadded858ada724e" integrity sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ== -globule@^1.0.0: - version "1.3.4" - resolved "https://registry.yarnpkg.com/globule/-/globule-1.3.4.tgz#7c11c43056055a75a6e68294453c17f2796170fb" - integrity sha512-OPTIfhMBh7JbBYDpa5b+Q5ptmMWKwcNcFSR/0c6t8V4f3ZAVBEsKNY37QdVqmLRYSMhOUGYrY0QhSoEpzGr/Eg== - dependencies: - glob "~7.1.1" - lodash "^4.17.21" - minimatch "~3.0.2" - gopd@^1.0.1, gopd@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1" @@ -3899,95 +3303,6 @@ graceful-fs@^4.1.2, graceful-fs@^4.2.9: resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== -graphemer@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" - integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== - -grunt-cli@~1.4.3: - version "1.4.3" - resolved "https://registry.yarnpkg.com/grunt-cli/-/grunt-cli-1.4.3.tgz#22c9f1a3d2780bf9b0d206e832e40f8f499175ff" - integrity sha512-9Dtx/AhVeB4LYzsViCjUQkd0Kw0McN2gYpdmGYKtE2a5Yt7v1Q+HYZVWhqXc/kGnxlMtqKDxSwotiGeFmkrCoQ== - dependencies: - grunt-known-options "~2.0.0" - interpret "~1.1.0" - liftup "~3.0.1" - nopt "~4.0.1" - v8flags "~3.2.0" - -grunt-contrib-watch@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/grunt-contrib-watch/-/grunt-contrib-watch-1.1.0.tgz#c143ca5b824b288a024b856639a5345aedb78ed4" - integrity sha512-yGweN+0DW5yM+oo58fRu/XIRrPcn3r4tQx+nL7eMRwjpvk+rQY6R8o94BPK0i2UhTg9FN21hS+m8vR8v9vXfeg== - dependencies: - async "^2.6.0" - gaze "^1.1.0" - lodash "^4.17.10" - tiny-lr "^1.1.1" - -grunt-eslint@^25.0.0: - version "25.0.0" - resolved "https://registry.yarnpkg.com/grunt-eslint/-/grunt-eslint-25.0.0.tgz#e04d21fdc8e772511dae201d4c13e8f4f60823bc" - integrity sha512-JIV5IPgOuacorFLmYtUTq0n+0qGIL9FSQJ4KVnNfCg/8Fm+K1t6OWrzXXI8TxWTwq2K9E3parFVXCpn1sGLbKQ== - dependencies: - chalk "^4.1.2" - eslint "^9.0.0" - -grunt-known-options@~2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/grunt-known-options/-/grunt-known-options-2.0.0.tgz#cac641e897f9a0a680b8c9839803d35f3325103c" - integrity sha512-GD7cTz0I4SAede1/+pAbmJRG44zFLPipVtdL9o3vqx9IEyb7b4/Y3s7r6ofI3CchR5GvYJ+8buCSioDv5dQLiA== - -grunt-legacy-log-utils@~2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/grunt-legacy-log-utils/-/grunt-legacy-log-utils-2.1.0.tgz#49a8c7dc74051476dcc116c32faf9db8646856ef" - integrity sha512-lwquaPXJtKQk0rUM1IQAop5noEpwFqOXasVoedLeNzaibf/OPWjKYvvdqnEHNmU+0T0CaReAXIbGo747ZD+Aaw== - dependencies: - chalk "~4.1.0" - lodash "~4.17.19" - -grunt-legacy-log@~3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/grunt-legacy-log/-/grunt-legacy-log-3.0.0.tgz#1c6eaf92371ea415af31ea84ce50d434ef6d39c4" - integrity sha512-GHZQzZmhyq0u3hr7aHW4qUH0xDzwp2YXldLPZTCjlOeGscAOWWPftZG3XioW8MasGp+OBRIu39LFx14SLjXRcA== - dependencies: - colors "~1.1.2" - grunt-legacy-log-utils "~2.1.0" - hooker "~0.2.3" - lodash "~4.17.19" - -grunt-legacy-util@~2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/grunt-legacy-util/-/grunt-legacy-util-2.0.1.tgz#0f929d13a2faf9988c9917c82bff609e2d9ba255" - integrity sha512-2bQiD4fzXqX8rhNdXkAywCadeqiPiay0oQny77wA2F3WF4grPJXCvAcyoWUJV+po/b15glGkxuSiQCK299UC2w== - dependencies: - async "~3.2.0" - exit "~0.1.2" - getobject "~1.0.0" - hooker "~0.2.3" - lodash "~4.17.21" - underscore.string "~3.3.5" - which "~2.0.2" - -grunt@^1.6.1: - version "1.6.1" - resolved "https://registry.yarnpkg.com/grunt/-/grunt-1.6.1.tgz#0b4dd1524f26676dcf45d8f636b8d9061a8ede16" - integrity sha512-/ABUy3gYWu5iBmrUSRBP97JLpQUm0GgVveDCp6t3yRNIoltIYw7rEj3g5y1o2PGPR2vfTRGa7WC/LZHLTXnEzA== - dependencies: - dateformat "~4.6.2" - eventemitter2 "~0.4.13" - exit "~0.1.2" - findup-sync "~5.0.0" - glob "~7.1.6" - grunt-cli "~1.4.3" - grunt-known-options "~2.0.0" - grunt-legacy-log "~3.0.0" - grunt-legacy-util "~2.0.1" - iconv-lite "~0.6.3" - js-yaml "~3.14.0" - minimatch "~3.0.4" - nopt "~3.0.6" - has-flag@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" @@ -4024,40 +3339,16 @@ he@^1.2.0: resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== -homedir-polyfill@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz#743298cef4e5af3e194161fbadcc2151d3a058e8" - integrity sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA== - dependencies: - parse-passwd "^1.0.0" - -hooker@~0.2.3: - version "0.2.3" - resolved "https://registry.yarnpkg.com/hooker/-/hooker-0.2.3.tgz#b834f723cc4a242aa65963459df6d984c5d3d959" - integrity sha512-t+UerCsQviSymAInD01Pw+Dn/usmz1sRO+3Zk1+lx8eg+WKpD2ulcwWqHHL0+aseRBr+3+vIhiG1K1JTwaIcTA== - html-escaper@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== -http-parser-js@>=0.5.1: - version "0.5.8" - resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.8.tgz#af23090d9ac4e24573de6f6aecc9d84a48bf20e3" - integrity sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q== - human-signals@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== -iconv-lite@~0.6.3: - version "0.6.3" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" - integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== - dependencies: - safer-buffer ">= 2.1.2 < 3.0.0" - ieee754@1.1.13: version "1.1.13" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84" @@ -4068,7 +3359,7 @@ ieee754@^1.1.4: resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== -ignore@^5.2.0, ignore@^5.3.1: +ignore@^5.2.0: version "5.3.2" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5" integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== @@ -4107,24 +3398,6 @@ inherits@2, inherits@^2.0.1, inherits@^2.0.3: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== -ini@^1.3.4: - version "1.3.8" - resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" - integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== - -interpret@~1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.1.0.tgz#7ed1b1410c6a0e0f78cf95d3b8440c63f78b8614" - integrity sha512-CLM8SNMDu7C5psFCn6Wg/tgpj/bKAg7hc2gWqcuR9OD5Ft9PhBpIu8PLicPeis+xDd6YX2ncI8MCA64I9tftIA== - -is-absolute@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-absolute/-/is-absolute-1.0.0.tgz#395e1ae84b11f26ad1795e73c17378e48a301576" - integrity sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA== - dependencies: - is-relative "^1.0.0" - is-windows "^1.0.1" - is-arguments@^1.0.4: version "1.1.1" resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.1.1.tgz#15b3f88fda01f2a97fec84ca761a560f123efa9b" @@ -4201,20 +3474,6 @@ is-plain-obj@^2.1.0: resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287" integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA== -is-plain-object@^2.0.3, is-plain-object@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" - integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== - dependencies: - isobject "^3.0.1" - -is-relative@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-relative/-/is-relative-1.0.0.tgz#a1bb6935ce8c5dba1e8b9754b9b2dcc020e2260d" - integrity sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA== - dependencies: - is-unc-path "^1.0.0" - is-stream@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" @@ -4227,23 +3486,11 @@ is-typed-array@^1.1.3: dependencies: which-typed-array "^1.1.14" -is-unc-path@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-unc-path/-/is-unc-path-1.0.0.tgz#d731e8898ed090a12c352ad2eaed5095ad322c9d" - integrity sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ== - dependencies: - unc-path-regex "^0.1.2" - is-unicode-supported@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== -is-windows@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" - integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== - isarray@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" @@ -4254,11 +3501,6 @@ isexe@^2.0.0: resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== -isobject@^3.0.0, isobject@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" - integrity sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg== - istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: version "3.2.2" resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz#2d166c4b0644d43a39f04bf6c2edd1e585f31756" @@ -4321,16 +3563,6 @@ jackspeak@^3.1.2: optionalDependencies: "@pkgjs/parseargs" "^0.11.0" -jake@^10.8.5: - version "10.9.2" - resolved "https://registry.yarnpkg.com/jake/-/jake-10.9.2.tgz#6ae487e6a69afec3a5e167628996b59f35ae2b7f" - integrity sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA== - dependencies: - async "^3.2.3" - chalk "^4.0.2" - filelist "^1.0.4" - minimatch "^3.1.2" - jest-changed-files@^29.7.0: version "29.7.0" resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-29.7.0.tgz#1c06d07e77c78e1585d020424dedc10d6e17ac3a" @@ -4631,7 +3863,7 @@ jest-snapshot@^29.7.0: pretty-format "^29.7.0" semver "^7.5.3" -jest-util@^29.0.0, jest-util@^29.7.0: +jest-util@^29.7.0: version "29.7.0" resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.7.0.tgz#23c2b62bfb22be82b44de98055802ff3710fc0bc" integrity sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA== @@ -4689,11 +3921,6 @@ jest@^29.7.0: import-local "^3.0.2" jest-cli "^29.7.0" -jit-grunt@^0.10.0: - version "0.10.0" - resolved "https://registry.yarnpkg.com/jit-grunt/-/jit-grunt-0.10.0.tgz#008c3a7fe1e96bd0d84e260ea1fa1783457f79c2" - integrity sha512-eT/f4c9wgZ3buXB7X1JY1w6uNtAV0bhrbOGf/mFmBb0CDNLUETJ/VRoydayWOI54tOoam0cz9RooVCn3QY1WoA== - jmespath@0.16.0: version "0.16.0" resolved "https://registry.yarnpkg.com/jmespath/-/jmespath-0.16.0.tgz#b15b0a85dfd4d930d43e69ed605943c802785076" @@ -4704,7 +3931,7 @@ jmespath@0.16.0: resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== -js-yaml@^3.13.1, js-yaml@^3.14.1, js-yaml@~3.14.0: +js-yaml@^3.13.1, js-yaml@^3.14.1: version "3.14.1" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== @@ -4761,11 +3988,6 @@ keyv@^4.5.4: dependencies: json-buffer "3.0.1" -kind-of@^6.0.2: - version "6.0.3" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" - integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== - kleur@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" @@ -4798,30 +4020,11 @@ levn@^0.4.1: prelude-ls "^1.2.1" type-check "~0.4.0" -liftup@~3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/liftup/-/liftup-3.0.1.tgz#1cb81aff0f368464ed3a5f1a7286372d6b1a60ce" - integrity sha512-yRHaiQDizWSzoXk3APcA71eOI/UuhEkNN9DiW2Tt44mhYzX4joFoCZlxsSOF7RyeLlfqzFLQI1ngFq3ggMPhOw== - dependencies: - extend "^3.0.2" - findup-sync "^4.0.0" - fined "^1.2.0" - flagged-respawn "^1.0.1" - is-plain-object "^2.0.4" - object.map "^1.0.1" - rechoir "^0.7.0" - resolve "^1.19.0" - lines-and-columns@^1.1.6: version "1.2.4" resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== -livereload-js@^2.3.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/livereload-js/-/livereload-js-2.4.0.tgz#447c31cf1ea9ab52fc20db615c5ddf678f78009c" - integrity sha512-XPQH8Z2GDP/Hwz2PCDrh2mth4yFejwA1OZ/81Ti3LgKyhDcEjsSsqFWZojHG0va/duGd+WyosY7eXLDoOyqcPw== - locate-path@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" @@ -4858,17 +4061,12 @@ lodash.get@^4.4.2: resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" integrity sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ== -lodash.memoize@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" - integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== - lodash.merge@^4.6.2: version "4.6.2" resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== -lodash@^4.0.0, lodash@^4.17.10, lodash@^4.17.14, lodash@^4.17.21, lodash@~4.17.19, lodash@~4.17.21: +lodash@^4.0.0, lodash@^4.17.21: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -4931,18 +4129,6 @@ make-dir@^4.0.0: dependencies: semver "^7.5.3" -make-error@^1.1.1, make-error@^1.3.6: - version "1.3.6" - resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" - integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== - -make-iterator@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/make-iterator/-/make-iterator-1.0.1.tgz#29b33f312aa8f547c4a5e490f56afcec99133ad6" - integrity sha512-pxiuXh0iVEq7VM7KMIhs5gxsfxCux2URptUQaXo4iZZJxBAzTPOLE2BumO5dbfVYq/hBJFBR/a1mFDmOx5AGmw== - dependencies: - kind-of "^6.0.2" - makeerror@1.0.12: version "1.0.12" resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.12.tgz#3e5dd2079a82e812e983cc6610c4a2cb0eaa801a" @@ -4950,11 +4136,6 @@ makeerror@1.0.12: dependencies: tmpl "1.0.5" -map-cache@^0.2.0: - version "0.2.2" - resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" - integrity sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg== - markdown-parser-react@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/markdown-parser-react/-/markdown-parser-react-1.1.2.tgz#5817a708ea1edc33579f436346cba866d58d4792" @@ -4967,12 +4148,7 @@ merge-stream@^2.0.0: resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== -merge2@^1.3.0: - version "1.4.1" - resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" - integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== - -micromatch@^4.0.2, micromatch@^4.0.4: +micromatch@^4.0.4: version "4.0.8" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== @@ -4992,7 +4168,7 @@ minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2: dependencies: brace-expansion "^1.1.7" -minimatch@^5.0.1, minimatch@^5.1.6: +minimatch@^5.1.6: version "5.1.6" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96" integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== @@ -5006,13 +4182,6 @@ minimatch@^9.0.4: dependencies: brace-expansion "^2.0.1" -minimatch@~3.0.2, minimatch@~3.0.4: - version "3.0.8" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.8.tgz#5e6a59bd11e2ab0de1cfb843eb2d82e546c321c1" - integrity sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q== - dependencies: - brace-expansion "^1.1.7" - "minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.1.2: version "7.1.2" resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.2.tgz#93a9626ce5e5e66bd4db86849e7515e92340a707" @@ -5087,21 +4256,6 @@ nodemailer@^6.9.12: resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-6.9.16.tgz#3ebdf6c6f477c571c0facb0727b33892635e0b8b" integrity sha512-psAuZdTIRN08HKVd/E8ObdV6NO7NTBY3KsC30F7M4H1OnmLCUNaS56FpYxyb26zWLSyYF9Ozch9KYHhHegsiOQ== -nopt@~3.0.6: - version "3.0.6" - resolved "https://registry.yarnpkg.com/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9" - integrity sha512-4GUt3kSEYmk4ITxzB/b9vaIDfUVWN/Ml1Fwl11IlnIG2iaJ9O6WXZ9SrYM9NLI8OCBieN2Y8SWC2oJV0RQ7qYg== - dependencies: - abbrev "1" - -nopt@~4.0.1: - version "4.0.3" - resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.3.tgz#a375cad9d02fd921278d954c2254d5aa57e15e48" - integrity sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg== - dependencies: - abbrev "1" - osenv "^0.1.4" - normalize-path@^3.0.0, normalize-path@~3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" @@ -5114,41 +4268,6 @@ npm-run-path@^4.0.1: dependencies: path-key "^3.0.0" -object-assign@^4.1.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" - integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== - -object-inspect@^1.13.1: - version "1.13.3" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.3.tgz#f14c183de51130243d6d18ae149375ff50ea488a" - integrity sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA== - -object.defaults@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/object.defaults/-/object.defaults-1.1.0.tgz#3a7f868334b407dea06da16d88d5cd29e435fecf" - integrity sha512-c/K0mw/F11k4dEUBMW8naXUuBuhxRCfG7W+yFy8EcijU/rSmazOUd1XAEEe6bC0OuXY4HUKjTJv7xbxIMqdxrA== - dependencies: - array-each "^1.0.1" - array-slice "^1.0.0" - for-own "^1.0.0" - isobject "^3.0.0" - -object.map@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/object.map/-/object.map-1.0.1.tgz#cf83e59dc8fcc0ad5f4250e1f78b3b81bd801d37" - integrity sha512-3+mAJu2PLfnSVGHwIWubpOFLscJANBKuB/6A4CxBstc4aqwQY0FWcsppuy4jU5GSB95yES5JHSI+33AWuS4k6w== - dependencies: - for-own "^1.0.0" - make-iterator "^1.0.0" - -object.pick@^1.2.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747" - integrity sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ== - dependencies: - isobject "^3.0.1" - obliterator@^1.6.1: version "1.6.1" resolved "https://registry.yarnpkg.com/obliterator/-/obliterator-1.6.1.tgz#dea03e8ab821f6c4d96a299e17aef6a3af994ef3" @@ -5187,24 +4306,6 @@ optionator@^0.9.3: type-check "^0.4.0" word-wrap "^1.2.5" -os-homedir@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" - integrity sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ== - -os-tmpdir@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" - integrity sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g== - -osenv@^0.1.4: - version "0.1.5" - resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.5.tgz#85cdfafaeb28e8677f416e287592b5f3f49ea410" - integrity sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g== - dependencies: - os-homedir "^1.0.0" - os-tmpdir "^1.0.0" - p-limit@^2.2.0: version "2.3.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" @@ -5250,15 +4351,6 @@ parent-module@^1.0.0: dependencies: callsites "^3.0.0" -parse-filepath@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/parse-filepath/-/parse-filepath-1.0.2.tgz#a632127f53aaf3d15876f5872f3ffac763d6c891" - integrity sha512-FwdRXKCohSVeXqwtYonZTXtbGJKrn+HNyWDYVcp5yuJlesTwNH4rsmRZ+GrKAPJ5bLpRxESMeS+Rl0VCHRvB2Q== - dependencies: - is-absolute "^1.0.0" - map-cache "^0.2.0" - path-root "^0.1.1" - parse-json@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" @@ -5269,11 +4361,6 @@ parse-json@^5.2.0: json-parse-even-better-errors "^2.3.0" lines-and-columns "^1.1.6" -parse-passwd@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6" - integrity sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q== - path-exists@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" @@ -5294,18 +4381,6 @@ path-parse@^1.0.7: resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== -path-root-regex@^0.1.0: - version "0.1.2" - resolved "https://registry.yarnpkg.com/path-root-regex/-/path-root-regex-0.1.2.tgz#bfccdc8df5b12dc52c8b43ec38d18d72c04ba96d" - integrity sha512-4GlJ6rZDhQZFE0DPVKh0e9jmZ5egZfxTkp7bcRDuPlJXbAwhxcl2dINPUAsjLdejqaLsCeg8axcLjIbvBjN4pQ== - -path-root@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/path-root/-/path-root-0.1.1.tgz#9a4a6814cac1c0cd73360a95f32083c8ea4745b7" - integrity sha512-QLcPegTHF11axjfojBIoDygmS2E3Lf+8+jI6wOVmNVenrKSo3mFdSGiIgdSHenczw3wPtlVMQaFVwGmM7BJdtg== - dependencies: - path-root-regex "^0.1.0" - path-scurry@^1.11.1: version "1.11.1" resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.11.1.tgz#7960a668888594a0720b12a911d1a742ab9f11d2" @@ -5361,7 +4436,7 @@ prelude-ls@^1.2.1: resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== -pretty-format@^29.0.0, pretty-format@^29.7.0: +pretty-format@^29.7.0: version "29.7.0" resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.7.0.tgz#ca42c758310f365bfa71a0bda0a807160b776812" integrity sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ== @@ -5393,23 +4468,11 @@ pure-rand@^6.0.0: resolved "https://registry.yarnpkg.com/pure-rand/-/pure-rand-6.1.0.tgz#d173cf23258231976ccbdb05247c9787957604f2" integrity sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA== -qs@^6.4.0: - version "6.13.1" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.13.1.tgz#3ce5fc72bd3a8171b85c99b93c65dd20b7d1b16e" - integrity sha512-EJPeIn0CYrGu+hli1xilKAPXODtJ12T0sP63Ijx2/khC2JtuaN3JyNIpvmnkmaEtha9ocbG4A4cMcr+TvqvwQg== - dependencies: - side-channel "^1.0.6" - querystring@0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" integrity sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g== -queue-microtask@^1.2.2: - version "1.2.3" - resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" - integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== - randombytes@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" @@ -5417,28 +4480,12 @@ randombytes@^2.1.0: dependencies: safe-buffer "^5.1.0" -raw-body@~1.1.0: - version "1.1.7" - resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-1.1.7.tgz#1d027c2bfa116acc6623bca8f00016572a87d425" - integrity sha512-WmJJU2e9Y6M5UzTOkHaM7xJGAPQD8PNzx3bAd2+uhZAim6wDk6dAZxPVYLF67XhbR4hmKGh33Lpmh4XWrCH5Mg== - dependencies: - bytes "1" - string_decoder "0.10" - -react-dom@^18.3.1: - version "18.3.1" - resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.3.1.tgz#c2265d79511b57d479b3dd3fdfa51536494c5cb4" - integrity sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw== - dependencies: - loose-envify "^1.1.0" - scheduler "^0.23.2" - react-is@^18.0.0: version "18.3.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e" integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg== -react@^18.2.0, react@^18.3.1: +react@^18.2.0: version "18.3.1" resolved "https://registry.yarnpkg.com/react/-/react-18.3.1.tgz#49ab892009c53933625bd16b2533fc754cab2891" integrity sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ== @@ -5461,13 +4508,6 @@ readdirp@~3.6.0: dependencies: picomatch "^2.2.1" -rechoir@^0.7.0: - version "0.7.1" - resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.7.1.tgz#9478a96a1ca135b5e88fc027f03ee92d6c645686" - integrity sha512-/njmZ8s1wVeR6pjTZ+0nCnv8SpZNRMT2D1RLOJQESlYFDBvwpTA4KWJpZ+sBJ4+vhjILRcK7JIFdGCdxEAAitg== - dependencies: - resolve "^1.9.0" - require-directory@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" @@ -5480,14 +4520,6 @@ resolve-cwd@^3.0.0: dependencies: resolve-from "^5.0.0" -resolve-dir@^1.0.0, resolve-dir@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/resolve-dir/-/resolve-dir-1.0.1.tgz#79a40644c362be82f26effe739c9bb5382046f43" - integrity sha512-R7uiTjECzvOsWSfdM0QKFNBVFcK27aHOUwdvK53BcW8zqnGdYp0Fbj82cy54+2A4P2tFM22J5kRfe1R+lM/1yg== - dependencies: - expand-tilde "^2.0.0" - global-modules "^1.0.0" - resolve-from@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" @@ -5498,17 +4530,12 @@ resolve-from@^5.0.0: resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== -resolve-pkg-maps@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz#616b3dc2c57056b5588c31cdf4b3d64db133720f" - integrity sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw== - resolve.exports@^2.0.0: version "2.0.3" resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-2.0.3.tgz#41955e6f1b4013b7586f873749a635dea07ebe3f" integrity sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A== -resolve@^1.19.0, resolve@^1.20.0, resolve@^1.9.0: +resolve@^1.20.0: version "1.22.8" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d" integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== @@ -5517,38 +4544,16 @@ resolve@^1.19.0, resolve@^1.20.0, resolve@^1.9.0: path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" -reusify@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" - integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== - -run-parallel@^1.1.9: - version "1.2.0" - resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" - integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== - dependencies: - queue-microtask "^1.2.2" - -safe-buffer@>=5.1.0, safe-buffer@^5.1.0, safe-buffer@~5.2.0: +safe-buffer@^5.1.0, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== -safe-json-parse@~1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/safe-json-parse/-/safe-json-parse-1.0.1.tgz#3e76723e38dfdda13c9b1d29a1e07ffee4b30b57" - integrity sha512-o0JmTu17WGUaUOHa1l0FPGXKBfijbxK6qoHzlkihsDXxzBHvJcA7zgviKR92Xs841rX9pK16unfphLq0/KqX7A== - safe-stable-stringify@^2.3.1: version "2.5.0" resolved "https://registry.yarnpkg.com/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz#4ca2f8e385f2831c432a719b108a3bf7af42a1dd" integrity sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA== -"safer-buffer@>= 2.1.2 < 3.0.0": - version "2.1.2" - resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" - integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== - sax@1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.1.tgz#7b8e656190b228e81a66aea748480d828cd2d37a" @@ -5559,19 +4564,12 @@ sax@>=0.6.0: resolved "https://registry.yarnpkg.com/sax/-/sax-1.4.1.tgz#44cc8988377f126304d3b3fc1010c733b929ef0f" integrity sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg== -scheduler@^0.23.2: - version "0.23.2" - resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.2.tgz#414ba64a3b282892e944cf2108ecc078d115cdc3" - integrity sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ== - dependencies: - loose-envify "^1.1.0" - semver@^6.3.0, semver@^6.3.1: version "6.3.1" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== -semver@^7.5.3, semver@^7.5.4, semver@^7.6.0, semver@^7.6.3: +semver@^7.5.3, semver@^7.5.4: version "7.6.3" resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143" integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== @@ -5607,16 +4605,6 @@ shebang-regex@^3.0.0: resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== -side-channel@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.6.tgz#abd25fb7cd24baf45466406b1096b7831c9215f2" - integrity sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA== - dependencies: - call-bind "^1.0.7" - es-errors "^1.3.0" - get-intrinsic "^1.2.4" - object-inspect "^1.13.1" - signal-exit@^3.0.3, signal-exit@^3.0.7: version "3.0.7" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" @@ -5669,11 +4657,6 @@ source-map@^0.6.0, source-map@^0.6.1: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== -sprintf-js@^1.1.1: - version "1.1.3" - resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.3.tgz#4914b903a2f8b685d17fdf78a70e917e872e444a" - integrity sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA== - sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" @@ -5699,11 +4682,6 @@ string-length@^4.0.1: char-regex "^1.0.2" strip-ansi "^6.0.0" -string-template@~0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/string-template/-/string-template-0.2.1.tgz#42932e598a352d01fc22ec3367d9d84eec6c9add" - integrity sha512-Yptehjogou2xm4UJbxJ4CxgZx12HBfeystp0y3x7s4Dj32ltVVG1Gg8YhKjHZkHicuKpZX/ffilA8505VbUbpw== - "string-width-cjs@npm:string-width@^4.2.0": version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" @@ -5731,11 +4709,6 @@ string-width@^5.0.1, string-width@^5.1.2: emoji-regex "^9.2.2" strip-ansi "^7.0.1" -string_decoder@0.10: - version "0.10.31" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" - integrity sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ== - string_decoder@^1.1.1: version "1.3.0" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" @@ -5817,18 +4790,6 @@ text-hex@1.0.x: resolved "https://registry.yarnpkg.com/text-hex/-/text-hex-1.0.0.tgz#69dc9c1b17446ee79a92bf5b884bb4b9127506f5" integrity sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg== -tiny-lr@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/tiny-lr/-/tiny-lr-1.1.1.tgz#9fa547412f238fedb068ee295af8b682c98b2aab" - integrity sha512-44yhA3tsaRoMOjQQ+5v5mVdqef+kH6Qze9jTpqtVufgYjYt08zyZAwNwwVBj3i1rJMnR52IxOW0LK0vBzgAkuA== - dependencies: - body "^5.1.0" - debug "^3.1.0" - faye-websocket "~0.10.0" - livereload-js "^2.3.0" - object-assign "^4.1.0" - qs "^6.4.0" - tinyrainbow@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/tinyrainbow/-/tinyrainbow-1.2.0.tgz#5c57d2fc0fb3d1afd78465c33ca885d04f02abb5" @@ -5856,60 +4817,11 @@ triple-beam@^1.3.0: resolved "https://registry.yarnpkg.com/triple-beam/-/triple-beam-1.4.1.tgz#6fde70271dc6e5d73ca0c3b24e2d92afb7441984" integrity sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg== -ts-api-utils@^1.3.0: - version "1.4.3" - resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.4.3.tgz#bfc2215fe6528fecab2b0fba570a2e8a4263b064" - integrity sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw== - -ts-jest@^29.2.5: - version "29.2.5" - resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.2.5.tgz#591a3c108e1f5ebd013d3152142cb5472b399d63" - integrity sha512-KD8zB2aAZrcKIdGk4OwpJggeLcH1FgrICqDSROWqlnJXGCXK4Mn6FcdK2B6670Xr73lHMG1kHw8R87A0ecZ+vA== - dependencies: - bs-logger "^0.2.6" - ejs "^3.1.10" - fast-json-stable-stringify "^2.1.0" - jest-util "^29.0.0" - json5 "^2.2.3" - lodash.memoize "^4.1.2" - make-error "^1.3.6" - semver "^7.6.3" - yargs-parser "^21.1.1" - -ts-node@^10.9.2: - version "10.9.2" - resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.2.tgz#70f021c9e185bccdca820e26dc413805c101c71f" - integrity sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ== - dependencies: - "@cspotcode/source-map-support" "^0.8.0" - "@tsconfig/node10" "^1.0.7" - "@tsconfig/node12" "^1.0.7" - "@tsconfig/node14" "^1.0.0" - "@tsconfig/node16" "^1.0.2" - acorn "^8.4.1" - acorn-walk "^8.1.1" - arg "^4.1.0" - create-require "^1.1.0" - diff "^4.0.1" - make-error "^1.1.1" - v8-compile-cache-lib "^3.0.1" - yn "3.1.1" - -tslib@^2.1.0, tslib@^2.6.2, tslib@^2.8.0: +tslib@^2.1.0, tslib@^2.6.2: version "2.8.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== -tsx@^4.19.2: - version "4.19.2" - resolved "https://registry.yarnpkg.com/tsx/-/tsx-4.19.2.tgz#2d7814783440e0ae42354d0417d9c2989a2ae92c" - integrity sha512-pOUl6Vo2LUq/bSa8S5q7b91cgNSjctn9ugq/+Mvow99qW6x/UZYwzxy/3NmqoT66eHYfCVvFvACC58UBPFf28g== - dependencies: - esbuild "~0.23.0" - get-tsconfig "^4.7.5" - optionalDependencies: - fsevents "~2.3.3" - type-check@^0.4.0, type-check@~0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" @@ -5932,29 +4844,6 @@ type-fest@^0.21.3: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== -typescript@5.6.3: - version "5.6.3" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.6.3.tgz#5f3449e31c9d94febb17de03cc081dd56d81db5b" - integrity sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw== - -unc-path-regex@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/unc-path-regex/-/unc-path-regex-0.1.2.tgz#e73dd3d7b0d7c5ed86fbac6b0ae7d8c6a69d50fa" - integrity sha512-eXL4nmJT7oCpkZsHZUOJo8hcX3GbsiDOa0Qu9F646fi8dT3XuSVopVqAcEiVzSKKH7UoDti23wNX3qGFxcW5Qg== - -underscore.string@~3.3.5: - version "3.3.6" - resolved "https://registry.yarnpkg.com/underscore.string/-/underscore.string-3.3.6.tgz#ad8cf23d7423cb3b53b898476117588f4e2f9159" - integrity sha512-VoC83HWXmCrF6rgkyxS9GHv8W9Q5nhMKho+OadDJGzL2oDYbYEppBaCMH6pFlwLeqj2QS+hhkw2kpXkSdD1JxQ== - dependencies: - sprintf-js "^1.1.1" - util-deprecate "^1.0.2" - -undici-types@~6.19.2: - version "6.19.8" - resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.19.8.tgz#35111c9d1437ab83a7cdc0abae2f26d88eda0a02" - integrity sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw== - undici-types@~6.20.0: version "6.20.0" resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.20.0.tgz#8171bf22c1f588d1554d55bf204bc624af388433" @@ -5983,7 +4872,7 @@ url@0.10.3: punycode "1.3.2" querystring "0.2.0" -util-deprecate@^1.0.1, util-deprecate@^1.0.2: +util-deprecate@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== @@ -6009,11 +4898,6 @@ uuid@^9.0.1: resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.1.tgz#e188d4c8853cc722220392c424cd637f32293f30" integrity sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA== -v8-compile-cache-lib@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" - integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== - v8-to-istanbul@^9.0.1: version "9.3.0" resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz#b9572abfa62bd556c16d75fdebc1a411d5ff3175" @@ -6023,13 +4907,6 @@ v8-to-istanbul@^9.0.1: "@types/istanbul-lib-coverage" "^2.0.1" convert-source-map "^2.0.0" -v8flags@~3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/v8flags/-/v8flags-3.2.0.tgz#b243e3b4dfd731fa774e7492128109a0fe66d656" - integrity sha512-mH8etigqMfiGWdeXpaaqGfs6BndypxusHHcv2qSHyZkGEznCd/qAXCWWRzeowtL54147cktFOC4P5y+kl8d8Jg== - dependencies: - homedir-polyfill "^1.0.1" - walker@^1.0.8: version "1.0.8" resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f" @@ -6045,20 +4922,6 @@ watchpack@^2.0.0-beta.10: glob-to-regexp "^0.4.1" graceful-fs "^4.1.2" -websocket-driver@>=0.5.1: - version "0.7.4" - resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.4.tgz#89ad5295bbf64b480abcba31e4953aca706f5760" - integrity sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg== - dependencies: - http-parser-js ">=0.5.1" - safe-buffer ">=5.1.0" - websocket-extensions ">=0.1.1" - -websocket-extensions@>=0.1.1: - version "0.1.4" - resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42" - integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg== - which-typed-array@^1.1.14, which-typed-array@^1.1.2: version "1.1.16" resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.16.tgz#db4db429c4706feca2f01677a144278e4a8c216b" @@ -6070,14 +4933,7 @@ which-typed-array@^1.1.14, which-typed-array@^1.1.2: gopd "^1.0.1" has-tostringtag "^1.0.2" -which@^1.2.14: - version "1.3.1" - resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" - integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== - dependencies: - isexe "^2.0.0" - -which@^2.0.1, which@~2.0.2: +which@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== @@ -6229,11 +5085,6 @@ yargs@^17.3.1: y18n "^5.0.5" yargs-parser "^21.1.1" -yn@3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" - integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== - yocto-queue@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" diff --git a/backend/compact-connect/lambdas/nodejs/Gruntfile.js b/backend/compact-connect/lambdas/nodejs/Gruntfile.js deleted file mode 100644 index 92efc316e..000000000 --- a/backend/compact-connect/lambdas/nodejs/Gruntfile.js +++ /dev/null @@ -1,64 +0,0 @@ -// -// Gruntfile.js -// CompactConnect -// -// Created by InspiringApps on 7/22/2024. -// - -module.exports = function (grunt) { - require('jit-grunt')(grunt); - - grunt.initConfig({ - //==================== - //= Directory config = - //==================== - lambdaFiles: [ - 'cloudfront-csp/**/**.js', - '!**/node_modules/**/*.js', - '!**/dist/**/*.js', - '!Gruntfile.js', - ], - changedFiles: ['<%= lambdaFiles %>'], - - //=========== - //= ES Lint = - //=========== - eslint: { - initial: { - src: ['<%= lambdaFiles %>'], - }, - watch: { - src: '<%= changedFiles %>', - } - }, - - //================ - //= Watch config = - //================ - watch: { - lambdaFiles: { - files: ['<%= lambdaFiles %>'], - tasks: ['eslint:watch'], - options: { spawn: false } - }, - } - - }); - - // Update the `changedFiles` value with just the files that changed. - // Tasks that operate on <%= changedFiles %> will then operate faster. - var changedFiles = Object.create(null); - var onChange = grunt.util._.debounce(function() { - grunt.config('changedFiles', Object.keys(changedFiles)); - changedFiles = Object.create(null); - }, 200); - - grunt.event.on('watch', function(action, filepath) { - changedFiles[filepath] = action; - onChange(); - }); - - // Task definition - grunt.registerTask('default', ['eslint:initial', 'watch']); - grunt.registerTask('check', ['eslint:initial']); -}; diff --git a/backend/compact-connect/lambdas/nodejs/package.json b/backend/compact-connect/lambdas/nodejs/package.json index 18dd179c4..178e328d6 100644 --- a/backend/compact-connect/lambdas/nodejs/package.json +++ b/backend/compact-connect/lambdas/nodejs/package.json @@ -6,15 +6,10 @@ "scripts": { "build": "tsc", "watch": "tsc -w", - "test:ingest": "NODE_OPTIONS=\"$NODE_OPTIONS --experimental-vm-modules\" jest --coverage", - "lint:ingest": "eslint '**/*.ts'", - "lint:ingest:fix": "eslint --fix", - "test:csp": "mocha cloudfront-csp/test", - "lint:csp": "grunt check", - "lint": "eslint '**/*.ts' && grunt check", + "lint:fix": "eslint '**/*.ts' --fix", + "lint": "eslint '**/*.ts'", "test": "NODE_OPTIONS=\"$NODE_OPTIONS --experimental-vm-modules\" jest --coverage", - "audit:dependencies": "yarn audit --groups dependencies --level moderate", - "grunt": "grunt" + "audit:dependencies": "yarn audit --groups dependencies --level moderate" }, "author": "Inspiring Apps", "license": "UNLICENSED", @@ -33,11 +28,7 @@ "chalk": "^4.1.2", "esbuild": "0.24.0", "eslint": "^9.13.0", - "grunt": "^1.6.1", - "grunt-contrib-watch": "^1.1.0", - "grunt-eslint": "^26.0.0", "jest": "^29.7.0", - "jit-grunt": "^0.10.0", "lambda-local": "^2.2.0", "mocha": "^11.0.0", "react": "^18.3.1", diff --git a/backend/compact-connect/lambdas/nodejs/yarn.lock b/backend/compact-connect/lambdas/nodejs/yarn.lock index 2a4516004..1d84fea78 100644 --- a/backend/compact-connect/lambdas/nodejs/yarn.lock +++ b/backend/compact-connect/lambdas/nodejs/yarn.lock @@ -1282,13 +1282,6 @@ dependencies: eslint-visitor-keys "^3.4.3" -"@eslint-community/eslint-utils@^4.8.0": - version "4.9.0" - resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz#7308df158e064f0dd8b8fdb58aa14fa2a7f913b3" - integrity sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g== - dependencies: - eslint-visitor-keys "^3.4.3" - "@eslint-community/regexpp@^4.10.0", "@eslint-community/regexpp@^4.12.1": version "4.12.1" resolved "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz" @@ -1303,27 +1296,6 @@ debug "^4.3.1" minimatch "^3.1.2" -"@eslint/config-array@^0.21.0": - version "0.21.0" - resolved "https://registry.yarnpkg.com/@eslint/config-array/-/config-array-0.21.0.tgz#abdbcbd16b124c638081766392a4d6b509f72636" - integrity sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ== - dependencies: - "@eslint/object-schema" "^2.1.6" - debug "^4.3.1" - minimatch "^3.1.2" - -"@eslint/config-helpers@^0.3.1": - version "0.3.1" - resolved "https://registry.yarnpkg.com/@eslint/config-helpers/-/config-helpers-0.3.1.tgz#d316e47905bd0a1a931fa50e669b9af4104d1617" - integrity sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA== - -"@eslint/core@^0.15.2": - version "0.15.2" - resolved "https://registry.yarnpkg.com/@eslint/core/-/core-0.15.2.tgz#59386327d7862cc3603ebc7c78159d2dcc4a868f" - integrity sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg== - dependencies: - "@types/json-schema" "^7.0.15" - "@eslint/core@^0.9.0": version "0.9.1" resolved "https://registry.npmjs.org/@eslint/core/-/core-0.9.1.tgz" @@ -1346,41 +1318,16 @@ minimatch "^3.1.2" strip-json-comments "^3.1.1" -"@eslint/eslintrc@^3.3.1": - version "3.3.1" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-3.3.1.tgz#e55f7f1dd400600dd066dbba349c4c0bac916964" - integrity sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ== - dependencies: - ajv "^6.12.4" - debug "^4.3.2" - espree "^10.0.1" - globals "^14.0.0" - ignore "^5.2.0" - import-fresh "^3.2.1" - js-yaml "^4.1.0" - minimatch "^3.1.2" - strip-json-comments "^3.1.1" - "@eslint/js@9.16.0": version "9.16.0" resolved "https://registry.npmjs.org/@eslint/js/-/js-9.16.0.tgz" integrity sha512-tw2HxzQkrbeuvyj1tG2Yqq+0H9wGoI2IMk4EOsQeX+vmd75FtJAzf+gTA69WF+baUKRYQ3x2kbLE08js5OsTVg== -"@eslint/js@9.36.0": - version "9.36.0" - resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.36.0.tgz#b1a3893dd6ce2defed5fd49de805ba40368e8fef" - integrity sha512-uhCbYtYynH30iZErszX78U+nR3pJU3RHGQ57NXy5QupD4SBVwDeU8TNBy+MjMngc1UyIW9noKqsRqfjQTBU2dw== - "@eslint/object-schema@^2.1.5": version "2.1.5" resolved "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.5.tgz" integrity sha512-o0bhxnL89h5Bae5T318nFoFzGy+YE5i/gGkoPAgkmTVdRKTiv3p8JHevPiPaMwoloKfEiiaHlawCqaZMqRm+XQ== -"@eslint/object-schema@^2.1.6": - version "2.1.6" - resolved "https://registry.yarnpkg.com/@eslint/object-schema/-/object-schema-2.1.6.tgz#58369ab5b5b3ca117880c0f6c0b0f32f6950f24f" - integrity sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA== - "@eslint/plugin-kit@^0.2.3": version "0.2.4" resolved "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.4.tgz" @@ -1388,14 +1335,6 @@ dependencies: levn "^0.4.1" -"@eslint/plugin-kit@^0.3.5": - version "0.3.5" - resolved "https://registry.yarnpkg.com/@eslint/plugin-kit/-/plugin-kit-0.3.5.tgz#fd8764f0ee79c8ddab4da65460c641cefee017c5" - integrity sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w== - dependencies: - "@eslint/core" "^0.15.2" - levn "^0.4.1" - "@humanfs/core@^0.19.1": version "0.19.1" resolved "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz" @@ -1424,11 +1363,6 @@ resolved "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.1.tgz" integrity sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA== -"@humanwhocodes/retry@^0.4.2": - version "0.4.3" - resolved "https://registry.yarnpkg.com/@humanwhocodes/retry/-/retry-0.4.3.tgz#c2b9d2e374ee62c586d3adbea87199b1d7a7a6ba" - integrity sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ== - "@isaacs/cliui@^8.0.2": version "8.0.2" resolved "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz" @@ -2609,11 +2543,6 @@ loupe "^3.1.2" tinyrainbow "^1.2.0" -abbrev@1: - version "1.1.1" - resolved "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz" - integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== - acorn-jsx@^5.3.2: version "5.3.2" resolved "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz" @@ -2631,11 +2560,6 @@ acorn@^8.11.0, acorn@^8.14.0, acorn@^8.4.1: resolved "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz" integrity sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA== -acorn@^8.15.0: - version "8.15.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.15.0.tgz#a360898bc415edaac46c8241f6383975b930b816" - integrity sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg== - ajv@^6.12.4: version "6.12.6" resolved "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz" @@ -2710,16 +2634,6 @@ argparse@^2.0.1: resolved "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz" integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== -array-each@^1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/array-each/-/array-each-1.0.1.tgz" - integrity sha512-zHjL5SZa68hkKHBFBK6DJCTtr9sfTCPCaph/L7tMSLcTFgy+zX7E+6q5UArbtOtMBCtxdICpfTCspRse+ywyXA== - -array-slice@^1.0.0: - version "1.1.0" - resolved "https://registry.npmjs.org/array-slice/-/array-slice-1.1.0.tgz" - integrity sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w== - assertion-error@^1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz" @@ -2730,14 +2644,7 @@ assertion-error@^2.0.1: resolved "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz" integrity sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA== -async@^2.6.0: - version "2.6.4" - resolved "https://registry.npmjs.org/async/-/async-2.6.4.tgz" - integrity sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA== - dependencies: - lodash "^4.17.14" - -async@^3.2.3, async@~3.2.0: +async@^3.2.3: version "3.2.6" resolved "https://registry.npmjs.org/async/-/async-3.2.6.tgz" integrity sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA== @@ -2871,16 +2778,6 @@ binary-extensions@^2.0.0: resolved "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz" integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw== -body@^5.1.0: - version "5.1.0" - resolved "https://registry.npmjs.org/body/-/body-5.1.0.tgz" - integrity sha512-chUsBxGRtuElD6fmw1gHLpvnKdVLK302peeFa9ZqAEk8TyzZ3fygLyUEDDPTJvL9+Bor0dIwn6ePOsRM2y0zQQ== - dependencies: - continuable-cache "^0.3.1" - error "^7.0.0" - raw-body "~1.1.0" - safe-json-parse "~1.0.1" - bowser@^2.11.0: version "2.11.0" resolved "https://registry.npmjs.org/bowser/-/bowser-2.11.0.tgz" @@ -2951,11 +2848,6 @@ buffer@4.9.2: ieee754 "^1.1.4" isarray "^1.0.0" -bytes@1: - version "1.0.0" - resolved "https://registry.npmjs.org/bytes/-/bytes-1.0.0.tgz" - integrity sha512-/x68VkHLeTl3/Ll8IvxdwzhrT+IyKc52e/oyHhA2RwqPqswSnjVbSddfPRwAsJtbilMAPSRWwAlpxdYsSWOTKQ== - call-bind-apply-helpers@^1.0.0: version "1.0.1" resolved "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz" @@ -3025,7 +2917,7 @@ chai@^5.1.2: loupe "^3.1.0" pathval "^2.0.0" -chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.0, chalk@^4.1.2, chalk@~4.1.0: +chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.0, chalk@^4.1.2: version "4.1.2" resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== @@ -3151,11 +3043,6 @@ color@^3.1.3: color-convert "^1.9.3" color-string "^1.6.0" -colors@~1.1.2: - version "1.1.2" - resolved "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz" - integrity sha512-ENwblkFQpqqia6b++zLD/KUWafYlVY/UNnAp7oz7LY7E924wmpye416wBOmvv/HMWzl8gL1kJlfvId/1Dg176w== - colorspace@1.1.x: version "1.1.4" resolved "https://registry.npmjs.org/colorspace/-/colorspace-1.1.4.tgz" @@ -3179,11 +3066,6 @@ concat-map@0.0.1: resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== -continuable-cache@^0.3.1: - version "0.3.1" - resolved "https://registry.npmjs.org/continuable-cache/-/continuable-cache-0.3.1.tgz" - integrity sha512-TF30kpKhTH8AGCG3dut0rdd/19B7Z+qCnrMoBLpyQu/2drZdNrrpcjPEoJeSVsQM+8KmWG5O56oPDjSSUsuTyA== - convert-source-map@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz" @@ -3207,7 +3089,7 @@ create-require@^1.1.0: resolved "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz" integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== -cross-spawn@^7.0.0, cross-spawn@^7.0.3, cross-spawn@^7.0.5, cross-spawn@^7.0.6: +cross-spawn@^7.0.0, cross-spawn@^7.0.3, cross-spawn@^7.0.5: version "7.0.6" resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz" integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== @@ -3221,18 +3103,6 @@ csstype@^3.0.2: resolved "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz" integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw== -dateformat@~4.6.2: - version "4.6.3" - resolved "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz" - integrity sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA== - -debug@^3.1.0: - version "3.2.7" - resolved "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz" - integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== - dependencies: - ms "^2.1.1" - debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@^4.3.5: version "4.4.0" resolved "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz" @@ -3281,11 +3151,6 @@ define-data-property@^1.1.4: es-errors "^1.3.0" gopd "^1.0.1" -detect-file@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz" - integrity sha512-DtCOLG98P007x7wiiOmfI0fi3eIKyWiLTGJ2MDnVi/E04lWGbf+JzrRHMm0rgIIZJGtHpKpbVgLWHrv8xXpc3Q== - detect-newline@^3.0.0: version "3.1.0" resolved "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz" @@ -3399,13 +3264,6 @@ error-ex@^1.3.1: dependencies: is-arrayish "^0.2.1" -error@^7.0.0: - version "7.2.1" - resolved "https://registry.npmjs.org/error/-/error-7.2.1.tgz" - integrity sha512-fo9HBvWnx3NGUKMvMwB/CBCMMrfEJgbDTVDEkPygA3Bdd3lM1OyCd+rbQ8BwnpF6GdVeOLDNmyL4N5Bg80ZvdA== - dependencies: - string-template "~0.2.1" - es-define-property@^1.0.0, es-define-property@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz" @@ -3499,14 +3357,6 @@ eslint-scope@^8.2.0: esrecurse "^4.3.0" estraverse "^5.2.0" -eslint-scope@^8.4.0: - version "8.4.0" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-8.4.0.tgz#88e646a207fad61436ffa39eb505147200655c82" - integrity sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg== - dependencies: - esrecurse "^4.3.0" - estraverse "^5.2.0" - eslint-visitor-keys@^3.4.3: version "3.4.3" resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz" @@ -3517,11 +3367,6 @@ eslint-visitor-keys@^4.2.0: resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz" integrity sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw== -eslint-visitor-keys@^4.2.1: - version "4.2.1" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz#4cfea60fe7dd0ad8e816e1ed026c1d5251b512c1" - integrity sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ== - eslint@^9.13.0: version "9.16.0" resolved "https://registry.npmjs.org/eslint/-/eslint-9.16.0.tgz" @@ -3562,47 +3407,6 @@ eslint@^9.13.0: natural-compare "^1.4.0" optionator "^0.9.3" -eslint@^9.22.0: - version "9.36.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.36.0.tgz#9cc5cbbfb9c01070425d9bfed81b4e79a1c09088" - integrity sha512-hB4FIzXovouYzwzECDcUkJ4OcfOEkXTv2zRY6B9bkwjx/cprAq0uvm1nl7zvQ0/TsUk0zQiN4uPfJpB9m+rPMQ== - dependencies: - "@eslint-community/eslint-utils" "^4.8.0" - "@eslint-community/regexpp" "^4.12.1" - "@eslint/config-array" "^0.21.0" - "@eslint/config-helpers" "^0.3.1" - "@eslint/core" "^0.15.2" - "@eslint/eslintrc" "^3.3.1" - "@eslint/js" "9.36.0" - "@eslint/plugin-kit" "^0.3.5" - "@humanfs/node" "^0.16.6" - "@humanwhocodes/module-importer" "^1.0.1" - "@humanwhocodes/retry" "^0.4.2" - "@types/estree" "^1.0.6" - "@types/json-schema" "^7.0.15" - ajv "^6.12.4" - chalk "^4.0.0" - cross-spawn "^7.0.6" - debug "^4.3.2" - escape-string-regexp "^4.0.0" - eslint-scope "^8.4.0" - eslint-visitor-keys "^4.2.1" - espree "^10.4.0" - esquery "^1.5.0" - esutils "^2.0.2" - fast-deep-equal "^3.1.3" - file-entry-cache "^8.0.0" - find-up "^5.0.0" - glob-parent "^6.0.2" - ignore "^5.2.0" - imurmurhash "^0.1.4" - is-glob "^4.0.0" - json-stable-stringify-without-jsonify "^1.0.1" - lodash.merge "^4.6.2" - minimatch "^3.1.2" - natural-compare "^1.4.0" - optionator "^0.9.3" - espree@^10.0.1, espree@^10.3.0: version "10.3.0" resolved "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz" @@ -3612,15 +3416,6 @@ espree@^10.0.1, espree@^10.3.0: acorn-jsx "^5.3.2" eslint-visitor-keys "^4.2.0" -espree@^10.4.0: - version "10.4.0" - resolved "https://registry.yarnpkg.com/espree/-/espree-10.4.0.tgz#d54f4949d4629005a1fa168d937c3ff1f7e2a837" - integrity sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ== - dependencies: - acorn "^8.15.0" - acorn-jsx "^5.3.2" - eslint-visitor-keys "^4.2.1" - esprima@^4.0.0: version "4.0.1" resolved "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz" @@ -3650,11 +3445,6 @@ esutils@^2.0.2: resolved "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== -eventemitter2@~0.4.13: - version "0.4.14" - resolved "https://registry.npmjs.org/eventemitter2/-/eventemitter2-0.4.14.tgz" - integrity sha512-K7J4xq5xAD5jHsGM5ReWXRTFa3JRGofHiMcVgQ8PRwgWxzjHpMWCIzsmyf60+mh8KLsqYPcjUMa0AC4hd6lPyQ== - events@1.1.1: version "1.1.1" resolved "https://registry.npmjs.org/events/-/events-1.1.1.tgz" @@ -3675,18 +3465,11 @@ execa@^5.0.0: signal-exit "^3.0.3" strip-final-newline "^2.0.0" -exit@^0.1.2, exit@~0.1.2: +exit@^0.1.2: version "0.1.2" resolved "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz" integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ== -expand-tilde@^2.0.0, expand-tilde@^2.0.2: - version "2.0.2" - resolved "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz" - integrity sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw== - dependencies: - homedir-polyfill "^1.0.1" - expect@>28.1.3, expect@^29.0.0, expect@^29.7.0: version "29.7.0" resolved "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz" @@ -3698,11 +3481,6 @@ expect@>28.1.3, expect@^29.0.0, expect@^29.7.0: jest-message-util "^29.7.0" jest-util "^29.7.0" -extend@^3.0.2: - version "3.0.2" - resolved "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz" - integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== - fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: version "3.1.3" resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz" @@ -3743,13 +3521,6 @@ fastq@^1.6.0: dependencies: reusify "^1.0.4" -faye-websocket@~0.10.0: - version "0.10.0" - resolved "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz" - integrity sha512-Xhj93RXbMSq8urNCUq4p9l0P6hnySJ/7YNRhYNug0bLOuii7pKO7xQFb5mx9xZXWCar88pLPb805PvUkwrLZpQ== - dependencies: - websocket-driver ">=0.5.1" - fb-watchman@^2.0.0: version "2.0.2" resolved "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz" @@ -3799,42 +3570,6 @@ find-up@^5.0.0: locate-path "^6.0.0" path-exists "^4.0.0" -findup-sync@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/findup-sync/-/findup-sync-4.0.0.tgz" - integrity sha512-6jvvn/12IC4quLBL1KNokxC7wWTvYncaVUYSoxWw7YykPLuRrnv4qdHcSOywOI5RpkOVGeQRtWM8/q+G6W6qfQ== - dependencies: - detect-file "^1.0.0" - is-glob "^4.0.0" - micromatch "^4.0.2" - resolve-dir "^1.0.1" - -findup-sync@~5.0.0: - version "5.0.0" - resolved "https://registry.npmjs.org/findup-sync/-/findup-sync-5.0.0.tgz" - integrity sha512-MzwXju70AuyflbgeOhzvQWAvvQdo1XL0A9bVvlXsYcFEBM87WR4OakL4OfZq+QRmr+duJubio+UtNQCPsVESzQ== - dependencies: - detect-file "^1.0.0" - is-glob "^4.0.3" - micromatch "^4.0.4" - resolve-dir "^1.0.1" - -fined@^1.2.0: - version "1.2.0" - resolved "https://registry.npmjs.org/fined/-/fined-1.2.0.tgz" - integrity sha512-ZYDqPLGxDkDhDZBjZBb+oD1+j0rA4E0pXY50eplAAOPg2N/gUBSSk5IM1/QhPfyVo19lJ+CvXpqfvk+b2p/8Ng== - dependencies: - expand-tilde "^2.0.2" - is-plain-object "^2.0.3" - object.defaults "^1.1.0" - object.pick "^1.2.0" - parse-filepath "^1.0.1" - -flagged-respawn@^1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/flagged-respawn/-/flagged-respawn-1.0.1.tgz" - integrity sha512-lNaHNVymajmk0OJMBn8fVUAU1BtDeKIqKoVhk4xAALB57aALg6b4W0MfJ/cUE0g9YBXy5XhSlPIpYIJ7HaY/3Q== - flat-cache@^4.0.0: version "4.0.1" resolved "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz" @@ -3865,18 +3600,6 @@ for-each@^0.3.3: dependencies: is-callable "^1.1.3" -for-in@^1.0.1: - version "1.0.2" - resolved "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz" - integrity sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ== - -for-own@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz" - integrity sha512-0OABksIGrxKK8K4kynWkQ7y1zounQxP+CWnyclVwj81KW3vlLlGUx57DKGcP/LH216GzqnstnPocF16Nxs0Ycg== - dependencies: - for-in "^1.0.1" - foreground-child@^3.1.0: version "3.3.0" resolved "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz" @@ -3900,13 +3623,6 @@ function-bind@^1.1.2: resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz" integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== -gaze@^1.1.0: - version "1.1.3" - resolved "https://registry.npmjs.org/gaze/-/gaze-1.1.3.tgz" - integrity sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g== - dependencies: - globule "^1.0.0" - gensync@^1.0.0-beta.2: version "1.0.0-beta.2" resolved "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz" @@ -3953,11 +3669,6 @@ get-tsconfig@^4.7.5: dependencies: resolve-pkg-maps "^1.0.0" -getobject@~1.0.0: - version "1.0.2" - resolved "https://registry.npmjs.org/getobject/-/getobject-1.0.2.tgz" - integrity sha512-2zblDBaFcb3rB4rF77XVnuINOE2h2k/OnqXAiy0IrTxUfV1iFp3la33oAQVY9pCpWU268WFYVt2t71hlMuLsOg== - glob-parent@^5.1.2, glob-parent@~5.1.2: version "5.1.2" resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz" @@ -4001,38 +3712,6 @@ glob@^7.1.3, glob@^7.1.4: once "^1.3.0" path-is-absolute "^1.0.0" -glob@~7.1.1, glob@~7.1.6: - version "7.1.7" - resolved "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz" - integrity sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.4" - once "^1.3.0" - path-is-absolute "^1.0.0" - -global-modules@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz" - integrity sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg== - dependencies: - global-prefix "^1.0.1" - is-windows "^1.0.1" - resolve-dir "^1.0.0" - -global-prefix@^1.0.1: - version "1.0.2" - resolved "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz" - integrity sha512-5lsx1NUDHtSjfg0eHlmYvZKv8/nVqX4ckFbM+FrGcQ+04KWcWFo9P5MxPZYSzUvyzmdTbI7Eix8Q4IbELDqzKg== - dependencies: - expand-tilde "^2.0.2" - homedir-polyfill "^1.0.1" - ini "^1.3.4" - is-windows "^1.0.1" - which "^1.2.14" - globals@^11.1.0: version "11.12.0" resolved "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz" @@ -4043,15 +3722,6 @@ globals@^14.0.0: resolved "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz" integrity sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ== -globule@^1.0.0: - version "1.3.4" - resolved "https://registry.npmjs.org/globule/-/globule-1.3.4.tgz" - integrity sha512-OPTIfhMBh7JbBYDpa5b+Q5ptmMWKwcNcFSR/0c6t8V4f3ZAVBEsKNY37QdVqmLRYSMhOUGYrY0QhSoEpzGr/Eg== - dependencies: - glob "~7.1.1" - lodash "^4.17.21" - minimatch "~3.0.2" - gopd@^1.0.1, gopd@^1.2.0: version "1.2.0" resolved "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz" @@ -4067,90 +3737,6 @@ graphemer@^1.4.0: resolved "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz" integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== -grunt-cli@~1.4.3: - version "1.4.3" - resolved "https://registry.npmjs.org/grunt-cli/-/grunt-cli-1.4.3.tgz" - integrity sha512-9Dtx/AhVeB4LYzsViCjUQkd0Kw0McN2gYpdmGYKtE2a5Yt7v1Q+HYZVWhqXc/kGnxlMtqKDxSwotiGeFmkrCoQ== - dependencies: - grunt-known-options "~2.0.0" - interpret "~1.1.0" - liftup "~3.0.1" - nopt "~4.0.1" - v8flags "~3.2.0" - -grunt-contrib-watch@^1.1.0: - version "1.1.0" - resolved "https://registry.npmjs.org/grunt-contrib-watch/-/grunt-contrib-watch-1.1.0.tgz" - integrity sha512-yGweN+0DW5yM+oo58fRu/XIRrPcn3r4tQx+nL7eMRwjpvk+rQY6R8o94BPK0i2UhTg9FN21hS+m8vR8v9vXfeg== - dependencies: - async "^2.6.0" - gaze "^1.1.0" - lodash "^4.17.10" - tiny-lr "^1.1.1" - -grunt-eslint@^26.0.0: - version "26.0.0" - resolved "https://registry.yarnpkg.com/grunt-eslint/-/grunt-eslint-26.0.0.tgz#9f779cf64f618318d7e26fe025bcc5f19f1f4a05" - integrity sha512-HP/Mu00nBtdLDk0i1V1gLplYBgce4nBjxwmtpo3Eit/h5OUzGxEsgBxprkT4I5bN1x79w6wDnrN+7CX29OZatg== - dependencies: - chalk "^4.1.2" - eslint "^9.22.0" - -grunt-known-options@~2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/grunt-known-options/-/grunt-known-options-2.0.0.tgz" - integrity sha512-GD7cTz0I4SAede1/+pAbmJRG44zFLPipVtdL9o3vqx9IEyb7b4/Y3s7r6ofI3CchR5GvYJ+8buCSioDv5dQLiA== - -grunt-legacy-log-utils@~2.1.0: - version "2.1.0" - resolved "https://registry.npmjs.org/grunt-legacy-log-utils/-/grunt-legacy-log-utils-2.1.0.tgz" - integrity sha512-lwquaPXJtKQk0rUM1IQAop5noEpwFqOXasVoedLeNzaibf/OPWjKYvvdqnEHNmU+0T0CaReAXIbGo747ZD+Aaw== - dependencies: - chalk "~4.1.0" - lodash "~4.17.19" - -grunt-legacy-log@~3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/grunt-legacy-log/-/grunt-legacy-log-3.0.0.tgz" - integrity sha512-GHZQzZmhyq0u3hr7aHW4qUH0xDzwp2YXldLPZTCjlOeGscAOWWPftZG3XioW8MasGp+OBRIu39LFx14SLjXRcA== - dependencies: - colors "~1.1.2" - grunt-legacy-log-utils "~2.1.0" - hooker "~0.2.3" - lodash "~4.17.19" - -grunt-legacy-util@~2.0.1: - version "2.0.1" - resolved "https://registry.npmjs.org/grunt-legacy-util/-/grunt-legacy-util-2.0.1.tgz" - integrity sha512-2bQiD4fzXqX8rhNdXkAywCadeqiPiay0oQny77wA2F3WF4grPJXCvAcyoWUJV+po/b15glGkxuSiQCK299UC2w== - dependencies: - async "~3.2.0" - exit "~0.1.2" - getobject "~1.0.0" - hooker "~0.2.3" - lodash "~4.17.21" - underscore.string "~3.3.5" - which "~2.0.2" - -grunt@^1.6.1: - version "1.6.1" - resolved "https://registry.npmjs.org/grunt/-/grunt-1.6.1.tgz" - integrity sha512-/ABUy3gYWu5iBmrUSRBP97JLpQUm0GgVveDCp6t3yRNIoltIYw7rEj3g5y1o2PGPR2vfTRGa7WC/LZHLTXnEzA== - dependencies: - dateformat "~4.6.2" - eventemitter2 "~0.4.13" - exit "~0.1.2" - findup-sync "~5.0.0" - glob "~7.1.6" - grunt-cli "~1.4.3" - grunt-known-options "~2.0.0" - grunt-legacy-log "~3.0.0" - grunt-legacy-util "~2.0.1" - iconv-lite "~0.6.3" - js-yaml "~3.14.0" - minimatch "~3.0.4" - nopt "~3.0.6" - has-flag@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz" @@ -4187,18 +3773,6 @@ he@^1.2.0: resolved "https://registry.npmjs.org/he/-/he-1.2.0.tgz" integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== -homedir-polyfill@^1.0.1: - version "1.0.3" - resolved "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz" - integrity sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA== - dependencies: - parse-passwd "^1.0.0" - -hooker@~0.2.3: - version "0.2.3" - resolved "https://registry.npmjs.org/hooker/-/hooker-0.2.3.tgz" - integrity sha512-t+UerCsQviSymAInD01Pw+Dn/usmz1sRO+3Zk1+lx8eg+WKpD2ulcwWqHHL0+aseRBr+3+vIhiG1K1JTwaIcTA== - html-escaper@^2.0.0: version "2.0.2" resolved "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz" @@ -4214,23 +3788,11 @@ htmlparser2@^8.0.0: domutils "^3.0.1" entities "^4.4.0" -http-parser-js@>=0.5.1: - version "0.5.8" - resolved "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz" - integrity sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q== - human-signals@^2.1.0: version "2.1.0" resolved "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz" integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== -iconv-lite@~0.6.3: - version "0.6.3" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" - integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== - dependencies: - safer-buffer ">= 2.1.2 < 3.0.0" - ieee754@1.1.13: version "1.1.13" resolved "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz" @@ -4280,24 +3842,6 @@ inherits@2, inherits@^2.0.1, inherits@^2.0.3: resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== -ini@^1.3.4: - version "1.3.8" - resolved "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz" - integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== - -interpret@~1.1.0: - version "1.1.0" - resolved "https://registry.npmjs.org/interpret/-/interpret-1.1.0.tgz" - integrity sha512-CLM8SNMDu7C5psFCn6Wg/tgpj/bKAg7hc2gWqcuR9OD5Ft9PhBpIu8PLicPeis+xDd6YX2ncI8MCA64I9tftIA== - -is-absolute@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz" - integrity sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA== - dependencies: - is-relative "^1.0.0" - is-windows "^1.0.1" - is-arguments@^1.0.4: version "1.1.1" resolved "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz" @@ -4374,25 +3918,11 @@ is-plain-obj@^2.1.0: resolved "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz" integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA== -is-plain-object@^2.0.3, is-plain-object@^2.0.4: - version "2.0.4" - resolved "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz" - integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== - dependencies: - isobject "^3.0.1" - is-plain-object@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-5.0.0.tgz#4427f50ab3429e9025ea7d52e9043a9ef4159344" integrity sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q== -is-relative@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz" - integrity sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA== - dependencies: - is-unc-path "^1.0.0" - is-stream@^2.0.0: version "2.0.1" resolved "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz" @@ -4405,23 +3935,11 @@ is-typed-array@^1.1.3: dependencies: which-typed-array "^1.1.14" -is-unc-path@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz" - integrity sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ== - dependencies: - unc-path-regex "^0.1.2" - is-unicode-supported@^0.1.0: version "0.1.0" resolved "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz" integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== -is-windows@^1.0.1: - version "1.0.2" - resolved "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz" - integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== - isarray@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz" @@ -4432,11 +3950,6 @@ isexe@^2.0.0: resolved "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz" integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== -isobject@^3.0.0, isobject@^3.0.1: - version "3.0.1" - resolved "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz" - integrity sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg== - istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: version "3.2.2" resolved "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz" @@ -4867,11 +4380,6 @@ jest@^29.7.0: import-local "^3.0.2" jest-cli "^29.7.0" -jit-grunt@^0.10.0: - version "0.10.0" - resolved "https://registry.npmjs.org/jit-grunt/-/jit-grunt-0.10.0.tgz" - integrity sha512-eT/f4c9wgZ3buXB7X1JY1w6uNtAV0bhrbOGf/mFmBb0CDNLUETJ/VRoydayWOI54tOoam0cz9RooVCn3QY1WoA== - jmespath@0.16.0: version "0.16.0" resolved "https://registry.npmjs.org/jmespath/-/jmespath-0.16.0.tgz" @@ -4882,7 +4390,7 @@ jmespath@0.16.0: resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== -js-yaml@^3.13.1, js-yaml@^3.14.1, js-yaml@~3.14.0: +js-yaml@^3.13.1, js-yaml@^3.14.1: version "3.14.1" resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz" integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== @@ -4939,11 +4447,6 @@ keyv@^4.5.4: dependencies: json-buffer "3.0.1" -kind-of@^6.0.2: - version "6.0.3" - resolved "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz" - integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== - kleur@^3.0.3: version "3.0.3" resolved "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz" @@ -4976,30 +4479,11 @@ levn@^0.4.1: prelude-ls "^1.2.1" type-check "~0.4.0" -liftup@~3.0.1: - version "3.0.1" - resolved "https://registry.npmjs.org/liftup/-/liftup-3.0.1.tgz" - integrity sha512-yRHaiQDizWSzoXk3APcA71eOI/UuhEkNN9DiW2Tt44mhYzX4joFoCZlxsSOF7RyeLlfqzFLQI1ngFq3ggMPhOw== - dependencies: - extend "^3.0.2" - findup-sync "^4.0.0" - fined "^1.2.0" - flagged-respawn "^1.0.1" - is-plain-object "^2.0.4" - object.map "^1.0.1" - rechoir "^0.7.0" - resolve "^1.19.0" - lines-and-columns@^1.1.6: version "1.2.4" resolved "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz" integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== -livereload-js@^2.3.0: - version "2.4.0" - resolved "https://registry.npmjs.org/livereload-js/-/livereload-js-2.4.0.tgz" - integrity sha512-XPQH8Z2GDP/Hwz2PCDrh2mth4yFejwA1OZ/81Ti3LgKyhDcEjsSsqFWZojHG0va/duGd+WyosY7eXLDoOyqcPw== - locate-path@^5.0.0: version "5.0.0" resolved "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz" @@ -5046,7 +4530,7 @@ lodash.merge@^4.6.2: resolved "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz" integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== -lodash@^4.0.0, lodash@^4.17.10, lodash@^4.17.14, lodash@^4.17.21, lodash@~4.17.19, lodash@~4.17.21: +lodash@^4.0.0, lodash@^4.17.21: version "4.17.21" resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -5114,13 +4598,6 @@ make-error@^1.1.1, make-error@^1.3.6: resolved "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz" integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== -make-iterator@^1.0.0: - version "1.0.1" - resolved "https://registry.npmjs.org/make-iterator/-/make-iterator-1.0.1.tgz" - integrity sha512-pxiuXh0iVEq7VM7KMIhs5gxsfxCux2URptUQaXo4iZZJxBAzTPOLE2BumO5dbfVYq/hBJFBR/a1mFDmOx5AGmw== - dependencies: - kind-of "^6.0.2" - makeerror@1.0.12: version "1.0.12" resolved "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz" @@ -5128,11 +4605,6 @@ makeerror@1.0.12: dependencies: tmpl "1.0.5" -map-cache@^0.2.0: - version "0.2.2" - resolved "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz" - integrity sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg== - marked@^12.0.2: version "12.0.2" resolved "https://registry.npmjs.org/marked/-/marked-12.0.2.tgz" @@ -5148,7 +4620,7 @@ merge2@^1.3.0: resolved "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz" integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== -micromatch@^4.0.2, micromatch@^4.0.4: +micromatch@^4.0.4: version "4.0.8" resolved "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz" integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== @@ -5182,13 +4654,6 @@ minimatch@^9.0.4: dependencies: brace-expansion "^2.0.1" -minimatch@~3.0.2, minimatch@~3.0.4: - version "3.0.8" - resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.0.8.tgz" - integrity sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q== - dependencies: - brace-expansion "^1.1.7" - "minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.1.2: version "7.1.2" resolved "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz" @@ -5268,21 +4733,6 @@ nodemailer@^6.9.12: resolved "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.16.tgz" integrity sha512-psAuZdTIRN08HKVd/E8ObdV6NO7NTBY3KsC30F7M4H1OnmLCUNaS56FpYxyb26zWLSyYF9Ozch9KYHhHegsiOQ== -nopt@~3.0.6: - version "3.0.6" - resolved "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz" - integrity sha512-4GUt3kSEYmk4ITxzB/b9vaIDfUVWN/Ml1Fwl11IlnIG2iaJ9O6WXZ9SrYM9NLI8OCBieN2Y8SWC2oJV0RQ7qYg== - dependencies: - abbrev "1" - -nopt@~4.0.1: - version "4.0.3" - resolved "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz" - integrity sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg== - dependencies: - abbrev "1" - osenv "^0.1.4" - normalize-path@^3.0.0, normalize-path@~3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz" @@ -5295,41 +4745,6 @@ npm-run-path@^4.0.1: dependencies: path-key "^3.0.0" -object-assign@^4.1.0: - version "4.1.1" - resolved "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz" - integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== - -object-inspect@^1.13.1: - version "1.13.3" - resolved "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz" - integrity sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA== - -object.defaults@^1.1.0: - version "1.1.0" - resolved "https://registry.npmjs.org/object.defaults/-/object.defaults-1.1.0.tgz" - integrity sha512-c/K0mw/F11k4dEUBMW8naXUuBuhxRCfG7W+yFy8EcijU/rSmazOUd1XAEEe6bC0OuXY4HUKjTJv7xbxIMqdxrA== - dependencies: - array-each "^1.0.1" - array-slice "^1.0.0" - for-own "^1.0.0" - isobject "^3.0.0" - -object.map@^1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/object.map/-/object.map-1.0.1.tgz" - integrity sha512-3+mAJu2PLfnSVGHwIWubpOFLscJANBKuB/6A4CxBstc4aqwQY0FWcsppuy4jU5GSB95yES5JHSI+33AWuS4k6w== - dependencies: - for-own "^1.0.0" - make-iterator "^1.0.0" - -object.pick@^1.2.0: - version "1.3.0" - resolved "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz" - integrity sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ== - dependencies: - isobject "^3.0.1" - obliterator@^1.6.1: version "1.6.1" resolved "https://registry.npmjs.org/obliterator/-/obliterator-1.6.1.tgz" @@ -5368,24 +4783,6 @@ optionator@^0.9.3: type-check "^0.4.0" word-wrap "^1.2.5" -os-homedir@^1.0.0: - version "1.0.2" - resolved "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz" - integrity sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ== - -os-tmpdir@^1.0.0: - version "1.0.2" - resolved "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz" - integrity sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g== - -osenv@^0.1.4: - version "0.1.5" - resolved "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz" - integrity sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g== - dependencies: - os-homedir "^1.0.0" - os-tmpdir "^1.0.0" - p-limit@^2.2.0: version "2.3.0" resolved "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz" @@ -5431,15 +4828,6 @@ parent-module@^1.0.0: dependencies: callsites "^3.0.0" -parse-filepath@^1.0.1: - version "1.0.2" - resolved "https://registry.npmjs.org/parse-filepath/-/parse-filepath-1.0.2.tgz" - integrity sha512-FwdRXKCohSVeXqwtYonZTXtbGJKrn+HNyWDYVcp5yuJlesTwNH4rsmRZ+GrKAPJ5bLpRxESMeS+Rl0VCHRvB2Q== - dependencies: - is-absolute "^1.0.0" - map-cache "^0.2.0" - path-root "^0.1.1" - parse-json@^5.2.0: version "5.2.0" resolved "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz" @@ -5450,11 +4838,6 @@ parse-json@^5.2.0: json-parse-even-better-errors "^2.3.0" lines-and-columns "^1.1.6" -parse-passwd@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz" - integrity sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q== - parse-srcset@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/parse-srcset/-/parse-srcset-1.0.2.tgz#f2bd221f6cc970a938d88556abc589caaaa2bde1" @@ -5480,18 +4863,6 @@ path-parse@^1.0.7: resolved "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== -path-root-regex@^0.1.0: - version "0.1.2" - resolved "https://registry.npmjs.org/path-root-regex/-/path-root-regex-0.1.2.tgz" - integrity sha512-4GlJ6rZDhQZFE0DPVKh0e9jmZ5egZfxTkp7bcRDuPlJXbAwhxcl2dINPUAsjLdejqaLsCeg8axcLjIbvBjN4pQ== - -path-root@^0.1.1: - version "0.1.1" - resolved "https://registry.npmjs.org/path-root/-/path-root-0.1.1.tgz" - integrity sha512-QLcPegTHF11axjfojBIoDygmS2E3Lf+8+jI6wOVmNVenrKSo3mFdSGiIgdSHenczw3wPtlVMQaFVwGmM7BJdtg== - dependencies: - path-root-regex "^0.1.0" - path-scurry@^1.11.1: version "1.11.1" resolved "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz" @@ -5588,13 +4959,6 @@ pure-rand@^6.0.0: resolved "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz" integrity sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA== -qs@^6.4.0: - version "6.13.1" - resolved "https://registry.npmjs.org/qs/-/qs-6.13.1.tgz" - integrity sha512-EJPeIn0CYrGu+hli1xilKAPXODtJ12T0sP63Ijx2/khC2JtuaN3JyNIpvmnkmaEtha9ocbG4A4cMcr+TvqvwQg== - dependencies: - side-channel "^1.0.6" - querystring@0.2.0: version "0.2.0" resolved "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz" @@ -5612,14 +4976,6 @@ randombytes@^2.1.0: dependencies: safe-buffer "^5.1.0" -raw-body@~1.1.0: - version "1.1.7" - resolved "https://registry.npmjs.org/raw-body/-/raw-body-1.1.7.tgz" - integrity sha512-WmJJU2e9Y6M5UzTOkHaM7xJGAPQD8PNzx3bAd2+uhZAim6wDk6dAZxPVYLF67XhbR4hmKGh33Lpmh4XWrCH5Mg== - dependencies: - bytes "1" - string_decoder "0.10" - react-dom@^18.3.1: version "18.3.1" resolved "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz" @@ -5656,13 +5012,6 @@ readdirp@~3.6.0: dependencies: picomatch "^2.2.1" -rechoir@^0.7.0: - version "0.7.1" - resolved "https://registry.npmjs.org/rechoir/-/rechoir-0.7.1.tgz" - integrity sha512-/njmZ8s1wVeR6pjTZ+0nCnv8SpZNRMT2D1RLOJQESlYFDBvwpTA4KWJpZ+sBJ4+vhjILRcK7JIFdGCdxEAAitg== - dependencies: - resolve "^1.9.0" - require-directory@^2.1.1: version "2.1.1" resolved "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz" @@ -5675,14 +5024,6 @@ resolve-cwd@^3.0.0: dependencies: resolve-from "^5.0.0" -resolve-dir@^1.0.0, resolve-dir@^1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz" - integrity sha512-R7uiTjECzvOsWSfdM0QKFNBVFcK27aHOUwdvK53BcW8zqnGdYp0Fbj82cy54+2A4P2tFM22J5kRfe1R+lM/1yg== - dependencies: - expand-tilde "^2.0.0" - global-modules "^1.0.0" - resolve-from@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz" @@ -5703,7 +5044,7 @@ resolve.exports@^2.0.0: resolved "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz" integrity sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A== -resolve@^1.19.0, resolve@^1.20.0, resolve@^1.9.0: +resolve@^1.20.0: version "1.22.8" resolved "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz" integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== @@ -5724,26 +5065,16 @@ run-parallel@^1.1.9: dependencies: queue-microtask "^1.2.2" -safe-buffer@>=5.1.0, safe-buffer@^5.1.0, safe-buffer@~5.2.0: +safe-buffer@^5.1.0, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== -safe-json-parse@~1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/safe-json-parse/-/safe-json-parse-1.0.1.tgz" - integrity sha512-o0JmTu17WGUaUOHa1l0FPGXKBfijbxK6qoHzlkihsDXxzBHvJcA7zgviKR92Xs841rX9pK16unfphLq0/KqX7A== - safe-stable-stringify@^2.3.1: version "2.5.0" resolved "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz" integrity sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA== -"safer-buffer@>= 2.1.2 < 3.0.0": - version "2.1.2" - resolved "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz" - integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== - sanitize-html@^2.17.0: version "2.17.0" resolved "https://registry.yarnpkg.com/sanitize-html/-/sanitize-html-2.17.0.tgz#a8f66420a6be981d8fe412e3397cc753782598e4" @@ -5814,16 +5145,6 @@ shebang-regex@^3.0.0: resolved "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== -side-channel@^1.0.6: - version "1.0.6" - resolved "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz" - integrity sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA== - dependencies: - call-bind "^1.0.7" - es-errors "^1.3.0" - get-intrinsic "^1.2.4" - object-inspect "^1.13.1" - signal-exit@^3.0.3, signal-exit@^3.0.7: version "3.0.7" resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz" @@ -5881,11 +5202,6 @@ source-map@^0.6.0, source-map@^0.6.1: resolved "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== -sprintf-js@^1.1.1: - version "1.1.3" - resolved "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz" - integrity sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA== - sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz" @@ -5911,11 +5227,6 @@ string-length@^4.0.1: char-regex "^1.0.2" strip-ansi "^6.0.0" -string-template@~0.2.1: - version "0.2.1" - resolved "https://registry.npmjs.org/string-template/-/string-template-0.2.1.tgz" - integrity sha512-Yptehjogou2xm4UJbxJ4CxgZx12HBfeystp0y3x7s4Dj32ltVVG1Gg8YhKjHZkHicuKpZX/ffilA8505VbUbpw== - "string-width-cjs@npm:string-width@^4.2.0": version "4.2.3" resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" @@ -5943,11 +5254,6 @@ string-width@^5.0.1, string-width@^5.1.2: emoji-regex "^9.2.2" strip-ansi "^7.0.1" -string_decoder@0.10: - version "0.10.31" - resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz" - integrity sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ== - string_decoder@^1.1.1: version "1.3.0" resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz" @@ -6029,18 +5335,6 @@ text-hex@1.0.x: resolved "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz" integrity sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg== -tiny-lr@^1.1.1: - version "1.1.1" - resolved "https://registry.npmjs.org/tiny-lr/-/tiny-lr-1.1.1.tgz" - integrity sha512-44yhA3tsaRoMOjQQ+5v5mVdqef+kH6Qze9jTpqtVufgYjYt08zyZAwNwwVBj3i1rJMnR52IxOW0LK0vBzgAkuA== - dependencies: - body "^5.1.0" - debug "^3.1.0" - faye-websocket "~0.10.0" - livereload-js "^2.3.0" - object-assign "^4.1.0" - qs "^6.4.0" - tinyrainbow@^1.2.0: version "1.2.0" resolved "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz" @@ -6149,19 +5443,6 @@ typescript@5.6.3: resolved "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz" integrity sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw== -unc-path-regex@^0.1.2: - version "0.1.2" - resolved "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz" - integrity sha512-eXL4nmJT7oCpkZsHZUOJo8hcX3GbsiDOa0Qu9F646fi8dT3XuSVopVqAcEiVzSKKH7UoDti23wNX3qGFxcW5Qg== - -underscore.string@~3.3.5: - version "3.3.6" - resolved "https://registry.npmjs.org/underscore.string/-/underscore.string-3.3.6.tgz" - integrity sha512-VoC83HWXmCrF6rgkyxS9GHv8W9Q5nhMKho+OadDJGzL2oDYbYEppBaCMH6pFlwLeqj2QS+hhkw2kpXkSdD1JxQ== - dependencies: - sprintf-js "^1.1.1" - util-deprecate "^1.0.2" - undici-types@~6.19.2: version "6.19.8" resolved "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz" @@ -6190,7 +5471,7 @@ url@0.10.3: punycode "1.3.2" querystring "0.2.0" -util-deprecate@^1.0.1, util-deprecate@^1.0.2: +util-deprecate@^1.0.1: version "1.0.2" resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== @@ -6230,13 +5511,6 @@ v8-to-istanbul@^9.0.1: "@types/istanbul-lib-coverage" "^2.0.1" convert-source-map "^2.0.0" -v8flags@~3.2.0: - version "3.2.0" - resolved "https://registry.npmjs.org/v8flags/-/v8flags-3.2.0.tgz" - integrity sha512-mH8etigqMfiGWdeXpaaqGfs6BndypxusHHcv2qSHyZkGEznCd/qAXCWWRzeowtL54147cktFOC4P5y+kl8d8Jg== - dependencies: - homedir-polyfill "^1.0.1" - walker@^1.0.8: version "1.0.8" resolved "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz" @@ -6252,20 +5526,6 @@ watchpack@^2.0.0-beta.10: glob-to-regexp "^0.4.1" graceful-fs "^4.1.2" -websocket-driver@>=0.5.1: - version "0.7.4" - resolved "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz" - integrity sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg== - dependencies: - http-parser-js ">=0.5.1" - safe-buffer ">=5.1.0" - websocket-extensions ">=0.1.1" - -websocket-extensions@>=0.1.1: - version "0.1.4" - resolved "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz" - integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg== - which-typed-array@^1.1.14, which-typed-array@^1.1.2: version "1.1.16" resolved "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.16.tgz" @@ -6277,14 +5537,7 @@ which-typed-array@^1.1.14, which-typed-array@^1.1.2: gopd "^1.0.1" has-tostringtag "^1.0.2" -which@^1.2.14: - version "1.3.1" - resolved "https://registry.npmjs.org/which/-/which-1.3.1.tgz" - integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== - dependencies: - isexe "^2.0.0" - -which@^2.0.1, which@~2.0.2: +which@^2.0.1: version "2.0.2" resolved "https://registry.npmjs.org/which/-/which-2.0.2.tgz" integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== From 038f769c5f9fa2ea39bb88de6c7495b99e91aea6 Mon Sep 17 00:00:00 2001 From: Justin Frahm Date: Wed, 1 Oct 2025 12:00:47 -0600 Subject: [PATCH 23/32] Clean up `cc-ui-app` --- backend/compact-connect-ui-app/app.py | 1 - backend/compact-connect-ui-app/cdk.json | 31 ------------------ .../.!35287!military_affiliation.pdf | Bin 9 -> 0 bytes .../test_files/military_affiliation.pdf | Bin 24457 -> 0 bytes 4 files changed, 32 deletions(-) delete mode 100644 backend/compact-connect-ui-app/tests/resources/test_files/.!35287!military_affiliation.pdf delete mode 100644 backend/compact-connect-ui-app/tests/resources/test_files/military_affiliation.pdf diff --git a/backend/compact-connect-ui-app/app.py b/backend/compact-connect-ui-app/app.py index 522444d97..7d515ff8f 100644 --- a/backend/compact-connect-ui-app/app.py +++ b/backend/compact-connect-ui-app/app.py @@ -40,7 +40,6 @@ class CompactConnectApp(App): Pipeline Execution Flow: ---------------------- - - GitHub push → Frontend Pipeline - GitHub push → Backend Pipeline → Frontend Pipeline Stack Structure: diff --git a/backend/compact-connect-ui-app/cdk.json b/backend/compact-connect-ui-app/cdk.json index cfedbddf2..7828ec435 100644 --- a/backend/compact-connect-ui-app/cdk.json +++ b/backend/compact-connect-ui-app/cdk.json @@ -19,37 +19,6 @@ "project": "compact-connect", "service": "license-data" }, - "jurisdictions": [ - "al", "ak", "az", "ar", "ca", "co", "ct", "de", "dc", "fl", - "ga", "hi", "id", "il", "in", "ia", "ks", "ky", "la", "me", - "md", "ma", "mi", "mn", "ms", "mo", "mt", "ne", "nv", "nh", - "nj", "nm", "ny", "nc", "nd", "oh", "ok", "or", "pa", "pr", - "ri", "sc", "sd", "tn", "tx", "ut", "vt", "va", "vi", "wa", - "wv", "wi", "wy" - ], - "license_types": { - "aslp": [ - {"name": "audiologist", "abbreviation": "aud"}, - {"name": "speech-language pathologist", "abbreviation": "slp"} - ], - "octp": [ - {"name": "occupational therapist", "abbreviation": "ot"}, - {"name": "occupational therapy assistant", "abbreviation": "ota"} - ], - "coun": [ - {"name": "licensed professional counselor", "abbreviation": "lpc"} - ] - }, - "compacts": [ - "aslp", - "octp", - "coun" - ], - "active_compact_member_jurisdictions": { - "aslp": ["al", "ak", "ar", "co", "de", "fl", "ga", "id", "in", "ia", "ks", "ky", "la", "me", "md", "mn", "ms", "mo", "mt", "ne", "nh", "nc", "oh", "ok", "ri", "sc", "tn", "ut", "vt", "va", "vi", "wa", "wv", "wi", "wy"], - "octp": ["al", "ar", "az", "co", "de", "ga", "ia", "in", "ky", "la", "me", "md", "mn", "ms", "mo", "mt", "ne", "nh", "nc", "nd", "oh", "ri", "sc", "sd", "tn", "ut", "vt", "va", "wa", "wv", "wi", "wy"], - "coun": ["al", "ar", "az", "co", "ct", "dc", "de", "fl", "ga", "ia", "in", "ks", "ky", "la", "me", "md", "mn", "ms", "mo", "mt", "ne", "nh", "nj", "nc", "nd", "oh", "ok", "ri", "sc", "sd", "tn", "ut", "vt", "va", "wa", "wv", "wi", "wy"] - }, "@aws-cdk/aws-lambda:recognizeLayerVersion": true, "@aws-cdk/core:checkSecretUsage": true, "@aws-cdk/core:target-partitions": [ diff --git a/backend/compact-connect-ui-app/tests/resources/test_files/.!35287!military_affiliation.pdf b/backend/compact-connect-ui-app/tests/resources/test_files/.!35287!military_affiliation.pdf deleted file mode 100644 index 2fbf8bb39211c9538298936703d12721cce882c4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9 QcmY!laB)k={E&s+53& zK&U~gKtc;7gb?^P;Qf5hIo~+{&KT!CW88lX?qu(^)?91NdChBHYjbNoQo17~C~}^= zZGLlpB^UScM_b2v5%#<6PG+{}Wn|bvN;dXxmaf33y{Vh!BTI893rlv8nx&()+bec) zNp?B8^AI;zOH+sQUMB^foqKlc+1(QlMHD#CKvX#cGoJ-I2g;lwzy6gu_v=l}KpS&l zrrO6Fw45R(Qk=AqGpgoiI3b*}I)QUWXZ-q&6wV|-i|z;BKe2g}_SAVxM~lBt2L3wi zjnIGf>Y2AQ&!W{NEgjw1g@LcN*+JTt5GQw6b4v*Oz2gfCPL6KC_Yn5OQ2{?t zx3sV^edy%HZg>~CASNUX3`5xXJTQ>I?Hu0s&*-&Woy>JC-PjF*#vUoLgH#-VCLjL& zRQUU;!fwnC($_SzwKR8QKeU;OgAlvuQEPH?hZg$#;KNJ*JotD3|J(%9aW`{28q!e% z>>xcG3&6e-qQdN;$CftMuiV)0N!|k<3=GB9+39d9*xg*+Ezg4#oa~)kb(~GjE!oA6 zT0Hs{J3|gl>ADj`V zyH?awQ&P(@9g_K`=Hr`jc^Aa4?NdLE#%;kR4bOp$#U<->oo(H;ue;Bzr@$HWo^ta| zIvF$XPMoATeeMeT{n!8g@vjm7T?zm0f`50xzq{c7rCl%`bq4m*iF_%c4_%ruf+_Or z=8*r{sCUqZnJ8h{+HKq>UH*l;Fw%Fie%Lz0_=Xkfj-g$^GLUo3K#jWk&* z?#e#K&v6usm2XC!^n#O6Q;G6yz76Ki)ABMfm)=1LY7LE&jOmmK3!N%`Y;J)e><0Y7 z`&pD0w~9abh+4Y~3;m^X-0SlDV+muI9t3PE;2@pEbn3OK2u%!Oq*_qM8BTaMy1{lJ z?b$a3s&35X#gl3&#Shk_C-Iv7?wuJcLQgAeXIOkWWi=N$j|T#xdQTp6!bK?FaIZ%r zWS(ju_ru0l@#JZwp9pkCb*l~L@F9AB!yFO){2Jv%skpY$2+t9+sL?yq2E&T`cHRt^ z!jp#b4HfP20ltgl1Th<;ZoJaMfo#R78oa3bIaFBkB_Ru~0*XvD3nIQ%V{T78NE)|WUn2J}8 zVw0>OQQiZFOnq5hz|@1GcqRMb7X3BdcoHTv-d50GPlTKKBRb3mXI2s z9jFtl9xn^@Ie~TJVoyJKv|8eY_Mcw0A#6(ggE4ie%2=%U=0NP^V!fXa9-{jp;7aPF zv9-FUGz~o4lwtqXR&Wz+VbnU|!@B|nPiaJv!H5JR6I@=po1?mjD`rQ0sMbk=99tE8 zf!PZ~NV2A<^&!vB`eIR^jrR$;#@!7;r~Pr%Y8hgZ>K5oy$m%S~uxm_@F;2wyrqJ_Y zQKqx;_2g<^K~Z`4OSR(*gLF-P)(-m5X|ctNJ{C8XY`q=;eZAt7nGA83f|d(ECKHBShfF(J2b)jE3f%!GHoL-y%;5bpXtb|a zu0+iuYeytmN!Pc;Y3@vZ&srgCmRFIF2+bN!FTppxn)t}R{I zI3Iul8EY^E`T6mV1}rh5LKnUOQkZbKRA@ha0P=`pjj1yege|*|nk_R%ZMC zU-P(pb3)}Exx2X4mGQLgunA&>-MIUXXGwI&4uanuGjflS+`I2Se|EBRb|jS(>}lFq zxZ!y)=(6XYLK{Y4V$7;^$~))px_z&=iuzSIXTIzg@t9r|y1u^ulY_8^cd7)!YyANx zK@~CoguwPd9ivA;h!^LT-Z1$7>fOwP-4ttO-9IM|`zAp}L3x$r_p^4sj{IpycX_l| zqR}Pjh2`A1lhI_tne5!YzR0WKYX&-(`3DlW^s*z!!h7~wDlbw)QsApxjs65Qod42S z?2V~)FiTUOOe<)dq|@|noGg60!VASG2Cjfr9UzBG!BP{aJEDWB)@cMKctn<&mm3faaqTQfuPLqiiiVx{Mi|lB>osj&$3Z-V>(Fmb7*kX;lCXV! z!{B7%jnKQ#P4$805 zYD}u_YF`TA_`DsBKguEftD3QGFvgL%lN#*|Ki)YRzq*k;#$cO3OTVu_1o~iA*H~MN zH^sW%AcS1VKAG!x-&&I8qx@6jTZ6{Z)O~$RH^WUUw=u=QBH{idDR)Vyy|v0bXrZJo zO8n{pzP8JBfEn+=%ZcxSCf!sw3mVwovlW%&gQEApOU95-KBpm7&JW&7(7^AVty*zP zpmi7?{N35VU|jqofv3}h+MIRer6K`@WdkNz$cq)mwxl`z zU!%22a`iJzV&$q*k-dH9S4rGr`=n01x`l=(uEf`IO;b45V<+16>^(8c)F>2aFyj2% zwwIS4!Z|iGP#NbJG@VTTNLuP+2n^z&Ik0Yja6shd1@MHewCeZ@jC0tZ_=?dbv1=7>j;44tG4 zw+f+HbZ^|CWopNGy1-?*-2vO6wL~~eSi$JYL!&tNUr=Kq4S@~2Ld2GC(joNcPxZD% zY2YHCQIV^xJ8fU8IWYc-_lG0CfHNDa#?zF+E=7cv9 z%B?NRs!-%R1Cr%Y^+#VEk0a(!~Bwv>)R^(52$tF%LU z?kit=G^#X&Jg}n^=GvP8BRP)G`#@jT$&FKH-NxGm%&i5^or0U$aCqZ|X(Vc2$DGe0 ztBT$NjLMDlBJt10t!(Qq*EHtw#(r{D#gg`>KQojiK8C!eI6{7Z7!n2XJ^Jzf;CETrmlAcWl&D1y{Dhct>4dq=Xs?Iy8EC17--=SVrgX%Sx9+umU-$7w1}& z%T_LJ4{5t>K1=e{9eh+@`{CQ$FmzJ*y;1w#$t5z(}kS-t&zCTXIT+Rb@9Cm}A< z5R*_U@Taeud_fSc9uDu#1o4xzh0?XA1U7(z!VNqFHJ*s!`b>2$j+=qB1NX=AMRjdQ=d zNZ?%iqMlODM@h$0@D2FDn-N%1g~5=7!R-P+weGYHTyO|k*lX<&ey}t@%$riXv7Wg| z=xc+44dz!!QBR6_$`6>xgCFFj8(YQ2! zdtfxY!N1_Av!7{`(knc3H3}rId#{v->>ea1CN6V}?vvLpTAK28S}F?EdN#yjnv7t2 zN}hb@Y2vzX{{joMghwQ}U1Au+BBc;$H!N(4PjL66{MH{6^3@fWDsZ{bY794aT$kr? z=&`pChrZBbiAkV&Vq+*Bm9g)E!1r|9zXx_EbfmBLc967{>dPB%g0^$A7tF7dELZQ- zR5!djP#f?$Su^71zO%3dY^wwbenj}07rr9lzBYosJ@H5j|I1cyt=V?wv~MT_4iLL% z6u;Qu*l@@76R-RLUBHX+gEw(U?&I`%H3rf($d%3Z-NJEYnQ0mHjsYzfEmwKNV^V07 zptVO=$@b3(Q%pifhM82a&IsO}G)ydYy2Oa~dT53??Y^z5IpTn~ezF!%B$^K1I(x7e z$&Uw6rK`(0mHPOB@(a1=RhM`!xsy&<9^sO^5zTI;U>3!_xp|s=jWCD^rMsHaj?sI?XvjlDt|qP2pPcKb@&B+{l8FL_!upr%f3JH$9Wd3 z|B@Mk>&F)yMtzrCx+ed&V)FZZ;u5oXqQ55cBwyB0>0+>yd^cCzjG=p_0HqT@=rR+c zxCB?%>Y?##HQ`A}$sJn5vjT~9v5r@7UamJYR!QGtUBQK`Z0weOaiM-pep+^7e(jA1 zx2G_^2hFKCN3<3{AO%_>lkvvcGJS44yC<>7={R6w7uBYTnVvr+yp(Xv_Jt9>H8nfk zw-$^|LtqE8&1zxIc;65@8Ec>?d(>ssXoH2ToM08Vm)lRe8zDuQ( zz5b1i-QXi{c9J5n`By2}iV-?D+@zEX6<6Pif97f4Na)Z=>lYK>Tk4q~umV@fcN%YK zZCgdJ-!afxHZRKsaskU4-1qSbNt+BOK|(jy_0qiOD)qDdlIm7*(Fvwfa6i*%5q*a` z!}5;PvSF1}&Y8*ID3GV2MYe`KOnb266e5lku(!cB8hwOMD9+?Wkj=J33WaZUea%}E zWqP+^V4bjUdT@FO`gvZu2sd%j4Wuo;DDAg*hEA^f#U{p`p{_m`T_O*D;>ahLR_&G| zo%^0F*dIUHq$u-4@>!W*hNn43e2Ixq*W$uY>kRBG702?dRD?v+?p{;Xd8)*(XF?YJ?nV;|=%T?!WFf=@^Lu|x+reNofEFowm6(#81Kl8wb~5qQ6n zH~9HgcM`m#InltWfMbAN2yW;AWmJ(SFRY zTn8V*StITZCE4HNXx${LP42}$t3)4>yC7QH7)G8YU8#}D$1!z{Q%1~PzX(6ShDw5? zyL=l&r1g01RaE;#`fihZ=flm$tX2zoC6!id_rI!_p(~u5=JuW>2X%m*iVm&;KGo7z z$6Tne~g$ptNnQAz7M8%y6(;G=tS-ENha10~U!1UfO&b)C-pohkZ z0+7Ue)dcl%1-(D7ihO}S>NG@)n@oro=W;yxtq{3N-mNU$N*abMU%adu>#qCWF+=#) z;!akm3hrfqALQX<>o>wO2Lk6vLn(`qY!h(OA_qM#I?!mB=rp? zVSy_BleJ#KX(_~UF1cDG9-&L27enLYAnP3MFz>e&&poEB7a#4UC#)V*Zd&JhKs*!H8> z6VE$NianAItOu~lO!%B4Au{|nOmpB>>Pf0g#0Cu|M%vCd4xVvP)s<7Nw@_hUQ zKPuE!9m~g4+fP}FIh}+bm@91`tWWuveK+N#5}}a@=k0nR3;Q)pDSLz1?zVzuWK8I@uu$R8 zx~caYBqK3;gS!(~J8|s@Gy@so{X6R|ws#`;jCI(osQ#Zx5y&X=-^{?B6ocK7C8 zVZ3Vo^yKF4)!3r}0D0d>@JD%kPfXzO6%6GT-#<+XUcF9F^4kjz1w&VRjknkxl$3Ru(LHm^ymQ)n5L`K1amFKX&}*0sk23uMPhv z{l2s;~{6TV_3qW;+K^|RF z>+||@?yCIu{E9VfdwIAsqh^Oqv;NoKUJq@8Pm%DxK|k17a239?g(*o!xRUK%)-k6< z(kS6Q!!LkT%+#}|&llseO7HP^4kP04&*W31>wcUzSLb*-+TFW!s&)wAV&OaA9d#h} zKWItnZo}1U1(o^g#Id~}^zGutQ@1XgNDYy*5QKFNmpT8}%+I+As9EM~pI5FPPKAH{ zi)l##RYk8s`z9|k1}i@{){6m8VBX)M)MKP4sr!6DIi-qoFI7?B`)qMElim7Tn1vm(2!L>Le^y|gB5{dB&oX$Q-zUXb<4F=KzRl-+3=tg ztnlf0O=#pa17sogVsCO<5{t96@JgTWU)Wvtg4&qc zCLKGu~$?Bc90@rx(;G{#+ zW*^zLu1Y4I3LO&`H${Cz?l1M(YOonxA%%0f{@oqUkyKoTXl2%M@e8x&k;bX;=`Fns zFkfb6Zoub8ld*)82dBQkryX!?)k2E54c5wm0dta z(jsMFm-=oPI6z*bia%-n-KZMxn+;hfZc{Y$%alua;ej&AHMYk2?gh8o?sNnICqzG2 z_Z=RJ$!gc(L%jn$&eioqI%CAlgwGByTc;WH{t4VzaXDgjuSg_OY=3N8n~)sQn)9uCf01^do8A8M zA3lpaRfI{D18jfur>kgn!P+o1`Jr}GRuwQeXr=To#tz+-F|*vE=@t3p04CZwFJjny zJ5WU_kp2-`vzx(&NSwm+b|k8$1&#g!*z9c!9Qr`O+yipom9;8ZyD6>Hdu*UZN3U90 zt`Eo`b`*S940EkRRSEX{eXY_++1zzb1_JUle0VhbQO`EW0=GjYM@zYoP_=kf?&i^M z;5A~W(ZdZ%-ZDzda7xLt$(ER|E()(~8egzi7GBK!ieGqX9C_H;_0n!^Dhx}Ac#?`z zx*kVsA&00|*kiQr=16wu#>!&7U@t`{>sVD7jjQ9TK+h6rPc~V3I`r~AY&RzVX~zZB zt4k!D?Wo)AleMXV?GC`HU%WanJFdE&q!6d?u7Gu|8c(u7)pLCIvb?}FU{dd;@9&+UZj;*%R ziFNmT=>7^VgS@-JPA+BY(dv(*aChD;3m0DN9>^=e`Ihcfk#n#=^N2a2Gda@vEUL`0 zsI{SbyE9{SDWDqzjj%;?gkk7~u62=i&U?9gL!TLZq|lU3B@txS?z&4`RkXlA(6<53#3W^6J&&Txl%lw`N}BtyXbsc7-c5>e@fU`MF5@RW(Rtw77dg9KkHRDf zz4_{{-@%1_-OpjazagJVU|wt+S3cNGTskQ1NACSOU5DkQoTr{jeJQ75Tqt`SKUitM z%*M8PrS&J4%w>Gl%~Sh=k?}(H@nzOL6Kyw8W+mN8P3H6I8hiyr2ywNXM)^wyiWg%U zE3v1fdfwe2`3)&0E%IXRiLIY-8e`HTGeTV^A)Q?dkf2;jHhNMvji`%(kMdP@Y@9h8}<9=cEOHdoU_xpEZ))`CC5!0H@TNA{c~FvpRLH$ zKo;qq8Z9Ty6mUk8ZO8kb#-%+OP1>->>c!HJm@12rs6qIAvdEBUW}>{pNqLh?etTOS zvCcpRVPq91{M!QeJuR?qu;Kd^l=p{YF~&64xrXW|D}=52rVU@4$S18YrFm3{*86rI z9f?NnbS}ja1k&oHA4^(ngjA2~HHXCqW38~c)=+YceXXAeUnKg^$OJ_8`r0-_U284C z`FkW($cjDRW{9D$uNhaxa2uIm9|dSvMzT3;kGD?-tz0O;@`UMvELma&6qi zGl@`pFCtOLleNm|?{j)zB3#k{RJmh@Tu8X2a$nC7phJsc_zgmqbmeuh=i5^BLE(T8 zuY2!K{{VAR7f3~SaDTq*N>iH+@}&Bw2y%xFv!h1;dds6+>;>+8HC*0tfCBveQe$E4 zmPz``?Q3icqLx1^T1-y;2$kxfO@ zIODLxcPLZOhklo2JhzgMI##1-#NE$Pt71~ph7s1i69RjwSFubXA0UIT%Vni?@mEX1 z{z*z-Z%3wZ*n&$ucm|)sX9kFUdtHE4Ed3EPdX@uO5lGGvh5fPt7o3gGEi=TPhhiSl zE%jmPZVJkY`CjKCglcZQvmQEhlLcK?`GMUcH3JG}_;z2cQ|+@$3y`c;TZtjX?Thx; zr*?tT;C5#uhQ8Lw7WFM15K)9k@DpOgkb`8P273hzZ8DO*E6Pv`_6gx_G;--TL#Ui& z(L6*Zgv9&w=T?mD?DeOG`u63S&HbwHruU*iJ+y~m6DTu%#ifRsrSZhf{vgfeDi zZT6__EMY^jR$poe{lxZtVMQ>^1dV;v4g;Y00WAJO={u?LQbr>}49+@1BTbOLUwvf# zJ9?n?`&xp~iS|nfbBxQU?Mow6)d(2GtsaE%dnF)OJ40y=X^F{tGfvM$E^r zI+m8i?W^+U1MB#`)olNjJ9=WVXLc1=tVR4Y8wp8KTTQZiM3050Bm z)IY>Sz7))xbG#d0s`;H@HOf!ERvLwEI{n? zCs_+4tH|s+y$!EmBB5`G^6f3IJ^D+b3Z0q4|Ipo#-QR8eNqaBodHOSVMTgPe^Hr@8 z{rf;TSn87+Llrv;)Jur#uC4?L+6h!UMB1i1t>#`1D;IWxWXv+8mzVv5WyXonH3KUiw@^7OTo{Ei{6n$r(0o1ZD z`tW`SQ7%T3D_m``@p8x+_p@uFW}Fuc^lK$#Qvm+lK(*+=^)R}-vt~T^3^ja`lK>In zZ$ABqw+oig@g+l zr8f=lD_P0)8c<~+>O#uSJ{XZGMKgWv?aHWYK*4m}Yu}f-7Vaiu3?xK#`c1qSx3O6l zmt~qzL}Tgs=GVP~n!|r)O03Fj1WLi2pN|v14!XyyO}q7^c4JlkFauLkNgsXt$ZEPB z+hCmL<3puT*`U}y*?dkAi5#-NM1j~7SHLLcya&W5W#877K@oqF?qn92mV!;t356LB zjyZf>RHpDjvjIx?eVZThj17lv;rf;Tl5BihkyTHmwKZD-S&7&?ON=fp^~LiBT9qOW z(^SFa7{U4zT~@m)2=hgwZ%xo;H#Hb`S+^9bB->ULJXAic#EBN<`uG);ypFkMn4grN zLf_v1CHZ9NErHx>f!p0q*cW+RLRfK!Vv}@%To~5_vzGSTnnq}CZHAn{@-GdI1{3k9 z;_%OEM>%2#jTdRi)vq)o!+F8x;JHboVU@%@(}968;6DL@gOO8%{z9l@=Etz8Js=MX zc9!$WXXclb;7L!5edP)rXwUL4!MSJ6mz-ly5x9IFU8^J3LH|_u@=Aes3B-M2D?>*6 zED_1QskPSBL{_DCW?Gl^e}AFD~bethYoXNE{mnEA_lUf`PTMmz_%^=I(Q zIvEpRYSR0>f+MKAyB2lX;I3wfc1o+|g&!~SP_g3M4&!z%Jz909(WaVb+uv>}24a!p zfNW5YYCI;Y)_C#JynLg#Y3g%8ld>(({8#X|L(x^E4SkTCA*ciDEH+K$#8sLYaT}*z zppMg%1ia>tvXF^K(F1^5uR_Nmx44R(#U2mDhIaAWX1%r5Y^3>511jWD>JP~8X#@j| zT1=}81;XedM!XLpaTGgG_?uF|pA~jW*~6Ct<;(&dxuuOPO)PF#06fTGTg0%2ot|1z+UL^N$b=l zjaj2TpNnGcu(aDAN7TSc3eV7HKu#;}RCvhZx2XpBJ1>nR?^a!m@|_IaR%T%EVV&E^ z4lJHDB6jsKCE0GwCiKbImL@N>rO)D?T4~RPQ1zSCmBz5n6)bLMuUJFk0~|2OxY%6K z9Uw8gBiXw0;0&8DW=6WmAs0zpZh2pih0|yJZTs}9tv?X3R>~b`>hsz7U8cu+?cWT9 z?M=wZrldaf+@7uR{)buo{}KQ8KeMaO)~AaEdlA0}h~q>eAS%I`no6iwvw2GIE3kjiaD`!oTW%X61p`Satx}`yf1RZA_A2qZ$fG z^uGGtnfHA@O(G1BBObzue*%5CRb&~CY)~_P<}xSVuH`ks2<`198g#;dUos+$xPC47hKC>iPLS{gWja7)OEmKvkwhxbwznFiFj$>wyc+$T%Xjay=}7Y=_mF;4N0hNOsr^a~CZ!r>4*r;e;h zUzdS*sI{qm#M@j>V8H&qDu=4yD`ZqQ&V7dfXnWVVW&4G%TpTEF&7{`u$qCL7=FR~aKi)C%`8p4Y!FA6K2; zaX5PccnTlMHhL#{ALixc7m_RCchCzc;V#C zIC*TCO|C3uSLCd)>a?u8!wLX8WJ#ETiBp5UC98;CfFoYAJxC58G7@(KB+DakC~j#q zX|OiCcXEHBurhTU5NU$+rEz&Zp?M{eR$5m`*VzA}z0zwEzffz&Bh{gR4yol49Z=O1 z`Zgsm*QEDr(0-pZCB8 zuhr3PAps}04^>U3l2p>5xHN=|y8sf5ed5#2l>Y+UzL#Dn;O>=zZvA-WM#$5AWPQ zh#<#g32_*h&CXmUmx6EBJ&J7SOG^nB8{A4xC(G2MiWeE0m5-BIeJW-XOu{}=%Ifhl z!i81-=|rdU9Xf5jbXj?K;@)x|)r}K_;VBU#eP4GybJQ`wa5yvOm-qI-02L1&D$%5E z=QhTFR|WO}^@MzE(Q;q1uNmT?(I+Q2xU#(J_2LUNM09c2k*M$fbmFXf-{9+>1)+<@`bj{Duf9 z{KZ8;);mB?2r4=)9iVzKl)QPnRmxj>t#CA{JsJMesc}?|LxDT8tGIgnFN=HraD@wJ zSF=!OrCP!@!@IG2XY8z(9lKLgBx#az*MS1kV&QFGE7hVzr%4l100-xmu)En@ncLDb zdC~TUt6&_y->qc*XWL)dgFe6Z8a6?{69#r&wdX)jVZz9pSqDF|qiG?n z@~$qjv<`>*rkrK2iX!DZR;OQ*04R*VpnY7_>wI#!(>oLBmmgcry+piK*fw*fIwR8M zrhog8gVS*t_wW3eYVw_R@$E;1U*l}SX-TRMDNVIChebZHA1qr=?v4{eyU;E*YH8ea z*e4T>%e9tPX5DWINC_}?-wrGTvM_F@$K zK;zhMz}Do>;fd;xFDWrm;EGWZ7eQ zGrK~6Sef3}HJHnnPcRy}Sa30|LmN1@0U2~p$Ja@D2?*|Hugp*8#9W+HwN0D(j<@=N zU9sV}XUjWAkms1$`~$+t13!r@(u*Oi&8M#hLzVi%zWDNsOuEm#s`j^#GC>=+hs(p3 zpt+Fb!sX$1iziDmHKmgqe;;RvwJeLy#owI zX)42LfQPr1F`4X_KF8nWQU2My;^P;}HNV;68Y)xm=P!sMF0X91pLop7@Q*?_i19an z5mDw0gXkA*qqV$uY}%JV=mR&#P<`g8yr$W=+Q|6+nyl?HTRyvpw`Xfc$Ns7)VA4Tx_2xRV5?DZVTBP0K)C8l=n7#nZY zy^xs+E0Gl!x_CE_@?ptT5r?ci3kwd$^^p9%wtcZyAI!u~I1D)y`?Id*$?!cJIR^$H zR<2Df$<_GHd`zWppLv*6VaPUIz9^m#eQkzd6+99YpYZ>D=y^dvDlCPSpica0sd!pj zQ{}orKT!5yi2o%bH=u679E-HWnNM0kP9;r^7jea^Cr2)Szgrl+#OHYVdigqBx5v8c z^mrcxP{AX^mb$RORC!y%n#k;Yx$_K^~|JVv$$<2MN}q@e(4n4j}BO=`Zg06EVi->zB{GOv;Dlx!-o$PGhEu`O}}1n}b3*?x9G{(_V% z(wA0H#pQN2Uu04MHfzzERU}X2sUc5K#q)MpV5IsHBy)xyf-ck0S7DIxR{Ab-tg_(6 z6{32sv$U%O%2oHe4cf!?Ed4gaC~=HZ^QoXxlO+VD6Isxpaep#t3(#SG=rHV);4pg- zjU3(J38oW25M?4|dX|EJn;#PnfHOE1N#1;=!Z%Snqj2@Chn$ygPhmh*z!;9PFf>QD zyr}9A-D*RtkY}H~mXeOmJ7(O(Zb^beocFnME+an_pf?k~NqR&mD@2D2XBb=FTl)eS$sAd${L<^*4EH(~vSRPu0Wf)Q|}p{ce6@loM8_ zvu=ze%lNvy6}2oq886pYBbDkbB-t{39xv~MsC&ZOilMUYP#|mnug)ukke9`>fx}#* zSPh9iK+Ou!cd|YlO9q$cawk_4c$#h|Kf!oPvrM%67Wh7mr7DrI+C!RAD6Tf7spaHZ zFQY(dlYt#*_3>4Lu{6mC+YUpHKRphQo$7~yMaF74_RhmlwH7RNuYgjIS@P#YrZjht z)!yE?nTMmjkK+3Ra#05T>5n6t_?8({H}mzXL+Z(U8wy^tu^B%5wrZowE%6;xsO=dXMJ2rpeSFnN7SW471@{)cLIsM+S~EguHfe zZ%AU;{(LR}UDjAGHltct!gARI(f2dbd|^g#6<>|dy$J*wx&Iw#8c@Z36PxKPlld_~ zU8J_|QP~{J)u$|4mfR64#qs#oExKxW@{N$W0VZcXFy5hhZuxV&UibwpcE4+|MMMc0zmXzNVEFAGGBE~Y%$M=PYX zKTn^yo*Y!__tQgC$T@mc2{VF!`k&Yi*s0*QZre5N^}O&XtBTBU0bY~T6gxH%D9l7|3*BqtIlr_ zpxs*gjaFB~8`DrAMuyOOf>K}bf_X~;bspr(<*2qyg&8M73!UC8a|ILyJ8}n1;hdKVR?z$D|mVi z1J_5?hHP_y#IT|!LY}1_N0VNyJ!QLUgc-51i&Ys%)Xcb1#MY?NOZ8eKw6( zS4*I;)8e7O$C|RaU6%+`C>1VUwmt9X7V%8uNi6xHL?*EwjRBt*Pqg(JsDblGz+r%J za}lWEDbCOv1CoaF-(U6vq)i?NF*DT8QoAehQv7efk!|eEmVF30sPXpEVUgF((H8)K z>-l+^toE$rF0>Y5{534bYT9v|8tk5TL|MiqNY{|BKRCL|JtR4h0BqAj!i^dpG9zc~yb+Z7|^&8YW|T80$g>)uuSeF7K{(aNE~ z0v02}ZSh?ApP!AMVDx@Ar8w;vc@#Nm%FrDf!FnD#n5d!~kN-e9aER!3sF1vV3_ zs1BxJm7m=m_2;qV*<5WOkKNe)=bw%^+G@tJ^bx zLmdHllc|WmH9*H>bfmx{lhtTvvH8JmZP;LT`D=(QV@b6rwAmyV+JboG;%=z>6yhsF z9j}#JUgcv=F$pdZ?ys<8(%lEr{n^v7+L;IKusnW;cms$x>dp{J|gva0+;RJA80UCt2oHLz#X{eQDieC}aOO6XLL-Y|GkGDMg z-Msq?Mlx`BVkao=V7<`q^Unco!nc%q#@h+^I8_>jZ7S0`bE#I^SFF*82cL`9xMp4I za%qFO!EQV^thKvW^P|sRlr|U+#PE1tP4~lb-sT!$nioTBfp%882 z@RsZTEuph-LRbu*Ip6uB-yBpFa!cRFqpj~Iij*mMzPs*AsBtaiApk%i8pIoI@rnP0MJOcuslQyo+cqxxY^GZ$q+Xu5;k>W`;Yxk~Uh4$BRn}h_ zzUFl5?e)nL4*T%U16u8C=O8;L*lDz+6r)=EgLZD6@Hzv&i9GXKZM~!1mHTwir7xQP%StgC8$t%e}|8QAA%qVM*VX zJ~}FL`UnfTFXBEGhunc1z7s9Sk405bq%T+u7PXE$OE8&f zJe_=HoXQKvab@KpGkah1qXO}7xmh6;HCOWQ%NWM>g!}7Xn6YeOKKR3tgdJdfs?hYK z?pYql`aPm8_TjxhE2XzZK1@@QoCX6YI7o@wFdsFUIhzwNdAu`gRIUg-uuq9)RBtGQn*;j5 zIF+X>2Ei278UEJ5|FqNTQgaQHttdg(1c7i4uj{<&{9WPfw&!Nsa3WscXs)z+tq-x& z?NG=!QwXE9n=eM4eB{u$-ROKzg7$>h{qqB_ub?gpwcgm+WE6rb1whvst|%m>(#WzA z3$DyP*dIS9{l)H-_I$awYq;7vv(NXl9PAe&EU#Sn)K^k1vCeDph;!C-P3BcB^(^vD zc;dm@-d$<_E`MUs<)!W**|NBI+g@~&4TU)u+-OsCgKv!2hGDjA)%pKzCmeCj;_ z(WYGDTcXd4b#towa(`DRyyZS`@2irahx%wXTmO^?DH=7@t0xN;dTMxzPT4yv|9KbS z`=DPVYMZ4y>Pz=}$X?>$^}STw*l9q&gFa~CtoJi()Ci{n$5IM zKWVltVYNRy-mFo=zfrHBtO z60*`Ef&xn~0fa?L1SNpRIXAg(HqyKO%6H}~InNX3r`TTz3-n?IIP5Ui$Om$?3vNWED!0tXkYbjUP z)1e8e25A@&{_wvu(A#QP|TgBWDw#$)$9cj6iE{mo0GuA?vVR z{aRT{Spv@Jq*ch-keuAyih(x&)|#qx~8PE(Tp9lAS=w~Du09;opkK0-ZEdtr+E2dDE~FXYKN&x*&7cyloc zmDUfe!>tRRDY`SE0#r{BE1)ER9TkpFY1K{iPF%~jwJ9T3O>gav$v)iC!4vV4m_7Gs z_k8YBcyD+d-eCubT8)~ggRR5BZ^s;q)#2*p_GOOuahAAg+&HfHfwhm9Pr}>1;@E)9 zfWpzH(c>+`qWc1W-v!_NOKC5?`)~B`efdIAcZdW#MSRbkmuq2^Q;OS*1%%}b5&b_8 zZ`-nqsZ>*1UOXvC77S4@pf;n_s2BZ%MlZfrp$^Oh^+rwap7@QQ*EfByB&LL4`Fmwc za}&5Pt8sj%BTJ#drJ-)jjQlphc`CT^ZQ{KW$3g+VoGZLE02eM-(yb6|mDCC!5nNVL zHmzl6oTXKgSMg+Er=_NmW}01AuMNqa6u#^ykvoX7-0G!2SsKTR<4>k^O1o+gt<-tF zhj34G-MCr|I9S<$t>>#gdxbnzrremg}1fwpBgu8C|Hv4%A&p9$sls zKe8A<70)|jWmI68b>9Mi(cb@M|D(u6bLA-Ip2%zde((C{&U`=dJzO#N%xy)*qrlJT zGZw!v*Crzb_y0M>!$8TDB=aQ1=xu4MW>q9nv;L0E8Vn8Vf!Pkm96i* zj5kI{WBOTq{XWLRYOAwNYW8l6O1+-YnOAwqIn(Ct%s_J00(Edvu{Cq--B?(w>*bLe zGZ%)8cGl6XI7$0ZJp(+q_rb2k@AQ7oYLv@6l$Vt!BpxFAbO%4Lect{&#H22rm#!^u zATNNO^0?tO+j{UxbZ0&f_r&t4-)>G6yjdN-=+%+->_}14ZnTb970*c@K*JmrvaXGp z^N3HdkQ>WYds4?KE@RST>soT`cD6vb(|=HO z*=m9^A+f)zcJ`;Pnyw)HNMoT%c+8SUw)#`2_aWiV2E+z^j3^ngXJ(Ja@z5pAux5{m znaT6?lNe#LpW_Y9UFtrLzzJ#hf}}Y@^dmSBOaQyu&DlAv&>9~eU?|w%tt!6s&o}u6 zoK{XVhxBmXp`_fl6v18W>wi>A6WfcUdq0$1SzT=>#1aHrU0P4HsD9gpFCVpfbkA70 zYUhyZu(LKUj`tmlYDACsHYAB1Ys+8wb_;ODuipedTdF=OMdxFg*reCTBprg4P`iP` z7;X`R(_c_h?Xg_0C(>huWGt20R@!1;cMHq&_ZK8#mtV1qxgV|$h2PkJkEu|~ECow9 z81ywHN8Xkdo;pabSov=4EYG?-V^2#;OSCw?>I5z9?etsLj;I|9w;FEoEM_fQVg-kW zD2;Q*E9hH^9Lr~xd(!r!Sd6Omn`7pZzTZkZnZT(&D%pS56XSsqti-=b*4XTXq9;)1 z7r}8DWsPcQznaFn^NgMqZ)c8;xM+xVMgPUb0FKTZcQC5<39)HuinZyjEo9e@&_^3B zW_g@)CE-kkLde=IOO>u8k(mt~YOh+JxH9-bQ+BW3F+H^_nG3nEie^XK^wrjLUsGpw zSDGYfarab?Y}!1#b`?Ge(2lNm%6GQA?5oM0DU+OfwyUfABYB~{(C)UKYjDiR^B*{( z(q?f6BV=9JpWM~^)^yc#_;obBU$XRm^qm!jVZ1)N+$Z6Ns(j4-5Fk-r%86l*>T}=0 z#6OOUe*8~1*XWjKceH$<$d&~7UyaORuQI*~N)7e<`xvvqw%$Nz0~~I& zk&z*uLht}>j5g;%pJtwotquP7mvrq7JUHdQ0lz^2I-3TNuz`68L-e2kgzpW$I1ukM zU^nV-A&u}ekpGPZ^7~VR!L^Y9@%#%7c!7Ow z2T0mLdB3uQYklnt#Df9E_Al&U5Cj6q`Ij0T1qTZEwT1%PPaI4i*fDn)f iDAE(DbxP*{oBYvR6rdZZpK=P61_F@*gH5bVW&Q Date: Wed, 1 Oct 2025 15:48:57 -0600 Subject: [PATCH 24/32] Split front/back ssm context --- .../common_constructs/base_pipeline_stack.py | 6 +- .../deployment_resources_stack.py | 14 +- .../cdk.context.beta-example.json | 46 +----- .../cdk.context.deploy-example.json | 2 - .../cdk.context.prod-example.json | 46 +----- .../cdk.context.sandbox-example.json | 16 +-- .../cdk.context.test-example.json | 47 +----- backend/compact-connect-ui-app/cdk.json | 2 +- .../pipeline/__init__.py | 2 + .../tests/app/test_sandbox.py | 26 ++++ ...xUI-FrontendDeploymentStack-UI_BUCKET.json | 63 ++++++++ ...ontendDeploymentStack-UI_DISTRIBUTION.json | 136 ++++++++++++++++++ ...Stack-UI_DISTRIBUTION_LAMBDA_FUNCTION.json | 33 +++++ .../cdk.context.beta-example.json | 2 - .../cdk.context.deploy-example.json | 2 - .../cdk.context.prod-example.json | 2 - .../cdk.context.sandbox-example.json | 2 - .../cdk.context.test-example.json | 2 - backend/compact-connect/pipeline/__init__.py | 2 + 19 files changed, 277 insertions(+), 174 deletions(-) create mode 100644 backend/compact-connect-ui-app/tests/app/test_sandbox.py create mode 100644 backend/compact-connect-ui-app/tests/resources/snapshots/SandboxUI-FrontendDeploymentStack-UI_BUCKET.json create mode 100644 backend/compact-connect-ui-app/tests/resources/snapshots/SandboxUI-FrontendDeploymentStack-UI_DISTRIBUTION.json create mode 100644 backend/compact-connect-ui-app/tests/resources/snapshots/SandboxUI-FrontendDeploymentStack-UI_DISTRIBUTION_LAMBDA_FUNCTION.json diff --git a/backend/common-cdk/common_constructs/base_pipeline_stack.py b/backend/common-cdk/common_constructs/base_pipeline_stack.py index fc7c5856d..03d40d7b7 100644 --- a/backend/common-cdk/common_constructs/base_pipeline_stack.py +++ b/backend/common-cdk/common_constructs/base_pipeline_stack.py @@ -32,6 +32,7 @@ def __init__( construct_id: str, environment_name: str, env: Environment, + pipeline_type: CCPipelineType, removal_policy: RemovalPolicy, pipeline_access_logs_bucket: IBucket, **kwargs, @@ -43,7 +44,10 @@ def __init__( self.removal_policy = removal_policy self.access_logs_bucket = pipeline_access_logs_bucket - pipeline_context_parameter_name = f'{self.environment_name}-compact-connect-context' + if pipeline_type == CCPipelineType.BACKEND: + pipeline_context_parameter_name = f'{self.environment_name}-compact-connect-context' + else: + pipeline_context_parameter_name = f'{self.environment_name}-ui-compact-connect-context' # Fetch ssm_context if not provided locally self.parameter = StringParameter.from_string_parameter_name( diff --git a/backend/common-cdk/common_constructs/deployment_resources_stack.py b/backend/common-cdk/common_constructs/deployment_resources_stack.py index cb7f07a0d..60a919808 100644 --- a/backend/common-cdk/common_constructs/deployment_resources_stack.py +++ b/backend/common-cdk/common_constructs/deployment_resources_stack.py @@ -24,7 +24,10 @@ def __init__( ): super().__init__(scope, construct_id, environment_name='deploy', **kwargs) - pipeline_context_parameter_name = f'{DEPLOY_ENVIRONMENT_NAME}-compact-connect-context' + if pipeline_type == CCPipelineType.BACKEND: + pipeline_context_parameter_name = f'{DEPLOY_ENVIRONMENT_NAME}-compact-connect-context' + else: + pipeline_context_parameter_name = f'{DEPLOY_ENVIRONMENT_NAME}-ui-compact-connect-context' # Fetch ssm_context if not provided locally self.parameter = StringParameter.from_string_parameter_name( @@ -54,14 +57,7 @@ def __init__( removal_policy=RemovalPolicy.RETAIN, ) - match pipeline_type: - case CCPipelineType.BACKEND: - notifications = self.deploy_environment_context.get('back_end_notifications', {}) - case CCPipelineType.FRONTEND: - notifications = self.deploy_environment_context.get('front_end_notifications', {}) - case _: - raise ValueError(f'Invalid pipeline type: {pipeline_type}') - + notifications = self.deploy_environment_context.get('notifications', {}) self.pipeline_alarm_topic = AlarmTopic( self, 'AlarmTopic', diff --git a/backend/compact-connect-ui-app/cdk.context.beta-example.json b/backend/compact-connect-ui-app/cdk.context.beta-example.json index fdba61161..593898f91 100644 --- a/backend/compact-connect-ui-app/cdk.context.beta-example.json +++ b/backend/compact-connect-ui-app/cdk.context.beta-example.json @@ -12,53 +12,9 @@ "account_id": "222233334444", "region": "us-east-1", "domain_name": "beta.compactconnect.org", - "backup_enabled": false, "recaptcha_public_key": "123-KFEUsjehfuejILDVUKkRnAF9SSzb8o9uv5lY7Ih", - "robots_meta": "noindex,nofollow", - "notifications": { - "ses_operations_support_email": "justin@example.com", - "email": [ - "justin@example.com" - ], - "slack": [ - { - "channel_name": "ops-monitoring", - "channel_id": "C0123456789", - "workspace_id": "T01234567" - } - ] - }, - "backup_policies": { - "general_data": { - "schedule": { - "week_day": "5", - "year": "*", - "month": "*", - "hour": "5", - "minute": "0" - }, - "delete_after_days": 180, - "cold_storage_after_days": 30 - }, - "frequent_updates": { - "schedule": { - "week_day": "5", - "year": "*", - "month": "*", - "hour": "5", - "minute": "0" - }, - "delete_after_days": 180, - "cold_storage_after_days": 30 - } - } + "robots_meta": "noindex,nofollow" } - }, - "backup_config": { - "backup_account_id": "111122223333", - "backup_region": "us-west-2", - "general_vault_name": "CompactConnectBackupVault", - "ssn_vault_name": "CompactConnectBackupVault-SSN" } } } diff --git a/backend/compact-connect-ui-app/cdk.context.deploy-example.json b/backend/compact-connect-ui-app/cdk.context.deploy-example.json index b9f4efce5..0c6cf9fd2 100644 --- a/backend/compact-connect-ui-app/cdk.context.deploy-example.json +++ b/backend/compact-connect-ui-app/cdk.context.deploy-example.json @@ -1,7 +1,5 @@ { "ssm_context": { - "github_repo_string": "csg-org/CompactConnect", - "app_name": "compact-connect", "environments": { "deploy": { "account_id": "000000000000", diff --git a/backend/compact-connect-ui-app/cdk.context.prod-example.json b/backend/compact-connect-ui-app/cdk.context.prod-example.json index a6e2789b5..f591fdead 100644 --- a/backend/compact-connect-ui-app/cdk.context.prod-example.json +++ b/backend/compact-connect-ui-app/cdk.context.prod-example.json @@ -12,53 +12,9 @@ "account_id": "000011112222", "region": "us-east-1", "domain_name": "compactconnect.org", - "backup_enabled": true, "robots_meta": "index,follow", - "recaptcha_public_key": "123-KFEUsjehfuejILDVUKkRnAF9SSzb8o9uv5lY7Ih", - "notifications": { - "ses_operations_support_email": "justin@example.com", - "email": [ - "justin@example.com" - ], - "slack": [ - { - "channel_name": "ops-monitoring", - "channel_id": "C0123456789", - "workspace_id": "T01234567" - } - ] - }, - "backup_policies": { - "general_data": { - "schedule": { - "year": "*", - "month": "*", - "day": "*", - "hour": "5", - "minute": "0" - }, - "delete_after_days": 730, - "cold_storage_after_days": 30 - }, - "frequent_updates": { - "schedule": { - "year": "*", - "month": "*", - "day": "*", - "hour": "*", - "minute": "0" - }, - "delete_after_days": 730, - "cold_storage_after_days": 30 - } - } + "recaptcha_public_key": "123-KFEUsjehfuejILDVUKkRnAF9SSzb8o9uv5lY7Ih" } - }, - "backup_config": { - "backup_account_id": "111122223333", - "backup_region": "us-west-2", - "general_vault_name": "CompactConnectBackupVault", - "ssn_vault_name": "CompactConnectSSNBackupVault" } } } diff --git a/backend/compact-connect-ui-app/cdk.context.sandbox-example.json b/backend/compact-connect-ui-app/cdk.context.sandbox-example.json index 593eae5c2..af98a3d87 100644 --- a/backend/compact-connect-ui-app/cdk.context.sandbox-example.json +++ b/backend/compact-connect-ui-app/cdk.context.sandbox-example.json @@ -1,11 +1,6 @@ { "sandbox": true, "environment_name": "justin", - "sandbox_active_compact_member_jurisdictions": { - "aslp": ["al", "ak", "ar", "co", "de", "ky", "la", "me", "md", "mn", "ms", "mo", "ne", "oh"], - "octp": ["al", "ar", "ky", "la", "ms", "ne", "oh"], - "coun": ["al", "ar", "fl", "ga", "ky", "ne", "oh", "ut"] - }, "ssm_context": { "github_repo_string": "csg-org/CompactConnect", "app_name": "compact-connect", @@ -14,17 +9,8 @@ "account_id": "111122223333", "region": "us-east-1", "domain_name": "justin.compactconnect.org", - "backup_enabled": false, - "allow_local_ui": true, - "security_profile": "VULNERABLE", "recaptcha_public_key": "123-KFEUsjehfuejILDVUKkRnAF9SSzb8o9uv5lY7Ih", - "robots_meta": "noindex,nofollow", - "notifications": { - "ses_operations_support_email": "justin@example.com", - "email": [ - "justin@example.com" - ] - } + "robots_meta": "noindex,nofollow" } } } diff --git a/backend/compact-connect-ui-app/cdk.context.test-example.json b/backend/compact-connect-ui-app/cdk.context.test-example.json index 56cf93bad..c291c1468 100644 --- a/backend/compact-connect-ui-app/cdk.context.test-example.json +++ b/backend/compact-connect-ui-app/cdk.context.test-example.json @@ -12,54 +12,9 @@ "account_id": "111122223333", "region": "us-east-1", "domain_name": "test.compactconnect.org", - "backup_enabled": true, - "allow_local_ui": true, "recaptcha_public_key": "123-KFEUsjehfuejILDVUKkRnAF9SSzb8o9uv5lY7Ih", - "robots_meta": "noindex,nofollow", - "notifications": { - "ses_operations_support_email": "justin@example.com", - "email": [ - "justin@example.com" - ], - "slack": [ - { - "channel_name": "preprod-ops-monitoring", - "channel_id": "C1234567890", - "workspace_id": "T01234567" - } - ] - }, - "backup_policies": { - "general_data": { - "schedule": { - "week_day": "5", - "year": "*", - "month": "*", - "hour": "5", - "minute": "0" - }, - "delete_after_days": 180, - "cold_storage_after_days": 30 - }, - "frequent_updates": { - "schedule": { - "week_day": "5", - "year": "*", - "month": "*", - "hour": "5", - "minute": "0" - }, - "delete_after_days": 180, - "cold_storage_after_days": 30 - } - } + "robots_meta": "noindex,nofollow" } - }, - "backup_config": { - "backup_account_id": "111122223333", - "backup_region": "us-west-2", - "general_vault_name": "CompactConnectBackupVault", - "ssn_vault_name": "CompactConnectBackupVault-SSN" } } } diff --git a/backend/compact-connect-ui-app/cdk.json b/backend/compact-connect-ui-app/cdk.json index 7828ec435..aa3a63520 100644 --- a/backend/compact-connect-ui-app/cdk.json +++ b/backend/compact-connect-ui-app/cdk.json @@ -17,7 +17,7 @@ "context": { "tags": { "project": "compact-connect", - "service": "license-data" + "service": "ui-app" }, "@aws-cdk/aws-lambda:recognizeLayerVersion": true, "@aws-cdk/core:checkSecretUsage": true, diff --git a/backend/compact-connect-ui-app/pipeline/__init__.py b/backend/compact-connect-ui-app/pipeline/__init__.py index 9d649ba30..fb1e20b9e 100644 --- a/backend/compact-connect-ui-app/pipeline/__init__.py +++ b/backend/compact-connect-ui-app/pipeline/__init__.py @@ -7,6 +7,7 @@ PROD_ENVIRONMENT_NAME, TEST_ENVIRONMENT_NAME, BasePipelineStack, + CCPipelineType, ) from constructs import Construct @@ -42,6 +43,7 @@ def __init__( construct_id, environment_name=environment_name, env=env, + pipeline_type=CCPipelineType.FRONTEND, removal_policy=removal_policy, pipeline_access_logs_bucket=pipeline_access_logs_bucket, **kwargs, diff --git a/backend/compact-connect-ui-app/tests/app/test_sandbox.py b/backend/compact-connect-ui-app/tests/app/test_sandbox.py new file mode 100644 index 000000000..ac83956c5 --- /dev/null +++ b/backend/compact-connect-ui-app/tests/app/test_sandbox.py @@ -0,0 +1,26 @@ +import json +from unittest import TestCase + +from tests.app.base import TstAppABC + + +class TstSandbox(TstAppABC, TestCase): + @classmethod + def get_context(cls): + with open('cdk.json') as f: + context = json.load(f)['context'] + with open('cdk.context.sandbox-example.json') as f: + context.update(json.load(f)) + + # Suppresses lambda bundling for tests + context['aws:cdk:bundling-stacks'] = [] + + return context + + def test_synth_pipeline(self): + """ + Test infrastructure as deployed to a sandbox + """ + # Identify any findings from our AwsSolutions rule sets + self._check_no_frontend_stage_annotations(self.app.sandbox_frontend_stage) + self._inspect_frontend_deployment_stack(self.app.sandbox_frontend_stage.frontend_deployment_stack) diff --git a/backend/compact-connect-ui-app/tests/resources/snapshots/SandboxUI-FrontendDeploymentStack-UI_BUCKET.json b/backend/compact-connect-ui-app/tests/resources/snapshots/SandboxUI-FrontendDeploymentStack-UI_BUCKET.json new file mode 100644 index 000000000..d1174fd97 --- /dev/null +++ b/backend/compact-connect-ui-app/tests/resources/snapshots/SandboxUI-FrontendDeploymentStack-UI_BUCKET.json @@ -0,0 +1,63 @@ +{ + "Type": "AWS::S3::Bucket", + "Properties": { + "BucketEncryption": { + "ServerSideEncryptionConfiguration": [ + { + "ServerSideEncryptionByDefault": { + "SSEAlgorithm": "AES256" + } + } + ] + }, + "LoggingConfiguration": { + "DestinationBucketName": { + "Ref": "UIAccessLogsBucket83B32FFC" + }, + "LogFilePrefix": "_logs/111122223333/us-east-1/SandboxUI/FrontendDeploymentStack/UIBucket" + }, + "OwnershipControls": { + "Rules": [ + { + "ObjectOwnership": "BucketOwnerEnforced" + } + ] + }, + "PublicAccessBlockConfiguration": { + "BlockPublicAcls": true, + "BlockPublicPolicy": true, + "IgnorePublicAcls": true, + "RestrictPublicBuckets": true + }, + "Tags": [ + { + "Key": "aws-cdk:auto-delete-objects", + "Value": "true" + }, + { + "Key": "aws-cdk:cr-owned:286447d0", + "Value": "true" + } + ] + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete", + "Metadata": { + "cdk_nag": { + "rules_to_suppress": [ + { + "reason": "This bucket contains built files that are replaced each deploy of the UI. We have no desire for the resilience of bucket replication for this data.", + "id": "HIPAA.Security-S3BucketReplicationEnabled" + }, + { + "reason": "The data in this bucket is public web app static files. Default S3 encryption is more than enough for protecting this data.", + "id": "HIPAA.Security-S3DefaultEncryptionKMS" + }, + { + "reason": "This bucket contains built files that are replaced each deploy. We have no desire for the resilience of versioning", + "id": "HIPAA.Security-S3BucketVersioningEnabled" + } + ] + } + } +} diff --git a/backend/compact-connect-ui-app/tests/resources/snapshots/SandboxUI-FrontendDeploymentStack-UI_DISTRIBUTION.json b/backend/compact-connect-ui-app/tests/resources/snapshots/SandboxUI-FrontendDeploymentStack-UI_DISTRIBUTION.json new file mode 100644 index 000000000..4274769b3 --- /dev/null +++ b/backend/compact-connect-ui-app/tests/resources/snapshots/SandboxUI-FrontendDeploymentStack-UI_DISTRIBUTION.json @@ -0,0 +1,136 @@ +{ + "UIDistribution2F12206B": { + "Type": "AWS::CloudFront::Distribution", + "Properties": { + "DistributionConfig": { + "Aliases": [ + "app.justin.compactconnect.org" + ], + "CacheBehaviors": [ + { + "AllowedMethods": [ + "GET", + "HEAD", + "OPTIONS" + ], + "CachePolicyId": "4135ea2d-6df8-44a3-9df3-4b5a84be39ad", + "Compress": true, + "PathPattern": "service-worker.js", + "TargetOriginId": "SandboxUIFrontendDeploymentStackUIDistributionOrigin2017382D3", + "ViewerProtocolPolicy": "redirect-to-https" + } + ], + "CustomErrorResponses": [ + { + "ErrorCode": 404, + "ResponseCode": 200, + "ResponsePagePath": "/index.html" + }, + { + "ErrorCode": 403, + "ResponseCode": 200, + "ResponsePagePath": "/index.html" + } + ], + "DefaultCacheBehavior": { + "CachePolicyId": "4135ea2d-6df8-44a3-9df3-4b5a84be39ad", + "Compress": true, + "LambdaFunctionAssociations": [ + { + "EventType": "viewer-response", + "LambdaFunctionARN": { + "Ref": "CSPFunctionCurrentVersionB61A66115988ea1180930366a7af32c8681342bd" + } + } + ], + "TargetOriginId": "SandboxUIFrontendDeploymentStackUIDistributionOrigin1871A4614", + "ViewerProtocolPolicy": "redirect-to-https" + }, + "DefaultRootObject": "index.html", + "Enabled": true, + "HttpVersion": "http2", + "IPV6Enabled": true, + "Logging": { + "Bucket": { + "Fn::GetAtt": [ + "UIAccessLogsBucket83B32FFC", + "RegionalDomainName" + ] + }, + "Prefix": "_logs/111122223333/us-east-1/SandboxUI/FrontendDeploymentStack/UIDistribution/" + }, + "Origins": [ + { + "DomainName": { + "Fn::GetAtt": [ + "UIBucketB980636D", + "RegionalDomainName" + ] + }, + "Id": "SandboxUIFrontendDeploymentStackUIDistributionOrigin1871A4614", + "OriginAccessControlId": { + "Fn::GetAtt": [ + "UIDistributionOrigin1S3OriginAccessControl71BC2D71", + "Id" + ] + }, + "OriginShield": { + "Enabled": false + }, + "S3OriginConfig": { + "OriginAccessIdentity": "" + } + }, + { + "DomainName": { + "Fn::GetAtt": [ + "UIBucketB980636D", + "RegionalDomainName" + ] + }, + "Id": "SandboxUIFrontendDeploymentStackUIDistributionOrigin2017382D3", + "OriginAccessControlId": { + "Fn::GetAtt": [ + "UIDistributionOrigin2S3OriginAccessControl3DEC1613", + "Id" + ] + }, + "OriginShield": { + "Enabled": false + }, + "S3OriginConfig": { + "OriginAccessIdentity": "" + } + } + ], + "ViewerCertificate": { + "AcmCertificateArn": { + "Ref": "UICert5FB68509" + }, + "MinimumProtocolVersion": "TLSv1.2_2021", + "SslSupportMethod": "sni-only" + }, + "WebACLId": { + "Fn::GetAtt": [ + "DistributionAcl2117273A", + "Arn" + ] + } + } + }, + "Metadata": { + "cdk_nag": { + "rules_to_suppress": [ + { + "reason": "An ACM certificate will be added to this distribution once we have linked its domain name", + "id": "AwsSolutions-CFR4" + }, + { + "reason": "Geo restrictions are not desirable at this time", + "id": "AwsSolutions-CFR1" + } + ] + } + } + } +} diff --git a/backend/compact-connect-ui-app/tests/resources/snapshots/SandboxUI-FrontendDeploymentStack-UI_DISTRIBUTION_LAMBDA_FUNCTION.json b/backend/compact-connect-ui-app/tests/resources/snapshots/SandboxUI-FrontendDeploymentStack-UI_DISTRIBUTION_LAMBDA_FUNCTION.json new file mode 100644 index 000000000..edb3ad0d9 --- /dev/null +++ b/backend/compact-connect-ui-app/tests/resources/snapshots/SandboxUI-FrontendDeploymentStack-UI_DISTRIBUTION_LAMBDA_FUNCTION.json @@ -0,0 +1,33 @@ +{ + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ZipFile": "//\n// index.js\n// CompactConnect\n//\n// Created by InspiringApps on 7/22/2024.\n//\n\n// ============================================================================\n// CONFIGURATION =\n// ============================================================================\n/**\n * Configuration of supported domains.\n *\n * These values are injected into the lambda function at build time. See the\n * `generate_csp_lambda_code` function in\n * backend/compact-connect/stacks/frontend_deployment_stack/distribution.py\n * @type {object}\n */\nconst environmentValues = {\n webFrontend: `test-ui.example.com`,\n dataApi: `test-api.example.com`,\n s3UploadUrlState: `test-bulk-uploads-bucket-name.s3.amazonaws.com`,\n s3UploadUrlProvider: `test-provider-users-bucket-name.s3.amazonaws.com`,\n cognitoStaff: `test-staff-domain.auth.us-east-1.amazoncognito.com`,\n cognitoProvider: `test-provider-domain.auth.us-east-1.amazoncognito.com`,\n};\n\n// ============================================================================\n// HELPERS =\n// ============================================================================\n/**\n * Get the request domain from the lambda event record.\n * @param {object} eventRecord The cloudfront record from the lambda event.\n * @return {string} The bare domain of the request domain.\n */\n\n/**\n * Get a fully qualified domain URI with the protocol scheme.\n * @param {string} domain The bare domain string.\n * @return {string} The fully-qualified domain string.\n */\nconst getFullyQualified = (domain) => {\n const protocol = 'https://';\n let fullyQualified = '';\n\n if (domain && typeof domain === 'string' && !domain.startsWith(protocol)) {\n fullyQualified = `${protocol}${domain}`;\n }\n\n return fullyQualified;\n};\n\n/**\n * Helper to get the fully-qualified domains for connected services.\n * @return {object} A map of fully-qualified domains for the environment.\n * @return {string} dataApi The data API fully-qualified domain.\n * @return {string} s3UploadUrlState The S3 fully-qualified domain for uploading state files.\n * @return {string} s3UploadUrlProvider The S3 fully-qualified domain for uploading provider files.\n * @return {string} cognitoStaff The Cognito fully-qualified domain for authenticating staff users.\n */\nconst getEnvironmentUrls = () => {\n const environmentUrls = {};\n\n environmentUrls.dataApi = getFullyQualified(environmentValues.dataApi);\n environmentUrls.s3UploadUrlState = getFullyQualified(environmentValues.s3UploadUrlState);\n environmentUrls.s3UploadUrlProvider = getFullyQualified(environmentValues.s3UploadUrlProvider);\n environmentUrls.cognitoStaff = getFullyQualified(environmentValues.cognitoStaff);\n environmentUrls.cognitoProvider = getFullyQualified(environmentValues.cognitoProvider);\n\n return environmentUrls;\n};\n\n/**\n * Helper to escape CSP src keywords.\n * @param {string} keyword The standard keyword value.\n * @return {string} The escaped keyword value.\n */\nconst srcKeywordEscape = (keyword) => {\n let escaped = '';\n\n if (keyword && typeof keyword === 'string') {\n escaped = `'${keyword}'`.toLowerCase();\n }\n\n return escaped;\n};\n\n/**\n * Helper to automatically escape and prep CSP src keyword values (by reference).\n * @param {Array} srcList The CSP src list.\n * @param {string} [listName=''] Optional src list name for logging.\n */\nconst srcKeywordsEscape = (srcList, listName = '') => {\n // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy#keyword_values\n const srcKeywordsConfig = [\n { value: 'self', isAllowed: true },\n { value: 'none', isAllowed: true },\n { value: 'strict-dynamic', isAllowed: true },\n { value: 'report-sample', isAllowed: true },\n { value: 'inline-speculation-rules', isAllowed: true },\n { value: 'unsafe-inline', isAllowed: false },\n { value: 'unsafe-eval', isAllowed: false },\n { value: 'unsafe-hashes', isAllowed: false },\n { value: 'wasm-unsafe-eval', isAllowed: false },\n ];\n const srcKeywords = srcKeywordsConfig.map((config) => config.value.toLowerCase());\n\n if (Array.isArray(srcList)) {\n srcList.forEach((srcItem, idx) => {\n const isString = typeof srcItem === 'string';\n\n if (!isString) {\n srcList[idx] = '';\n } else {\n const srcItemLowerCase = srcItem.toLowerCase();\n\n if (srcKeywords.includes(srcItemLowerCase)) {\n const keywordConfig = srcKeywordsConfig.find(\n (config) => srcItemLowerCase === config.value.toLowerCase()\n );\n\n if (keywordConfig) {\n if (!keywordConfig.isAllowed) {\n console.warn(`${listName} ${srcItem} keyword is not allowed in srcKeywordsConfig policy. We likely should not be using this keyword for security reasons.`.trim());\n srcList[idx] = '';\n } else {\n srcList[idx] = srcKeywordEscape(srcItem);\n }\n }\n }\n }\n });\n }\n};\n\n/**\n * Helper to build a CSP src group list string from input params.\n * @param {string} name src name for the CSP group.\n * @param {Array} list The static src list for the CSP group.\n * @return {string} The prepped src list string;\n */\nconst buildSrcString = (name = '', list = []) => {\n let srcString = '';\n\n if (Array.isArray(list)) {\n srcKeywordsEscape(list, name);\n srcString = `${name} ${list.join(' ')};`;\n }\n\n return srcString;\n};\n\n// ============================================================================\n// RESPONSE HEADERS =\n// ============================================================================\n/**\n * Set the CSP header on the response (by reference).\n * @param {object} [headers={}] The event response headers (updated by reference).\n */\nconst setCspHeader = (headers = {}) => {\n const domains = getEnvironmentUrls();\n const cognitoIdpUrl = 'https://cognito-idp.us-east-1.amazonaws.com';\n\n // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy\n headers['content-security-policy'] = [{\n key: 'Content-Security-Policy',\n value: `${[\n `default-src 'none';`,\n buildSrcString('manifest-src', [\n 'self',\n ]),\n buildSrcString('script-src', [\n 'self',\n 'https://www.google.com/recaptcha/',\n 'https://www.gstatic.com/recaptcha/',\n 'https://jstest.authorize.net/',\n 'https://js.authorize.net/',\n ]),\n buildSrcString('script-src-elem', [\n 'self',\n 'https://www.google.com/recaptcha/',\n 'https://www.gstatic.com/recaptcha/',\n 'https://jstest.authorize.net/',\n 'https://js.authorize.net/',\n ]),\n buildSrcString('script-src-attr', [\n 'self',\n ]),\n buildSrcString('worker-src', [\n 'self',\n ]),\n buildSrcString('style-src', [\n 'self',\n 'https://fonts.googleapis.com',\n 'https://www.gstatic.com/recaptcha/',\n ]),\n buildSrcString('style-src-elem', [\n 'self',\n 'https://fonts.googleapis.com',\n 'https://jstest.authorize.net/',\n 'https://js.authorize.net/',\n `'sha256-YwWQHXh4Vw0oD2Oo8pV9huEF2sE9mD8i5nZUuHzEg9A='`, // diff --git a/webroot/src/components/StyleGuide/ExampleForm/ExampleForm.ts b/webroot/src/components/StyleGuide/ExampleForm/ExampleForm.ts index fb1b1cbb5..865854baf 100644 --- a/webroot/src/components/StyleGuide/ExampleForm/ExampleForm.ts +++ b/webroot/src/components/StyleGuide/ExampleForm/ExampleForm.ts @@ -12,6 +12,7 @@ import Section from '@components/Section/Section.vue'; import InputText from '@components/Forms/InputText/InputText.vue'; import InputTextarea from '@components/Forms/InputTextarea/InputTextarea.vue'; import InputSelect from '@components/Forms/InputSelect/InputSelect.vue'; +import InputSelectMultiple from '@components/Forms/InputSelectMultiple/InputSelectMultiple.vue'; import InputCheckbox from '@components/Forms/InputCheckbox/InputCheckbox.vue'; import InputRadioGroup from '@components/Forms/InputRadioGroup/InputRadioGroup.vue'; import InputDate from '@components/Forms/InputDate/InputDate.vue'; @@ -34,6 +35,7 @@ const joiPassword = Joi.extend(joiPasswordExtendCore); InputText, InputTextarea, InputSelect, + InputSelectMultiple, InputCheckbox, InputRadioGroup, InputDate, @@ -113,7 +115,7 @@ class ExampleForm extends mixins(MixinForm) { showMax: true, enforceMax: true, }), - state: new FormInput({ + state: new FormInput({ // Single select id: 'state', name: 'state', label: computed(() => this.$t('common.stateJurisdiction')), @@ -122,6 +124,14 @@ class ExampleForm extends mixins(MixinForm) { valueOptions: [{ value: '', name: computed(() => this.$t('common.chooseOne')) }] .concat(this.states.map((state) => ({ value: state.abbrev, name: state.full }))), }), + states: new FormInput({ // Multi select + id: 'states', + name: 'states', + label: computed(() => this.$t('common.statesMultiple')), + validation: Joi.array().min(1).messages(this.joiMessages.array), + valueOptions: this.states.map((state) => ({ value: state.abbrev, name: state.full })), + value: [], + }), isSubscribed: new FormInput({ id: 'subscribe', name: 'subscribe', diff --git a/webroot/src/components/StyleGuide/ExampleForm/ExampleForm.vue b/webroot/src/components/StyleGuide/ExampleForm/ExampleForm.vue index fdb29192e..98f12156f 100644 --- a/webroot/src/components/StyleGuide/ExampleForm/ExampleForm.vue +++ b/webroot/src/components/StyleGuide/ExampleForm/ExampleForm.vue @@ -18,6 +18,7 @@ :shouldResizeY="true" /> + diff --git a/webroot/src/locales/en.json b/webroot/src/locales/en.json index cc980d74c..fa7d7c450 100644 --- a/webroot/src/locales/en.json +++ b/webroot/src/locales/en.json @@ -18,6 +18,7 @@ "chooseOne": "Choose one", "select": "Select", "selectOption": "- Select -", + "selectMultipleKeys": "Hold Ctrl / Cmd key to select multiple", "selectFile": "Select file", "selectFiles": "Select files", "replaceFile": "Replace file", @@ -68,6 +69,7 @@ "address": "Address", "city": "City", "state": "State", + "statesMultiple": "States", "stateJurisdiction": "State / Jurisdiction", "zipCode": "Zip Code", "language": "Language", diff --git a/webroot/src/locales/es.json b/webroot/src/locales/es.json index 82fefe357..d6aeb4f9c 100644 --- a/webroot/src/locales/es.json +++ b/webroot/src/locales/es.json @@ -18,6 +18,7 @@ "chooseOne": "Elige uno", "select": "Seleccionar", "selectOption": "- Seleccionar -", + "selectMultipleKeys": "Mantenga presionada la tecla Ctrl / Cmd para seleccionar varios", "selectFile": "Seleccione Archivo", "selectFiles": "Selecciona archivos", "replaceFile": "Reemplazar el archivo", @@ -69,6 +70,7 @@ "formValidationErrorMessage": "Corrija los errores en el formulario que se muestra en rojo y marque la casilla que reconoce que no hay reembolsos.", "city": "Ciudad", "state": "Estado", + "statesMultiple": "Estados", "stateJurisdiction": "Estado o Jurisdicción", "zipCode": "Código postal", "language": "Idioma", diff --git a/webroot/yarn.lock b/webroot/yarn.lock index 6c2674121..f6dee9b90 100644 --- a/webroot/yarn.lock +++ b/webroot/yarn.lock @@ -11942,9 +11942,9 @@ tmp@^0.0.33: os-tmpdir "~1.0.2" tmp@~0.2.3: - version "0.2.3" - resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.3.tgz#eb783cc22bc1e8bebd0671476d46ea4eb32a79ae" - integrity sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w== + version "0.2.5" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.5.tgz#b06bcd23f0f3c8357b426891726d16015abfd8f8" + integrity sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow== to-fast-properties@^2.0.0: version "2.0.0" From 0b56ec50a283002f89e60a890a80fc9a4873047e Mon Sep 17 00:00:00 2001 From: John Sandoval Date: Tue, 7 Oct 2025 11:12:29 -0600 Subject: [PATCH 27/32] WIP: adverse action category multi-select - Add npdb example to review w/ design team - @todo: Coordinate with design for the input UI - @todo: Apply to the adverse action UI - @todo: Consider using a feature flag --- .../StyleGuide/ExampleForm/ExampleForm.ts | 19 ++++++++++++++++++- .../StyleGuide/ExampleForm/ExampleForm.vue | 1 + 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/webroot/src/components/StyleGuide/ExampleForm/ExampleForm.ts b/webroot/src/components/StyleGuide/ExampleForm/ExampleForm.ts index 865854baf..6ace11969 100644 --- a/webroot/src/components/StyleGuide/ExampleForm/ExampleForm.ts +++ b/webroot/src/components/StyleGuide/ExampleForm/ExampleForm.ts @@ -6,7 +6,7 @@ // import { Component, mixins, toNative } from 'vue-facing-decorator'; -import { reactive, computed } from 'vue'; +import { reactive, computed, ComputedRef } from 'vue'; import MixinForm from '@components/Forms/_mixins/form.mixin'; import Section from '@components/Section/Section.vue'; import InputText from '@components/Forms/InputText/InputText.vue'; @@ -76,6 +76,15 @@ class ExampleForm extends mixins(MixinForm) { return this.$tm('common.states'); } + get npdbCategoryOptions(): Array<{ value: string, name: string | ComputedRef }> { + const options = this.$tm('licensing.npdbTypes').map((npdbType) => ({ + value: npdbType.key, + name: npdbType.name, + })); + + return options; + } + get statusOptions(): any { return this.$tm('styleGuide.statusOptions'); } @@ -132,6 +141,14 @@ class ExampleForm extends mixins(MixinForm) { valueOptions: this.states.map((state) => ({ value: state.abbrev, name: state.full })), value: [], }), + npdbCategories: new FormInput({ // Multi select + id: 'npdb-categories', + name: 'npdb-categories', + label: computed(() => this.$t('licensing.npdbCategoryLabel')), + validation: Joi.array().min(1).messages(this.joiMessages.array), + valueOptions: this.npdbCategoryOptions, + value: [], + }), isSubscribed: new FormInput({ id: 'subscribe', name: 'subscribe', diff --git a/webroot/src/components/StyleGuide/ExampleForm/ExampleForm.vue b/webroot/src/components/StyleGuide/ExampleForm/ExampleForm.vue index 98f12156f..395d1b670 100644 --- a/webroot/src/components/StyleGuide/ExampleForm/ExampleForm.vue +++ b/webroot/src/components/StyleGuide/ExampleForm/ExampleForm.vue @@ -19,6 +19,7 @@ /> + From ef2391c9fa43459c365f6ebcff9fe3383e1cdc4a Mon Sep 17 00:00:00 2001 From: John Sandoval Date: Thu, 9 Oct 2025 12:21:03 -0600 Subject: [PATCH 28/32] WIP: adverse action category multi-select - Added multi-select NPDB category to encumber UI - Updated model / store / network modules - Update unencumber UI to display encumbrance type rather than NPDB category - Updated webpack config to suppress css load order warnings - @todo: Make final UI updates once designs are complete - @todo: Use a feature flag --- .../InputSelectMultiple.less | 8 ++++++- .../src/components/LicenseCard/LicenseCard.ts | 20 ++++++++++++++++-- .../components/LicenseCard/LicenseCard.vue | 5 ++++- .../components/PrivilegeCard/PrivilegeCard.ts | 20 ++++++++++++++++-- .../PrivilegeCard/PrivilegeCard.vue | 5 ++++- .../StyleGuide/ExampleForm/ExampleForm.ts | 8 ------- .../StyleGuide/ExampleForm/ExampleForm.vue | 1 - .../AdverseAction/AdverseAction.model.spec.ts | 8 +++++++ .../AdverseAction/AdverseAction.model.ts | 11 ++++++++++ webroot/src/network/data.api.ts | 16 +++++++++++++- webroot/src/network/licenseApi/data.api.ts | 10 +++++++-- webroot/src/network/mocks/mock.data.api.ts | 14 ++++++++++++- webroot/src/store/users/users.actions.ts | 4 ++++ webroot/vue.config.js | 21 +++++++++++++++++++ 14 files changed, 131 insertions(+), 20 deletions(-) diff --git a/webroot/src/components/Forms/InputSelectMultiple/InputSelectMultiple.less b/webroot/src/components/Forms/InputSelectMultiple/InputSelectMultiple.less index 8051b82ce..e09c83d53 100644 --- a/webroot/src/components/Forms/InputSelectMultiple/InputSelectMultiple.less +++ b/webroot/src/components/Forms/InputSelectMultiple/InputSelectMultiple.less @@ -7,8 +7,13 @@ .input-container { .multi-select-description { + display: none; font-size: @fontSizeSmaller; font-style: italic; + + @media (hover: hover) { + display: flex; + } } .select-dropdown { @@ -41,11 +46,12 @@ .remove { position: absolute; - top: 0; + top: 50%; right: 0; width: @removeSize; height: @removeSize; margin-left: auto; + transform: translateY(-50%); cursor: pointer; stroke: @primaryColor; } diff --git a/webroot/src/components/LicenseCard/LicenseCard.ts b/webroot/src/components/LicenseCard/LicenseCard.ts index ef66ec115..34617f1a3 100644 --- a/webroot/src/components/LicenseCard/LicenseCard.ts +++ b/webroot/src/components/LicenseCard/LicenseCard.ts @@ -20,6 +20,7 @@ import { dateFormatPatterns } from '@/app.config'; import MixinForm from '@components/Forms/_mixins/form.mixin'; import InputDate from '@components/Forms/InputDate/InputDate.vue'; import InputSelect from '@components/Forms/InputSelect/InputSelect.vue'; +import InputSelectMultiple from '@components/Forms/InputSelectMultiple/InputSelectMultiple.vue'; import InputCheckbox from '@components/Forms/InputCheckbox/InputCheckbox.vue'; import InputButton from '@components/Forms/InputButton/InputButton.vue'; import InputSubmit from '@components/Forms/InputSubmit/InputSubmit.vue'; @@ -46,6 +47,7 @@ import moment from 'moment'; MockPopulate, InputDate, InputSelect, + InputSelectMultiple, InputCheckbox, InputButton, InputSubmit, @@ -236,6 +238,7 @@ class LicenseCard extends mixins(MixinForm) { name: npdbType.name, })); + // @TODO: Feature flag options.unshift({ value: '', name: computed(() => this.$t('common.selectOption')), @@ -279,6 +282,14 @@ class LicenseCard extends mixins(MixinForm) { validation: Joi.string().required().messages(this.joiMessages.string), valueOptions: this.npdbCategoryOptions, }), + encumberModalNpdbCategories: new FormInput({ + id: 'npdb-categories', + name: 'npdb-categories', + label: computed(() => this.$t('licensing.npdbCategoryLabel')), + validation: Joi.array().min(1).messages(this.joiMessages.array), + valueOptions: this.npdbCategoryOptions, + value: [], + }), encumberModalStartDate: new FormInput({ id: 'encumber-start', name: 'encumber-start', @@ -310,7 +321,7 @@ class LicenseCard extends mixins(MixinForm) { const adverseActionInput = new FormInput({ id: `adverse-action-data-${adverseActionId}`, name: `adverse-action-data-${adverseActionId}`, - label: adverseAction.npdbTypeName(), + label: adverseAction.encumbranceTypeName(), isDisabled: Boolean(adverseAction.endDate), }); @@ -413,19 +424,23 @@ class LicenseCard extends mixins(MixinForm) { licenseTypeAbbrev } = this; - await this.$store.dispatch(`users/encumberLicenseRequest`, { + const response = await this.$store.dispatch(`users/encumberLicenseRequest`, { compact: compactType, licenseeId, licenseState: stateAbbrev, licenseType: licenseTypeAbbrev.toLowerCase(), encumbranceType: this.formData.encumberModalDisciplineAction.value, npdbCategory: this.formData.encumberModalNpdbCategory.value, + npdbCategories: this.formData.encumberModalNpdbCategories.value, startDate: this.formData.encumberModalStartDate.value, }).catch((err) => { this.modalErrorMessage = err?.message || this.$t('common.error'); this.isFormError = true; }); + // @TODO + console.log(response); + if (!this.isFormError) { this.isFormSuccessful = true; await this.$store.dispatch('license/getLicenseeRequest', { compact: compactType, licenseeId }); @@ -620,6 +635,7 @@ class LicenseCard extends mixins(MixinForm) { } else if (this.isEncumberLicenseModalDisplayed) { this.formData.encumberModalDisciplineAction.value = this.encumberDisciplineOptions[1]?.value; this.formData.encumberModalNpdbCategory.value = this.npdbCategoryOptions[1]?.value; + this.formData.encumberModalNpdbCategories.value = [this.npdbCategoryOptions[1]?.value]; this.formData.encumberModalStartDate.value = moment().format('YYYY-MM-DD'); await nextTick(); this.validateAll({ asTouched: true }); diff --git a/webroot/src/components/LicenseCard/LicenseCard.vue b/webroot/src/components/LicenseCard/LicenseCard.vue index 17f71070f..b5901ca04 100644 --- a/webroot/src/components/LicenseCard/LicenseCard.vue +++ b/webroot/src/components/LicenseCard/LicenseCard.vue @@ -130,9 +130,12 @@

-
+
+
+ +
this.$t('common.selectOption')), @@ -271,6 +274,14 @@ class PrivilegeCard extends mixins(MixinForm) { validation: Joi.string().required().messages(this.joiMessages.string), valueOptions: this.npdbCategoryOptions, }), + encumberModalNpdbCategories: new FormInput({ + id: 'npdb-categories', + name: 'npdb-categories', + label: computed(() => this.$t('licensing.npdbCategoryLabel')), + validation: Joi.array().min(1).messages(this.joiMessages.array), + valueOptions: this.npdbCategoryOptions, + value: [], + }), encumberModalStartDate: new FormInput({ id: 'encumber-start', name: 'encumber-start', @@ -302,7 +313,7 @@ class PrivilegeCard extends mixins(MixinForm) { const adverseActionInput = new FormInput({ id: `adverse-action-data-${adverseActionId}`, name: `adverse-action-data-${adverseActionId}`, - label: adverseAction.npdbTypeName(), + label: adverseAction.encumbranceTypeName(), isDisabled: Boolean(adverseAction.endDate), }); @@ -468,19 +479,23 @@ class PrivilegeCard extends mixins(MixinForm) { privilegeTypeAbbrev } = this; - await this.$store.dispatch(`users/encumberPrivilegeRequest`, { + const response = await this.$store.dispatch(`users/encumberPrivilegeRequest`, { compact: compactType, licenseeId, privilegeState: stateAbbrev, licenseType: privilegeTypeAbbrev.toLowerCase(), encumbranceType: this.formData.encumberModalDisciplineAction.value, npdbCategory: this.formData.encumberModalNpdbCategory.value, + npdbCategories: this.formData.encumberModalNpdbCategories.value, startDate: this.formData.encumberModalStartDate.value, }).catch((err) => { this.modalErrorMessage = err?.message || this.$t('common.error'); this.isFormError = true; }); + // @TODO + console.log(response); + if (!this.isFormError) { this.isFormSuccessful = true; await this.$store.dispatch('license/getLicenseeRequest', { compact: compactType, licenseeId }); @@ -675,6 +690,7 @@ class PrivilegeCard extends mixins(MixinForm) { } else if (this.isEncumberPrivilegeModalDisplayed) { this.formData.encumberModalDisciplineAction.value = this.encumberDisciplineOptions[1]?.value; this.formData.encumberModalNpdbCategory.value = this.npdbCategoryOptions[1]?.value; + this.formData.encumberModalNpdbCategories.value = [this.npdbCategoryOptions[1]?.value]; this.formData.encumberModalStartDate.value = moment().format('YYYY-MM-DD'); await nextTick(); this.validateAll({ asTouched: true }); diff --git a/webroot/src/components/PrivilegeCard/PrivilegeCard.vue b/webroot/src/components/PrivilegeCard/PrivilegeCard.vue index 638148aa2..b2bbf1c65 100644 --- a/webroot/src/components/PrivilegeCard/PrivilegeCard.vue +++ b/webroot/src/components/PrivilegeCard/PrivilegeCard.vue @@ -190,9 +190,12 @@
-
+
+
+ +
({ value: state.abbrev, name: state.full })), value: [], }), - npdbCategories: new FormInput({ // Multi select - id: 'npdb-categories', - name: 'npdb-categories', - label: computed(() => this.$t('licensing.npdbCategoryLabel')), - validation: Joi.array().min(1).messages(this.joiMessages.array), - valueOptions: this.npdbCategoryOptions, - value: [], - }), isSubscribed: new FormInput({ id: 'subscribe', name: 'subscribe', diff --git a/webroot/src/components/StyleGuide/ExampleForm/ExampleForm.vue b/webroot/src/components/StyleGuide/ExampleForm/ExampleForm.vue index 395d1b670..98f12156f 100644 --- a/webroot/src/components/StyleGuide/ExampleForm/ExampleForm.vue +++ b/webroot/src/components/StyleGuide/ExampleForm/ExampleForm.vue @@ -19,7 +19,6 @@ /> - diff --git a/webroot/src/models/AdverseAction/AdverseAction.model.spec.ts b/webroot/src/models/AdverseAction/AdverseAction.model.spec.ts index 6d6b6288c..99e8a5c3b 100644 --- a/webroot/src/models/AdverseAction/AdverseAction.model.spec.ts +++ b/webroot/src/models/AdverseAction/AdverseAction.model.spec.ts @@ -47,6 +47,7 @@ describe('AdverseAction model', () => { expect(adverseAction.state).to.be.an.instanceof(State); expect(adverseAction.type).to.equal(null); expect(adverseAction.npdbType).to.equal(null); + expect(adverseAction.npdbTypes).to.matchPattern([]); expect(adverseAction.creationDate).to.equal(null); expect(adverseAction.startDate).to.equal(null); expect(adverseAction.endDate).to.equal(null); @@ -58,6 +59,7 @@ describe('AdverseAction model', () => { expect(adverseAction.hasEndDate()).to.equal(false); expect(adverseAction.encumbranceTypeName()).to.equal(''); expect(adverseAction.npdbTypeName()).to.equal(''); + expect(adverseAction.getNpdbTypeName()).to.equal(''); expect(adverseAction.isActive()).to.equal(false); }); it('should create an AdverseAction model with specific values', () => { @@ -69,6 +71,7 @@ describe('AdverseAction model', () => { type: 'test-type', encumbranceType: 'test-encumbranceType', npdbType: 'test-npdbType', + npdbTypes: ['test-npdbType'], creationDate: 'test-creationDate', startDate: 'test-startDate', endDate: 'test-endDate', @@ -83,6 +86,7 @@ describe('AdverseAction model', () => { expect(adverseAction.state).to.be.an.instanceof(State); expect(adverseAction.type).to.equal(data.type); expect(adverseAction.npdbType).to.equal(data.npdbType); + expect(adverseAction.npdbTypes).to.matchPattern(data.npdbTypes); expect(adverseAction.creationDate).to.equal(data.creationDate); expect(adverseAction.startDate).to.equal(data.startDate); expect(adverseAction.endDate).to.equal(data.endDate); @@ -94,6 +98,7 @@ describe('AdverseAction model', () => { expect(adverseAction.hasEndDate()).to.equal(true); expect(adverseAction.encumbranceTypeName()).to.equal(''); expect(adverseAction.npdbTypeName()).to.equal(''); + expect(adverseAction.getNpdbTypeName('Other')).to.equal('Other'); expect(adverseAction.isActive()).to.equal(false); }); it('should create an AdverseAction model with specific values (startDate but no endDate)', () => { @@ -148,6 +153,7 @@ describe('AdverseAction model', () => { type: 'test-type', encumbranceType: 'fine', clinicalPrivilegeActionCategory: 'Non-compliance With Requirements', + clinicalPrivilegeActionCategories: ['Non-compliance With Requirements'], creationDate: moment.utc().format(serverDatetimeFormat), effectiveStartDate: moment().subtract(1, 'day').format(serverDateFormat), effectiveLiftDate: moment().add(1, 'day').format(serverDateFormat), @@ -163,6 +169,7 @@ describe('AdverseAction model', () => { expect(adverseAction.state.name()).to.equal('Alabama'); expect(adverseAction.type).to.equal(data.type); expect(adverseAction.npdbType).to.equal(data.clinicalPrivilegeActionCategory); + expect(adverseAction.npdbTypes).to.matchPattern(data.clinicalPrivilegeActionCategories); expect(adverseAction.creationDate).to.equal(data.creationDate); expect(adverseAction.startDate).to.equal(data.effectiveStartDate); expect(adverseAction.endDate).to.equal(data.effectiveLiftDate); @@ -178,6 +185,7 @@ describe('AdverseAction model', () => { expect(adverseAction.hasEndDate()).to.equal(true); expect(adverseAction.encumbranceTypeName()).to.equal('Fine'); expect(adverseAction.npdbTypeName()).to.equal('Non-compliance With Requirements'); + expect(adverseAction.getNpdbTypeName(data.clinicalPrivilegeActionCategories[0])).to.equal('Non-compliance With Requirements'); expect(adverseAction.isActive()).to.equal(true); }); }); diff --git a/webroot/src/models/AdverseAction/AdverseAction.model.ts b/webroot/src/models/AdverseAction/AdverseAction.model.ts index 58ffa48a5..2189a3507 100644 --- a/webroot/src/models/AdverseAction/AdverseAction.model.ts +++ b/webroot/src/models/AdverseAction/AdverseAction.model.ts @@ -23,6 +23,7 @@ export interface InterfaceAdverseActionCreate { type?: string | null; encumbranceType?: string | null; npdbType?: string | null; + npdbTypes?: Array; creationDate?: string | null; startDate?: string | null; endDate?: string | null; @@ -40,6 +41,7 @@ export class AdverseAction implements InterfaceAdverseActionCreate { public type? = null; public encumbranceType? = null; public npdbType? = null; + public npdbTypes? = []; public creationDate? = null; public startDate? = null; public endDate? = null; @@ -89,6 +91,14 @@ export class AdverseAction implements InterfaceAdverseActionCreate { return typeName; } + public getNpdbTypeName(npdbType: string): string { + const npdbTypes = this.$tm('licensing.npdbTypes') || []; + const npdbTypeRecord = npdbTypes.find((translate) => translate.key === npdbType); + const typeName = npdbTypeRecord?.name || ''; + + return typeName; + } + public isActive(): boolean { // Determine whether the adverse action is currently in effect const { startDate, endDate } = this; @@ -124,6 +134,7 @@ export class AdverseActionSerializer { type: json.type, encumbranceType: json.encumbranceType, npdbType: json.clinicalPrivilegeActionCategory, + npdbTypes: json.clinicalPrivilegeActionCategories, creationDate: json.creationDate, startDate: json.effectiveStartDate, endDate: json.effectiveLiftDate, diff --git a/webroot/src/network/data.api.ts b/webroot/src/network/data.api.ts index 3c2b9d9d2..6d4422d44 100644 --- a/webroot/src/network/data.api.ts +++ b/webroot/src/network/data.api.ts @@ -165,10 +165,20 @@ export class DataApi { * @param {string} licenseType The license type. * @param {string} encumbranceType The discipline action type. * @param {string} npdbCategory The NPDB category name. + * @param {Array} npdbCategories The NPDB category list. * @param {string} startDate The encumber start date. * @return {Promise} The server response. */ - public encumberLicense(compact, licenseeId, licenseState, licenseType, encumbranceType, npdbCategory, startDate) { + public encumberLicense( + compact, + licenseeId, + licenseState, + licenseType, + encumbranceType, + npdbCategory, + npdbCategories, + startDate + ) { return licenseDataApi.encumberLicense( compact, licenseeId, @@ -176,6 +186,7 @@ export class DataApi { licenseType, encumbranceType, npdbCategory, + npdbCategories, startDate ); } @@ -222,6 +233,7 @@ export class DataApi { * @param {string} licenseType The license type. * @param {string} encumbranceType The discipline action type. * @param {string} npdbCategory The NPDB category name. + * @param {Array} npdbCategories The NPDB category list. * @param {string} startDate The encumber start date. * @return {Promise} The server response. */ @@ -232,6 +244,7 @@ export class DataApi { licenseType, encumbranceType, npdbCategory, + npdbCategories, startDate ) { return licenseDataApi.encumberPrivilege( @@ -241,6 +254,7 @@ export class DataApi { licenseType, encumbranceType, npdbCategory, + npdbCategories, startDate ); } diff --git a/webroot/src/network/licenseApi/data.api.ts b/webroot/src/network/licenseApi/data.api.ts index 5185d3251..0a0d12bc1 100644 --- a/webroot/src/network/licenseApi/data.api.ts +++ b/webroot/src/network/licenseApi/data.api.ts @@ -260,6 +260,7 @@ export class LicenseDataApi implements DataApiInterface { * @param {string} licenseType The license type. * @param {string} encumbranceType The discipline action type. * @param {string} npdbCategory The NPDB category name. + * @param {Array} npdbCategories The NPDB category list. * @param {string} startDate The encumber start date. * @return {Promise} The server response. */ @@ -270,11 +271,13 @@ export class LicenseDataApi implements DataApiInterface { licenseType: string, encumbranceType: string, npdbCategory: string, + npdbCategories: Array, startDate: string ) { const serverResponse: any = await this.api.post(`/v1/compacts/${compact}/providers/${licenseeId}/licenses/jurisdiction/${licenseState}/licenseType/${licenseType}/encumbrance`, { encumbranceType, - clinicalPrivilegeActionCategory: npdbCategory, + clinicalPrivilegeActionCategory: npdbCategory, // @TODO: Feature flag (ternary w/ undefined?) + clinicalPrivilegeActionCategories: npdbCategories, // @TODO: Feature flag (ternary w/ undefined?) encumbranceEffectiveDate: startDate, }); @@ -337,6 +340,7 @@ export class LicenseDataApi implements DataApiInterface { * @param {string} licenseType The license type. * @param {string} encumbranceType The discipline action type. * @param {string} npdbCategory The NPDB category name. + * @param {Array} npdbCategories The NPDB category list. * @param {string} startDate The encumber start date. * @return {Promise} The server response. */ @@ -347,11 +351,13 @@ export class LicenseDataApi implements DataApiInterface { licenseType: string, encumbranceType: string, npdbCategory: string, + npdbCategories: Array, startDate: string ) { const serverResponse: any = await this.api.post(`/v1/compacts/${compact}/providers/${licenseeId}/privileges/jurisdiction/${privilegeState}/licenseType/${licenseType}/encumbrance`, { encumbranceType, - clinicalPrivilegeActionCategory: npdbCategory, + clinicalPrivilegeActionCategory: npdbCategory, // @TODO: Feature flag (ternary w/ undefined?) + clinicalPrivilegeActionCategories: npdbCategories, // @TODO: Feature flag (ternary w/ undefined?) encumbranceEffectiveDate: startDate, }); diff --git a/webroot/src/network/mocks/mock.data.api.ts b/webroot/src/network/mocks/mock.data.api.ts index 31355c2a2..0316ce89a 100644 --- a/webroot/src/network/mocks/mock.data.api.ts +++ b/webroot/src/network/mocks/mock.data.api.ts @@ -197,7 +197,16 @@ export class DataApi { } // Encumber License for a licensee. - public encumberLicense(compact, licenseeId, licenseState, licenseType, encumbranceType, npdbCategory, startDate) { + public encumberLicense( + compact, + licenseeId, + licenseState, + licenseType, + encumbranceType, + npdbCategory, + npdbCategories, + startDate + ) { if (!compact) { return Promise.reject(new Error('failed license encumber')); } @@ -210,6 +219,7 @@ export class DataApi { licenseType, encumbranceType, npdbCategory, + npdbCategories, startDate, })); } @@ -254,6 +264,7 @@ export class DataApi { licenseType, encumbranceType, npdbCategory, + npdbCategories, startDate ) { if (!compact) { @@ -268,6 +279,7 @@ export class DataApi { licenseType, encumbranceType, npdbCategory, + npdbCategories, startDate, })); } diff --git a/webroot/src/store/users/users.actions.ts b/webroot/src/store/users/users.actions.ts index 7d9367eb2..3c3940db3 100644 --- a/webroot/src/store/users/users.actions.ts +++ b/webroot/src/store/users/users.actions.ts @@ -132,6 +132,7 @@ export default { licenseType, encumbranceType, npdbCategory, + npdbCategories, startDate }: any) => { commit(MutationTypes.ENCUMBER_LICENSE_REQUEST); @@ -142,6 +143,7 @@ export default { licenseType, encumbranceType, npdbCategory, + npdbCategories, startDate ).then(async (response) => { dispatch('encumberLicenseSuccess'); @@ -228,6 +230,7 @@ export default { licenseType, encumbranceType, npdbCategory, + npdbCategories, startDate }: any) => { commit(MutationTypes.ENCUMBER_PRIVILEGE_REQUEST); @@ -238,6 +241,7 @@ export default { licenseType, encumbranceType, npdbCategory, + npdbCategories, startDate ).then(async (response) => { dispatch('encumberPrivilegeSuccess'); diff --git a/webroot/vue.config.js b/webroot/vue.config.js index 8c4a00d26..50e765b2f 100644 --- a/webroot/vue.config.js +++ b/webroot/vue.config.js @@ -90,6 +90,22 @@ const htmlPlugin = (args) => { return args; }; +/** + * extract-css-plugin configuration (https://github.com/webpack/mini-css-extract-plugin) + * Included with Vue CLI, so we are just chaining + * @param {array} args The webpack plugin options array. + * @return {array} The webpack plugin options array (updated by reference). + */ +const extractCssPlugin = (args) => { + const opts = args[0]; + + // Suppress CSS order warnings from mini-css-extract-plugin + // These warnings do not affect functionality and are common in apps that use code splitting / chunking + opts.ignoreOrder = true; + + return args; +}; + /** * fork-ts-checker-webpack-plugin (https://github.com/TypeStrong/fork-ts-checker-webpack-plugin) * Included with Vue CLI, so we are just chaining @@ -277,6 +293,11 @@ module.exports = { // Update the Typescript-Checker plugin settings config.plugin('fork-ts-checker').tap(forkTsCheckerWebpackPlugin); + // Update the CSS-Build plugin settings (only exists for builds) + if (env === ENV_PRODUCTION) { + config.plugin('extract-css').tap(extractCssPlugin); + } + // Inject common LESS styles into each module // https://cli.vuejs.org/guide/css.html#automatic-imports const types = ['vue-modules', 'vue', 'normal-modules', 'normal']; From 03f56e9e71b56cca380a2033fe56496fcbc92337 Mon Sep 17 00:00:00 2001 From: John Sandoval Date: Wed, 15 Oct 2025 13:05:14 -0600 Subject: [PATCH 29/32] WIP: adverse action category multi-select - Added initial feature flag conditionals - @todo: Update for any design changes once ready - @todo: Wire up backend once ready --- webroot/src/app.config.ts | 1 + .../src/components/LicenseCard/LicenseCard.ts | 70 ++++++++++++------- .../components/LicenseCard/LicenseCard.vue | 8 +-- .../components/PrivilegeCard/PrivilegeCard.ts | 70 ++++++++++++------- .../PrivilegeCard/PrivilegeCard.vue | 8 +-- webroot/src/network/licenseApi/data.api.ts | 24 +++++-- webroot/src/network/mocks/mock.data.api.ts | 24 +++++-- 7 files changed, 138 insertions(+), 67 deletions(-) diff --git a/webroot/src/app.config.ts b/webroot/src/app.config.ts index df803d58e..6b58f89c5 100644 --- a/webroot/src/app.config.ts +++ b/webroot/src/app.config.ts @@ -289,6 +289,7 @@ export const compacts = { // = Feature gate IDs = // ============================= export enum FeatureGates { + ENCUMBER_MULTI_CATEGORY = 'encumbrance-multi-category-flag', EXAMPLE_FEATURE_1 = 'test-feature-1', // Keep this ID in place for examples & tests } diff --git a/webroot/src/components/LicenseCard/LicenseCard.ts b/webroot/src/components/LicenseCard/LicenseCard.ts index 34617f1a3..140d90c07 100644 --- a/webroot/src/components/LicenseCard/LicenseCard.ts +++ b/webroot/src/components/LicenseCard/LicenseCard.ts @@ -16,7 +16,7 @@ import { ComputedRef, nextTick } from 'vue'; -import { dateFormatPatterns } from '@/app.config'; +import { dateFormatPatterns, FeatureGates } from '@/app.config'; import MixinForm from '@components/Forms/_mixins/form.mixin'; import InputDate from '@components/Forms/InputDate/InputDate.vue'; import InputSelect from '@components/Forms/InputSelect/InputSelect.vue'; @@ -91,6 +91,10 @@ class LicenseCard extends mixins(MixinForm) { // // Computed // + get featureGates(): typeof FeatureGates { + return FeatureGates; + } + get userStore() { return this.$store.state.user; } @@ -238,11 +242,12 @@ class LicenseCard extends mixins(MixinForm) { name: npdbType.name, })); - // @TODO: Feature flag - options.unshift({ - value: '', - name: computed(() => this.$t('common.selectOption')), - }); + if (!this.$features.checkGate(FeatureGates.ENCUMBER_MULTI_CATEGORY)) { + options.unshift({ + value: '', + name: computed(() => this.$t('common.selectOption')), + }); + } return options; } @@ -275,21 +280,27 @@ class LicenseCard extends mixins(MixinForm) { validation: Joi.string().required().messages(this.joiMessages.string), valueOptions: this.encumberDisciplineOptions, }), - encumberModalNpdbCategory: new FormInput({ - id: 'npdb-category', - name: 'npdb-category', - label: computed(() => this.$t('licensing.npdbCategoryLabel')), - validation: Joi.string().required().messages(this.joiMessages.string), - valueOptions: this.npdbCategoryOptions, - }), - encumberModalNpdbCategories: new FormInput({ - id: 'npdb-categories', - name: 'npdb-categories', - label: computed(() => this.$t('licensing.npdbCategoryLabel')), - validation: Joi.array().min(1).messages(this.joiMessages.array), - valueOptions: this.npdbCategoryOptions, - value: [], - }), + ...(this.$features.checkGate(FeatureGates.ENCUMBER_MULTI_CATEGORY) + ? { + encumberModalNpdbCategories: new FormInput({ + id: 'npdb-categories', + name: 'npdb-categories', + label: computed(() => this.$t('licensing.npdbCategoryLabel')), + validation: Joi.array().min(1).messages(this.joiMessages.array), + valueOptions: this.npdbCategoryOptions, + value: [], + }), + } + : { + encumberModalNpdbCategory: new FormInput({ + id: 'npdb-category', + name: 'npdb-category', + label: computed(() => this.$t('licensing.npdbCategoryLabel')), + validation: Joi.string().required().messages(this.joiMessages.string), + valueOptions: this.npdbCategoryOptions, + }), + } + ), encumberModalStartDate: new FormInput({ id: 'encumber-start', name: 'encumber-start', @@ -430,8 +441,14 @@ class LicenseCard extends mixins(MixinForm) { licenseState: stateAbbrev, licenseType: licenseTypeAbbrev.toLowerCase(), encumbranceType: this.formData.encumberModalDisciplineAction.value, - npdbCategory: this.formData.encumberModalNpdbCategory.value, - npdbCategories: this.formData.encumberModalNpdbCategories.value, + ...(this.$features.checkGate(FeatureGates.ENCUMBER_MULTI_CATEGORY) + ? { + npdbCategories: this.formData.encumberModalNpdbCategories.value, + } + : { + npdbCategory: this.formData.encumberModalNpdbCategory.value, + } + ), startDate: this.formData.encumberModalStartDate.value, }).catch((err) => { this.modalErrorMessage = err?.message || this.$t('common.error'); @@ -634,8 +651,11 @@ class LicenseCard extends mixins(MixinForm) { this.validateAll({ asTouched: true }); } else if (this.isEncumberLicenseModalDisplayed) { this.formData.encumberModalDisciplineAction.value = this.encumberDisciplineOptions[1]?.value; - this.formData.encumberModalNpdbCategory.value = this.npdbCategoryOptions[1]?.value; - this.formData.encumberModalNpdbCategories.value = [this.npdbCategoryOptions[1]?.value]; + if (this.$features.checkGate(FeatureGates.ENCUMBER_MULTI_CATEGORY)) { + this.formData.encumberModalNpdbCategories.value = [this.npdbCategoryOptions[1]?.value]; + } else { + this.formData.encumberModalNpdbCategory.value = this.npdbCategoryOptions[1]?.value; + } this.formData.encumberModalStartDate.value = moment().format('YYYY-MM-DD'); await nextTick(); this.validateAll({ asTouched: true }); diff --git a/webroot/src/components/LicenseCard/LicenseCard.vue b/webroot/src/components/LicenseCard/LicenseCard.vue index 1ede1331b..8341eb36b 100644 --- a/webroot/src/components/LicenseCard/LicenseCard.vue +++ b/webroot/src/components/LicenseCard/LicenseCard.vue @@ -130,12 +130,12 @@
-
- -
-
+
+
+ +
this.$t('common.selectOption')), - }); + if (!this.$features.checkGate(FeatureGates.ENCUMBER_MULTI_CATEGORY)) { + options.unshift({ + value: '', + name: computed(() => this.$t('common.selectOption')), + }); + } return options; } @@ -267,21 +272,27 @@ class PrivilegeCard extends mixins(MixinForm) { validation: Joi.string().required().messages(this.joiMessages.string), valueOptions: this.encumberDisciplineOptions, }), - encumberModalNpdbCategory: new FormInput({ - id: 'npdb-category', - name: 'npdb-category', - label: computed(() => this.$t('licensing.npdbCategoryLabel')), - validation: Joi.string().required().messages(this.joiMessages.string), - valueOptions: this.npdbCategoryOptions, - }), - encumberModalNpdbCategories: new FormInput({ - id: 'npdb-categories', - name: 'npdb-categories', - label: computed(() => this.$t('licensing.npdbCategoryLabel')), - validation: Joi.array().min(1).messages(this.joiMessages.array), - valueOptions: this.npdbCategoryOptions, - value: [], - }), + ...(this.$features.checkGate(FeatureGates.ENCUMBER_MULTI_CATEGORY) + ? { + encumberModalNpdbCategories: new FormInput({ + id: 'npdb-categories', + name: 'npdb-categories', + label: computed(() => this.$t('licensing.npdbCategoryLabel')), + validation: Joi.array().min(1).messages(this.joiMessages.array), + valueOptions: this.npdbCategoryOptions, + value: [], + }), + } + : { + encumberModalNpdbCategory: new FormInput({ + id: 'npdb-category', + name: 'npdb-category', + label: computed(() => this.$t('licensing.npdbCategoryLabel')), + validation: Joi.string().required().messages(this.joiMessages.string), + valueOptions: this.npdbCategoryOptions, + }), + } + ), encumberModalStartDate: new FormInput({ id: 'encumber-start', name: 'encumber-start', @@ -485,8 +496,14 @@ class PrivilegeCard extends mixins(MixinForm) { privilegeState: stateAbbrev, licenseType: privilegeTypeAbbrev.toLowerCase(), encumbranceType: this.formData.encumberModalDisciplineAction.value, - npdbCategory: this.formData.encumberModalNpdbCategory.value, - npdbCategories: this.formData.encumberModalNpdbCategories.value, + ...(this.$features.checkGate(FeatureGates.ENCUMBER_MULTI_CATEGORY) + ? { + npdbCategories: this.formData.encumberModalNpdbCategories.value, + } + : { + npdbCategory: this.formData.encumberModalNpdbCategory.value, + } + ), startDate: this.formData.encumberModalStartDate.value, }).catch((err) => { this.modalErrorMessage = err?.message || this.$t('common.error'); @@ -689,8 +706,11 @@ class PrivilegeCard extends mixins(MixinForm) { this.validateAll({ asTouched: true }); } else if (this.isEncumberPrivilegeModalDisplayed) { this.formData.encumberModalDisciplineAction.value = this.encumberDisciplineOptions[1]?.value; - this.formData.encumberModalNpdbCategory.value = this.npdbCategoryOptions[1]?.value; - this.formData.encumberModalNpdbCategories.value = [this.npdbCategoryOptions[1]?.value]; + if (this.$features.checkGate(FeatureGates.ENCUMBER_MULTI_CATEGORY)) { + this.formData.encumberModalNpdbCategories.value = [this.npdbCategoryOptions[1]?.value]; + } else { + this.formData.encumberModalNpdbCategory.value = this.npdbCategoryOptions[1]?.value; + } this.formData.encumberModalStartDate.value = moment().format('YYYY-MM-DD'); await nextTick(); this.validateAll({ asTouched: true }); diff --git a/webroot/src/components/PrivilegeCard/PrivilegeCard.vue b/webroot/src/components/PrivilegeCard/PrivilegeCard.vue index 5bad0097e..582a9dba5 100644 --- a/webroot/src/components/PrivilegeCard/PrivilegeCard.vue +++ b/webroot/src/components/PrivilegeCard/PrivilegeCard.vue @@ -190,12 +190,12 @@
-
- -
-
+
+
+ +
, startDate: string ) { + const { $features } = (window as any).Vue?.config?.globalProperties || {}; const serverResponse: any = await this.api.post(`/v1/compacts/${compact}/providers/${licenseeId}/licenses/jurisdiction/${licenseState}/licenseType/${licenseType}/encumbrance`, { encumbranceType, - clinicalPrivilegeActionCategory: npdbCategory, // @TODO: Feature flag (ternary w/ undefined?) - clinicalPrivilegeActionCategories: npdbCategories, // @TODO: Feature flag (ternary w/ undefined?) + ...($features?.checkGate(FeatureGates.ENCUMBER_MULTI_CATEGORY) + ? { + clinicalPrivilegeActionCategories: npdbCategories, + } + : { + clinicalPrivilegeActionCategory: npdbCategory, + } + ), encumbranceEffectiveDate: startDate, }); @@ -354,10 +361,17 @@ export class LicenseDataApi implements DataApiInterface { npdbCategories: Array, startDate: string ) { + const { $features } = (window as any).Vue?.config?.globalProperties || {}; const serverResponse: any = await this.api.post(`/v1/compacts/${compact}/providers/${licenseeId}/privileges/jurisdiction/${privilegeState}/licenseType/${licenseType}/encumbrance`, { encumbranceType, - clinicalPrivilegeActionCategory: npdbCategory, // @TODO: Feature flag (ternary w/ undefined?) - clinicalPrivilegeActionCategories: npdbCategories, // @TODO: Feature flag (ternary w/ undefined?) + ...($features?.checkGate(FeatureGates.ENCUMBER_MULTI_CATEGORY) + ? { + clinicalPrivilegeActionCategories: npdbCategories, + } + : { + clinicalPrivilegeActionCategory: npdbCategory, + } + ), encumbranceEffectiveDate: startDate, }); diff --git a/webroot/src/network/mocks/mock.data.api.ts b/webroot/src/network/mocks/mock.data.api.ts index 7f7edef8a..02e7b4bbc 100644 --- a/webroot/src/network/mocks/mock.data.api.ts +++ b/webroot/src/network/mocks/mock.data.api.ts @@ -234,6 +234,8 @@ export class DataApi { npdbCategories, startDate ) { + const { $features } = (window as any).Vue?.config?.globalProperties || {}; + if (!compact) { return Promise.reject(new Error('failed license encumber')); } @@ -245,8 +247,14 @@ export class DataApi { licenseState, licenseType, encumbranceType, - npdbCategory, - npdbCategories, + ...($features?.checkGate(FeatureGates.ENCUMBER_MULTI_CATEGORY) + ? { + npdbCategories, + } + : { + npdbCategory, + } + ), startDate, })); } @@ -294,6 +302,8 @@ export class DataApi { npdbCategories, startDate ) { + const { $features } = (window as any).Vue?.config?.globalProperties || {}; + if (!compact) { return Promise.reject(new Error('failed privilege encumber')); } @@ -305,8 +315,14 @@ export class DataApi { privilegeState, licenseType, encumbranceType, - npdbCategory, - npdbCategories, + ...($features?.checkGate(FeatureGates.ENCUMBER_MULTI_CATEGORY) + ? { + npdbCategories, + } + : { + npdbCategory, + } + ), startDate, })); } From c84556040c442c5a812bcddce461f56aee5984a2 Mon Sep 17 00:00:00 2001 From: John Sandoval Date: Wed, 15 Oct 2025 13:53:06 -0600 Subject: [PATCH 30/32] WIP: adverse action category multi-select - Added defensive case for serializer in AdverseAction model - @todo: Update for any design changes once ready - @todo: Wire up backend once ready --- .../models/AdverseAction/AdverseAction.model.spec.ts | 10 ++++++++++ .../src/models/AdverseAction/AdverseAction.model.ts | 4 +++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/webroot/src/models/AdverseAction/AdverseAction.model.spec.ts b/webroot/src/models/AdverseAction/AdverseAction.model.spec.ts index 99e8a5c3b..f87d70ab4 100644 --- a/webroot/src/models/AdverseAction/AdverseAction.model.spec.ts +++ b/webroot/src/models/AdverseAction/AdverseAction.model.spec.ts @@ -188,4 +188,14 @@ describe('AdverseAction model', () => { expect(adverseAction.getNpdbTypeName(data.clinicalPrivilegeActionCategories[0])).to.equal('Non-compliance With Requirements'); expect(adverseAction.isActive()).to.equal(true); }); + it('should create an AdverseAction model with specific values through serializer (invalid data type from server)', () => { + const data = { + clinicalPrivilegeActionCategories: 'Non-compliance With Requirements', + }; + const adverseAction = AdverseActionSerializer.fromServer(data); + + // Test field values + expect(adverseAction).to.be.an.instanceof(AdverseAction); + expect(adverseAction.npdbTypes).to.matchPattern([]); + }); }); diff --git a/webroot/src/models/AdverseAction/AdverseAction.model.ts b/webroot/src/models/AdverseAction/AdverseAction.model.ts index 2189a3507..7a93b3b3f 100644 --- a/webroot/src/models/AdverseAction/AdverseAction.model.ts +++ b/webroot/src/models/AdverseAction/AdverseAction.model.ts @@ -134,7 +134,9 @@ export class AdverseActionSerializer { type: json.type, encumbranceType: json.encumbranceType, npdbType: json.clinicalPrivilegeActionCategory, - npdbTypes: json.clinicalPrivilegeActionCategories, + npdbTypes: Array.isArray(json.clinicalPrivilegeActionCategories) + ? json.clinicalPrivilegeActionCategories + : [], creationDate: json.creationDate, startDate: json.effectiveStartDate, endDate: json.effectiveLiftDate, From 97ed2d0b3249f728fd3d64aebb6e5c366880d7d0 Mon Sep 17 00:00:00 2001 From: John Sandoval Date: Fri, 17 Oct 2025 13:32:25 -0600 Subject: [PATCH 31/32] WIP: adverse action category multi-select - Update for UI design changes - @todo: Wire up backend once ready --- .../Forms/InputSelectMultiple/InputSelectMultiple.less | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/webroot/src/components/Forms/InputSelectMultiple/InputSelectMultiple.less b/webroot/src/components/Forms/InputSelectMultiple/InputSelectMultiple.less index e09c83d53..963aa443e 100644 --- a/webroot/src/components/Forms/InputSelectMultiple/InputSelectMultiple.less +++ b/webroot/src/components/Forms/InputSelectMultiple/InputSelectMultiple.less @@ -37,8 +37,11 @@ min-height: @removeSize; margin-bottom: 1rem; padding: 0 @removeSize 0 0.8rem; - border: 1px solid @primaryColor; - border-radius: 8px; + border: 1px solid @fontColor; + border-radius: @borderRadiusPillShape; + color: @white; + font-size: @fontSize; + background-color: @fontColor; &:not(:last-child) { margin-right: 1rem; @@ -53,7 +56,7 @@ margin-left: auto; transform: translateY(-50%); cursor: pointer; - stroke: @primaryColor; + stroke: @white; } } } From 7c01b09f8cb05ea7778a348ff88b1f91d541a048 Mon Sep 17 00:00:00 2001 From: John Sandoval Date: Tue, 21 Oct 2025 16:04:17 -0600 Subject: [PATCH 32/32] WIP: adverse action category multi-select - Wire up backend --- webroot/src/components/LicenseCard/LicenseCard.ts | 5 +---- webroot/src/components/PrivilegeCard/PrivilegeCard.ts | 5 +---- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/webroot/src/components/LicenseCard/LicenseCard.ts b/webroot/src/components/LicenseCard/LicenseCard.ts index 140d90c07..e1355de10 100644 --- a/webroot/src/components/LicenseCard/LicenseCard.ts +++ b/webroot/src/components/LicenseCard/LicenseCard.ts @@ -435,7 +435,7 @@ class LicenseCard extends mixins(MixinForm) { licenseTypeAbbrev } = this; - const response = await this.$store.dispatch(`users/encumberLicenseRequest`, { + await this.$store.dispatch(`users/encumberLicenseRequest`, { compact: compactType, licenseeId, licenseState: stateAbbrev, @@ -455,9 +455,6 @@ class LicenseCard extends mixins(MixinForm) { this.isFormError = true; }); - // @TODO - console.log(response); - if (!this.isFormError) { this.isFormSuccessful = true; await this.$store.dispatch('license/getLicenseeRequest', { compact: compactType, licenseeId }); diff --git a/webroot/src/components/PrivilegeCard/PrivilegeCard.ts b/webroot/src/components/PrivilegeCard/PrivilegeCard.ts index 2f7f70b10..b29d1b99e 100644 --- a/webroot/src/components/PrivilegeCard/PrivilegeCard.ts +++ b/webroot/src/components/PrivilegeCard/PrivilegeCard.ts @@ -490,7 +490,7 @@ class PrivilegeCard extends mixins(MixinForm) { privilegeTypeAbbrev } = this; - const response = await this.$store.dispatch(`users/encumberPrivilegeRequest`, { + await this.$store.dispatch(`users/encumberPrivilegeRequest`, { compact: compactType, licenseeId, privilegeState: stateAbbrev, @@ -510,9 +510,6 @@ class PrivilegeCard extends mixins(MixinForm) { this.isFormError = true; }); - // @TODO - console.log(response); - if (!this.isFormError) { this.isFormSuccessful = true; await this.$store.dispatch('license/getLicenseeRequest', { compact: compactType, licenseeId });