diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 689b9acbbe7..2dd5fb46ad9 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -22,6 +22,11 @@ /.github/ @villebro @geido @eschutho @rusackas @betodealmeida @nytai @mistercrunch @craig-rueda @kgabryje @dpgaspar @sadpandajoe @hainenber +# Notify PMC members of changes to CI-executed scripts (supply-chain risk: +# scripts/ files run directly in CI workflows and can execute arbitrary code) + +/scripts/ @villebro @geido @eschutho @rusackas @betodealmeida @nytai @mistercrunch @craig-rueda @kgabryje @dpgaspar @sadpandajoe @hainenber + # Notify PMC members of changes to required GitHub Actions /.asf.yaml @villebro @geido @eschutho @rusackas @betodealmeida @nytai @mistercrunch @craig-rueda @kgabryje @dpgaspar @Antonio-RiveroMartnez diff --git a/.github/SECURITY.md b/.github/SECURITY.md index 086ff8c0cad..9c8c68874c3 100644 --- a/.github/SECURITY.md +++ b/.github/SECURITY.md @@ -18,10 +18,32 @@ e-mail address [security@superset.apache.org](mailto:security@superset.apache.or More details can be found on the ASF website at [ASF vulnerability reporting process](https://apache.org/security/#reporting-a-vulnerability) -We kindly ask you to include the following information in your report: -- Apache Superset version that you are using -- A sanitized copy of your `superset_config.py` file or any config overrides -- Detailed steps to reproduce the vulnerability +**Submission Standards & AI Policy** + +To ensure engineering focus remains on verified risks and to manage high reporting volumes, all reports must meet the following criteria: +- Plain Text Format: In accordance with Apache guidelines, please provide all details in plain text within the email body. Avoid sending PDFs, Word documents, or password-protected archives. +- Mandatory AI Disclosure: If you utilized Large Language Models (LLMs) or AI tools to identify a flaw or assist in writing a report, you must disclose this in your submission so our triage team can contextualize the findings. +- Human-Verified PoC: All submissions must include a manual, step-by-step Proof of Concept (PoC) performed on a supported release. Raw AI outputs, hypothetical chat transcripts, or unverified scanner logs will be closed as Invalid. + +We kindly ask you to include the following information in your report to assist our developers in triaging and remediating issues efficiently: +- Version/Commit: The specific version of Apache Superset or the Git commit hash you are using. +- Configuration: A sanitized copy of your `superset_config.py` file or any config overrides. +- Environment: Your deployment method (e.g., Docker Compose, Helm, or source) and relevant OS/Browser details. +- Impacted Component: Identification of the affected area (e.g., Python backend, React frontend, or a specific database connector). +- Expected vs. Actual Behavior: A clear description of the intended system behavior versus the observed vulnerability. +- Detailed Reproduction Steps: Clear, manual steps to reproduce the vulnerability. + +**Out of Scope Vulnerabilities** + +To prioritize engineering efforts on genuine architectural risks, the following scenarios are explicitly out of scope and will not be issued a CVE: +- Attacks requiring Admin privileges: (e.g., CSS injection, template manipulation, dashboard ownership overrides, or modifying global system settings). Per the CVE vulnerability definition in CNA Operational Rules 4.1, a qualifying vulnerability must allow violation of a security policy. The Admin role is a fully trusted operational boundary defined by Apache Superset's security policy; actions within this boundary do not violate that policy and are therefore considered intended capabilities 'by design,' not vulnerabilities. +- Brute Force and Rate Limiting: Reports targeting a lack of resource exhaustion protections, generic rate-limiting, or volumetric Denial of Service (DoS) attempts. +- Theoretical attack vectors: Issues without a demonstrable, reproducible exploit path. +- Non-Exploitable Findings: Missing security headers, generic banner disclosures, or descriptive error messages that do not lead to a direct, documented exploit. + +**Outcome of Reports** + +Reports that are deemed out-of-scope for a CVE but represent valid security best practices or hardening opportunities may be converted into public GitHub issues. This allows the community to contribute to the general hardening of the platform even when a specific vulnerability threshold is not met. Note that Apache Superset is not responsible for any third-party dependencies that may have security issues. Any vulnerabilities found in third-party dependencies should be @@ -29,6 +51,13 @@ reported to the maintainers of those projects. Results from security scans of Ap Superset dependencies found on its official Docker image can be remediated at release time by extending the image itself. +**Vulnerability Aggregation & CVE Attribution** + +In accordance with MITRE CNA Operational Rules (4.1.10, 4.1.11, and 4.2.13), Apache Superset issues CVEs based on the underlying architectural root cause rather than the number of affected endpoints or exploit payloads. +- Aggregation: If multiple exploit vectors stem from the same programmatic failure or shared vulnerable code, they must be aggregated into a single, comprehensive report. +- Independent Fixes: Separate CVEs will only be assigned if the vulnerabilities reside in decoupled architectural modules and can be fixed independently of one another. +Reports that fail to aggregate related findings will be merged during triage to ensure an accurate and defensible CVE record. + **Your responsible disclosure and collaboration are invaluable.** ## Extra Information diff --git a/.github/actions/setup-docker/action.yml b/.github/actions/setup-docker/action.yml index 71a559829f6..91f2c8ce954 100644 --- a/.github/actions/setup-docker/action.yml +++ b/.github/actions/setup-docker/action.yml @@ -26,16 +26,16 @@ runs: - name: Set up QEMU if: ${{ inputs.build == 'true' }} - uses: docker/setup-qemu-action@v3 + uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0 - name: Set up Docker Buildx if: ${{ inputs.build == 'true' }} - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0 - name: Try to login to DockerHub if: ${{ inputs.login-to-dockerhub == 'true' }} continue-on-error: true - uses: docker/login-action@v3 + uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0 with: username: ${{ inputs.dockerhub-user }} password: ${{ inputs.dockerhub-token }} diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 7a1b034d325..86feb412782 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -4,6 +4,10 @@ updates: - package-ecosystem: "github-actions" directory: "/" + ignore: + # Ignore temporarily as release schedule is too mentally taxing for dep-handling maintainers + # Additionally, very few PRs are reviewed by this action. + - dependency-name: anthropics/claude-code-action schedule: interval: "daily" diff --git a/.github/labeler.yml b/.github/labeler.yml index bda01f0223e..9cb7f3aad4c 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -17,6 +17,11 @@ - any-glob-to-any-file: - 'superset/migrations/**' +"risk:ci-script": +- changed-files: + - any-glob-to-any-file: + - 'scripts/**' + ############################################ # Dependencies ############################################ diff --git a/.github/workflows/bump-python-package.yml b/.github/workflows/bump-python-package.yml index 22bb1844077..d69b17d09e5 100644 --- a/.github/workflows/bump-python-package.yml +++ b/.github/workflows/bump-python-package.yml @@ -32,7 +32,7 @@ jobs: steps: - name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )" - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: true ref: master @@ -41,7 +41,7 @@ jobs: uses: ./.github/actions/setup-supersetbot/ - name: Set up Python ${{ inputs.python-version }} - uses: actions/setup-python@v6 + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6 with: python-version: "3.10" @@ -51,27 +51,31 @@ jobs: - name: supersetbot bump-python -p "${{ github.event.inputs.package }}" env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + INPUT_PACKAGE: ${{ github.event.inputs.package }} + INPUT_GROUP: ${{ github.event.inputs.group }} + INPUT_EXTRA_FLAGS: ${{ github.event.inputs.extra-flags }} + INPUT_LIMIT: ${{ github.event.inputs.limit }} run: | git config --global user.email "action@github.com" git config --global user.name "GitHub Action" PACKAGE_OPT="" - if [ -n "${{ github.event.inputs.package }}" ]; then - PACKAGE_OPT="-p ${{ github.event.inputs.package }}" + if [ -n "${INPUT_PACKAGE}" ]; then + PACKAGE_OPT="-p ${INPUT_PACKAGE}" fi GROUP_OPT="" - if [ -n "${{ github.event.inputs.group }}" ]; then - GROUP_OPT="-g ${{ github.event.inputs.group }}" + if [ -n "${INPUT_GROUP}" ]; then + GROUP_OPT="-g ${INPUT_GROUP}" fi - EXTRA_FLAGS="${{ github.event.inputs.extra-flags }}" + EXTRA_FLAGS="${INPUT_EXTRA_FLAGS}" supersetbot bump-python \ --verbose \ --use-current-repo \ --include-subpackages \ - --limit ${{ github.event.inputs.limit }} \ + --limit ${INPUT_LIMIT} \ $PACKAGE_OPT \ $GROUP_OPT \ $EXTRA_FLAGS diff --git a/.github/workflows/cancel_duplicates.yml b/.github/workflows/cancel_duplicates.yml index 27b07fee03a..76525767ad0 100644 --- a/.github/workflows/cancel_duplicates.yml +++ b/.github/workflows/cancel_duplicates.yml @@ -31,7 +31,7 @@ jobs: - name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )" if: steps.check_queued.outputs.count >= 20 - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - name: Cancel duplicate workflow runs if: steps.check_queued.outputs.count >= 20 diff --git a/.github/workflows/check-python-deps.yml b/.github/workflows/check-python-deps.yml index 4438c09d6cd..844b7bee94e 100644 --- a/.github/workflows/check-python-deps.yml +++ b/.github/workflows/check-python-deps.yml @@ -18,7 +18,7 @@ jobs: runs-on: ubuntu-22.04 steps: - name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )" - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false submodules: recursive diff --git a/.github/workflows/check_db_migration_confict.yml b/.github/workflows/check_db_migration_confict.yml index e5db0dd5554..fe82cee28dd 100644 --- a/.github/workflows/check_db_migration_confict.yml +++ b/.github/workflows/check_db_migration_confict.yml @@ -25,9 +25,9 @@ jobs: pull-requests: write steps: - name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )" - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - name: Check and notify - uses: actions/github-script@v8 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: github-token: ${{ github.token }} script: | diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml index abc8129d788..c52e5b9f980 100644 --- a/.github/workflows/claude.yml +++ b/.github/workflows/claude.yml @@ -44,7 +44,7 @@ jobs: pull-requests: write steps: - name: Comment access denied - uses: actions/github-script@v8 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: script: | const message = `👋 Hi @${{ github.event.comment.user.login || github.event.review.user.login || github.event.issue.user.login }}! @@ -71,12 +71,12 @@ jobs: id-token: write steps: - name: Checkout repository - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: fetch-depth: 1 - name: Run Claude PR Action - uses: anthropics/claude-code-action@beta + uses: anthropics/claude-code-action@5fb899572b81d2bb648d4d187173a2f423a9677c # beta with: anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} timeout_minutes: "60" diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 3dd15400d78..5ae6ebd2c02 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -31,7 +31,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - name: Check for file changes id: check diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index b7c09bff755..13d05bcbc9a 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -27,9 +27,9 @@ jobs: runs-on: ubuntu-24.04 steps: - name: "Checkout Repository" - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - name: "Dependency Review" - uses: actions/dependency-review-action@v4 + uses: actions/dependency-review-action@2031cfc080254a8a887f58cffee85186f0e49e48 # v4.9.0 continue-on-error: true with: fail-on-severity: critical @@ -49,7 +49,7 @@ jobs: runs-on: ubuntu-22.04 steps: - name: "Checkout Repository" - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - name: Setup Python uses: ./.github/actions/setup-backend/ diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 94a8de67ccf..930f94b1484 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -42,7 +42,7 @@ jobs: steps: - name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )" - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false @@ -101,23 +101,6 @@ jobs: docker images $IMAGE_TAG docker history $IMAGE_TAG - # Scan for vulnerabilities in built container image after pushes to mainline branch. - - name: Run Trivy container image vulnerabity scan - if: github.event_name == 'push' && github.ref == 'refs/heads/master' && (steps.check.outputs.python || steps.check.outputs.frontend || steps.check.outputs.docker) && matrix.build_preset == 'lean' - uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # v0.35.0 - with: - image-ref: ${{ env.IMAGE_TAG }} - format: 'sarif' - output: 'trivy-results.sarif' - vuln-type: 'os' - severity: 'CRITICAL,HIGH' - ignore-unfixed: true - - name: Upload Trivy scan results to GitHub Security tab - if: github.event_name == 'push' && github.ref == 'refs/heads/master' && (steps.check.outputs.python || steps.check.outputs.frontend || steps.check.outputs.docker) && matrix.build_preset == 'lean' - uses: github/codeql-action/upload-sarif@1b168cd39490f61582a9beae412bb7057a6b2c4e # v4.31.8 - with: - sarif_file: 'trivy-results.sarif' - - name: docker-compose sanity check if: (steps.check.outputs.python || steps.check.outputs.frontend || steps.check.outputs.docker) && matrix.build_preset == 'dev' shell: bash @@ -134,7 +117,7 @@ jobs: runs-on: ubuntu-24.04 steps: - name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )" - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - name: Check for file changes diff --git a/.github/workflows/embedded-sdk-release.yml b/.github/workflows/embedded-sdk-release.yml index ff8efa3b8e5..8bcf42a56be 100644 --- a/.github/workflows/embedded-sdk-release.yml +++ b/.github/workflows/embedded-sdk-release.yml @@ -16,10 +16,12 @@ jobs: id: check shell: bash run: | - if [ -n "${{ (secrets.NPM_TOKEN != '') || '' }}" ]; then + if [ -n "${NPM_TOKEN}" ]; then echo "has-secrets=1" >> "$GITHUB_OUTPUT" fi + env: + NPM_TOKEN: ${{ (secrets.NPM_TOKEN != '') || '' }} build: needs: config if: needs.config.outputs.has-secrets @@ -28,8 +30,8 @@ jobs: run: working-directory: superset-embedded-sdk steps: - - uses: actions/checkout@v6 - - uses: actions/setup-node@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 with: node-version-file: './superset-embedded-sdk/.nvmrc' registry-url: 'https://registry.npmjs.org' diff --git a/.github/workflows/embedded-sdk-test.yml b/.github/workflows/embedded-sdk-test.yml index f31a094fcf1..9d5237fce34 100644 --- a/.github/workflows/embedded-sdk-test.yml +++ b/.github/workflows/embedded-sdk-test.yml @@ -18,8 +18,8 @@ jobs: run: working-directory: superset-embedded-sdk steps: - - uses: actions/checkout@v6 - - uses: actions/setup-node@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 with: node-version-file: './superset-embedded-sdk/.nvmrc' registry-url: 'https://registry.npmjs.org' diff --git a/.github/workflows/ephemeral-env-pr-close.yml b/.github/workflows/ephemeral-env-pr-close.yml index 313c3ae521d..9defe4db1b5 100644 --- a/.github/workflows/ephemeral-env-pr-close.yml +++ b/.github/workflows/ephemeral-env-pr-close.yml @@ -20,10 +20,12 @@ jobs: id: check shell: bash run: | - if [ -n "${{ (secrets.AWS_ACCESS_KEY_ID != '' && secrets.AWS_SECRET_ACCESS_KEY != '') || '' }}" ]; then + if [ -n "${AWS_ACCESS_KEY_ID}" ]; then echo "has-secrets=1" >> "$GITHUB_OUTPUT" fi + env: + AWS_ACCESS_KEY_ID: ${{ (secrets.AWS_ACCESS_KEY_ID != '' && secrets.AWS_SECRET_ACCESS_KEY != '') || '' }} ephemeral-env-cleanup: needs: config if: needs.config.outputs.has-secrets @@ -33,7 +35,7 @@ jobs: pull-requests: write steps: - name: Configure AWS credentials - uses: aws-actions/configure-aws-credentials@v6 + uses: aws-actions/configure-aws-credentials@8df5847569e6427dd6c4fb1cf565c83acfa8afa7 # v6 with: aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} @@ -56,7 +58,7 @@ jobs: - name: Login to Amazon ECR if: steps.describe-services.outputs.active == 'true' id: login-ecr - uses: aws-actions/amazon-ecr-login@v2 + uses: aws-actions/amazon-ecr-login@376925c9d111252e87ae59691e5a442dd100ef6a # v2 - name: Delete ECR image tag if: steps.describe-services.outputs.active == 'true' @@ -69,7 +71,7 @@ jobs: - name: Comment (success) if: steps.describe-services.outputs.active == 'true' - uses: actions/github-script@v8 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: github-token: ${{github.token}} script: | diff --git a/.github/workflows/ephemeral-env.yml b/.github/workflows/ephemeral-env.yml index 70311682a8b..7815ddb09b3 100644 --- a/.github/workflows/ephemeral-env.yml +++ b/.github/workflows/ephemeral-env.yml @@ -47,7 +47,7 @@ jobs: id: eval-label run: | if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then - LABEL_NAME="${{ github.event.inputs.label_name }}" + LABEL_NAME="${INPUT_LABEL_NAME}" else LABEL_NAME="${{ github.event.label.name }}" fi @@ -60,10 +60,12 @@ jobs: echo "result=noop" >> $GITHUB_OUTPUT fi + env: + INPUT_LABEL_NAME: ${{ github.event.inputs.label_name }} - name: Get event SHA id: get-sha if: steps.eval-label.outputs.result == 'up' - uses: actions/github-script@v8 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | @@ -94,7 +96,7 @@ jobs: core.setOutput("sha", prSha); - name: Looking for feature flags in PR description - uses: actions/github-script@v8 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 id: eval-feature-flags if: steps.eval-label.outputs.result == 'up' with: @@ -116,7 +118,7 @@ jobs: return results; - name: Reply with confirmation comment - uses: actions/github-script@v8 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 if: steps.eval-label.outputs.result == 'up' with: github-token: ${{ secrets.GITHUB_TOKEN }} @@ -160,7 +162,7 @@ jobs: runs-on: ubuntu-24.04 steps: - name: "Checkout ${{ github.ref }} ( ${{ needs.ephemeral-env-label.outputs.sha }} : ${{steps.get-sha.outputs.sha}} )" - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: ref: ${{ needs.ephemeral-env-label.outputs.sha }} persist-credentials: false @@ -189,7 +191,7 @@ jobs: --extra-flags "--build-arg INCLUDE_CHROMIUM=false" - name: Configure AWS credentials - uses: aws-actions/configure-aws-credentials@v6 + uses: aws-actions/configure-aws-credentials@8df5847569e6427dd6c4fb1cf565c83acfa8afa7 # v6 with: aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} @@ -197,7 +199,7 @@ jobs: - name: Login to Amazon ECR id: login-ecr - uses: aws-actions/amazon-ecr-login@v2 + uses: aws-actions/amazon-ecr-login@376925c9d111252e87ae59691e5a442dd100ef6a # v2 - name: Load, tag and push image to ECR id: push-image @@ -220,12 +222,12 @@ jobs: pull-requests: write steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - name: Configure AWS credentials - uses: aws-actions/configure-aws-credentials@v6 + uses: aws-actions/configure-aws-credentials@8df5847569e6427dd6c4fb1cf565c83acfa8afa7 # v6 with: aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} @@ -233,7 +235,7 @@ jobs: - name: Login to Amazon ECR id: login-ecr - uses: aws-actions/amazon-ecr-login@v2 + uses: aws-actions/amazon-ecr-login@376925c9d111252e87ae59691e5a442dd100ef6a # v2 - name: Check target image exists in ECR id: check-image @@ -248,7 +250,7 @@ jobs: - name: Fail on missing container image if: steps.check-image.outcome == 'failure' - uses: actions/github-script@v8 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: github-token: ${{ github.token }} script: | @@ -263,7 +265,7 @@ jobs: - name: Fill in the new image ID in the Amazon ECS task definition id: task-def - uses: aws-actions/amazon-ecs-render-task-definition@v1 + uses: aws-actions/amazon-ecs-render-task-definition@77954e213ba1f9f9cb016b86a1d4f6fcdea0d57e # v1 with: task-definition: .github/workflows/ecs-task-definition.json container-name: superset-ci @@ -276,7 +278,9 @@ jobs: - name: Describe ECS service id: describe-services run: | - echo "active=$(aws ecs describe-services --cluster superset-ci --services pr-${{ github.event.inputs.issue_number || github.event.pull_request.number }}-service | jq '.services[] | select(.status == "ACTIVE") | any')" >> $GITHUB_OUTPUT + echo "active=$(aws ecs describe-services --cluster superset-ci --services pr-${INPUT_ISSUE_NUMBER}-service | jq '.services[] | select(.status == "ACTIVE") | any')" >> $GITHUB_OUTPUT + env: + INPUT_ISSUE_NUMBER: ${{ github.event.inputs.issue_number || github.event.pull_request.number }} - name: Create ECS service id: create-service if: steps.describe-services.outputs.active != 'true' @@ -296,7 +300,7 @@ jobs: --tags key=pr,value=$PR_NUMBER key=github_user,value=${{ github.actor }} - name: Deploy Amazon ECS task definition id: deploy-task - uses: aws-actions/amazon-ecs-deploy-task-definition@v2 + uses: aws-actions/amazon-ecs-deploy-task-definition@fc8fc60f3a60ffd500fcb13b209c59d221ac8c8c # v2 with: task-definition: ${{ steps.task-def.outputs.task-definition }} service: pr-${{ github.event.inputs.issue_number || github.event.pull_request.number }}-service @@ -307,7 +311,9 @@ jobs: - name: List tasks id: list-tasks run: | - echo "task=$(aws ecs list-tasks --cluster superset-ci --service-name pr-${{ github.event.inputs.issue_number || github.event.pull_request.number }}-service | jq '.taskArns | first')" >> $GITHUB_OUTPUT + echo "task=$(aws ecs list-tasks --cluster superset-ci --service-name pr-${INPUT_ISSUE_NUMBER}-service | jq '.taskArns | first')" >> $GITHUB_OUTPUT + env: + INPUT_ISSUE_NUMBER: ${{ github.event.inputs.issue_number || github.event.pull_request.number }} - name: Get network interface id: get-eni run: | @@ -318,7 +324,7 @@ jobs: echo "ip=$(aws ec2 describe-network-interfaces --network-interface-ids ${{ steps.get-eni.outputs.eni }} | jq -r '.NetworkInterfaces | first | .Association.PublicIp')" >> $GITHUB_OUTPUT - name: Comment (success) if: ${{ success() }} - uses: actions/github-script@v8 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: github-token: ${{github.token}} script: | @@ -331,7 +337,7 @@ jobs: }); - name: Comment (failure) if: ${{ failure() }} - uses: actions/github-script@v8 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: github-token: ${{github.token}} script: | diff --git a/.github/workflows/generate-FOSSA-report.yml b/.github/workflows/generate-FOSSA-report.yml index 731545453fc..1962626100d 100644 --- a/.github/workflows/generate-FOSSA-report.yml +++ b/.github/workflows/generate-FOSSA-report.yml @@ -16,10 +16,12 @@ jobs: id: check shell: bash run: | - if [ -n "${{ (secrets.FOSSA_API_KEY != '' ) || '' }}" ]; then + if [ -n "${FOSSA_API_KEY}" ]; then echo "has-secrets=1" >> "$GITHUB_OUTPUT" fi + env: + FOSSA_API_KEY: ${{ (secrets.FOSSA_API_KEY != '' ) || '' }} license_check: needs: config if: needs.config.outputs.has-secrets @@ -27,12 +29,12 @@ jobs: runs-on: ubuntu-24.04 steps: - name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )" - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false submodules: recursive - name: Setup Java - uses: actions/setup-java@v5 + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: distribution: "temurin" java-version: "11" diff --git a/.github/workflows/github-action-validator.yml b/.github/workflows/github-action-validator.yml index 4a36bb304c7..9a341871c76 100644 --- a/.github/workflows/github-action-validator.yml +++ b/.github/workflows/github-action-validator.yml @@ -14,10 +14,10 @@ jobs: runs-on: ubuntu-24.04 steps: - name: Checkout Repository - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - name: Set up Node.js - uses: actions/setup-node@v6 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 with: node-version: '20' diff --git a/.github/workflows/issue_creation.yml b/.github/workflows/issue_creation.yml index 16ba0d8b4d2..a0d77f0fc0a 100644 --- a/.github/workflows/issue_creation.yml +++ b/.github/workflows/issue_creation.yml @@ -17,7 +17,7 @@ jobs: steps: - name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )" - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false diff --git a/.github/workflows/latest-release-tag.yml b/.github/workflows/latest-release-tag.yml index 63601750cf3..97cd73df462 100644 --- a/.github/workflows/latest-release-tag.yml +++ b/.github/workflows/latest-release-tag.yml @@ -12,7 +12,7 @@ jobs: steps: - name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )" - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false submodules: recursive diff --git a/.github/workflows/license-check.yml b/.github/workflows/license-check.yml index af57d40f46f..b1796c4b07d 100644 --- a/.github/workflows/license-check.yml +++ b/.github/workflows/license-check.yml @@ -15,12 +15,12 @@ jobs: runs-on: ubuntu-24.04 steps: - name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )" - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false submodules: recursive - name: Setup Java - uses: actions/setup-java@v5 + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: distribution: 'temurin' java-version: '11' diff --git a/.github/workflows/no-hold-label.yml b/.github/workflows/no-hold-label.yml index 021858103dc..32125e22ab9 100644 --- a/.github/workflows/no-hold-label.yml +++ b/.github/workflows/no-hold-label.yml @@ -17,7 +17,7 @@ jobs: runs-on: ubuntu-24.04 steps: - name: Check for 'hold' label - uses: actions/github-script@v8 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: github-token: ${{secrets.GITHUB_TOKEN}} script: | diff --git a/.github/workflows/pr-lint.yml b/.github/workflows/pr-lint.yml index ebe5324ebdf..8a02c6afc36 100644 --- a/.github/workflows/pr-lint.yml +++ b/.github/workflows/pr-lint.yml @@ -16,7 +16,7 @@ jobs: pull-requests: write steps: - name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )" - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false submodules: recursive diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index 4af4e743536..d16a729bd21 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -24,7 +24,7 @@ jobs: python-version: ["current", "previous", "next"] steps: - name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )" - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false submodules: recursive @@ -42,7 +42,7 @@ jobs: echo "HOMEBREW_REPOSITORY=$HOMEBREW_REPOSITORY" >>"${GITHUB_ENV}" brew install norwoodj/tap/helm-docs - name: Setup Node.js - uses: actions/setup-node@v6 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 with: node-version: '20' @@ -57,7 +57,7 @@ jobs: yarn install --immutable - name: Cache pre-commit environments - uses: actions/cache@v5 + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5 with: path: ~/.cache/pre-commit key: pre-commit-v2-${{ runner.os }}-py${{ matrix.python-version }}-${{ hashFiles('.pre-commit-config.yaml') }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3932b18e3c2..23e2c0175d3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -16,17 +16,19 @@ jobs: id: check shell: bash run: | - if [ -n "${{ (secrets.NPM_TOKEN != '' && secrets.GH_PERSONAL_ACCESS_TOKEN != '') || '' }}" ]; then + if [ -n "${NPM_TOKEN}" ]; then echo "has-secrets=1" >> "$GITHUB_OUTPUT" fi + env: + NPM_TOKEN: ${{ (secrets.NPM_TOKEN != '' && secrets.GH_PERSONAL_ACCESS_TOKEN != '') || '' }} build: needs: config if: needs.config.outputs.has-secrets name: Bump version and publish package(s) runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: # pulls all commits (needed for lerna / semantic release to correctly version) fetch-depth: 0 @@ -42,13 +44,13 @@ jobs: - name: Install Node.js if: env.HAS_TAGS - uses: actions/setup-node@v6 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 with: node-version-file: './superset-frontend/.nvmrc' - name: Cache npm if: env.HAS_TAGS - uses: actions/cache@v5 + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5 with: path: ~/.npm # npm cache files are stored in `~/.npm` on Linux/macOS key: ${{ runner.OS }}-node-${{ hashFiles('**/package-lock.json') }} @@ -62,7 +64,7 @@ jobs: run: echo "dir=$(npm config get cache)" >> $GITHUB_OUTPUT - name: Cache npm if: env.HAS_TAGS - uses: actions/cache@v5 + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5 id: npm-cache # use this to check for `cache-hit` (`steps.npm-cache.outputs.cache-hit != 'true'`) with: path: ${{ steps.npm-cache-dir-path.outputs.dir }} diff --git a/.github/workflows/showtime-trigger.yml b/.github/workflows/showtime-trigger.yml index 80454bccd41..1c0da7fd147 100644 --- a/.github/workflows/showtime-trigger.yml +++ b/.github/workflows/showtime-trigger.yml @@ -37,7 +37,7 @@ jobs: steps: - name: Security Check - Authorize Maintainers Only id: auth - uses: actions/github-script@v8 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: @@ -102,10 +102,12 @@ jobs: - name: Install Superset Showtime if: steps.auth.outputs.authorized == 'true' run: | - echo "::notice::Maintainer ${{ github.actor }} triggered deploy for PR ${{ github.event.pull_request.number || github.event.inputs.pr_number }}" + echo "::notice::Maintainer ${{ github.actor }} triggered deploy for PR ${PULL_REQUEST_NUMBER}" pip install --upgrade superset-showtime showtime version + env: + PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number || github.event.inputs.pr_number }} - name: Check what actions are needed if: steps.auth.outputs.authorized == 'true' id: check @@ -113,12 +115,14 @@ jobs: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + INPUT_PR_NUMBER: ${{ github.event.inputs.pr_number }} + INPUT_SHA: ${{ github.event.inputs.sha }} run: | # Bulletproof PR number extraction if [[ -n "${{ github.event.pull_request.number }}" ]]; then PR_NUM="${{ github.event.pull_request.number }}" - elif [[ -n "${{ github.event.inputs.pr_number }}" ]]; then - PR_NUM="${{ github.event.inputs.pr_number }}" + elif [[ -n "${INPUT_PR_NUMBER}" ]]; then + PR_NUM="${INPUT_PR_NUMBER}" else echo "❌ No PR number found in event or inputs" exit 1 @@ -127,8 +131,8 @@ jobs: echo "Using PR number: $PR_NUM" # Run sync check-only with optional SHA override - if [[ -n "${{ github.event.inputs.sha }}" ]]; then - OUTPUT=$(python -m showtime sync $PR_NUM --check-only --sha "${{ github.event.inputs.sha }}") + if [[ -n "${INPUT_SHA}" ]]; then + OUTPUT=$(python -m showtime sync $PR_NUM --check-only --sha "${INPUT_SHA}") else OUTPUT=$(python -m showtime sync $PR_NUM --check-only) fi @@ -147,7 +151,7 @@ jobs: - name: Checkout PR code (only if build needed) if: steps.auth.outputs.authorized == 'true' && steps.check.outputs.build_needed == 'true' - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: ref: ${{ steps.check.outputs.target_sha }} persist-credentials: false diff --git a/.github/workflows/superset-app-cli.yml b/.github/workflows/superset-app-cli.yml index 3cc839eb285..1c6d8ec9d41 100644 --- a/.github/workflows/superset-app-cli.yml +++ b/.github/workflows/superset-app-cli.yml @@ -37,7 +37,7 @@ jobs: - 16379:6379 steps: - name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )" - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false submodules: recursive diff --git a/.github/workflows/superset-docs-deploy.yml b/.github/workflows/superset-docs-deploy.yml index c7dc57b4426..52e3468dc22 100644 --- a/.github/workflows/superset-docs-deploy.yml +++ b/.github/workflows/superset-docs-deploy.yml @@ -27,10 +27,12 @@ jobs: id: check shell: bash run: | - if [ -n "${{ (secrets.SUPERSET_SITE_BUILD != '' && secrets.SUPERSET_SITE_BUILD != '') || '' }}" ]; then + if [ -n "${SUPERSET_SITE_BUILD}" ]; then echo "has-secrets=1" >> "$GITHUB_OUTPUT" fi + env: + SUPERSET_SITE_BUILD: ${{ (secrets.SUPERSET_SITE_BUILD != '' && secrets.SUPERSET_SITE_BUILD != '') || '' }} build-deploy: needs: config if: needs.config.outputs.has-secrets @@ -38,18 +40,18 @@ jobs: runs-on: ubuntu-24.04 steps: - name: "Checkout ${{ github.event.workflow_run.head_sha || github.sha }}" - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: ref: ${{ github.event.workflow_run.head_sha || github.sha }} persist-credentials: false submodules: recursive - name: Set up Node.js - uses: actions/setup-node@v6 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 with: node-version-file: './docs/.nvmrc' - name: Setup Python uses: ./.github/actions/setup-backend/ - - uses: actions/setup-java@v5 + - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: distribution: 'zulu' java-version: '21' @@ -68,7 +70,7 @@ jobs: yarn install --check-cache - name: Download database diagnostics (if triggered by integration tests) if: github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success' - uses: dawidd6/action-download-artifact@v17 + uses: dawidd6/action-download-artifact@8305c0f1062bb0d184d09ef4493ecb9288447732 # v20 continue-on-error: true with: workflow: superset-python-integrationtest.yml @@ -77,7 +79,7 @@ jobs: path: docs/src/data/ - name: Try to download latest diagnostics (for push/dispatch triggers) if: github.event_name != 'workflow_run' - uses: dawidd6/action-download-artifact@v17 + uses: dawidd6/action-download-artifact@8305c0f1062bb0d184d09ef4493ecb9288447732 # v20 continue-on-error: true with: workflow: superset-python-integrationtest.yml diff --git a/.github/workflows/superset-docs-verify.yml b/.github/workflows/superset-docs-verify.yml index 1306fec1bf1..9e45258ad9d 100644 --- a/.github/workflows/superset-docs-verify.yml +++ b/.github/workflows/superset-docs-verify.yml @@ -24,7 +24,7 @@ jobs: name: Link Checking runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 # Do not bump this linkinator-action version without opening # an ASF Infra ticket to allow the new version first! - uses: JustinBeckwith/linkinator-action@af984b9f30f63e796ae2ea5be5e07cb587f1bbd9 # v2.3 @@ -67,12 +67,12 @@ jobs: working-directory: docs steps: - name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )" - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false submodules: recursive - name: Set up Node.js - uses: actions/setup-node@v6 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 with: node-version-file: './docs/.nvmrc' - name: yarn install @@ -98,20 +98,20 @@ jobs: working-directory: docs steps: - name: "Checkout PR head: ${{ github.event.workflow_run.head_sha }}" - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: ref: ${{ github.event.workflow_run.head_sha }} persist-credentials: false submodules: recursive - name: Set up Node.js - uses: actions/setup-node@v6 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 with: node-version-file: './docs/.nvmrc' - name: yarn install run: | yarn install --check-cache - name: Download database diagnostics from integration tests - uses: dawidd6/action-download-artifact@v17 + uses: dawidd6/action-download-artifact@8305c0f1062bb0d184d09ef4493ecb9288447732 # v20 with: workflow: superset-python-integrationtest.yml run_id: ${{ github.event.workflow_run.id }} diff --git a/.github/workflows/superset-e2e.yml b/.github/workflows/superset-e2e.yml index 4bdbb79e482..43aea2833e3 100644 --- a/.github/workflows/superset-e2e.yml +++ b/.github/workflows/superset-e2e.yml @@ -69,21 +69,21 @@ jobs: # Conditional checkout based on context - name: Checkout for push or pull_request event if: github.event_name == 'push' || github.event_name == 'pull_request' - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false submodules: recursive ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }} - name: Checkout using ref (workflow_dispatch) if: github.event_name == 'workflow_dispatch' && github.event.inputs.ref != '' - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false ref: ${{ github.event.inputs.ref }} submodules: recursive - name: Checkout using PR ID (workflow_dispatch) if: github.event_name == 'workflow_dispatch' && github.event.inputs.pr_id != '' - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false ref: refs/pull/${{ github.event.inputs.pr_id }}/merge @@ -109,7 +109,7 @@ jobs: run: testdata - name: Setup Node.js if: steps.check.outputs.python || steps.check.outputs.frontend - uses: actions/setup-node@v6 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 with: node-version-file: './superset-frontend/.nvmrc' - name: Install npm dependencies @@ -146,7 +146,7 @@ jobs: SAFE_APP_ROOT=${APP_ROOT//\//_} echo "safe_app_root=$SAFE_APP_ROOT" >> $GITHUB_OUTPUT - name: Upload Artifacts - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 if: failure() with: path: ${{ github.workspace }}/superset-frontend/cypress-base/cypress/screenshots @@ -186,21 +186,21 @@ jobs: # Conditional checkout based on context (same as Cypress workflow) - name: Checkout for push or pull_request event if: github.event_name == 'push' || github.event_name == 'pull_request' - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false submodules: recursive ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }} - name: Checkout using ref (workflow_dispatch) if: github.event_name == 'workflow_dispatch' && github.event.inputs.ref != '' - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false ref: ${{ github.event.inputs.ref }} submodules: recursive - name: Checkout using PR ID (workflow_dispatch) if: github.event_name == 'workflow_dispatch' && github.event.inputs.pr_id != '' - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false ref: refs/pull/${{ github.event.inputs.pr_id }}/merge @@ -226,7 +226,7 @@ jobs: run: playwright_testdata - name: Setup Node.js if: steps.check.outputs.python || steps.check.outputs.frontend - uses: actions/setup-node@v6 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 with: node-version-file: './superset-frontend/.nvmrc' - name: Install npm dependencies @@ -259,7 +259,7 @@ jobs: SAFE_APP_ROOT=${APP_ROOT//\//_} echo "safe_app_root=$SAFE_APP_ROOT" >> $GITHUB_OUTPUT - name: Upload Playwright Artifacts - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 if: failure() with: path: | diff --git a/.github/workflows/superset-extensions-cli.yml b/.github/workflows/superset-extensions-cli.yml index 698c42d4a1f..da8d12aee58 100644 --- a/.github/workflows/superset-extensions-cli.yml +++ b/.github/workflows/superset-extensions-cli.yml @@ -24,7 +24,7 @@ jobs: working-directory: superset-extensions-cli steps: - name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )" - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false submodules: recursive @@ -49,7 +49,7 @@ jobs: - name: Upload coverage reports to Codecov if: steps.check.outputs.superset-extensions-cli - uses: codecov/codecov-action@v5 + uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v5 with: file: ./coverage.xml flags: superset-extensions-cli @@ -58,7 +58,7 @@ jobs: - name: Upload HTML coverage report if: steps.check.outputs.superset-extensions-cli - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: name: superset-extensions-cli-coverage-html path: htmlcov/ diff --git a/.github/workflows/superset-frontend.yml b/.github/workflows/superset-frontend.yml index db22843e30b..a5c5042ba3f 100644 --- a/.github/workflows/superset-frontend.yml +++ b/.github/workflows/superset-frontend.yml @@ -23,7 +23,7 @@ jobs: should-run: ${{ steps.check.outputs.frontend }} steps: - name: Checkout Code - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false fetch-depth: 0 @@ -54,14 +54,14 @@ jobs: - name: Save Docker Image as Artifact if: steps.check.outputs.frontend run: | - docker save $TAG | gzip > docker-image.tar.gz + docker save $TAG | zstd -3 --threads=0 > docker-image.tar.zst - name: Upload Docker Image Artifact if: steps.check.outputs.frontend - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: name: docker-image - path: docker-image.tar.gz + path: docker-image.tar.zst sharded-jest-tests: needs: frontend-build @@ -73,12 +73,13 @@ jobs: runs-on: ubuntu-24.04 steps: - name: Download Docker Image Artifact - uses: actions/download-artifact@v8 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 with: name: docker-image - name: Load Docker Image - run: docker load < docker-image.tar.gz + run: | + zstd -d < docker-image.tar.zst | docker load - name: npm run test with coverage run: | @@ -90,7 +91,7 @@ jobs: "npm run test -- --coverage --shard=${{ matrix.shard }}/8 --coverageReporters=json" - name: Upload Coverage Artifact - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: name: coverage-artifacts-${{ matrix.shard }} path: superset-frontend/coverage @@ -103,14 +104,14 @@ jobs: id-token: write steps: - name: Checkout Code - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false fetch-depth: 0 ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }} - name: Download Coverage Artifacts - uses: actions/download-artifact@v8 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 with: pattern: coverage-artifacts-* path: coverage/ @@ -127,7 +128,7 @@ jobs: run: npx nyc merge coverage/ merged-output/coverage-summary.json - name: Upload Code Coverage - uses: codecov/codecov-action@v5 + uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v5 with: flags: javascript use_oidc: true @@ -142,13 +143,13 @@ jobs: runs-on: ubuntu-24.04 steps: - name: Download Docker Image Artifact - uses: actions/download-artifact@v8 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 with: name: docker-image - name: Load Docker Image run: | - docker load < docker-image.tar.gz + zstd -d < docker-image.tar.zst | docker load - name: lint run: | @@ -166,12 +167,13 @@ jobs: runs-on: ubuntu-24.04 steps: - name: Download Docker Image Artifact - uses: actions/download-artifact@v8 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 with: name: docker-image - name: Load Docker Image - run: docker load < docker-image.tar.gz + run: | + zstd -d < docker-image.tar.zst | docker load - name: Build Plugins Packages run: | @@ -184,12 +186,13 @@ jobs: runs-on: ubuntu-24.04 steps: - name: Download Docker Image Artifact - uses: actions/download-artifact@v8 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 with: name: docker-image - name: Load Docker Image - run: docker load < docker-image.tar.gz + run: | + zstd -d < docker-image.tar.zst | docker load - name: Build Storybook and Run Tests run: | diff --git a/.github/workflows/superset-helm-lint.yml b/.github/workflows/superset-helm-lint.yml index 846e36f8f37..b616aedeaf8 100644 --- a/.github/workflows/superset-helm-lint.yml +++ b/.github/workflows/superset-helm-lint.yml @@ -16,14 +16,14 @@ jobs: runs-on: ubuntu-24.04 steps: - name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )" - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false submodules: recursive fetch-depth: 0 - name: Set up Helm - uses: azure/setup-helm@v4 + uses: azure/setup-helm@dda3372f752e03dde6b3237bc9431cdc2f7a02a2 # v5.0.0 with: version: v3.16.4 diff --git a/.github/workflows/superset-helm-release.yml b/.github/workflows/superset-helm-release.yml index d8b02d44c21..732f64ea980 100644 --- a/.github/workflows/superset-helm-release.yml +++ b/.github/workflows/superset-helm-release.yml @@ -29,7 +29,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: ref: ${{ inputs.ref || github.ref_name }} persist-credentials: true @@ -42,7 +42,7 @@ jobs: git config user.email "$GITHUB_ACTOR@users.noreply.github.com" - name: Install Helm - uses: azure/setup-helm@v4 + uses: azure/setup-helm@dda3372f752e03dde6b3237bc9431cdc2f7a02a2 # v5.0.0 with: version: v3.5.4 @@ -101,7 +101,7 @@ jobs: CR_RELEASE_NAME_TEMPLATE: "superset-helm-chart-{{ .Version }}" - name: Open Pull Request - uses: actions/github-script@v8 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: script: | const branchName = '${{ env.branch_name }}'; diff --git a/.github/workflows/superset-playwright.yml b/.github/workflows/superset-playwright.yml index f26d4aa1649..915833fe3ce 100644 --- a/.github/workflows/superset-playwright.yml +++ b/.github/workflows/superset-playwright.yml @@ -60,21 +60,21 @@ jobs: # Conditional checkout based on context (same as Cypress workflow) - name: Checkout for push or pull_request event if: github.event_name == 'push' || github.event_name == 'pull_request' - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false submodules: recursive ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }} - name: Checkout using ref (workflow_dispatch) if: github.event_name == 'workflow_dispatch' && github.event.inputs.ref != '' - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false ref: ${{ github.event.inputs.ref }} submodules: recursive - name: Checkout using PR ID (workflow_dispatch) if: github.event_name == 'workflow_dispatch' && github.event.inputs.pr_id != '' - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false ref: refs/pull/${{ github.event.inputs.pr_id }}/merge @@ -100,7 +100,7 @@ jobs: run: playwright_testdata - name: Setup Node.js if: steps.check.outputs.python || steps.check.outputs.frontend - uses: actions/setup-node@v6 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 with: node-version-file: './superset-frontend/.nvmrc' - name: Install npm dependencies @@ -133,7 +133,7 @@ jobs: SAFE_APP_ROOT=${APP_ROOT//\//_} echo "safe_app_root=$SAFE_APP_ROOT" >> $GITHUB_OUTPUT - name: Upload Playwright Artifacts - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 if: failure() with: path: | diff --git a/.github/workflows/superset-python-integrationtest.yml b/.github/workflows/superset-python-integrationtest.yml index d4418fcdd2a..06ae5183945 100644 --- a/.github/workflows/superset-python-integrationtest.yml +++ b/.github/workflows/superset-python-integrationtest.yml @@ -16,6 +16,8 @@ concurrency: jobs: test-mysql: runs-on: ubuntu-24.04 + permissions: + id-token: write env: PYTHONPATH: ${{ github.workspace }} SUPERSET_CONFIG: tests.integration_tests.superset_test_config @@ -41,7 +43,7 @@ jobs: - 16379:6379 steps: - name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )" - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false submodules: recursive @@ -68,11 +70,12 @@ jobs: run: | ./scripts/python_tests.sh - name: Upload code coverage - uses: codecov/codecov-action@v5 + uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v5 with: flags: python,mysql - token: ${{ secrets.CODECOV_TOKEN }} verbose: true + use_oidc: true + slug: apache/superset - name: Generate database diagnostics for docs if: steps.check.outputs.python env: @@ -98,13 +101,15 @@ jobs: " - name: Upload database diagnostics artifact if: steps.check.outputs.python - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: name: database-diagnostics path: databases-diagnostics.json retention-days: 7 test-postgres: runs-on: ubuntu-24.04 + permissions: + id-token: write strategy: matrix: python-version: ["current", "previous", "next"] @@ -129,7 +134,7 @@ jobs: - 16379:6379 steps: - name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )" - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false submodules: recursive @@ -159,14 +164,17 @@ jobs: run: | ./scripts/python_tests.sh - name: Upload code coverage - uses: codecov/codecov-action@v5 + uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v5 with: flags: python,postgres - token: ${{ secrets.CODECOV_TOKEN }} verbose: true + use_oidc: true + slug: apache/superset test-sqlite: runs-on: ubuntu-24.04 + permissions: + id-token: write env: PYTHONPATH: ${{ github.workspace }} SUPERSET_CONFIG: tests.integration_tests.superset_test_config @@ -182,7 +190,7 @@ jobs: - 16379:6379 steps: - name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )" - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false submodules: recursive @@ -211,8 +219,9 @@ jobs: run: | ./scripts/python_tests.sh - name: Upload code coverage - uses: codecov/codecov-action@v5 + uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v5 with: flags: python,sqlite - token: ${{ secrets.CODECOV_TOKEN }} verbose: true + use_oidc: true + slug: apache/superset diff --git a/.github/workflows/superset-python-presto-hive.yml b/.github/workflows/superset-python-presto-hive.yml index 762c1a19d45..68157386ec1 100644 --- a/.github/workflows/superset-python-presto-hive.yml +++ b/.github/workflows/superset-python-presto-hive.yml @@ -17,6 +17,8 @@ concurrency: jobs: test-postgres-presto: runs-on: ubuntu-24.04 + permissions: + id-token: write env: PYTHONPATH: ${{ github.workspace }} SUPERSET_CONFIG: tests.integration_tests.superset_test_config @@ -48,7 +50,7 @@ jobs: - 16379:6379 steps: - name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )" - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false submodules: recursive @@ -77,14 +79,17 @@ jobs: run: | ./scripts/python_tests.sh -m 'chart_data_flow or sql_json_flow' - name: Upload code coverage - uses: codecov/codecov-action@v5 + uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v5 with: flags: python,presto - token: ${{ secrets.CODECOV_TOKEN }} verbose: true + use_oidc: true + slug: apache/superset test-postgres-hive: runs-on: ubuntu-24.04 + permissions: + id-token: write env: PYTHONPATH: ${{ github.workspace }} SUPERSET_CONFIG: tests.integration_tests.superset_test_config @@ -108,7 +113,7 @@ jobs: - 16379:6379 steps: - name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )" - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false submodules: recursive @@ -145,8 +150,9 @@ jobs: pip install -e .[hive] ./scripts/python_tests.sh -m 'chart_data_flow or sql_json_flow' - name: Upload code coverage - uses: codecov/codecov-action@v5 + uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v5 with: flags: python,hive - token: ${{ secrets.CODECOV_TOKEN }} verbose: true + use_oidc: true + slug: apache/superset diff --git a/.github/workflows/superset-python-unittest.yml b/.github/workflows/superset-python-unittest.yml index 1dcf8a82237..4065e81d86c 100644 --- a/.github/workflows/superset-python-unittest.yml +++ b/.github/workflows/superset-python-unittest.yml @@ -17,6 +17,8 @@ concurrency: jobs: unit-tests: runs-on: ubuntu-24.04 + permissions: + id-token: write strategy: matrix: python-version: ["previous", "current", "next"] @@ -24,7 +26,7 @@ jobs: PYTHONPATH: ${{ github.workspace }} steps: - name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )" - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false submodules: recursive @@ -53,8 +55,9 @@ jobs: run: | pytest --durations-min=0.5 --cov=superset/sql/ ./tests/unit_tests/sql/ --cache-clear --cov-fail-under=100 - name: Upload code coverage - uses: codecov/codecov-action@v5 + uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v5 with: flags: python,unit - token: ${{ secrets.CODECOV_TOKEN }} verbose: true + use_oidc: true + slug: apache/superset diff --git a/.github/workflows/superset-translations.yml b/.github/workflows/superset-translations.yml index 0f1cfd62556..3223eb5fa88 100644 --- a/.github/workflows/superset-translations.yml +++ b/.github/workflows/superset-translations.yml @@ -18,7 +18,7 @@ jobs: runs-on: ubuntu-24.04 steps: - name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )" - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false submodules: recursive @@ -31,7 +31,7 @@ jobs: - name: Setup Node.js if: steps.check.outputs.frontend - uses: actions/setup-node@v6 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 with: node-version-file: './superset-frontend/.nvmrc' - name: Install dependencies @@ -49,7 +49,7 @@ jobs: runs-on: ubuntu-24.04 steps: - name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )" - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false submodules: recursive @@ -62,6 +62,10 @@ jobs: - name: Setup Python if: steps.check.outputs.python uses: ./.github/actions/setup-backend/ + + - name: Install msgcat + run: sudo apt update && sudo apt install gettext + - name: Test babel extraction if: steps.check.outputs.python run: ./scripts/translations/babel_update.sh diff --git a/.github/workflows/superset-websocket.yml b/.github/workflows/superset-websocket.yml index c4e8171dd9a..83458b7a609 100644 --- a/.github/workflows/superset-websocket.yml +++ b/.github/workflows/superset-websocket.yml @@ -21,7 +21,7 @@ jobs: runs-on: ubuntu-24.04 steps: - name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )" - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - name: Install dependencies diff --git a/.github/workflows/supersetbot.yml b/.github/workflows/supersetbot.yml index 463dedd3824..c187a45040e 100644 --- a/.github/workflows/supersetbot.yml +++ b/.github/workflows/supersetbot.yml @@ -26,7 +26,7 @@ jobs: steps: - name: Quickly add thumbs up! if: github.event_name == 'issue_comment' && contains(github.event.comment.body, '@supersetbot') - uses: actions/github-script@v8 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: script: | const [owner, repo] = process.env.GITHUB_REPOSITORY.split('/') @@ -38,7 +38,7 @@ jobs: }); - name: "Checkout ( ${{ github.sha }} )" - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false diff --git a/.github/workflows/tag-release.yml b/.github/workflows/tag-release.yml index 444422ff2e8..f97d9aaa449 100644 --- a/.github/workflows/tag-release.yml +++ b/.github/workflows/tag-release.yml @@ -31,10 +31,12 @@ jobs: id: check shell: bash run: | - if [ -n "${{ (secrets.DOCKERHUB_USER != '' && secrets.DOCKERHUB_TOKEN != '') || '' }}" ]; then + if [ -n "${DOCKERHUB_USER}" ]; then echo "has-secrets=1" >> "$GITHUB_OUTPUT" fi + env: + DOCKERHUB_USER: ${{ (secrets.DOCKERHUB_USER != '' && secrets.DOCKERHUB_TOKEN != '') || '' }} docker-release: needs: config if: needs.config.outputs.has-secrets @@ -47,7 +49,7 @@ jobs: steps: - name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )" - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: fetch-depth: 0 @@ -60,7 +62,7 @@ jobs: build: "true" - name: Use Node.js 20 - uses: actions/setup-node@v6 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 with: node-version: 20 @@ -72,17 +74,20 @@ jobs: DOCKERHUB_USER: ${{ secrets.DOCKERHUB_USER }} DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + INPUT_RELEASE: ${{ github.event.inputs.release }} + INPUT_FORCE_LATEST: ${{ github.event.inputs.force-latest }} + INPUT_GIT_REF: ${{ github.event.inputs.git-ref }} run: | RELEASE="${{ github.event.release.tag_name }}" FORCE_LATEST="" EVENT="${{github.event_name}}" if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then # in the case of a manually-triggered run, read release from input - RELEASE="${{ github.event.inputs.release }}" - if [ "${{ github.event.inputs.force-latest }}" = "true" ]; then + RELEASE="${INPUT_RELEASE}" + if [ "${INPUT_FORCE_LATEST}" = "true" ]; then FORCE_LATEST="--force-latest" fi - git checkout "${{ github.event.inputs.git-ref }}" + git checkout "${INPUT_GIT_REF}" EVENT="release" fi @@ -107,12 +112,12 @@ jobs: steps: - name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )" - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: fetch-depth: 0 - name: Use Node.js 20 - uses: actions/setup-node@v6 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 with: node-version: 20 @@ -122,6 +127,7 @@ jobs: - name: Label the PRs with the right release-related labels env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + INPUT_RELEASE: ${{ github.event.inputs.release }} run: | export GITHUB_ACTOR="" git fetch --all --tags @@ -129,6 +135,6 @@ jobs: RELEASE="${{ github.event.release.tag_name }}" if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then # in the case of a manually-triggered run, read release from input - RELEASE="${{ github.event.inputs.release }}" + RELEASE="${INPUT_RELEASE}" fi supersetbot release-label $RELEASE diff --git a/.github/workflows/tech-debt.yml b/.github/workflows/tech-debt.yml index b83aceb1842..d11ae6d3e1e 100644 --- a/.github/workflows/tech-debt.yml +++ b/.github/workflows/tech-debt.yml @@ -19,10 +19,12 @@ jobs: id: check shell: bash run: | - if [ -n "${{ (secrets.GSHEET_KEY != '' ) || '' }}" ]; then + if [ -n "${GSHEET_KEY}" ]; then echo "has-secrets=1" >> "$GITHUB_OUTPUT" fi + env: + GSHEET_KEY: ${{ (secrets.GSHEET_KEY != '' ) || '' }} process-and-upload: needs: config if: needs.config.outputs.has-secrets @@ -30,10 +32,10 @@ jobs: name: Generate Reports steps: - name: Checkout Repository - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - name: Set up Node.js - uses: actions/setup-node@v6 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 with: node-version-file: './superset-frontend/.nvmrc' diff --git a/.gitignore b/.gitignore index c9699e830dc..851cbab14e3 100644 --- a/.gitignore +++ b/.gitignore @@ -62,6 +62,7 @@ rat-results.txt superset/app/ superset-websocket/config.json .direnv +*.log # Node.js, webpack artifacts, storybook *.entry.js @@ -133,6 +134,7 @@ CLAUDE.local.md PROJECT.md .aider* .claude_rc* +.claude/settings.local.json .env.local oxc-custom-build/ *.code-workspace diff --git a/RELEASING/README.md b/RELEASING/README.md index af001ad6af9..d49671c598c 100644 --- a/RELEASING/README.md +++ b/RELEASING/README.md @@ -458,7 +458,7 @@ cd ../ sed -i '' "s/version_string = .*/version_string = \"$SUPERSET_VERSION\"/" setup.py # build the python distribution -python setup.py sdist +python -m build ``` Publish to PyPI diff --git a/RESOURCES/INTHEWILD.yaml b/RESOURCES/INTHEWILD.yaml index d84ae68c49d..fa193318769 100644 --- a/RESOURCES/INTHEWILD.yaml +++ b/RESOURCES/INTHEWILD.yaml @@ -287,6 +287,11 @@ categories: url: https://www.gfk.com/home contributors: ["@mherr"] + - name: Hifadih Business & Technology + url: https://hifadih.net/en + logo: hifadih.png + contributors: ["@saintLaurent00"] + # Logo approved by @anmol-hpe on behalf of HPE - name: HPE url: https://www.hpe.com/in/en/home.html diff --git a/UPDATING.md b/UPDATING.md index bbfd509ea44..27fc3428b9e 100644 --- a/UPDATING.md +++ b/UPDATING.md @@ -24,6 +24,20 @@ assists people when migrating to a new version. ## Next +### Granular Export Controls + +A new feature flag `GRANULAR_EXPORT_CONTROLS` introduces three fine-grained permissions that replace the legacy `can_csv` permission: + +| Permission | Controls | +|---|---| +| `can_export_data` | CSV, Excel, JSON exports | +| `can_export_image` | Screenshot/PDF exports | +| `can_copy_clipboard` | Copy-to-clipboard operations | + +When the feature flag is enabled, these permissions are enforced on both the frontend (disabled buttons with tooltips) and backend (403 responses from API endpoints). When disabled, legacy `can_csv` behavior is preserved. + +**Migration behavior:** All three new permissions are granted to every role that currently has `can_csv`, preserving existing access. Admins can then selectively revoke individual export permissions from specific roles as needed. + ### Deck.gl MapBox viewport and opacity controls are functional The Deck.gl MapBox chart's **Opacity**, **Default longitude**, **Default latitude**, and **Zoom** controls were previously non-functional — changing them had no effect on the rendered map. These controls are now wired up correctly. @@ -308,13 +322,13 @@ Note: Pillow is now a required dependency (previously optional) to support image There's a migration added that can potentially affect a significant number of existing charts. - [32317](https://github.com/apache/superset/pull/32317) The horizontal filter bar feature is now out of testing/beta development and its feature flag `HORIZONTAL_FILTER_BAR` has been removed. - [31590](https://github.com/apache/superset/pull/31590) Marks the begining of intricate work around supporting dynamic Theming, and breaks support for [THEME_OVERRIDES](https://github.com/apache/superset/blob/732de4ac7fae88e29b7f123b6cbb2d7cd411b0e4/superset/config.py#L671) in favor of a new theming system based on AntD V5. Likely this will be in disrepair until settling over the 5.x lifecycle. -- [32432](https://github.com/apache/superset/pull/31260) Moves the List Roles FAB view to the frontend and requires `FAB_ADD_SECURITY_API` to be enabled in the configuration and `superset init` to be executed. +- [32432](https://github.com/apache/superset/pull/32432) Moves the List Roles FAB view to the frontend and requires `FAB_ADD_SECURITY_API` to be enabled in the configuration and `superset init` to be executed. - [34319](https://github.com/apache/superset/pull/34319) Drill to Detail and Drill By is now supported in Embedded mode, and also with the `DASHBOARD_RBAC` FF. If you don't want to expose these features in Embedded / `DASHBOARD_RBAC`, make sure the roles used for Embedded / `DASHBOARD_RBAC`don't have the required permissions to perform D2D actions. ## 5.0.0 - [31976](https://github.com/apache/superset/pull/31976) Removed the `DISABLE_LEGACY_DATASOURCE_EDITOR` feature flag. The previous value of the feature flag was `True` and now the feature is permanently removed. -- [31959](https://github.com/apache/superset/pull/32000) Removes CSV_UPLOAD_MAX_SIZE config, use your web server to control file upload size. +- [32000](https://github.com/apache/superset/pull/32000) Removes CSV_UPLOAD_MAX_SIZE config, use your web server to control file upload size. - [31959](https://github.com/apache/superset/pull/31959) Removes the following endpoints from data uploads: `/api/v1/database//_upload` and `/api/v1/database/_metadata`, in favour of new one (Details on the PR). And simplifies permissions. - [31844](https://github.com/apache/superset/pull/31844) The `ALERT_REPORTS_EXECUTE_AS` and `THUMBNAILS_EXECUTE_AS` config parameters have been renamed to `ALERT_REPORTS_EXECUTORS` and `THUMBNAILS_EXECUTORS` respectively. A new config flag `CACHE_WARMUP_EXECUTORS` has also been introduced to be able to control which user is used to execute cache warmup tasks. Finally, the config flag `THUMBNAILS_SELENIUM_USER` has been removed. To use a fixed executor for async tasks, use the new `FixedExecutor` class. See the config and docs for more info on setting up different executor profiles. - [31894](https://github.com/apache/superset/pull/31894) Domain sharding is deprecated in favor of HTTP2. The `SUPERSET_WEBSERVER_DOMAINS` configuration will be removed in the next major version (6.0) diff --git a/docker-compose-light.yml b/docker-compose-light.yml index 1d0a4a90be7..458708bd26a 100644 --- a/docker-compose-light.yml +++ b/docker-compose-light.yml @@ -115,6 +115,10 @@ services: DATABASE_HOST: db-light DATABASE_DB: superset_light POSTGRES_DB: superset_light + EXAMPLES_HOST: db-light + EXAMPLES_DB: superset_light + EXAMPLES_USER: superset + EXAMPLES_PASSWORD: superset SUPERSET_CONFIG_PATH: /app/docker/pythonpath_dev/superset_config_docker_light.py GITHUB_HEAD_REF: ${GITHUB_HEAD_REF:-} GITHUB_SHA: ${GITHUB_SHA:-} @@ -137,6 +141,10 @@ services: DATABASE_HOST: db-light DATABASE_DB: superset_light POSTGRES_DB: superset_light + EXAMPLES_HOST: db-light + EXAMPLES_DB: superset_light + EXAMPLES_USER: superset + EXAMPLES_PASSWORD: superset SUPERSET_CONFIG_PATH: /app/docker/pythonpath_dev/superset_config_docker_light.py healthcheck: disable: true @@ -157,6 +165,7 @@ services: BUILD_SUPERSET_FRONTEND_IN_DOCKER: true NPM_RUN_PRUNE: false SCARF_ANALYTICS: "${SCARF_ANALYTICS:-}" + DISABLE_TS_CHECKER: "${DISABLE_TS_CHECKER:-true}" # configuring the dev-server to use the host.docker.internal to connect to the backend superset: "http://superset-light:8088" # Webpack dev server must bind to 0.0.0.0 to be accessible from outside the container diff --git a/docker/docker-bootstrap.sh b/docker/docker-bootstrap.sh index 58d71d25c25..d20f28007f1 100755 --- a/docker/docker-bootstrap.sh +++ b/docker/docker-bootstrap.sh @@ -80,7 +80,7 @@ case "${1}" in ;; app) echo "Starting web app (using development server)..." - flask run -p $PORT --reload --debugger --without-threads --host=0.0.0.0 --exclude-patterns "*/node_modules/*:*/.venv/*:*/build/*:*/__pycache__/*" + flask run -p $PORT --reload --debugger --host=0.0.0.0 --exclude-patterns "*/node_modules/*:*/.venv/*:*/build/*:*/__pycache__/*:*/superset-frontend/*" ;; app-gunicorn) echo "Starting web app..." diff --git a/docs/admin_docs/configuration/aws-iam.mdx b/docs/admin_docs/configuration/aws-iam.mdx new file mode 100644 index 00000000000..e3fac57508f --- /dev/null +++ b/docs/admin_docs/configuration/aws-iam.mdx @@ -0,0 +1,162 @@ +{/* +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you under the Apache License, Version 2.0 (the +"License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. +*/} + +--- +title: AWS IAM Authentication +sidebar_label: AWS IAM Authentication +sidebar_position: 15 +--- + +# AWS IAM Authentication for AWS Databases + +Superset supports IAM-based authentication for **Amazon Aurora** (PostgreSQL and MySQL) and **Amazon Redshift**. IAM auth eliminates the need for database passwords — Superset generates a short-lived auth token using temporary AWS credentials instead. + +Cross-account IAM role assumption via STS `AssumeRole` is supported, allowing a Superset deployment in one AWS account to connect to databases in a different account. + +## Prerequisites + +- Enable the `AWS_DATABASE_IAM_AUTH` feature flag in `superset_config.py`. IAM authentication is gated behind this flag; if it is disabled, connections using `aws_iam` fail with *"AWS IAM database authentication is not enabled."* + ```python + FEATURE_FLAGS = { + "AWS_DATABASE_IAM_AUTH": True, + } + ``` +- `boto3` must be installed in your Superset environment: + ```bash + pip install boto3 + ``` +- The Superset server's IAM role (or static credentials) must have permission to call `sts:AssumeRole` (for cross-account) or the same-account permissions for the target service: + - **Aurora (RDS)**: `rds-db:connect` + - **Redshift provisioned**: `redshift:GetClusterCredentials` + - **Redshift Serverless**: `redshift-serverless:GetCredentials` and `redshift-serverless:GetWorkgroup` +- SSL must be enabled on the Aurora / Redshift endpoint (required for IAM token auth). + +## Configuration + +IAM authentication is configured via the **encrypted_extra** field of the database connection. Access this field in the **Advanced** → **Security** section of the database connection form, under **Secure Extra**. + +### Aurora PostgreSQL or Aurora MySQL + +```json +{ + "aws_iam": { + "enabled": true, + "role_arn": "arn:aws:iam::222222222222:role/SupersetDatabaseAccess", + "external_id": "superset-prod-12345", + "region": "us-east-1", + "db_username": "superset_iam_user", + "session_duration": 3600 + } +} +``` + +| Field | Required | Description | +|-------|----------|-------------| +| `enabled` | Yes | Set to `true` to activate IAM auth | +| `role_arn` | No | ARN of the cross-account IAM role to assume via STS. Omit for same-account auth | +| `external_id` | No | External ID for the STS `AssumeRole` call, if required by the target role's trust policy | +| `region` | Yes | AWS region of the database cluster | +| `db_username` | Yes | The database username associated with the IAM identity | +| `session_duration` | No | STS session duration in seconds (default: `3600`) | + +### Redshift (Serverless) + +```json +{ + "aws_iam": { + "enabled": true, + "role_arn": "arn:aws:iam::222222222222:role/SupersetRedshiftAccess", + "region": "us-east-1", + "workgroup_name": "my-workgroup", + "db_name": "dev" + } +} +``` + +### Redshift (Provisioned Cluster) + +```json +{ + "aws_iam": { + "enabled": true, + "role_arn": "arn:aws:iam::222222222222:role/SupersetRedshiftAccess", + "region": "us-east-1", + "cluster_identifier": "my-cluster", + "db_username": "superset_iam_user", + "db_name": "dev" + } +} +``` + +## Cross-Account IAM Setup + +To connect to a database in Account B from a Superset deployment in Account A: + +**1. In Account B — create a database-access role:** + +```json +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": ["rds-db:connect"], + "Resource": "arn:aws:rds-db:us-east-1:222222222222:dbuser/db-XXXXXXXXXXXX/superset_iam_user" + } + ] +} +``` + +**Trust policy** (allows Account A's Superset role to assume it): + +```json +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "AWS": "arn:aws:iam::111111111111:role/SupersetInstanceRole" + }, + "Action": "sts:AssumeRole", + "Condition": { + "StringEquals": { + "sts:ExternalId": "superset-prod-12345" + } + } + } + ] +} +``` + +**2. In Account A — grant Superset's role permission to assume the Account B role:** + +```json +{ + "Effect": "Allow", + "Action": "sts:AssumeRole", + "Resource": "arn:aws:iam::222222222222:role/SupersetDatabaseAccess" +} +``` + +**3. Configure the database connection in Superset** using the `role_arn` and `external_id` from the trust policy (as shown in the configuration example above). + +## Credential Caching + +STS credentials are cached in memory keyed by `(role_arn, region, external_id)` with a 10-minute TTL. This reduces the number of STS API calls when multiple queries are executed with the same connection. Tokens are refreshed automatically before expiry. diff --git a/docs/admin_docs/configuration/configuring-superset.mdx b/docs/admin_docs/configuration/configuring-superset.mdx index 657d5d4dc75..3842dd65ee5 100644 --- a/docs/admin_docs/configuration/configuring-superset.mdx +++ b/docs/admin_docs/configuration/configuring-superset.mdx @@ -109,6 +109,14 @@ SECRET_KEY = 'YOUR_OWN_RANDOM_GENERATED_SECRET_KEY' You can generate a strong secure key with `openssl rand -base64 42`. +Alternatively, you can set the secret key using `SUPERSET_SECRET_KEY` environment variable: + +On a Unix-based system, such as Linux or macOS, you can do so by running the following command in your terminal: + +```bash +export SUPERSET_SECRET_KEY=$(openssl rand -base64 42) +``` + :::caution Use a strong secret key This key will be used for securely signing session cookies and encrypting sensitive information stored in Superset's application metadata database. Your deployment must use a complex, unique key. diff --git a/docs/admin_docs/configuration/importing-exporting-datasources.mdx b/docs/admin_docs/configuration/importing-exporting-datasources.mdx index 400d64590ad..dadc5e0d4a7 100644 --- a/docs/admin_docs/configuration/importing-exporting-datasources.mdx +++ b/docs/admin_docs/configuration/importing-exporting-datasources.mdx @@ -10,6 +10,10 @@ version: 1 The superset cli allows you to import and export datasources from and to YAML. Datasources include databases. The data is expected to be organized in the following hierarchy: +:::info +Superset's ZIP-based import/export also covers **dashboards**, **charts**, and **saved queries**, exercised through the UI and REST API. The [Dashboard Import Overwrite Behavior](#dashboard-import-overwrite-behavior) and [UUIDs in API Responses](#uuids-in-api-responses) sections below document the behavior shared across all asset types. +::: + ```text ├──databases | ├──database_1 @@ -75,6 +79,29 @@ The optional username flag **-u** sets the user used for the datasource import. superset import_datasources -p -u 'admin' ``` +## Dashboard Import Overwrite Behavior + +When importing a dashboard ZIP with the **overwrite** option enabled, any existing charts that are part of the dashboard are **replaced** rather than duplicated. This applies to: + +- Charts whose UUID matches a chart already present in the target instance +- The full chart configuration (query, visualization type, columns, metrics) is replaced by the imported version + +If you import without the overwrite flag, existing charts with conflicting UUIDs are left unchanged and the import skips those objects. Use overwrite when you want to push a fully updated dashboard (including chart definitions) from a development or staging environment to production. + +## UUIDs in API Responses + +The REST API POST endpoints for **datasets**, **charts**, and **dashboards** include the auto-generated `uuid` field in the response body: + +```json +{ + "id": 42, + "uuid": "b8a8d5c3-1234-4abc-8def-0123456789ab", + ... +} +``` + +UUIDs remain stable across import/export cycles and can be used for cross-environment workflows — for example, recording a UUID when creating a chart in development and using it to identify the matching chart after importing into production. + ## Legacy Importing Datasources ### From older versions of Superset to current version diff --git a/docs/admin_docs/configuration/mcp-server.mdx b/docs/admin_docs/configuration/mcp-server.mdx index 22a80d645b3..1475f3d6468 100644 --- a/docs/admin_docs/configuration/mcp-server.mdx +++ b/docs/admin_docs/configuration/mcp-server.mdx @@ -501,6 +501,7 @@ All MCP settings go in `superset_config.py`. Defaults are defined in `superset/m | `MCP_SERVICE_URL` | `None` | Public base URL for MCP-generated links (set this when behind a reverse proxy) | | `MCP_DEBUG` | `False` | Enable debug logging | | `MCP_DEV_USERNAME` | -- | Superset username for development mode (no auth) | +| `MCP_PARSE_REQUEST_ENABLED` | `True` | Pre-parse MCP tool inputs from JSON strings into objects. Set to `False` for clients (Claude Desktop, LangChain) that do not double-serialize arguments — this produces cleaner tool schemas for those clients | ### Authentication @@ -664,6 +665,32 @@ MCP_CSRF_CONFIG = { --- +## Audit Events + +All MCP tool calls are logged to Superset's event logger, the same system used by the web UI (viewable at **Settings → Action Log**). Each event captures: + +- **Action**: `mcp..` (e.g., `mcp.list_databases.query`) +- **User**: the resolved Superset username from the JWT or dev config +- **Timestamp**: when the operation ran + +This means MCP activity is auditable alongside normal user activity. No additional configuration is required — logging is on by default whenever the event logger is enabled in your Superset deployment. + +## Tool Pagination + +MCP list tools (`list_datasets`, `list_charts`, `list_dashboards`, `list_databases`) use **offset pagination** via `page` (1-based) and `page_size` parameters. Responses include `page`, `page_size`, `total_count`, `total_pages`, `has_previous`, and `has_next`. To iterate through all results: + +```python +# Example: fetch all charts across pages +all_charts = [] +page = 1 +while True: + result = mcp.list_charts(page=page, page_size=50) + all_charts.extend(result["charts"]) + if not result.get("has_next"): + break + page += 1 +``` + ## Security Best Practices - **Use TLS** for all production MCP endpoints -- place the server behind a reverse proxy with HTTPS diff --git a/docs/admin_docs/configuration/sql-templating.mdx b/docs/admin_docs/configuration/sql-templating.mdx index 893c1886419..f2c21bcef79 100644 --- a/docs/admin_docs/configuration/sql-templating.mdx +++ b/docs/admin_docs/configuration/sql-templating.mdx @@ -22,6 +22,15 @@ While powerful, this feature executes template code on the server. Within the Su If you grant these permissions to untrusted users, this feature can be exploited as a **Server-Side Template Injection (SSTI)** vulnerability. Do not enable `ENABLE_TEMPLATE_PROCESSING` unless you fully understand and accept the associated security risks. +Additionally: + +- The `url_param()` macro allows URL parameters to influence the rendered SQL. Always validate or restrict `url_param()` values in your templates rather than interpolating them directly. +- `filter.get('val')` returns raw filter values without escaping. Use the safe helpers described below (`|where_in`, `| replace("'", "''")`) rather than concatenating values directly into SQL strings. + +::: + +:::tip +`ENABLE_TEMPLATE_PROCESSING` defaults to `False`. Only enable it if your deployment requires Jinja templates and all users with dataset/chart edit access are administrators or fully trusted internal users. ::: When templating is enabled, python code can be embedded in virtual datasets and @@ -324,6 +333,16 @@ cache hit in the future and Superset can retrieve cached data. The `{{ url_param('custom_variable') }}` macro lets you define arbitrary URL parameters and reference them in your SQL code. +:::warning +Always treat `url_param()` values as untrusted input. Escaping behaviour varies by context and configuration, so do not rely on it. Restrict values to an explicit allowlist before using them in SQL: + +```sql +{% set cc = url_param('countrycode') %} +{% if cc not in ('US', 'ES', 'FR') %}{% set cc = 'US' %}{% endif %} +WHERE country_code = '{{ cc }}' +``` +::: + Here's a concrete example: - You write the following query in SQL Lab: @@ -398,6 +417,16 @@ This is useful if: - You want to handle generating custom SQL conditions for a filter - You want to have the ability to filter inside the main query for speed purposes +:::warning +`filter.get('val')` returns the raw filter value without escaping. For multi-value filters, use the `|where_in` Jinja filter, which handles quoting safely. For single-value operators like `LIKE`, escape single quotes before interpolating: + +```sql +{%- if filter.get('op') == 'LIKE' -%} + AND full_name LIKE '{{ filter.get('val') | replace("'", "''") }}' +{%- endif -%} +``` +::: + Here's a concrete example: ```sql @@ -424,7 +453,7 @@ Here's a concrete example: {%- if filter.get('op') == 'LIKE' -%} AND - full_name LIKE {{ "'" + filter.get('val') + "'" }} + full_name LIKE '{{ filter.get('val') | replace("'", "''") }}' {%- endif -%} {%- endfor -%} diff --git a/docs/admin_docs/security/security.mdx b/docs/admin_docs/security/security.mdx index cc68b1241b9..867e3f87986 100644 --- a/docs/admin_docs/security/security.mdx +++ b/docs/admin_docs/security/security.mdx @@ -24,6 +24,14 @@ A table with the permissions for these roles can be found at [/RESOURCES/STANDAR Admins have all possible rights, including granting or revoking rights from other users and altering other people’s slices and dashboards. +>#### Threat Model and Privilege Boundaries: The Admin Role +> +>Apache Superset is built with a granular permission model where users assigned the Admin role are considered fully trusted. Admins possess complete control over the application's configuration, UI rendering, and access controls. +> +>Consequently, actions performed by an Admin that alter the application's behavior or presentation—such as injecting custom CSS, modifying Jinja templates, or altering security flags—are intended administrative capabilities by design. +> +>In accordance with MITRE CNA Rule 4.1, a vulnerability must represent a violation of an explicit security policy. Because the Admin role is defined as a trusted operational boundary, actions executed with Admin privileges do not cross a security perimeter. Therefore, exploit vectors that strictly require Admin access are not classified as security vulnerabilities and are ineligible for CVE assignment. + ### Alpha Alpha users have access to all data sources, but they cannot grant or revoke access diff --git a/docs/developer_docs/api.mdx b/docs/developer_docs/api.mdx index c97f8fb5648..6793408388c 100644 --- a/docs/developer_docs/api.mdx +++ b/docs/developer_docs/api.mdx @@ -412,7 +412,7 @@ curl -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ |--------|----------|-------------| | `GET` | [Get security roles](/developer-docs/api/get-security-roles) | `/api/v1/security/roles/` | | `POST` | [Create security roles](/developer-docs/api/create-security-roles) | `/api/v1/security/roles/` | -| `GET` | [Get security roles info](/developer-docs/api/get-security-roles-info) | `/api/v1/security/roles/_info` | +| `GET` | [Get security roles info](/developer-docs/api/get-security-roles-info) | `/api/v1/security/roles/_info` | | `DELETE` | [Delete security roles by pk](/developer-docs/api/delete-security-roles-by-pk) | `/api/v1/security/roles/{pk}` | | `GET` | [Get security roles by pk](/developer-docs/api/get-security-roles-by-pk) | `/api/v1/security/roles/{pk}` | | `PUT` | [Update security roles by pk](/developer-docs/api/update-security-roles-by-pk) | `/api/v1/security/roles/{pk}` | @@ -430,7 +430,7 @@ curl -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ |--------|----------|-------------| | `GET` | [Get security users](/developer-docs/api/get-security-users) | `/api/v1/security/users/` | | `POST` | [Create security users](/developer-docs/api/create-security-users) | `/api/v1/security/users/` | -| `GET` | [Get security users info](/developer-docs/api/get-security-users-info) | `/api/v1/security/users/_info` | +| `GET` | [Get security users info](/developer-docs/api/get-security-users-info) | `/api/v1/security/users/_info` | | `DELETE` | [Delete security users by pk](/developer-docs/api/delete-security-users-by-pk) | `/api/v1/security/users/{pk}` | | `GET` | [Get security users by pk](/developer-docs/api/get-security-users-by-pk) | `/api/v1/security/users/{pk}` | | `PUT` | [Update security users by pk](/developer-docs/api/update-security-users-by-pk) | `/api/v1/security/users/{pk}` | @@ -443,7 +443,7 @@ curl -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ | Method | Endpoint | Description | |--------|----------|-------------| | `GET` | [Get security permissions](/developer-docs/api/get-security-permissions) | `/api/v1/security/permissions/` | -| `GET` | [Get security permissions info](/developer-docs/api/get-security-permissions-info) | `/api/v1/security/permissions/_info` | +| `GET` | [Get security permissions info](/developer-docs/api/get-security-permissions-info) | `/api/v1/security/permissions/_info` | | `GET` | [Get security permissions by pk](/developer-docs/api/get-security-permissions-by-pk) | `/api/v1/security/permissions/{pk}` | @@ -455,7 +455,7 @@ curl -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ |--------|----------|-------------| | `GET` | [Get security resources](/developer-docs/api/get-security-resources) | `/api/v1/security/resources/` | | `POST` | [Create security resources](/developer-docs/api/create-security-resources) | `/api/v1/security/resources/` | -| `GET` | [Get security resources info](/developer-docs/api/get-security-resources-info) | `/api/v1/security/resources/_info` | +| `GET` | [Get security resources info](/developer-docs/api/get-security-resources-info) | `/api/v1/security/resources/_info` | | `DELETE` | [Delete security resources by pk](/developer-docs/api/delete-security-resources-by-pk) | `/api/v1/security/resources/{pk}` | | `GET` | [Get security resources by pk](/developer-docs/api/get-security-resources-by-pk) | `/api/v1/security/resources/{pk}` | | `PUT` | [Update security resources by pk](/developer-docs/api/update-security-resources-by-pk) | `/api/v1/security/resources/{pk}` | @@ -469,7 +469,7 @@ curl -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ |--------|----------|-------------| | `GET` | [Get security permissions resources](/developer-docs/api/get-security-permissions-resources) | `/api/v1/security/permissions-resources/` | | `POST` | [Create security permissions resources](/developer-docs/api/create-security-permissions-resources) | `/api/v1/security/permissions-resources/` | -| `GET` | [Get security permissions resources info](/developer-docs/api/get-security-permissions-resources-info) | `/api/v1/security/permissions-resources/_info` | +| `GET` | [Get security permissions resources info](/developer-docs/api/get-security-permissions-resources-info) | `/api/v1/security/permissions-resources/_info` | | `DELETE` | [Delete security permissions resources by pk](/developer-docs/api/delete-security-permissions-resources-by-pk) | `/api/v1/security/permissions-resources/{pk}` | | `GET` | [Get security permissions resources by pk](/developer-docs/api/get-security-permissions-resources-by-pk) | `/api/v1/security/permissions-resources/{pk}` | | `PUT` | [Update security permissions resources by pk](/developer-docs/api/update-security-permissions-resources-by-pk) | `/api/v1/security/permissions-resources/{pk}` | @@ -578,7 +578,29 @@ curl -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ | Method | Endpoint | Description | |--------|----------|-------------| -| `GET` | [Get api by version openapi](/developer-docs/api/get-api-by-version-openapi) | `/api/{version}/_openapi` | +| `GET` | [Get api by version openapi](/developer-docs/api/get-api-by-version-openapi) | `/api/{version}/_openapi` | + + + +
+Themes (14 endpoints) — Manage UI themes for customizing Superset's appearance. + +| Method | Endpoint | Description | +|--------|----------|-------------| +| `DELETE` | [Bulk delete themes](/developer-docs/api/bulk-delete-themes) | `/api/v1/theme/` | +| `GET` | [Get a list of themes](/developer-docs/api/get-a-list-of-themes) | `/api/v1/theme/` | +| `POST` | [Create a theme](/developer-docs/api/create-a-theme) | `/api/v1/theme/` | +| `GET` | [Get metadata information about this API resource (theme-info)](/developer-docs/api/get-metadata-information-about-this-api-resource-theme-info) | `/api/v1/theme/_info` | +| `DELETE` | [Delete a theme](/developer-docs/api/delete-a-theme) | `/api/v1/theme/{pk}` | +| `GET` | [Get a theme](/developer-docs/api/get-a-theme) | `/api/v1/theme/{pk}` | +| `PUT` | [Update a theme](/developer-docs/api/update-a-theme) | `/api/v1/theme/{pk}` | +| `PUT` | [Set a theme as the system dark theme](/developer-docs/api/set-a-theme-as-the-system-dark-theme) | `/api/v1/theme/{pk}/set_system_dark` | +| `PUT` | [Set a theme as the system default theme](/developer-docs/api/set-a-theme-as-the-system-default-theme) | `/api/v1/theme/{pk}/set_system_default` | +| `GET` | [Download multiple themes as YAML files](/developer-docs/api/download-multiple-themes-as-yaml-files) | `/api/v1/theme/export/` | +| `POST` | [Import themes from a ZIP file](/developer-docs/api/import-themes-from-a-zip-file) | `/api/v1/theme/import/` | +| `GET` | [Get related fields data (theme-related-column-name)](/developer-docs/api/get-related-fields-data-theme-related-column-name) | `/api/v1/theme/related/{column_name}` | +| `DELETE` | [Clear the system dark theme](/developer-docs/api/clear-the-system-dark-theme) | `/api/v1/theme/unset_system_dark` | +| `DELETE` | [Clear the system default theme](/developer-docs/api/clear-the-system-default-theme) | `/api/v1/theme/unset_system_default` |
diff --git a/docs/developer_docs/extensions/development.md b/docs/developer_docs/extensions/development.md index a10f7743110..838db2f1c76 100644 --- a/docs/developer_docs/extensions/development.md +++ b/docs/developer_docs/extensions/development.md @@ -49,7 +49,7 @@ superset-extensions update: Updates derived and generated files in the extension When creating a new extension with `superset-extensions init`, the CLI generates a standardized folder structure: ``` -my-org.dataset-references/ +dataset-references/ ├── extension.json ├── frontend/ │ ├── src/ @@ -80,7 +80,7 @@ my-org.dataset-references/ ``` **Note**: With publisher `my-org` and name `dataset-references`, the technical names are: -- Directory name: `my-org.dataset-references` (kebab-case) +- Directory name: `dataset-references` (kebab-case) - Backend Python namespace: `my_org.dataset_references` - Backend distribution package: `my_org-dataset_references` - Frontend package name: `@my-org/dataset-references` (scoped) diff --git a/docs/developer_docs/extensions/quick-start.md b/docs/developer_docs/extensions/quick-start.md index d43e4be6e62..f2c4388b8a5 100644 --- a/docs/developer_docs/extensions/quick-start.md +++ b/docs/developer_docs/extensions/quick-start.md @@ -75,7 +75,7 @@ This approach ensures that extensions from different organizations cannot confli This creates a complete project structure: ``` -my-org.hello-world/ +hello-world/ ├── extension.json # Extension metadata and configuration ├── backend/ # Backend Python code │ ├── src/ diff --git a/docs/developer_docs/testing/backend-testing.md b/docs/developer_docs/testing/backend-testing.md index 0af638fac5b..e8c8d229fcb 100644 --- a/docs/developer_docs/testing/backend-testing.md +++ b/docs/developer_docs/testing/backend-testing.md @@ -63,6 +63,109 @@ pytest tests/unit_tests/ pytest tests/integration_tests/ ``` +## Testing Alerts & Reports with Celery and MailHog + +The Alerts & Reports feature relies on Celery for task scheduling and execution. To test it locally, you need Redis (message broker), Celery Beat (scheduler), a Celery Worker (executor), and an SMTP server to receive email notifications. + +### Prerequisites + +- Redis running on `localhost:6379` +- [MailHog](https://github.com/mailhog/MailHog) installed (a local SMTP server with a web UI for viewing caught emails) + +### superset_config.py + +Your `CeleryConfig` **must** include `beat_schedule`. When you define a custom `CeleryConfig` class in `superset_config.py`, it replaces the default entirely. If you omit `beat_schedule`, Celery Beat will start but never schedule any report tasks. + +```python +from celery.schedules import crontab +from superset.tasks.types import ExecutorType + +REDIS_HOST = "localhost" +REDIS_PORT = "6379" + +class CeleryConfig: + broker_url = f"redis://{REDIS_HOST}:{REDIS_PORT}/0" + result_backend = f"redis://{REDIS_HOST}:{REDIS_PORT}/0" + broker_connection_retry_on_startup = True + imports = ( + "superset.sql_lab", + "superset.tasks.scheduler", + "superset.tasks.thumbnails", + "superset.tasks.cache", + ) + worker_prefetch_multiplier = 10 + task_acks_late = True + beat_schedule = { + "reports.scheduler": { + "task": "reports.scheduler", + "schedule": crontab(minute="*", hour="*"), + }, + "reports.prune_log": { + "task": "reports.prune_log", + "schedule": crontab(minute=0, hour=0), + }, + } + +CELERY_CONFIG = CeleryConfig + +# SMTP settings pointing to MailHog +SMTP_HOST = "localhost" +SMTP_PORT = 1025 +SMTP_STARTTLS = False +SMTP_SSL = False +SMTP_USER = "" +SMTP_PASSWORD = "" +SMTP_MAIL_FROM = "superset@localhost" + +# Must match where your frontend is running +WEBDRIVER_BASEURL = "http://localhost:9000/" + +ALERT_REPORTS_EXECUTE_AS = [ExecutorType.OWNER] + +FEATURE_FLAGS = { + "ALERT_REPORTS": True, + # Recommended for better screenshot support (WebGL/DeckGL charts) + "PLAYWRIGHT_REPORTS_AND_THUMBNAILS": True, +} +``` + +:::note +Do not include `"superset.tasks.async_queries"` in `CeleryConfig.imports` unless you need Global Async Queries. That module accesses `current_app.config` at import time and will crash the worker with a "Working outside of application context" error. +::: + +### Starting the Services + +Start MailHog, then Celery Beat and Worker in separate terminals: + +```bash +# Terminal 1 - MailHog (SMTP on :1025, Web UI on :8025) +MailHog + +# Terminal 2 - Celery Beat (scheduler) +celery --app=superset.tasks.celery_app:app beat --loglevel=info + +# Terminal 3 - Celery Worker (executor) +celery --app=superset.tasks.celery_app:app worker --concurrency=1 --loglevel=info +``` + +Use `--concurrency=1` to limit resource usage on your dev machine. + +### Verifying the Setup + +1. **Beat** should log `Scheduler: Sending due task reports.scheduler (reports.scheduler)` once per minute +2. **Worker** should log `Scheduling alert eta: ` for each active report +3. Create a test report in **Settings > Alerts & Reports** with a `* * * * *` cron schedule +4. Check **http://localhost:8025** (MailHog web UI) for the email within 1-2 minutes + +### Troubleshooting + +| Problem | Solution | +|---|---| +| Beat shows no output | Ensure `beat_schedule` is defined in your `CeleryConfig` and `--loglevel=info` is set | +| "Report Schedule is still working, refusing to re-compute" | Previous executions are stuck. Reset with: `UPDATE report_schedule SET last_state = 'Not triggered' WHERE id = ;` | +| Task backlog overwhelming the worker | Flush Redis: `redis-cli FLUSHDB`, then restart Beat and Worker | +| Screenshot timeout | Ensure your frontend dev server is running and `WEBDRIVER_BASEURL` matches its URL | + --- *This documentation is under active development. Check back soon for updates!* diff --git a/docs/docs/faq.mdx b/docs/docs/faq.mdx index e98cd70edc1..2e6ff2fefa8 100644 --- a/docs/docs/faq.mdx +++ b/docs/docs/faq.mdx @@ -91,7 +91,7 @@ or a view. When working with tables, the solution would be to create a table that contains all the fields needed for your analysis, most likely through some scheduled batch process. -A view is a simple logical layer that abstracts an arbitrary SQL queries as a virtual table. This can +A view is a simple logical layer that abstracts an arbitrary SQL query as a virtual table. This can allow you to join and union multiple tables and to apply some transformation using arbitrary SQL expressions. The limitation there is your database performance, as Superset effectively will run a query on top of your query (view). A good practice may be to limit yourself to joining your main diff --git a/docs/docs/security/granular-export-controls.mdx b/docs/docs/security/granular-export-controls.mdx new file mode 100644 index 00000000000..806e21e02cf --- /dev/null +++ b/docs/docs/security/granular-export-controls.mdx @@ -0,0 +1,78 @@ +--- +title: Granular Export Controls +sidebar_position: 4 +--- + +# Granular Export Controls + +Superset provides granular, permission-based controls for data export, image export, and clipboard operations. These replace the legacy `can_csv` permission with three fine-grained permissions that can be assigned independently to roles. + +## Feature Flag + +Granular export controls are gated behind the `GRANULAR_EXPORT_CONTROLS` feature flag. When the flag is disabled, the legacy `can_csv` permission behavior is preserved. + +```python +FEATURE_FLAGS = { + "GRANULAR_EXPORT_CONTROLS": True, +} +``` + +## Permissions + +| Permission | Resource | Controls | +| -------------------- | ---------- | ---------------------------------------------------------------------- | +| `can_export_data` | `Superset` | CSV, Excel, and JSON data exports from charts, dashboards, and SQL Lab | +| `can_export_image` | `Superset` | Screenshot (JPEG/PNG) and PDF exports from charts and dashboards | +| `can_copy_clipboard` | `Superset` | Copy-to-clipboard operations in SQL Lab and the Explore data pane | + +## Default Role Assignments + +The migration grants all three new permissions (`can_export_data`, `can_export_image`, `can_copy_clipboard`) to every role that currently has `can_csv`. This preserves existing behavior — no role loses access during the upgrade. + +After the migration, admins can selectively revoke individual export permissions from any role to restrict access. For example, to prevent Gamma users from exporting data or images while still allowing clipboard operations, revoke `can_export_data` and `can_export_image` from the Gamma role. + +## Configuration Steps + +1. **Enable the feature flag** in `superset_config.py`: + + ```python + FEATURE_FLAGS = { + "GRANULAR_EXPORT_CONTROLS": True, + } + ``` + +2. **Run the database migration** to register the new permissions: + + ```bash + superset db upgrade + ``` + +3. **Initialize permissions** so roles are populated: + + ```bash + superset init + ``` + +4. **Verify role assignments** in **Settings > List Roles**. Confirm that each role has the expected permissions from the table above. + +5. **Customize as needed**: Grant or revoke individual export permissions on any role through the role editor. + +## User Experience + +When a user lacks a required export permission: + +- **Menu items** (CSV, Excel, JSON, screenshot) appear **disabled** with an info tooltip icon explaining the restriction +- **Buttons** (SQL Lab download, clipboard copy) appear **disabled** with a tooltip on hover +- **API endpoints** return **403 Forbidden** when the corresponding permission is missing + +## API Enforcement + +The following API endpoints enforce granular export permissions when the feature flag is enabled: + +| Endpoint | Required Permission | +| --------------------------------------------------------- | ------------------- | +| `GET /api/v1/chart/{id}/data/` (CSV/Excel format) | `can_export_data` | +| `GET /api/v1/chart/{id}/cache_screenshot/` | `can_export_image` | +| `POST /api/v1/dashboard/{id}/cache_dashboard_screenshot/` | `can_export_image` | +| `GET /api/v1/sqllab/export/{client_id}/` | `can_export_data` | +| `POST /api/v1/sqllab/export_streaming/` | `can_export_data` | diff --git a/docs/docs/using-superset/creating-your-first-dashboard.mdx b/docs/docs/using-superset/creating-your-first-dashboard.mdx index febf3395a6f..99a859818af 100644 --- a/docs/docs/using-superset/creating-your-first-dashboard.mdx +++ b/docs/docs/using-superset/creating-your-first-dashboard.mdx @@ -63,6 +63,12 @@ by clicking the **Connect** button in the bottom right corner of the modal windo Congratulations, you've just added a new data source in Superset! +### Sharing a Database Connection + +When adding a new database, you can share the connection with other Superset users. Shared connections appear in other users' database lists, making it easier to collaborate on the same data without requiring each user to configure the same connection separately. + +To share a connection, enable the **Share connection with other users** option in the **Advanced** tab of the database connection modal before saving. You can change sharing settings later by editing the database connection. + ### Registering a new table Now that you’ve configured a data source, you can select specific tables (called **Datasets** in Superset) @@ -80,6 +86,22 @@ we register the **cleaned_sales_data** table from the **examples** database. To finish, click the **Add** button in the bottom right corner. You should now see your dataset in the list of datasets. +### Organizing Datasets into Folders + +The Datasets list view supports **folders** for organizing datasets into groups. To create and manage folders: + +1. In the **Datasets** list, click the **Folders** panel on the left sidebar. +2. Click **+ New Folder** to create a top-level folder, or drag an existing folder to nest it. +3. Drag dataset rows onto a folder to move them in, or right-click a dataset and select **Move to folder**. + +Folders are per-user organizational aids — they do not affect dataset access permissions or how other users see the datasets. + +### Uploading Files via the OS File Manager (PWA) + +When Superset is installed as a **Progressive Web App (PWA)** from your browser, your operating system will offer Superset as an option when opening CSV, Excel (`.xls`/`.xlsx`), and Parquet files. Double-clicking or right-clicking a supported file and selecting "Open with Superset" navigates directly to the upload workflow for that file. + +To install Superset as a PWA, look for the install icon in your browser's address bar (Chrome, Edge) when visiting your Superset instance over HTTPS. PWA installation requires HTTPS and a valid manifest — your admin needs to confirm the app manifest is served correctly. + ### Customizing column properties Now that you've registered your dataset, you can configure column properties @@ -234,6 +256,64 @@ For example, when running the local development build, the following will disabl Top Nav and remove the Filter Bar: `http://localhost:8088/superset/dashboard/my-dashboard/?standalone=1&show_filters=0` +### AG Grid Interactive Table + +The **AG Grid Interactive Table** chart type is Superset's fully-featured data grid, suitable for large paginated datasets where the standard Table chart is not enough. + +#### Server-Side Column Filters + +AG Grid supports server-side column filters that query the full dataset — not just the loaded page. Filters are applied before data is sent to the browser, so results are correct even across millions of rows. + +**Available filter types:** + +| Column type | Filter options | +|---|---| +| Text | Contains, equals, starts with, ends with | +| Number | Equals, not equal, less than, greater than, between | +| Date | Before, after, between, blank | +| Set | Select from a list of distinct values | + +**AND / OR logic:** Each column supports combining multiple conditions with AND or OR. Filters from different columns are always combined with AND. + +**Interaction with pagination:** Server-side filters run as WHERE clauses in the underlying SQL query, so pagination always operates over the already-filtered result set. + +#### Time Shift (Time Comparison) + +AG Grid Interactive Table supports **Time Shift** (time comparison), matching the behavior of the standard Table chart. In the **Advanced Analytics** → **Time Comparison** section of the chart configuration, enter a shift expression (e.g., `1 year ago`, `minus 7 days`) to add comparison columns showing values from the offset period. Dashboard-level time range overrides apply to both the base and comparison periods. + +### Dynamic Currency Formatting + +Chart metric values can display currencies dynamically rather than using a fixed currency code. To enable: + +1. Open the dataset editor for your dataset (**Datasets → Edit**). +2. In the **Advanced** tab, set **Currency Code Column** to the name of a column in your dataset that contains ISO 4217 currency codes (e.g., `USD`, `EUR`, `GBP`). +3. In the Explore chart configuration, open the metric's **Number format** section and select **Auto-detect** for currency. + +When Auto-detect is active, each row uses the currency code from the designated column, so a single chart can display values in multiple currencies — each formatted correctly for its currency. + +### ECharts Option Editor + +For ECharts-based chart types (line, bar, area, scatter, pie, and others), Explore includes an advanced **ECharts Option Editor** that accepts raw JSON overrides for the underlying ECharts configuration. + +Access it via the **Customize** tab → **ECharts Options** section at the bottom of the panel. The JSON you enter is deep-merged on top of Superset's generated ECharts config, so you can override specific options without rewriting the entire config. + +**Example:** override the legend position and add a custom title: + +```json +{ + "legend": { "orient": "vertical", "right": "5%", "top": "middle" }, + "title": { "text": "My Custom Title", "left": "center" } +} +``` + +:::caution +ECharts option overrides bypass Superset's validation layer. Invalid option keys are silently ignored by ECharts. Overrides that conflict with Superset-generated options (e.g., `series`) may produce unexpected results. +::: + +### Table Chart: Exporting Filtered Data + +When the **Search Box** is visible in a Table chart, the **Download** action exports only the rows currently visible after the search filter is applied — not the full underlying dataset. This matches the visual output and is intentional. To export the full dataset regardless of search state, use the **Download as CSV** option from the chart's three-dot menu in the dashboard or from the Explore chart toolbar before applying a search filter. + :::resources - [Dashboard Customization](https://docs.preset.io/docs/dashboard-customization) - Advanced dashboard styling and layout options - [Blog: BI Dashboard Best Practices](https://preset.io/blog/bi-dashboard-best-practices/) diff --git a/docs/docs/using-superset/embedding.mdx b/docs/docs/using-superset/embedding.mdx new file mode 100644 index 00000000000..59f561084b6 --- /dev/null +++ b/docs/docs/using-superset/embedding.mdx @@ -0,0 +1,130 @@ +{/* +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you under the Apache License, Version 2.0 (the +"License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. +*/} + +--- +title: Embedding Superset +sidebar_position: 6 +--- + +# Embedding Superset + +Superset dashboards can be embedded directly in host applications using the `@superset-ui/embedded-sdk` package. + +:::info Prerequisites +- The `EMBEDDED_SUPERSET` feature flag must be enabled. +- The embedding domain and allowed origins must be configured by an admin. +::: + +## Quick Start + +Install the SDK: + +```bash +npm install @superset-ui/embedded-sdk +``` + +Embed a dashboard: + +```javascript +import { embedDashboard } from '@superset-ui/embedded-sdk'; + +embedDashboard({ + id: 'dashboard-uuid-here', // from Dashboard → Embed + supersetDomain: 'https://superset.example.com', + mountPoint: document.getElementById('superset-container'), + fetchGuestToken: () => fetchTokenFromYourBackend(), + dashboardUiConfig: { + hideTitle: true, + filters: { expanded: false }, + }, +}); +``` + +`fetchGuestToken` must return a **guest token** obtained from your server by calling Superset's `/api/v1/security/guest_token/` endpoint with a service account. Do not call this endpoint from client-side code. + +--- + +## Callbacks + +### `resolvePermalinkUrl` + +When a user copies a permalink from an embedded dashboard, Superset generates a URL on its own domain. In an embedded context this URL is usually not meaningful to the host application's users — the dashboard is rendered inside the host app, not at the Superset URL. + +The `resolvePermalinkUrl` callback lets the host app intercept permalink generation and return a URL on the host domain instead: + +```javascript +embedDashboard({ + id: 'my-dashboard-uuid', + supersetDomain: 'https://superset.example.com', + mountPoint: document.getElementById('superset-container'), + fetchGuestToken: () => fetchGuestToken(), + /** + * Called when Superset generates a permalink. + * @param {Object} args - { key: string } — the permalink key + * @returns {string | null} - your host URL, or null to use Superset's default + */ + resolvePermalinkUrl: ({ key }) => { + return `https://myapp.example.com/dashboard?permalink=${key}`; + }, +}); +``` + +If the callback returns `null` or is not provided, Superset uses its own permalink URL as a fallback. + +--- + +## Feature Flags for Embedded Mode + +### `DISABLE_EMBEDDED_SUPERSET_LOGOUT` + +Hides the logout button when Superset is embedded in a host application. This is useful when the host application manages the session lifecycle and you do not want users to accidentally log out of the embedded Superset session: + +```python +# superset_config.py +FEATURE_FLAGS = { + "EMBEDDED_SUPERSET": True, + "DISABLE_EMBEDDED_SUPERSET_LOGOUT": True, +} +``` + +When enabled, the **Logout** menu item is removed from the user avatar dropdown in the embedded view. The session can still be invalidated server-side by revoking the guest token. + +### `EMBEDDED_SUPERSET` + +Must be `True` to enable the embedded SDK and the guest token endpoint. Without this flag, `embedDashboard` will fail to load. + +--- + +## URL Parameters + +The following URL parameters can be passed through the `urlParams` option in `dashboardUiConfig` or appended to the embedded iframe URL: + +| Parameter | Values | Effect | +|-----------|--------|--------| +| `standalone` | `0`, `1`, `2`, `3` | `0`: normal; `1`: hide nav; `2`: hide nav + title; `3`: hide nav + title + tabs | +| `show_filters` | `0`, `1` | Show or hide the native filter bar | +| `expand_filters` | `0`, `1` | Start with filter bar expanded or collapsed | + +--- + +## Security Notes + +- **Guest tokens expire** — their lifetime is controlled by the `GUEST_TOKEN_JWT_EXP_SECONDS` config (default: 5 minutes). Refresh tokens before they expire using a token refresh mechanism in your host app. +- **Row-level security** — pass `rls` rules in the guest token request to restrict which rows are visible to the embedded user. +- **Allowed domains** — restrict which host origins can embed a dashboard by setting **Allowed Domains** per-dashboard in the *Embed* settings modal. Superset checks the request's `Referer` header against this list before serving the embedded view; an empty list allows any origin, so configure this explicitly for production. diff --git a/docs/docs/using-superset/exploring-data.mdx b/docs/docs/using-superset/exploring-data.mdx index 0cc812382aa..8d83a0c2c63 100644 --- a/docs/docs/using-superset/exploring-data.mdx +++ b/docs/docs/using-superset/exploring-data.mdx @@ -329,6 +329,27 @@ various options in this section, refer to the Lastly, save your chart as Tutorial Resample and add it to the Tutorial Dashboard. Go to the tutorial dashboard to see the four charts side by side and compare the different outputs. +### SQL Lab Tips + +**Schema and table browser**: The left-side table browser uses a collapsible treeview — click a schema to expand its tables, and click a table to see its columns and sample data inline. This makes navigating large schemas much faster than the previous flat list. + +**Find in editor**: Press **Ctrl+F** (or **Cmd+F** on Mac) to open the Monaco find/replace widget inside the SQL editor without leaving the editor. + +**Resizable panels**: The dividers between the SQL editor, schema browser, and results pane are draggable. Adjust them to match your workflow and screen size. + +**Dialect-aware Format SQL**: The **Format SQL** button applies the SQL dialect of the currently selected database — Trino, Presto, MySQL, PostgreSQL, etc. — rather than a generic formatter. Switch to a different database in the toolbar and re-format to get dialect-specific output. Jinja template syntax (`{{ }}`, `{% %}`) is preserved during formatting and will not cause format errors. + +### Time Range Natural Language Expressions + +The **Custom** time range picker accepts natural language expressions alongside specific dates: + +- **Relative**: `Last 7 days`, `Last month`, `Last quarter`, `Last year` +- **Anchored**: `previous calendar week`, `previous calendar month` +- **"First of" expressions**: `first day of this week`, `first day of this month`, `first day of this quarter`, `first day of this year`, `first week of this year` +- **Offsets**: `30 days ago`, `1 year ago`, `next week` + +These expressions are evaluated at query time, so saved charts always display data relative to the current date. + :::resources - [Chart Walkthroughs](https://docs.preset.io/docs/chart-walkthroughs) - Detailed guides for most chart types - [Blog: Why Apache ECharts is the Future of Apache Superset](https://preset.io/blog/2021-4-1-why-echarts/) diff --git a/docs/docs/using-superset/using-ai-with-superset.mdx b/docs/docs/using-superset/using-ai-with-superset.mdx index 3969042bcd2..67becfd9482 100644 --- a/docs/docs/using-superset/using-ai-with-superset.mdx +++ b/docs/docs/using-superset/using-ai-with-superset.mdx @@ -56,8 +56,8 @@ Ask your AI assistant to browse what's available in your Superset instance: Describe the visualization you want and AI creates it for you: - **Create charts from natural language** -- describe what you want to see and AI picks the right chart type, metrics, and dimensions -- **Preview before saving** -- AI generates a preview so you can review before committing -- **Modify existing charts** -- update filters, change chart types, add metrics +- **Preview before saving** -- `generate_chart` defaults to `save_chart=False`, showing the chart in Explore before it's committed. Ask AI to save once you're satisfied. +- **Modify existing charts** -- `update_chart` also supports preview mode so you can review changes before saving - **Get Explore links** -- open any chart in Superset's Explore view for further refinement **Example prompts:** @@ -195,27 +195,59 @@ Ask your admin for the MCP server URL and any authentication tokens you need. ## Available Tools Reference +### Exploration & Discovery + | Tool | Description | |------|-------------| | `health_check` | Verify the MCP server is running and connected | | `get_instance_info` | Get instance statistics (dataset, chart, dashboard counts) | | `get_schema` | Discover available charts, datasets, and dashboards with schema info | + +### Datasets + +| Tool | Description | +|------|-------------| | `list_datasets` | List datasets with filtering and search | | `get_dataset_info` | Get dataset metadata (columns, metrics, filters) | +| `create_virtual_dataset` | Create a virtual dataset from a SQL query | + +### Charts + +| Tool | Description | +|------|-------------| | `list_charts` | List charts with filtering and search | | `get_chart_info` | Get chart metadata and configuration | | `get_chart_data` | Retrieve chart data (JSON, CSV, or Excel) | | `get_chart_preview` | Generate a chart preview (URL, ASCII, table, or Vega-Lite) | -| `generate_chart` | Create a new chart from a specification | -| `update_chart` | Modify an existing chart's configuration | +| `get_chart_type_schema` | Get the configuration schema for a chart type | +| `generate_chart` | Create a new chart from a specification (defaults to preview mode — review before saving) | +| `update_chart` | Modify an existing chart's configuration (pass `generate_preview=False` to persist immediately instead of returning a preview URL) | | `update_chart_preview` | Update a cached chart preview without saving | +| `generate_explore_link` | Generate an Explore URL for interactive visualization | + +### Dashboards + +| Tool | Description | +|------|-------------| | `list_dashboards` | List dashboards with filtering and search | | `get_dashboard_info` | Get dashboard metadata and layout | | `generate_dashboard` | Create a new dashboard with specified charts | | `add_chart_to_existing_dashboard` | Add a chart to an existing dashboard | + +### SQL + +| Tool | Description | +|------|-------------| | `execute_sql` | Run a SQL query with RBAC enforcement | +| `save_sql_query` | Persist a SQL query to SQL Lab's saved queries | | `open_sql_lab_with_context` | Open SQL Lab with a pre-populated query | -| `generate_explore_link` | Generate an Explore URL for interactive visualization | + +### Databases + +| Tool | Description | +|------|-------------| +| `list_databases` | List configured database connections | +| `get_database_info` | Get details about a specific database connection | --- diff --git a/docs/package.json b/docs/package.json index 6261bc5b1e0..496a26b0b49 100644 --- a/docs/package.json +++ b/docs/package.json @@ -40,13 +40,13 @@ "version:remove:components": "node scripts/manage-versions.mjs remove components" }, "dependencies": { - "@ant-design/icons": "^6.1.0", - "@docusaurus/core": "3.9.2", - "@docusaurus/faster": "^3.9.2", - "@docusaurus/plugin-client-redirects": "3.9.2", - "@docusaurus/preset-classic": "3.9.2", - "@docusaurus/theme-live-codeblock": "^3.9.2", - "@docusaurus/theme-mermaid": "^3.9.2", + "@ant-design/icons": "^6.1.1", + "@docusaurus/core": "^3.10.0", + "@docusaurus/faster": "^3.10.0", + "@docusaurus/plugin-client-redirects": "^3.10.0", + "@docusaurus/preset-classic": "3.10.0", + "@docusaurus/theme-live-codeblock": "^3.10.0", + "@docusaurus/theme-mermaid": "^3.10.0", "@emotion/core": "^11.0.0", "@emotion/react": "^11.13.3", "@emotion/styled": "^11.14.1", @@ -67,12 +67,12 @@ "@storybook/preview-api": "^8.6.18", "@storybook/theming": "^8.6.15", "@superset-ui/core": "^0.20.4", - "@swc/core": "^1.15.17", - "antd": "^6.3.2", - "baseline-browser-mapping": "^2.10.7", - "caniuse-lite": "^1.0.30001780", - "docusaurus-plugin-openapi-docs": "^4.6.0", - "docusaurus-theme-openapi-docs": "^4.6.0", + "@swc/core": "^1.15.30", + "antd": "^6.3.6", + "baseline-browser-mapping": "^2.10.20", + "caniuse-lite": "^1.0.30001790", + "docusaurus-plugin-openapi-docs": "^5.0.1", + "docusaurus-theme-openapi-docs": "^5.0.1", "js-yaml": "^4.1.1", "js-yaml-loader": "^1.2.2", "json-bigint": "^1.0.0", @@ -86,28 +86,28 @@ "remark-import-partial": "^0.0.2", "reselect": "^5.1.1", "storybook": "^8.6.18", - "swagger-ui-react": "^5.32.0", + "swagger-ui-react": "^5.32.4", "swc-loader": "^0.2.7", "tinycolor2": "^1.4.2", "unist-util-visit": "^5.1.0" }, "devDependencies": { - "@docusaurus/module-type-aliases": "^3.9.1", - "@docusaurus/tsconfig": "^3.9.2", + "@docusaurus/module-type-aliases": "^3.10.0", + "@docusaurus/tsconfig": "^3.10.0", "@eslint/js": "^9.39.2", "@types/js-yaml": "^4.0.9", "@types/react": "^19.1.8", "@typescript-eslint/eslint-plugin": "^8.52.0", - "@typescript-eslint/parser": "^8.56.1", + "@typescript-eslint/parser": "^8.59.0", "eslint": "^9.39.2", "eslint-config-prettier": "^10.1.8", "eslint-plugin-prettier": "^5.5.5", "eslint-plugin-react": "^7.37.5", - "globals": "^17.4.0", - "prettier": "^3.8.1", - "typescript": "~5.9.3", - "typescript-eslint": "^8.57.1", - "webpack": "^5.105.4" + "globals": "^17.5.0", + "prettier": "^3.8.3", + "typescript": "~6.0.3", + "typescript-eslint": "^8.59.0", + "webpack": "^5.106.2" }, "browserslist": { "production": [ @@ -124,7 +124,8 @@ "resolutions": { "react-redux": "^9.2.0", "@reduxjs/toolkit": "^2.5.0", - "baseline-browser-mapping": "^2.9.19" + "baseline-browser-mapping": "^2.9.19", + "webpackbar": "^7.0.0" }, "packageManager": "yarn@1.22.22+sha1.ac34549e6aa8e7ead463a7407e1c7390f61a6610" } diff --git a/docs/scripts/fix-openapi-spec.py b/docs/scripts/fix-openapi-spec.py index f2609b38c33..dc850a143ef 100644 --- a/docs/scripts/fix-openapi-spec.py +++ b/docs/scripts/fix-openapi-spec.py @@ -129,6 +129,30 @@ def add_missing_schemas(spec: dict[str, Any]) -> tuple[dict[str, Any], list[str] } fixed.append("DashboardColorsConfigUpdateSchema") + # DashboardChartCustomizationsConfigUpdateSchema (dashboards/schemas.py) + if "DashboardChartCustomizationsConfigUpdateSchema" not in schemas: + schemas["DashboardChartCustomizationsConfigUpdateSchema"] = { + "type": "object", + "properties": { + "deleted": { + "type": "array", + "items": {"type": "string"}, + "description": "List of deleted chart customization IDs.", + }, + "modified": { + "type": "array", + "items": {"type": "object"}, + "description": "List of modified chart customizations.", + }, + "reordered": { + "type": "array", + "items": {"type": "string"}, + "description": "List of chart customization IDs in new order.", + }, + }, + } + fixed.append("DashboardChartCustomizationsConfigUpdateSchema") + # FormatQueryPayloadSchema - based on superset/sqllab/schemas.py if "FormatQueryPayloadSchema" not in schemas: schemas["FormatQueryPayloadSchema"] = { @@ -295,6 +319,7 @@ TAG_DESCRIPTIONS = { "Security Roles": "Manage security roles and their permissions.", "Security Users": "Manage user accounts.", "Tags": "Organize assets with tags.", + "Themes": "Manage UI themes for customizing Superset's appearance.", "User": "User profile and preferences.", } diff --git a/docs/static/feature-flags.json b/docs/static/feature-flags.json index 5d7a86994f1..516115ac0ae 100644 --- a/docs/static/feature-flags.json +++ b/docs/static/feature-flags.json @@ -51,6 +51,12 @@ "lifecycle": "development", "description": "Enable Superset extensions for custom functionality without modifying core" }, + { + "name": "FAB_API_KEY_ENABLED", + "default": false, + "lifecycle": "development", + "description": "Enable API key authentication via FAB SecurityManager When enabled, users can create/manage API keys in the User Info page" + }, { "name": "GRANULAR_EXPORT_CONTROLS", "default": false, diff --git a/docs/static/img/logos/hifadih.png b/docs/static/img/logos/hifadih.png new file mode 100644 index 00000000000..d2923053f99 Binary files /dev/null and b/docs/static/img/logos/hifadih.png differ diff --git a/docs/static/resources/openapi.json b/docs/static/resources/openapi.json index 4fa850bd838..75f9d322656 100644 --- a/docs/static/resources/openapi.json +++ b/docs/static/resources/openapi.json @@ -276,7 +276,8 @@ "", "opacityLow", "opacityMedium", - "opacityHigh" + "opacityHigh", + null ], "nullable": true, "type": "string" @@ -980,15 +981,7 @@ "ChartDataDatasource": { "properties": { "id": { - "description": "Datasource id/uuid", - "oneOf": [ - { - "type": "integer" - }, - { - "type": "string" - } - ] + "description": "Datasource id or uuid" }, "type": { "description": "Datasource type", @@ -1009,6 +1002,14 @@ }, "ChartDataExtras": { "properties": { + "column_order": { + "description": "Ordered list of column names for result ordering. Used to preserve user's column reordering (including mixed dimension columns and metrics)", + "items": { + "type": "string" + }, + "nullable": true, + "type": "array" + }, "having": { "description": "HAVING clause to be added to aggregate queries using AND operator.", "type": "string" @@ -1055,12 +1056,18 @@ "1969-12-28T00:00:00Z/P1W", "1969-12-29T00:00:00Z/P1W", "P1W/1970-01-03T00:00:00Z", - "P1W/1970-01-04T00:00:00Z" + "P1W/1970-01-04T00:00:00Z", + null ], "example": "P1D", "nullable": true, "type": "string" }, + "transpile_to_dialect": { + "description": "If true, WHERE/HAVING clauses will be transpiled to the target database dialect using SQLGlot.", + "nullable": true, + "type": "boolean" + }, "where": { "description": "WHERE clause to be added to queries using AND operator.", "type": "string" @@ -1095,6 +1102,7 @@ "LIKE", "NOT LIKE", "ILIKE", + "NOT ILIKE", "IS NULL", "IS NOT NULL", "IN", @@ -1466,6 +1474,12 @@ "nullable": true, "type": "string" }, + "group_others_when_limit_reached": { + "default": false, + "description": "When true, groups remaining series into an 'Others' category when series limit is reached. Prevents incomplete data.", + "nullable": true, + "type": "boolean" + }, "groupby": { "description": "Columns by which to group the query. This field is deprecated, use `columns` instead.", "items": {}, @@ -1537,7 +1551,8 @@ "samples", "timegrains", "post_processed", - "drill_detail" + "drill_detail", + null ], "nullable": true }, @@ -1669,6 +1684,12 @@ }, "type": "array" }, + "detected_currency": { + "default": null, + "description": "Detected ISO 4217 currency code when AUTO mode is used. Returns the currency code if all filtered data contains a single currency or null if multiple currencies are present.", + "nullable": true, + "type": "string" + }, "error": { "description": "Error", "nullable": true, @@ -1683,8 +1704,14 @@ "description": "Is the result cached", "type": "boolean" }, + "queried_dttm": { + "description": "UTC timestamp when the query was executed (ISO 8601 format)", + "nullable": true, + "type": "string" + }, "query": { - "description": "The executed query statement", + "description": "The executed query statement. May be absent when validation errors occur.", + "nullable": true, "type": "string" }, "rejected_filters": { @@ -1727,7 +1754,7 @@ "cache_timeout", "cached_dttm", "is_cached", - "query" + "queried_dttm" ], "type": "object" }, @@ -1745,125 +1772,12 @@ }, "ChartDataRestApi.get": { "properties": { - "cache_timeout": { - "nullable": true, - "type": "integer" - }, - "certification_details": { - "nullable": true, - "type": "string" - }, - "certified_by": { - "nullable": true, - "type": "string" - }, - "changed_on_delta_humanized": { - "readOnly": true - }, - "dashboards": { - "$ref": "#/components/schemas/ChartDataRestApi.get.Dashboard" - }, - "description": { - "nullable": true, - "type": "string" - }, "id": { "type": "integer" - }, - "is_managed_externally": { - "type": "boolean" - }, - "owners": { - "$ref": "#/components/schemas/ChartDataRestApi.get.User" - }, - "params": { - "nullable": true, - "type": "string" - }, - "query_context": { - "nullable": true, - "type": "string" - }, - "slice_name": { - "maxLength": 250, - "nullable": true, - "type": "string" - }, - "tags": { - "$ref": "#/components/schemas/ChartDataRestApi.get.Tag" - }, - "thumbnail_url": { - "readOnly": true - }, - "url": { - "readOnly": true - }, - "viz_type": { - "maxLength": 250, - "nullable": true, - "type": "string" } }, "type": "object" }, - "ChartDataRestApi.get.Dashboard": { - "properties": { - "dashboard_title": { - "maxLength": 500, - "nullable": true, - "type": "string" - }, - "id": { - "type": "integer" - }, - "json_metadata": { - "nullable": true, - "type": "string" - } - }, - "type": "object" - }, - "ChartDataRestApi.get.Tag": { - "properties": { - "id": { - "type": "integer" - }, - "name": { - "maxLength": 250, - "nullable": true, - "type": "string" - }, - "type": { - "enum": [ - 1, - 2, - 3, - 4 - ] - } - }, - "type": "object" - }, - "ChartDataRestApi.get.User": { - "properties": { - "first_name": { - "maxLength": 64, - "type": "string" - }, - "id": { - "type": "integer" - }, - "last_name": { - "maxLength": 64, - "type": "string" - } - }, - "required": [ - "first_name", - "last_name" - ], - "type": "object" - }, "ChartDataRestApi.get_list": { "properties": { "cache_timeout": { @@ -2202,6 +2116,11 @@ "minLength": 1, "type": "string" }, + "uuid": { + "format": "uuid", + "nullable": true, + "type": "string" + }, "viz_type": { "description": "The type of chart visualization used.", "example": [ @@ -2257,7 +2176,8 @@ "dataset", "query", "saved_query", - "view" + "view", + null ], "nullable": true, "type": "string" @@ -2311,6 +2231,11 @@ }, "type": "array" }, + "uuid": { + "format": "uuid", + "nullable": true, + "type": "string" + }, "viz_type": { "description": "The type of chart visualization used.", "example": [ @@ -2568,127 +2493,97 @@ }, "type": "object" }, - "ChartRestApi.get": { + "ChartGetResponseSchema": { "properties": { "cache_timeout": { - "nullable": true, - "type": "integer" + "type": "string" }, "certification_details": { - "nullable": true, "type": "string" }, "certified_by": { - "nullable": true, "type": "string" }, "changed_on_delta_humanized": { - "readOnly": true + "type": "string" }, "dashboards": { - "$ref": "#/components/schemas/ChartRestApi.get.Dashboard" + "items": { + "$ref": "#/components/schemas/Dashboard" + }, + "type": "array" + }, + "datasource_id": { + "type": "integer" + }, + "datasource_name_text": { + "readOnly": true + }, + "datasource_type": { + "type": "string" + }, + "datasource_url": { + "readOnly": true + }, + "datasource_uuid": { + "format": "uuid", + "type": "string" }, "description": { - "nullable": true, "type": "string" }, "id": { + "description": "The id of the chart.", "type": "integer" }, "is_managed_externally": { "type": "boolean" }, "owners": { - "$ref": "#/components/schemas/ChartRestApi.get.User" + "items": { + "$ref": "#/components/schemas/User" + }, + "type": "array" }, "params": { - "nullable": true, "type": "string" }, "query_context": { - "nullable": true, "type": "string" }, "slice_name": { - "maxLength": 250, - "nullable": true, "type": "string" }, "tags": { - "$ref": "#/components/schemas/ChartRestApi.get.Tag" + "items": { + "$ref": "#/components/schemas/Tag" + }, + "type": "array" }, "thumbnail_url": { - "readOnly": true + "type": "string" }, "url": { - "readOnly": true + "type": "string" + }, + "uuid": { + "format": "uuid", + "type": "string" }, "viz_type": { - "maxLength": 250, - "nullable": true, "type": "string" } }, "type": "object" }, - "ChartRestApi.get.Dashboard": { - "properties": { - "dashboard_title": { - "maxLength": 500, - "nullable": true, - "type": "string" - }, - "id": { - "type": "integer" - }, - "json_metadata": { - "nullable": true, - "type": "string" - } - }, - "type": "object" - }, - "ChartRestApi.get.Tag": { + "ChartRestApi.get": { "properties": { "id": { "type": "integer" - }, - "name": { - "maxLength": 250, - "nullable": true, - "type": "string" - }, - "type": { - "enum": [ - 1, - 2, - 3, - 4 - ] } }, "type": "object" }, - "ChartRestApi.get.User": { - "properties": { - "first_name": { - "maxLength": 64, - "type": "string" - }, - "id": { - "type": "integer" - }, - "last_name": { - "maxLength": 64, - "type": "string" - } - }, - "required": [ - "first_name", - "last_name" - ], - "type": "object" - }, "ChartRestApi.get_list": { "properties": { "cache_timeout": { @@ -3027,6 +2922,11 @@ "minLength": 1, "type": "string" }, + "uuid": { + "format": "uuid", + "nullable": true, + "type": "string" + }, "viz_type": { "description": "The type of chart visualization used.", "example": [ @@ -3082,7 +2982,8 @@ "dataset", "query", "saved_query", - "view" + "view", + null ], "nullable": true, "type": "string" @@ -3136,6 +3037,11 @@ }, "type": "array" }, + "uuid": { + "format": "uuid", + "nullable": true, + "type": "string" + }, "viz_type": { "description": "The type of chart visualization used.", "example": [ @@ -3316,6 +3222,41 @@ }, "type": "object" }, + "CurrentUserPutSchema": { + "properties": { + "first_name": { + "description": "The current user's first name", + "maxLength": 64, + "minLength": 1, + "type": "string" + }, + "last_name": { + "description": "The current user's last name", + "maxLength": 64, + "minLength": 1, + "type": "string" + }, + "password": { + "description": "The current user's password for authentication", + "type": "string" + } + }, + "type": "object" + }, + "Dashboard": { + "properties": { + "dashboard_title": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "json_metadata": { + "type": "string" + } + }, + "type": "object" + }, "DashboardCacheScreenshotResponseSchema": { "properties": { "cache_key": { @@ -3397,6 +3338,9 @@ }, "type": "array" }, + "currency_code_column": { + "type": "string" + }, "database": { "$ref": "#/components/schemas/Database" }, @@ -3525,7 +3469,7 @@ "type": "string" }, "changed_by": { - "$ref": "#/components/schemas/User" + "$ref": "#/components/schemas/User1" }, "changed_by_name": { "type": "string" @@ -3545,7 +3489,7 @@ "type": "array" }, "created_by": { - "$ref": "#/components/schemas/User" + "$ref": "#/components/schemas/User1" }, "created_on_delta_humanized": { "type": "string" @@ -3554,6 +3498,12 @@ "description": "Override CSS for the dashboard.", "type": "string" }, + "custom_tags": { + "items": { + "$ref": "#/components/schemas/Tag1" + }, + "type": "array" + }, "dashboard_title": { "description": "A title for the dashboard.", "type": "string" @@ -3571,7 +3521,7 @@ }, "owners": { "items": { - "$ref": "#/components/schemas/User" + "$ref": "#/components/schemas/User1" }, "type": "array" }, @@ -3593,16 +3543,29 @@ }, "tags": { "items": { - "$ref": "#/components/schemas/Tag" + "$ref": "#/components/schemas/Tag1" }, "type": "array" }, + "theme": { + "allOf": [ + { + "$ref": "#/components/schemas/Theme" + } + ], + "nullable": true + }, "thumbnail_url": { "nullable": true, "type": "string" }, "url": { "type": "string" + }, + "uuid": { + "format": "uuid", + "nullable": true, + "type": "string" } }, "type": "object" @@ -3622,6 +3585,11 @@ "nullable": true, "type": "string" }, + "chartStates": { + "description": "Chart-level state for stateful tables (column order, sorting, filtering)", + "nullable": true, + "type": "object" + }, "dataMask": { "description": "Data mask used for native filter state", "nullable": true, @@ -3880,6 +3848,16 @@ "minLength": 1, "nullable": true, "type": "string" + }, + "theme_id": { + "description": "Theme ID for the dashboard", + "nullable": true, + "type": "integer" + }, + "uuid": { + "format": "uuid", + "nullable": true, + "type": "string" } }, "type": "object" @@ -3961,6 +3939,42 @@ "type": "integer" }, "type": "array" + }, + "theme_id": { + "description": "Theme ID for the dashboard", + "nullable": true, + "type": "integer" + }, + "uuid": { + "format": "uuid", + "nullable": true, + "type": "string" + } + }, + "type": "object" + }, + "DashboardScreenshotPostSchema": { + "properties": { + "activeTabs": { + "description": "A list representing active tabs.", + "items": { + "type": "string" + }, + "type": "array" + }, + "anchor": { + "description": "A string representing the anchor.", + "type": "string" + }, + "dataMask": { + "additionalProperties": {}, + "description": "An object representing the data mask.", + "type": "object" + }, + "urlParams": { + "description": "A list of tuples, each containing two strings.", + "items": {}, + "type": "array" } }, "type": "object" @@ -4333,6 +4347,11 @@ "changed_on_delta_humanized": { "readOnly": true }, + "configuration_method": { + "maxLength": 255, + "nullable": true, + "type": "string" + }, "created_by": { "$ref": "#/components/schemas/DatabaseRestApi.get_list.User1" }, @@ -5067,6 +5086,12 @@ "minLength": 1, "type": "string" }, + "datetime_format": { + "maxLength": 100, + "minLength": 1, + "nullable": true, + "type": "string" + }, "description": { "nullable": true, "type": "string" @@ -5170,6 +5195,21 @@ ], "type": "object" }, + "DatasetMetricCurrencyPut": { + "properties": { + "symbol": { + "maxLength": 128, + "minLength": 1, + "type": "string" + }, + "symbolPosition": { + "maxLength": 128, + "minLength": 1, + "type": "string" + } + }, + "type": "object" + }, "DatasetMetricRestApi.get": { "properties": { "id": { @@ -5205,10 +5245,12 @@ "DatasetMetricsPut": { "properties": { "currency": { - "maxLength": 128, - "minLength": 1, - "nullable": true, - "type": "string" + "allOf": [ + { + "$ref": "#/components/schemas/DatasetMetricCurrencyPut" + } + ], + "nullable": true }, "d3format": { "maxLength": 128, @@ -5342,8 +5384,7 @@ "type": "boolean" }, "cache_timeout": { - "nullable": true, - "type": "integer" + "readOnly": true }, "catalog": { "maxLength": 256, @@ -5378,6 +5419,11 @@ "created_on_humanized": { "readOnly": true }, + "currency_code_column": { + "maxLength": 250, + "nullable": true, + "type": "string" + }, "database": { "$ref": "#/components/schemas/DatasetRestApi.get.Database" }, @@ -5480,6 +5526,11 @@ "url": { "readOnly": true }, + "uuid": { + "format": "uuid", + "nullable": true, + "type": "string" + }, "verbose_map": { "readOnly": true } @@ -5506,6 +5557,11 @@ }, "id": { "type": "integer" + }, + "uuid": { + "format": "uuid", + "nullable": true, + "type": "string" } }, "required": [ @@ -5793,6 +5849,11 @@ }, "id": { "type": "integer" + }, + "uuid": { + "format": "uuid", + "nullable": true, + "type": "string" } }, "required": [ @@ -5887,6 +5948,15 @@ "maxLength": 250, "minLength": 1, "type": "string" + }, + "template_params": { + "nullable": true, + "type": "string" + }, + "uuid": { + "format": "uuid", + "nullable": true, + "type": "string" } }, "required": [ @@ -5917,6 +5987,12 @@ }, "type": "array" }, + "currency_code_column": { + "maxLength": 250, + "minLength": 0, + "nullable": true, + "type": "string" + }, "database_id": { "type": "integer" }, @@ -6003,6 +6079,11 @@ "template_params": { "nullable": true, "type": "string" + }, + "uuid": { + "format": "uuid", + "nullable": true, + "type": "string" } }, "type": "object" @@ -6090,7 +6171,7 @@ "type": "array" }, "changed_by": { - "$ref": "#/components/schemas/User1" + "$ref": "#/components/schemas/User2" }, "changed_on": { "format": "date-time", @@ -6214,10 +6295,6 @@ "nullable": true, "type": "boolean" }, - "json": { - "nullable": true, - "type": "boolean" - }, "queryLimit": { "nullable": true, "type": "integer" @@ -6404,6 +6481,31 @@ ], "type": "object" }, + "FormatQueryPayloadSchema": { + "properties": { + "database_id": { + "description": "The database id", + "nullable": true, + "type": "integer" + }, + "engine": { + "nullable": true, + "type": "string" + }, + "sql": { + "type": "string" + }, + "template_params": { + "description": "The SQL query template params as JSON string", + "nullable": true, + "type": "string" + } + }, + "required": [ + "sql" + ], + "type": "object" + }, "GetFavStarIdsSchema": { "properties": { "result": { @@ -6459,6 +6561,290 @@ ], "type": "object" }, + "GroupApi.get": { + "properties": { + "description": { + "maxLength": 512, + "nullable": true, + "type": "string" + }, + "id": { + "type": "integer" + }, + "label": { + "maxLength": 150, + "nullable": true, + "type": "string" + }, + "name": { + "maxLength": 100, + "type": "string" + }, + "roles": { + "$ref": "#/components/schemas/GroupApi.get.Role" + }, + "users": { + "$ref": "#/components/schemas/GroupApi.get.User" + } + }, + "required": [ + "name" + ], + "type": "object" + }, + "GroupApi.get.Role": { + "properties": { + "id": { + "type": "integer" + }, + "name": { + "maxLength": 64, + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object" + }, + "GroupApi.get.User": { + "properties": { + "id": { + "type": "integer" + }, + "username": { + "maxLength": 128, + "type": "string" + } + }, + "required": [ + "username" + ], + "type": "object" + }, + "GroupApi.get_list": { + "properties": { + "description": { + "maxLength": 512, + "nullable": true, + "type": "string" + }, + "id": { + "type": "integer" + }, + "label": { + "maxLength": 150, + "nullable": true, + "type": "string" + }, + "name": { + "maxLength": 100, + "type": "string" + }, + "roles": { + "$ref": "#/components/schemas/GroupApi.get_list.Role" + }, + "users": { + "$ref": "#/components/schemas/GroupApi.get_list.User" + } + }, + "required": [ + "name" + ], + "type": "object" + }, + "GroupApi.get_list.Role": { + "properties": { + "id": { + "type": "integer" + }, + "name": { + "maxLength": 64, + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object" + }, + "GroupApi.get_list.User": { + "properties": { + "id": { + "type": "integer" + }, + "username": { + "maxLength": 128, + "type": "string" + } + }, + "required": [ + "username" + ], + "type": "object" + }, + "GroupApi.post": { + "properties": { + "description": { + "description": "Group description", + "maxLength": 512, + "minLength": 0, + "nullable": true, + "type": "string" + }, + "label": { + "description": "Group label", + "maxLength": 150, + "minLength": 0, + "nullable": true, + "type": "string" + }, + "name": { + "description": "Group name", + "maxLength": 100, + "minLength": 1, + "type": "string" + }, + "roles": { + "description": "Group roles", + "items": { + "type": "integer" + }, + "type": "array" + }, + "users": { + "description": "Group users", + "items": { + "type": "integer" + }, + "type": "array" + } + }, + "required": [ + "name" + ], + "type": "object" + }, + "GroupApi.put": { + "properties": { + "description": { + "description": "Group description", + "maxLength": 512, + "minLength": 0, + "nullable": true, + "type": "string" + }, + "label": { + "description": "Group label", + "maxLength": 150, + "minLength": 0, + "nullable": true, + "type": "string" + }, + "name": { + "description": "Group name", + "maxLength": 100, + "minLength": 1, + "type": "string" + }, + "roles": { + "description": "Group roles", + "items": { + "type": "integer" + }, + "type": "array" + }, + "users": { + "description": "Group users", + "items": { + "type": "integer" + }, + "type": "array" + } + }, + "type": "object" + }, + "GroupPostSchema": { + "properties": { + "description": { + "description": "Group description", + "maxLength": 512, + "minLength": 0, + "nullable": true, + "type": "string" + }, + "label": { + "description": "Group label", + "maxLength": 150, + "minLength": 0, + "nullable": true, + "type": "string" + }, + "name": { + "description": "Group name", + "maxLength": 100, + "minLength": 1, + "type": "string" + }, + "roles": { + "description": "Group roles", + "items": { + "type": "integer" + }, + "type": "array" + }, + "users": { + "description": "Group users", + "items": { + "type": "integer" + }, + "type": "array" + } + }, + "required": [ + "name" + ], + "type": "object" + }, + "GroupPutSchema": { + "properties": { + "description": { + "description": "Group description", + "maxLength": 512, + "minLength": 0, + "nullable": true, + "type": "string" + }, + "label": { + "description": "Group label", + "maxLength": 150, + "minLength": 0, + "nullable": true, + "type": "string" + }, + "name": { + "description": "Group name", + "maxLength": 100, + "minLength": 1, + "type": "string" + }, + "roles": { + "description": "Group roles", + "items": { + "type": "integer" + }, + "type": "array" + }, + "users": { + "description": "Group users", + "items": { + "type": "integer" + }, + "type": "array" + } + }, + "type": "object" + }, "GuestTokenCreate": { "properties": { "resources": { @@ -6474,7 +6860,7 @@ "type": "array" }, "user": { - "$ref": "#/components/schemas/User2" + "$ref": "#/components/schemas/User3" } }, "required": [ @@ -6504,6 +6890,15 @@ "nullable": true, "type": "integer" }, + "configuration_method": { + "default": "sqlalchemy_form", + "enum": [ + "sqlalchemy_form", + "dynamic_form", + null + ], + "nullable": true + }, "database_name": { "type": "string" }, @@ -6593,6 +6988,9 @@ "additionalProperties": {}, "type": "object" }, + "per_user_caching": { + "type": "boolean" + }, "schema_options": { "additionalProperties": {}, "type": "object" @@ -6646,21 +7044,28 @@ "user": { "$ref": "#/components/schemas/LogRestApi.get.User" }, - "user_id": { - "nullable": true, - "type": "integer" - } + "user_id": {} }, "type": "object" }, "LogRestApi.get.User": { "properties": { - "username": { + "first_name": { "maxLength": 64, "type": "string" + }, + "last_name": { + "maxLength": 64, + "type": "string" + }, + "username": { + "maxLength": 128, + "type": "string" } }, "required": [ + "first_name", + "last_name", "username" ], "type": "object" @@ -6701,21 +7106,28 @@ "user": { "$ref": "#/components/schemas/LogRestApi.get_list.User" }, - "user_id": { - "nullable": true, - "type": "integer" - } + "user_id": {} }, "type": "object" }, "LogRestApi.get_list.User": { "properties": { - "username": { + "first_name": { "maxLength": 64, "type": "string" + }, + "last_name": { + "maxLength": 64, + "type": "string" + }, + "username": { + "maxLength": 128, + "type": "string" } }, "required": [ + "first_name", + "last_name", "username" ], "type": "object" @@ -6882,27 +7294,15 @@ }, "PermissionViewMenuApi.post": { "properties": { - "permission_id": { - "nullable": true, - "type": "integer" - }, - "view_menu_id": { - "nullable": true, - "type": "integer" - } + "permission_id": {}, + "view_menu_id": {} }, "type": "object" }, "PermissionViewMenuApi.put": { "properties": { - "permission_id": { - "nullable": true, - "type": "integer" - }, - "view_menu_id": { - "nullable": true, - "type": "integer" - } + "permission_id": {}, + "view_menu_id": {} }, "type": "object" }, @@ -7112,7 +7512,7 @@ "type": "string" }, "user": { - "$ref": "#/components/schemas/User" + "$ref": "#/components/schemas/User1" } }, "type": "object" @@ -7272,7 +7672,7 @@ "RLSRestApi.get_list": { "properties": { "changed_by": { - "$ref": "#/components/schemas/User" + "$ref": "#/components/schemas/User1" }, "changed_on_delta_humanized": { "readOnly": true @@ -7644,7 +8044,8 @@ "enum": [ "Email", "Slack", - "SlackV2" + "SlackV2", + "Webhook" ], "type": "string" } @@ -7896,10 +8297,7 @@ "changed_on_delta_humanized": { "readOnly": true }, - "chart_id": { - "nullable": true, - "type": "integer" - }, + "chart_id": {}, "created_by": { "$ref": "#/components/schemas/ReportScheduleRestApi.get_list.User1" }, @@ -7920,10 +8318,7 @@ "crontab_humanized": { "readOnly": true }, - "dashboard_id": { - "nullable": true, - "type": "integer" - }, + "dashboard_id": {}, "description": { "nullable": true, "type": "string" @@ -8255,6 +8650,7 @@ "America/Coral_Harbour", "America/Cordoba", "America/Costa_Rica", + "America/Coyhaique", "America/Creston", "America/Cuiaba", "America/Curacao", @@ -8807,7 +9203,8 @@ "enum": [ "charts", "dashboards", - "alerts_reports" + "alerts_reports", + null ], "nullable": true }, @@ -8996,6 +9393,7 @@ "America/Coral_Harbour", "America/Cordoba", "America/Costa_Rica", + "America/Coyhaique", "America/Creston", "America/Cuiaba", "America/Curacao", @@ -9511,7 +9909,8 @@ "description": "Determines when to trigger alert based off value from alert query. Alerts will be triggered with these validator types:\n- Not Null - When the return value is Not NULL, Empty, or 0\n- Operator - When `sql_return_value comparison_operator threshold` is True e.g. `50 <= 75`
Supports the comparison operators <, <=, >, >=, ==, and !=", "enum": [ "not null", - "operator" + "operator", + null ], "nullable": true, "type": "string" @@ -9557,6 +9956,21 @@ ], "type": "object" }, + "RoleGroupPutSchema": { + "properties": { + "group_ids": { + "description": "List of group ids", + "items": { + "type": "integer" + }, + "type": "array" + } + }, + "required": [ + "group_ids" + ], + "type": "object" + }, "RolePermissionListSchema": { "properties": { "id": { @@ -9831,10 +10245,7 @@ "database": { "$ref": "#/components/schemas/SavedQueryRestApi.get_list.Database" }, - "db_id": { - "nullable": true, - "type": "integer" - }, + "db_id": {}, "description": { "nullable": true, "type": "string" @@ -9958,10 +10369,7 @@ "nullable": true, "type": "string" }, - "db_id": { - "nullable": true, - "type": "integer" - }, + "db_id": {}, "description": { "nullable": true, "type": "string" @@ -9998,10 +10406,7 @@ "nullable": true, "type": "string" }, - "db_id": { - "nullable": true, - "type": "integer" - }, + "db_id": {}, "description": { "nullable": true, "type": "string" @@ -10265,6 +10670,9 @@ "maxLength": 64, "type": "string" }, + "groups": { + "$ref": "#/components/schemas/SupersetUserApi.get.Group" + }, "id": { "type": "integer" }, @@ -10285,7 +10693,7 @@ "$ref": "#/components/schemas/SupersetUserApi.get.Role" }, "username": { - "maxLength": 64, + "maxLength": 128, "type": "string" } }, @@ -10297,6 +10705,31 @@ ], "type": "object" }, + "SupersetUserApi.get.Group": { + "properties": { + "description": { + "maxLength": 512, + "nullable": true, + "type": "string" + }, + "id": { + "type": "integer" + }, + "label": { + "maxLength": 150, + "nullable": true, + "type": "string" + }, + "name": { + "maxLength": 100, + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object" + }, "SupersetUserApi.get.Role": { "properties": { "id": { @@ -10362,6 +10795,9 @@ "maxLength": 64, "type": "string" }, + "groups": { + "$ref": "#/components/schemas/SupersetUserApi.get_list.Group" + }, "id": { "type": "integer" }, @@ -10382,7 +10818,7 @@ "$ref": "#/components/schemas/SupersetUserApi.get_list.Role" }, "username": { - "maxLength": 64, + "maxLength": 128, "type": "string" } }, @@ -10394,6 +10830,31 @@ ], "type": "object" }, + "SupersetUserApi.get_list.Group": { + "properties": { + "description": { + "maxLength": 512, + "nullable": true, + "type": "string" + }, + "id": { + "type": "integer" + }, + "label": { + "maxLength": 150, + "nullable": true, + "type": "string" + }, + "name": { + "maxLength": 100, + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object" + }, "SupersetUserApi.get_list.Role": { "properties": { "id": { @@ -10439,6 +10900,13 @@ "description": "The user's first name", "type": "string" }, + "groups": { + "description": "The user's roles", + "items": { + "type": "integer" + }, + "type": "array" + }, "last_name": { "description": "The user's last name", "type": "string" @@ -10452,7 +10920,6 @@ "items": { "type": "integer" }, - "minItems": 1, "type": "array" }, "username": { @@ -10467,7 +10934,6 @@ "first_name", "last_name", "password", - "roles", "username" ], "type": "object" @@ -10486,6 +10952,13 @@ "description": "The user's first name", "type": "string" }, + "groups": { + "description": "The user's roles", + "items": { + "type": "integer" + }, + "type": "array" + }, "last_name": { "description": "The user's last name", "type": "string" @@ -10499,7 +10972,6 @@ "items": { "type": "integer" }, - "minItems": 1, "type": "array" }, "username": { @@ -10817,6 +11289,25 @@ }, "type": "object" }, + "Tag1": { + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "type": { + "enum": [ + 1, + 2, + 3, + 4 + ] + } + }, + "type": "object" + }, "TagGetResponseSchema": { "properties": { "id": { @@ -10939,63 +11430,18 @@ }, "TagRestApi.get.User1": { "properties": { - "active": { - "nullable": true, - "type": "boolean" - }, - "changed_on": { - "format": "date-time", - "nullable": true, - "type": "string" - }, - "created_on": { - "format": "date-time", - "nullable": true, - "type": "string" - }, - "email": { - "maxLength": 320, - "type": "string" - }, - "fail_login_count": { - "nullable": true, - "type": "integer" - }, "first_name": { "maxLength": 64, "type": "string" }, - "id": { - "type": "integer" - }, - "last_login": { - "format": "date-time", - "nullable": true, - "type": "string" - }, "last_name": { "maxLength": 64, "type": "string" - }, - "login_count": { - "nullable": true, - "type": "integer" - }, - "password": { - "maxLength": 256, - "nullable": true, - "type": "string" - }, - "username": { - "maxLength": 64, - "type": "string" } }, "required": [ - "email", "first_name", - "last_name", - "username" + "last_name" ], "type": "object" }, @@ -11113,7 +11559,7 @@ "type": "string" }, "created_by": { - "$ref": "#/components/schemas/User" + "$ref": "#/components/schemas/User1" }, "creator": { "type": "string" @@ -11126,7 +11572,7 @@ }, "owners": { "items": { - "$ref": "#/components/schemas/User1" + "$ref": "#/components/schemas/User2" }, "type": "array" }, @@ -11169,6 +11615,218 @@ ], "type": "object" }, + "Theme": { + "properties": { + "id": { + "type": "integer" + }, + "json_data": { + "type": "string" + }, + "theme_name": { + "type": "string" + } + }, + "type": "object" + }, + "ThemeRestApi.get": { + "properties": { + "changed_by": { + "$ref": "#/components/schemas/ThemeRestApi.get.User" + }, + "changed_on_delta_humanized": { + "readOnly": true + }, + "created_by": { + "$ref": "#/components/schemas/ThemeRestApi.get.User1" + }, + "id": { + "type": "integer" + }, + "is_system": { + "type": "boolean" + }, + "is_system_dark": { + "type": "boolean" + }, + "is_system_default": { + "type": "boolean" + }, + "json_data": { + "nullable": true, + "type": "string" + }, + "theme_name": { + "maxLength": 250, + "nullable": true, + "type": "string" + }, + "uuid": { + "format": "uuid", + "nullable": true, + "type": "string" + } + }, + "type": "object" + }, + "ThemeRestApi.get.User": { + "properties": { + "first_name": { + "maxLength": 64, + "type": "string" + }, + "id": { + "type": "integer" + }, + "last_name": { + "maxLength": 64, + "type": "string" + } + }, + "required": [ + "first_name", + "last_name" + ], + "type": "object" + }, + "ThemeRestApi.get.User1": { + "properties": { + "first_name": { + "maxLength": 64, + "type": "string" + }, + "id": { + "type": "integer" + }, + "last_name": { + "maxLength": 64, + "type": "string" + } + }, + "required": [ + "first_name", + "last_name" + ], + "type": "object" + }, + "ThemeRestApi.get_list": { + "properties": { + "changed_by": { + "$ref": "#/components/schemas/ThemeRestApi.get_list.User" + }, + "changed_by_name": { + "readOnly": true + }, + "changed_on_delta_humanized": { + "readOnly": true + }, + "created_by": { + "$ref": "#/components/schemas/ThemeRestApi.get_list.User1" + }, + "created_on": { + "format": "date-time", + "nullable": true, + "type": "string" + }, + "id": { + "type": "integer" + }, + "is_system": { + "type": "boolean" + }, + "is_system_dark": { + "type": "boolean" + }, + "is_system_default": { + "type": "boolean" + }, + "json_data": { + "nullable": true, + "type": "string" + }, + "theme_name": { + "maxLength": 250, + "nullable": true, + "type": "string" + }, + "uuid": { + "format": "uuid", + "nullable": true, + "type": "string" + } + }, + "type": "object" + }, + "ThemeRestApi.get_list.User": { + "properties": { + "first_name": { + "maxLength": 64, + "type": "string" + }, + "id": { + "type": "integer" + }, + "last_name": { + "maxLength": 64, + "type": "string" + } + }, + "required": [ + "first_name", + "last_name" + ], + "type": "object" + }, + "ThemeRestApi.get_list.User1": { + "properties": { + "first_name": { + "maxLength": 64, + "type": "string" + }, + "id": { + "type": "integer" + }, + "last_name": { + "maxLength": 64, + "type": "string" + } + }, + "required": [ + "first_name", + "last_name" + ], + "type": "object" + }, + "ThemeRestApi.post": { + "properties": { + "json_data": { + "type": "string" + }, + "theme_name": { + "type": "string" + } + }, + "required": [ + "json_data", + "theme_name" + ], + "type": "object" + }, + "ThemeRestApi.put": { + "properties": { + "json_data": { + "type": "string" + }, + "theme_name": { + "type": "string" + } + }, + "required": [ + "json_data", + "theme_name" + ], + "type": "object" + }, "UploadFileMetadata": { "properties": { "items": { @@ -11368,9 +12026,6 @@ }, "last_name": { "type": "string" - }, - "username": { - "type": "string" } }, "type": "object" @@ -11380,6 +12035,9 @@ "first_name": { "type": "string" }, + "id": { + "type": "integer" + }, "last_name": { "type": "string" }, @@ -11389,6 +12047,84 @@ }, "type": "object" }, + "User3": { + "properties": { + "first_name": { + "type": "string" + }, + "last_name": { + "type": "string" + }, + "username": { + "type": "string" + } + }, + "type": "object" + }, + "UserRegistrationsRestAPI.get": { + "properties": { + "id": { + "type": "integer" + } + }, + "type": "object" + }, + "UserRegistrationsRestAPI.get_list": { + "properties": { + "email": { + "maxLength": 320, + "type": "string" + }, + "first_name": { + "maxLength": 64, + "type": "string" + }, + "id": { + "type": "integer" + }, + "last_name": { + "maxLength": 64, + "type": "string" + }, + "registration_date": { + "format": "date-time", + "nullable": true, + "type": "string" + }, + "registration_hash": { + "maxLength": 256, + "nullable": true, + "type": "string" + }, + "username": { + "maxLength": 128, + "type": "string" + } + }, + "required": [ + "email", + "first_name", + "last_name", + "username" + ], + "type": "object" + }, + "UserRegistrationsRestAPI.post": { + "properties": { + "id": { + "type": "integer" + } + }, + "type": "object" + }, + "UserRegistrationsRestAPI.put": { + "properties": { + "id": { + "type": "integer" + } + }, + "type": "object" + }, "UserResponseSchema": { "properties": { "email": { @@ -11409,6 +12145,9 @@ "last_name": { "type": "string" }, + "login_count": { + "type": "integer" + }, "username": { "type": "string" } @@ -11714,6 +12453,19 @@ "type": "boolean" }, { + "items": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "string" + }, + { + "type": "boolean" + } + ] + }, "type": "array" } ] @@ -11857,39 +12609,6 @@ }, "type": "object" }, - "DashboardScreenshotPostSchema": { - "type": "object", - "properties": { - "dataMask": { - "type": "object", - "description": "An object representing the data mask.", - "additionalProperties": true - }, - "activeTabs": { - "type": "array", - "items": { - "type": "string" - }, - "description": "A list representing active tabs." - }, - "anchor": { - "type": "string", - "description": "A string representing the anchor." - }, - "urlParams": { - "type": "array", - "items": { - "type": "array", - "items": { - "type": "string" - }, - "minItems": 2, - "maxItems": 2 - }, - "description": "A list of tuples, each containing two strings." - } - } - }, "DashboardNativeFiltersConfigUpdateSchema": { "type": "object", "properties": { @@ -11959,33 +12678,6 @@ } } }, - "FormatQueryPayloadSchema": { - "type": "object", - "required": [ - "sql" - ], - "properties": { - "sql": { - "type": "string", - "description": "The SQL query to format." - }, - "engine": { - "type": "string", - "nullable": true, - "description": "The database engine." - }, - "database_id": { - "type": "integer", - "nullable": true, - "description": "The database id." - }, - "template_params": { - "type": "string", - "nullable": true, - "description": "The SQL query template params as JSON string." - } - } - }, "get_slack_channels_schema": { "type": "object", "properties": { @@ -12009,6 +12701,32 @@ "description": "Whether to match channel names exactly." } } + }, + "DashboardChartCustomizationsConfigUpdateSchema": { + "type": "object", + "properties": { + "deleted": { + "type": "array", + "items": { + "type": "string" + }, + "description": "List of deleted chart customization IDs." + }, + "modified": { + "type": "array", + "items": { + "type": "object" + }, + "description": "List of modified chart customization configurations." + }, + "reordered": { + "type": "array", + "items": { + "type": "string" + }, + "description": "List of chart customization IDs in new order." + } + } } }, "securitySchemes": { @@ -14185,6 +14903,7 @@ "query_context": "string", "query_context_generation": true, "slice_name": "string", + "uuid": "550e8400-e29b-41d4-a716-446655440000", "viz_type": [ "bar", "area", @@ -14229,6 +14948,7 @@ "query_context": "string", "query_context_generation": true, "slice_name": "string", + "uuid": "550e8400-e29b-41d4-a716-446655440000", "viz_type": [ "bar", "area", @@ -14956,6 +15676,103 @@ ] } }, + "/api/v1/chart/{id_or_uuid}": { + "get": { + "description": "Get a chart", + "parameters": [ + { + "description": "Either the id of the chart, or its uuid", + "in": "path", + "name": "id_or_uuid", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "properties": { + "result": { + "$ref": "#/components/schemas/ChartGetResponseSchema" + } + }, + "type": "object" + }, + "example": { + "result": { + "cache_timeout": "string", + "certification_details": "string", + "certified_by": "string", + "changed_on_delta_humanized": "string", + "dashboards": [], + "datasource_id": 1, + "datasource_name_text": {}, + "datasource_type": "string", + "datasource_url": {}, + "datasource_uuid": "550e8400-e29b-41d4-a716-446655440000", + "description": "string", + "id": 1, + "is_managed_externally": true, + "owners": [], + "params": "string", + "query_context": "string", + "slice_name": "string", + "tags": [], + "thumbnail_url": "string", + "url": "string", + "uuid": "550e8400-e29b-41d4-a716-446655440000", + "viz_type": "string" + } + } + } + }, + "description": "Chart" + }, + "302": { + "description": "Redirects to the current digest" + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "404": { + "$ref": "#/components/responses/404" + } + }, + "security": [ + { + "jwt": [] + } + ], + "summary": "Get a chart detail information", + "tags": [ + "Charts" + ], + "x-codeSamples": [ + { + "lang": "cURL", + "label": "cURL", + "source": "curl -X GET \"http://localhost:8088/api/v1/chart/{id_or_uuid}\" \\\n -H \"Authorization: Bearer $ACCESS_TOKEN\"" + }, + { + "lang": "Python", + "label": "Python", + "source": "import requests\n\nresponse = requests.get(\n \"http://localhost:8088/api/v1/chart/{id_or_uuid}\",\n headers={\"Authorization\": \"Bearer \" + access_token}\n)\nprint(response.json())" + }, + { + "lang": "JavaScript", + "label": "JavaScript", + "source": "const response = await fetch(\n \"http://localhost:8088/api/v1/chart/{id_or_uuid}\",\n {\n headers: {\n \"Authorization\": `Bearer ${accessToken}`\n }\n }\n);\nconst data = await response.json();\nconsole.log(data);" + } + ] + } + }, "/api/v1/chart/{pk}": { "delete": { "parameters": [ @@ -15030,152 +15847,6 @@ } ] }, - "get": { - "description": "Get an item model", - "parameters": [ - { - "in": "path", - "name": "pk", - "required": true, - "schema": { - "type": "integer" - } - }, - { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/get_item_schema" - } - } - }, - "in": "query", - "name": "q" - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "properties": { - "description_columns": { - "properties": { - "column_name": { - "description": "The description for the column name. Will be translated by babel", - "example": "A Nice description for the column", - "type": "string" - } - }, - "type": "object" - }, - "id": { - "description": "The item id", - "type": "string" - }, - "label_columns": { - "properties": { - "column_name": { - "description": "The label for the column name. Will be translated by babel", - "example": "A Nice label for the column", - "type": "string" - } - }, - "type": "object" - }, - "result": { - "$ref": "#/components/schemas/ChartRestApi.get" - }, - "show_columns": { - "description": "A list of columns", - "items": { - "type": "string" - }, - "type": "array" - }, - "show_title": { - "description": "A title to render. Will be translated by babel", - "example": "Show Item Details", - "type": "string" - } - }, - "type": "object" - }, - "example": { - "description_columns": { - "column_name": "A Nice description for the column" - }, - "id": "string", - "label_columns": { - "column_name": "A Nice label for the column" - }, - "result": { - "cache_timeout": 1, - "certification_details": "string", - "certified_by": "string", - "changed_on_delta_humanized": {}, - "description": "string", - "id": 1, - "is_managed_externally": true, - "params": "string", - "query_context": "string", - "slice_name": "string", - "thumbnail_url": {}, - "url": {}, - "viz_type": "string" - }, - "show_columns": [ - "string" - ], - "show_title": "Show Item Details" - } - } - }, - "description": "Item from Model" - }, - "400": { - "$ref": "#/components/responses/400" - }, - "401": { - "$ref": "#/components/responses/401" - }, - "404": { - "$ref": "#/components/responses/404" - }, - "422": { - "$ref": "#/components/responses/422" - }, - "500": { - "$ref": "#/components/responses/500" - } - }, - "security": [ - { - "jwt": [] - } - ], - "summary": "Get a chart detail information", - "tags": [ - "Charts" - ], - "x-codeSamples": [ - { - "lang": "cURL", - "label": "cURL", - "source": "curl -X GET \"http://localhost:8088/api/v1/chart/1\" \\\n -H \"Authorization: Bearer $ACCESS_TOKEN\"" - }, - { - "lang": "Python", - "label": "Python", - "source": "import requests\n\nresponse = requests.get(\n \"http://localhost:8088/api/v1/chart/1\",\n headers={\"Authorization\": \"Bearer \" + access_token}\n)\nprint(response.json())" - }, - { - "lang": "JavaScript", - "label": "JavaScript", - "source": "const response = await fetch(\n \"http://localhost:8088/api/v1/chart/1\",\n {\n headers: {\n \"Authorization\": `Bearer ${accessToken}`\n }\n }\n);\nconst data = await response.json();\nconsole.log(data);" - } - ] - }, "put": { "parameters": [ { @@ -15215,6 +15886,7 @@ "tags": [ 1 ], + "uuid": "550e8400-e29b-41d4-a716-446655440000", "viz_type": [ "bar", "area", @@ -15259,6 +15931,7 @@ "query_context_generation": true, "slice_name": "string", "tags": [], + "uuid": "550e8400-e29b-41d4-a716-446655440000", "viz_type": [ "bar", "area", @@ -16705,59 +17378,17 @@ "schema": { "properties": { "count": { - "description": "The total record count on the backend", - "type": "number" - }, - "description_columns": { - "properties": { - "column_name": { - "description": "The description for the column name. Will be translated by babel", - "example": "A Nice description for the column", - "type": "string" - } - }, - "type": "object" + "type": "integer" }, "ids": { - "description": "A list of item ids, useful when you don't know the column id", "items": { - "type": "string" - }, - "type": "array" - }, - "label_columns": { - "properties": { - "column_name": { - "description": "The label for the column name. Will be translated by babel", - "example": "A Nice label for the column", - "type": "string" - } - }, - "type": "object" - }, - "list_columns": { - "description": "A list of columns", - "items": { - "type": "string" - }, - "type": "array" - }, - "list_title": { - "description": "A title to render. Will be translated by babel", - "example": "List Items", - "type": "string" - }, - "order_columns": { - "description": "A list of allowed columns to sort", - "items": { - "type": "string" + "type": "integer" }, "type": "array" }, "result": { - "description": "The result from the get list query", "items": { - "$ref": "#/components/schemas/DashboardRestApi.get_list" + "type": "object" }, "type": "array" } @@ -16765,22 +17396,9 @@ "type": "object" }, "example": { - "count": 1.0, - "description_columns": { - "column_name": "A Nice description for the column" - }, + "count": 1, "ids": [ - "string" - ], - "label_columns": { - "column_name": "A Nice label for the column" - }, - "list_columns": [ - "string" - ], - "list_title": "List Items", - "order_columns": [ - "string" + 1 ], "result": [ {} @@ -16788,7 +17406,7 @@ } } }, - "description": "Items from Model" + "description": "Dashboards" }, "400": { "$ref": "#/components/responses/400" @@ -16853,7 +17471,9 @@ "roles": [ 1 ], - "slug": "string" + "slug": "string", + "theme_id": 1, + "uuid": "550e8400-e29b-41d4-a716-446655440000" } } }, @@ -16889,7 +17509,9 @@ "position_json": "string", "published": true, "roles": [], - "slug": "string" + "slug": "string", + "theme_id": 1, + "uuid": "550e8400-e29b-41d4-a716-446655440000" } } } @@ -17483,6 +18105,7 @@ "charts": [], "created_on_delta_humanized": "string", "css": "string", + "custom_tags": [], "dashboard_title": "string", "id": 1, "is_managed_externally": true, @@ -17493,8 +18116,10 @@ "roles": [], "slug": "string", "tags": [], + "theme": {}, "thumbnail_url": "string", - "url": "string" + "url": "string", + "uuid": "550e8400-e29b-41d4-a716-446655440000" } } } @@ -18292,7 +18917,9 @@ "slug": "string", "tags": [ 1 - ] + ], + "theme_id": 1, + "uuid": "550e8400-e29b-41d4-a716-446655440000" } } }, @@ -18333,7 +18960,9 @@ "published": true, "roles": [], "slug": "string", - "tags": [] + "tags": [], + "theme_id": 1, + "uuid": "550e8400-e29b-41d4-a716-446655440000" } } } @@ -18406,18 +19035,12 @@ "$ref": "#/components/schemas/DashboardScreenshotPostSchema" }, "example": { - "dataMask": { - "key": "value" - }, "activeTabs": [ "string" ], "anchor": "string", - "urlParams": [ - [ - "string" - ] - ] + "dataMask": {}, + "urlParams": [] } } } @@ -18481,6 +19104,106 @@ ] } }, + "/api/v1/dashboard/{pk}/chart_customizations": { + "put": { + "parameters": [ + { + "in": "path", + "name": "pk", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DashboardChartCustomizationsConfigUpdateSchema" + }, + "example": { + "deleted": [ + "string" + ], + "modified": [ + {} + ], + "reordered": [ + "string" + ] + } + } + }, + "description": "Chart customizations configuration", + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "properties": { + "result": { + "type": "array" + } + }, + "type": "object" + }, + "example": { + "result": [] + } + } + }, + "description": "Dashboard chart customizations updated" + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "404": { + "$ref": "#/components/responses/404" + }, + "422": { + "$ref": "#/components/responses/422" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [ + { + "jwt": [] + } + ], + "summary": "Update chart customizations configuration for a dashboard.", + "tags": [ + "Dashboards" + ], + "x-codeSamples": [ + { + "lang": "cURL", + "label": "cURL", + "source": "curl -X PUT \"http://localhost:8088/api/v1/dashboard/1/chart_customizations\" \\\n -H \"Authorization: Bearer $ACCESS_TOKEN\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\"key\": \"value\"}'" + }, + { + "lang": "Python", + "label": "Python", + "source": "import requests\n\nresponse = requests.put(\n \"http://localhost:8088/api/v1/dashboard/1/chart_customizations\",\n headers={\"Authorization\": \"Bearer \" + access_token},\n json={\"key\": \"value\"}\n)\nprint(response.json())" + }, + { + "lang": "JavaScript", + "label": "JavaScript", + "source": "const response = await fetch(\n \"http://localhost:8088/api/v1/dashboard/1/chart_customizations\",\n {\n method: \"PUT\",\n headers: {\n \"Authorization\": `Bearer ${accessToken}`,\n \"Content-Type\": \"application/json\"\n },\n body: JSON.stringify({ key: \"value\" })\n }\n);\nconst data = await response.json();\nconsole.log(data);" + } + ] + } + }, "/api/v1/dashboard/{pk}/colors": { "put": { "parameters": [ @@ -18594,6 +19317,90 @@ ] } }, + "/api/v1/dashboard/{pk}/export_as_example/": { + "get": { + "description": "Exports a dashboard with its charts and datasets in the example format used by the Superset example loading system. The export includes Parquet data files and YAML configuration files.", + "parameters": [ + { + "description": "The dashboard id", + "in": "path", + "name": "pk", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "description": "Whether to include Parquet data files", + "in": "query", + "name": "export_data", + "schema": { + "default": true, + "type": "boolean" + } + }, + { + "description": "Limit data export to this many rows per dataset", + "in": "query", + "name": "sample_rows", + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "content": { + "application/zip": { + "schema": { + "format": "binary", + "type": "string" + } + } + }, + "description": "Example bundle ZIP file" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "404": { + "$ref": "#/components/responses/404" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [ + { + "jwt": [] + } + ], + "summary": "Export dashboard as example bundle", + "tags": [ + "Dashboards" + ], + "x-codeSamples": [ + { + "lang": "cURL", + "label": "cURL", + "source": "curl -X GET \"http://localhost:8088/api/v1/dashboard/1/export_as_example/\" \\\n -H \"Authorization: Bearer $ACCESS_TOKEN\"" + }, + { + "lang": "Python", + "label": "Python", + "source": "import requests\n\nresponse = requests.get(\n \"http://localhost:8088/api/v1/dashboard/1/export_as_example/\",\n headers={\"Authorization\": \"Bearer \" + access_token}\n)\nprint(response.json())" + }, + { + "lang": "JavaScript", + "label": "JavaScript", + "source": "const response = await fetch(\n \"http://localhost:8088/api/v1/dashboard/1/export_as_example/\",\n {\n headers: {\n \"Authorization\": `Bearer ${accessToken}`\n }\n }\n);\nconst data = await response.json();\nconsole.log(data);" + } + ] + } + }, "/api/v1/dashboard/{pk}/favorites/": { "delete": { "parameters": [ @@ -19422,6 +20229,7 @@ "string" ], "anchor": "string", + "chartStates": {}, "dataMask": {}, "urlParams": [ {} @@ -21652,81 +22460,6 @@ ] } }, - "/api/v1/database/{pk}/ssh_tunnel/": { - "delete": { - "parameters": [ - { - "in": "path", - "name": "pk", - "required": true, - "schema": { - "type": "integer" - } - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "properties": { - "message": { - "type": "string" - } - }, - "type": "object" - }, - "example": { - "message": "string" - } - } - }, - "description": "SSH Tunnel deleted" - }, - "401": { - "$ref": "#/components/responses/401" - }, - "403": { - "$ref": "#/components/responses/403" - }, - "404": { - "$ref": "#/components/responses/404" - }, - "422": { - "$ref": "#/components/responses/422" - }, - "500": { - "$ref": "#/components/responses/500" - } - }, - "security": [ - { - "jwt": [] - } - ], - "summary": "Delete a SSH tunnel", - "tags": [ - "Database" - ], - "x-codeSamples": [ - { - "lang": "cURL", - "label": "cURL", - "source": "curl -X DELETE \"http://localhost:8088/api/v1/database/1/ssh_tunnel/\" \\\n -H \"Authorization: Bearer $ACCESS_TOKEN\"" - }, - { - "lang": "Python", - "label": "Python", - "source": "import requests\n\nresponse = requests.delete(\n \"http://localhost:8088/api/v1/database/1/ssh_tunnel/\",\n headers={\"Authorization\": \"Bearer \" + access_token}\n)\nprint(response.status_code)" - }, - { - "lang": "JavaScript", - "label": "JavaScript", - "source": "const response = await fetch(\n \"http://localhost:8088/api/v1/database/1/ssh_tunnel/\",\n {\n method: \"DELETE\",\n headers: {\n \"Authorization\": `Bearer ${accessToken}`\n }\n }\n);\nconsole.log(response.status);" - } - ] - } - }, "/api/v1/database/{pk}/sync_permissions/": { "post": { "parameters": [ @@ -22696,7 +23429,9 @@ ], "schema": "string", "sql": "string", - "table_name": "string" + "table_name": "string", + "template_params": "string", + "uuid": "550e8400-e29b-41d4-a716-446655440000" } } }, @@ -22730,7 +23465,9 @@ "owners": [], "schema": "string", "sql": "string", - "table_name": "string" + "table_name": "string", + "template_params": "string", + "uuid": "550e8400-e29b-41d4-a716-446655440000" } } } @@ -23476,6 +24213,207 @@ ] } }, + "/api/v1/dataset/{id_or_uuid}": { + "get": { + "description": "Get a dataset by ID", + "parameters": [ + { + "description": "Either the id of the dataset, or its uuid", + "in": "path", + "name": "id_or_uuid", + "required": true, + "schema": { + "type": "string" + } + }, + { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/get_item_schema" + } + } + }, + "in": "query", + "name": "q" + }, + { + "description": "Should Jinja macros from sql, metrics and columns be rendered and included in the response", + "in": "query", + "name": "include_rendered_sql", + "schema": { + "type": "boolean" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "properties": { + "id": { + "description": "The item id", + "type": "string" + }, + "result": { + "$ref": "#/components/schemas/DatasetRestApi.get" + } + }, + "type": "object" + }, + "example": { + "id": "string", + "result": { + "always_filter_main_dttm": true, + "cache_timeout": {}, + "catalog": "string", + "changed_on": "2024-01-15T10:30:00Z", + "changed_on_humanized": {}, + "column_formats": {}, + "created_on": "2024-01-15T10:30:00Z", + "created_on_humanized": {}, + "currency_code_column": "string", + "datasource_name": {}, + "datasource_type": {}, + "default_endpoint": "string", + "description": "string", + "extra": "string", + "fetch_values_predicate": "string", + "filter_select_enabled": true, + "folders": {}, + "granularity_sqla": {}, + "id": 1, + "is_managed_externally": true, + "is_sqllab_view": true, + "kind": {}, + "main_dttm_col": "string", + "name": {}, + "normalize_columns": true, + "offset": 1, + "order_by_choices": {}, + "schema": "string", + "select_star": {}, + "sql": "string", + "table_name": "string", + "template_params": "string", + "time_grain_sqla": {}, + "uid": {}, + "url": {}, + "uuid": "550e8400-e29b-41d4-a716-446655440000", + "verbose_map": {} + } + } + } + }, + "description": "Dataset object has been returned." + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "422": { + "$ref": "#/components/responses/422" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [ + { + "jwt": [] + } + ], + "summary": "Get a dataset", + "tags": [ + "Datasets" + ], + "x-codeSamples": [ + { + "lang": "cURL", + "label": "cURL", + "source": "curl -X GET \"http://localhost:8088/api/v1/dataset/{id_or_uuid}\" \\\n -H \"Authorization: Bearer $ACCESS_TOKEN\"" + }, + { + "lang": "Python", + "label": "Python", + "source": "import requests\n\nresponse = requests.get(\n \"http://localhost:8088/api/v1/dataset/{id_or_uuid}\",\n headers={\"Authorization\": \"Bearer \" + access_token}\n)\nprint(response.json())" + }, + { + "lang": "JavaScript", + "label": "JavaScript", + "source": "const response = await fetch(\n \"http://localhost:8088/api/v1/dataset/{id_or_uuid}\",\n {\n headers: {\n \"Authorization\": `Bearer ${accessToken}`\n }\n }\n);\nconst data = await response.json();\nconsole.log(data);" + } + ] + } + }, + "/api/v1/dataset/{id_or_uuid}/related_objects": { + "get": { + "parameters": [ + { + "in": "path", + "name": "id_or_uuid", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DatasetRelatedObjectsResponse" + }, + "example": { + "charts": {}, + "dashboards": {} + } + } + }, + "description": "Query result" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "404": { + "$ref": "#/components/responses/404" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [ + { + "jwt": [] + } + ], + "summary": "Get charts and dashboards count associated to a dataset", + "tags": [ + "Datasets" + ], + "x-codeSamples": [ + { + "lang": "cURL", + "label": "cURL", + "source": "curl -X GET \"http://localhost:8088/api/v1/dataset/{id_or_uuid}/related_objects\" \\\n -H \"Authorization: Bearer $ACCESS_TOKEN\"" + }, + { + "lang": "Python", + "label": "Python", + "source": "import requests\n\nresponse = requests.get(\n \"http://localhost:8088/api/v1/dataset/{id_or_uuid}/related_objects\",\n headers={\"Authorization\": \"Bearer \" + access_token}\n)\nprint(response.json())" + }, + { + "lang": "JavaScript", + "label": "JavaScript", + "source": "const response = await fetch(\n \"http://localhost:8088/api/v1/dataset/{id_or_uuid}/related_objects\",\n {\n headers: {\n \"Authorization\": `Bearer ${accessToken}`\n }\n }\n);\nconst data = await response.json();\nconsole.log(data);" + } + ] + } + }, "/api/v1/dataset/{pk}": { "delete": { "parameters": [ @@ -23550,138 +24488,6 @@ } ] }, - "get": { - "description": "Get a dataset by ID", - "parameters": [ - { - "description": "The dataset ID", - "in": "path", - "name": "pk", - "required": true, - "schema": { - "type": "integer" - } - }, - { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/get_item_schema" - } - } - }, - "in": "query", - "name": "q" - }, - { - "description": "Should Jinja macros from sql, metrics and columns be rendered and included in the response", - "in": "query", - "name": "include_rendered_sql", - "schema": { - "type": "boolean" - } - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "properties": { - "id": { - "description": "The item id", - "type": "string" - }, - "result": { - "$ref": "#/components/schemas/DatasetRestApi.get" - } - }, - "type": "object" - }, - "example": { - "id": "string", - "result": { - "always_filter_main_dttm": true, - "cache_timeout": 1, - "catalog": "string", - "changed_on": "2024-01-15T10:30:00Z", - "changed_on_humanized": {}, - "column_formats": {}, - "created_on": "2024-01-15T10:30:00Z", - "created_on_humanized": {}, - "datasource_name": {}, - "datasource_type": {}, - "default_endpoint": "string", - "description": "string", - "extra": "string", - "fetch_values_predicate": "string", - "filter_select_enabled": true, - "folders": {}, - "granularity_sqla": {}, - "id": 1, - "is_managed_externally": true, - "is_sqllab_view": true, - "kind": {}, - "main_dttm_col": "string", - "name": {}, - "normalize_columns": true, - "offset": 1, - "order_by_choices": {}, - "schema": "string", - "select_star": {}, - "sql": "string", - "table_name": "string", - "template_params": "string", - "time_grain_sqla": {}, - "uid": {}, - "url": {}, - "verbose_map": {} - } - } - } - }, - "description": "Dataset object has been returned." - }, - "400": { - "$ref": "#/components/responses/400" - }, - "401": { - "$ref": "#/components/responses/401" - }, - "422": { - "$ref": "#/components/responses/422" - }, - "500": { - "$ref": "#/components/responses/500" - } - }, - "security": [ - { - "jwt": [] - } - ], - "summary": "Get a dataset", - "tags": [ - "Datasets" - ], - "x-codeSamples": [ - { - "lang": "cURL", - "label": "cURL", - "source": "curl -X GET \"http://localhost:8088/api/v1/dataset/1\" \\\n -H \"Authorization: Bearer $ACCESS_TOKEN\"" - }, - { - "lang": "Python", - "label": "Python", - "source": "import requests\n\nresponse = requests.get(\n \"http://localhost:8088/api/v1/dataset/1\",\n headers={\"Authorization\": \"Bearer \" + access_token}\n)\nprint(response.json())" - }, - { - "lang": "JavaScript", - "label": "JavaScript", - "source": "const response = await fetch(\n \"http://localhost:8088/api/v1/dataset/1\",\n {\n headers: {\n \"Authorization\": `Bearer ${accessToken}`\n }\n }\n);\nconst data = await response.json();\nconsole.log(data);" - } - ] - }, "put": { "parameters": [ { @@ -23713,6 +24519,7 @@ "columns": [ {} ], + "currency_code_column": "string", "database_id": 1, "default_endpoint": "string", "description": "string", @@ -23737,7 +24544,8 @@ "schema": "string", "sql": "string", "table_name": "string", - "template_params": "string" + "template_params": "string", + "uuid": "550e8400-e29b-41d4-a716-446655440000" } } }, @@ -23766,6 +24574,7 @@ "cache_timeout": 1, "catalog": "string", "columns": [], + "currency_code_column": "string", "database_id": 1, "default_endpoint": "string", "description": "string", @@ -23784,7 +24593,8 @@ "schema": "string", "sql": "string", "table_name": "string", - "template_params": "string" + "template_params": "string", + "uuid": "550e8400-e29b-41d4-a716-446655440000" } } } @@ -23923,6 +24733,82 @@ ] } }, + "/api/v1/dataset/{pk}/drill_info/": { + "get": { + "parameters": [ + { + "description": "The dataset ID", + "in": "path", + "name": "pk", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "properties": { + "result": { + "type": "object" + } + }, + "type": "object" + }, + "example": { + "result": {} + } + } + }, + "description": "Dataset drill info" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "404": { + "$ref": "#/components/responses/404" + }, + "422": { + "$ref": "#/components/responses/422" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [ + { + "jwt": [] + } + ], + "summary": "Get dataset drill info", + "tags": [ + "Datasets" + ], + "x-codeSamples": [ + { + "lang": "cURL", + "label": "cURL", + "source": "curl -X GET \"http://localhost:8088/api/v1/dataset/1/drill_info/\" \\\n -H \"Authorization: Bearer $ACCESS_TOKEN\"" + }, + { + "lang": "Python", + "label": "Python", + "source": "import requests\n\nresponse = requests.get(\n \"http://localhost:8088/api/v1/dataset/1/drill_info/\",\n headers={\"Authorization\": \"Bearer \" + access_token}\n)\nprint(response.json())" + }, + { + "lang": "JavaScript", + "label": "JavaScript", + "source": "const response = await fetch(\n \"http://localhost:8088/api/v1/dataset/1/drill_info/\",\n {\n headers: {\n \"Authorization\": `Bearer ${accessToken}`\n }\n }\n);\nconst data = await response.json();\nconsole.log(data);" + } + ] + } + }, "/api/v1/dataset/{pk}/metric/{metric_id}": { "delete": { "parameters": [ @@ -24083,71 +24969,6 @@ ] } }, - "/api/v1/dataset/{pk}/related_objects": { - "get": { - "parameters": [ - { - "in": "path", - "name": "pk", - "required": true, - "schema": { - "type": "integer" - } - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/DatasetRelatedObjectsResponse" - }, - "example": { - "charts": {}, - "dashboards": {} - } - } - }, - "description": "Query result" - }, - "401": { - "$ref": "#/components/responses/401" - }, - "404": { - "$ref": "#/components/responses/404" - }, - "500": { - "$ref": "#/components/responses/500" - } - }, - "security": [ - { - "jwt": [] - } - ], - "summary": "Get charts and dashboards count associated to a dataset", - "tags": [ - "Datasets" - ], - "x-codeSamples": [ - { - "lang": "cURL", - "label": "cURL", - "source": "curl -X GET \"http://localhost:8088/api/v1/dataset/1/related_objects\" \\\n -H \"Authorization: Bearer $ACCESS_TOKEN\"" - }, - { - "lang": "Python", - "label": "Python", - "source": "import requests\n\nresponse = requests.get(\n \"http://localhost:8088/api/v1/dataset/1/related_objects\",\n headers={\"Authorization\": \"Bearer \" + access_token}\n)\nprint(response.json())" - }, - { - "lang": "JavaScript", - "label": "JavaScript", - "source": "const response = await fetch(\n \"http://localhost:8088/api/v1/dataset/1/related_objects\",\n {\n headers: {\n \"Authorization\": `Bearer ${accessToken}`\n }\n }\n);\nconst data = await response.json();\nconsole.log(data);" - } - ] - } - }, "/api/v1/datasource/{datasource_type}/{datasource_id}/column/{column_name}/values/": { "get": { "parameters": [ @@ -24263,6 +25084,159 @@ ] } }, + "/api/v1/datasource/{datasource_type}/{datasource_id}/validate_expression/": { + "post": { + "parameters": [ + { + "description": "The type of datasource", + "in": "path", + "name": "datasource_type", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "The id of the datasource", + "in": "path", + "name": "datasource_id", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "properties": { + "clause": { + "description": "SQL clause type for filter expressions", + "enum": [ + "WHERE", + "HAVING" + ], + "type": "string" + }, + "expression": { + "description": "The SQL expression to validate", + "type": "string" + }, + "expression_type": { + "default": "where", + "description": "The type of SQL expression", + "enum": [ + "column", + "metric", + "where", + "having" + ], + "type": "string" + } + }, + "required": [ + "expression" + ], + "type": "object" + }, + "example": { + "clause": "WHERE", + "expression": "string", + "expression_type": "column" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "properties": { + "result": { + "description": "Empty array for success, errors for failure", + "items": { + "properties": { + "end_column": { + "type": "integer" + }, + "line_number": { + "type": "integer" + }, + "message": { + "type": "string" + }, + "start_column": { + "type": "integer" + } + }, + "type": "object" + }, + "type": "array" + } + }, + "type": "object" + }, + "example": { + "result": [ + { + "end_column": 1, + "line_number": 1, + "message": "string", + "start_column": 1 + } + ] + } + } + }, + "description": "Validation result" + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "404": { + "$ref": "#/components/responses/404" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [ + { + "jwt": [] + } + ], + "summary": "Validate a SQL expression against a datasource", + "tags": [ + "Datasources" + ], + "x-codeSamples": [ + { + "lang": "cURL", + "label": "cURL", + "source": "curl -X POST \"http://localhost:8088/api/v1/datasource/{datasource_type}/{datasource_id}/validate_expression/\" \\\n -H \"Authorization: Bearer $ACCESS_TOKEN\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\"key\": \"value\"}'" + }, + { + "lang": "Python", + "label": "Python", + "source": "import requests\n\nresponse = requests.post(\n \"http://localhost:8088/api/v1/datasource/{datasource_type}/{datasource_id}/validate_expression/\",\n headers={\"Authorization\": \"Bearer \" + access_token},\n json={\"key\": \"value\"}\n)\nprint(response.json())" + }, + { + "lang": "JavaScript", + "label": "JavaScript", + "source": "const response = await fetch(\n \"http://localhost:8088/api/v1/datasource/{datasource_type}/{datasource_id}/validate_expression/\",\n {\n method: \"POST\",\n headers: {\n \"Authorization\": `Bearer ${accessToken}`,\n \"Content-Type\": \"application/json\"\n },\n body: JSON.stringify({ key: \"value\" })\n }\n);\nconst data = await response.json();\nconsole.log(data);" + } + ] + } + }, "/api/v1/embedded_dashboard/{uuid}": { "get": { "parameters": [ @@ -25377,7 +26351,7 @@ "json": "string", "referrer": "string", "slice_id": 1, - "user_id": 1 + "user_id": {} }, "show_columns": [ "string" @@ -25455,6 +26429,7 @@ "is_active": true, "is_anonymous": true, "last_name": "string", + "login_count": 1, "username": "string" } } @@ -25466,6 +26441,11 @@ "$ref": "#/components/responses/401" } }, + "security": [ + { + "jwt": [] + } + ], "summary": "Get the user object", "tags": [ "Current User" @@ -25487,6 +26467,85 @@ "source": "const response = await fetch(\n \"http://localhost:8088/api/v1/me/\",\n {\n headers: {\n \"Authorization\": `Bearer ${accessToken}`\n }\n }\n);\nconst data = await response.json();\nconsole.log(data);" } ] + }, + "put": { + "description": "Updates the current user's first name, last name, or password.", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CurrentUserPutSchema" + }, + "example": { + "first_name": "string", + "last_name": "string", + "password": "string" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "properties": { + "result": { + "$ref": "#/components/schemas/UserResponseSchema" + } + }, + "type": "object" + }, + "example": { + "result": { + "email": "string", + "first_name": "string", + "id": 1, + "is_active": true, + "is_anonymous": true, + "last_name": "string", + "login_count": 1, + "username": "string" + } + } + } + }, + "description": "User updated successfully" + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + } + }, + "security": [ + { + "jwt": [] + } + ], + "summary": "Update the current user", + "tags": [ + "Current User" + ], + "x-codeSamples": [ + { + "lang": "cURL", + "label": "cURL", + "source": "curl -X PUT \"http://localhost:8088/api/v1/me/\" \\\n -H \"Authorization: Bearer $ACCESS_TOKEN\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\"key\": \"value\"}'" + }, + { + "lang": "Python", + "label": "Python", + "source": "import requests\n\nresponse = requests.put(\n \"http://localhost:8088/api/v1/me/\",\n headers={\"Authorization\": \"Bearer \" + access_token},\n json={\"key\": \"value\"}\n)\nprint(response.json())" + }, + { + "lang": "JavaScript", + "label": "JavaScript", + "source": "const response = await fetch(\n \"http://localhost:8088/api/v1/me/\",\n {\n method: \"PUT\",\n headers: {\n \"Authorization\": `Bearer ${accessToken}`,\n \"Content-Type\": \"application/json\"\n },\n body: JSON.stringify({ key: \"value\" })\n }\n);\nconst data = await response.json();\nconsole.log(data);" + } + ] } }, "/api/v1/me/roles/": { @@ -25512,6 +26571,7 @@ "is_active": true, "is_anonymous": true, "last_name": "string", + "login_count": 1, "username": "string" } } @@ -25523,6 +26583,11 @@ "$ref": "#/components/responses/401" } }, + "security": [ + { + "jwt": [] + } + ], "summary": "Get the user roles", "tags": [ "Current User" @@ -28582,7 +29647,7 @@ }, "example": { "catalog": "string", - "db_id": 1, + "db_id": {}, "description": "string", "extra_json": "string", "label": "string", @@ -28614,7 +29679,7 @@ "id": "string", "result": { "catalog": "string", - "db_id": 1, + "db_id": {}, "description": "string", "extra_json": "string", "label": "string", @@ -29339,7 +30404,7 @@ }, "example": { "catalog": "string", - "db_id": 1, + "db_id": {}, "description": "string", "extra_json": "string", "label": "string", @@ -29367,7 +30432,7 @@ "example": { "result": { "catalog": "string", - "db_id": 1, + "db_id": {}, "description": "string", "extra_json": "string", "label": "string", @@ -29480,6 +30545,672 @@ ] } }, + "/api/v1/security/groups/": { + "get": { + "description": "Get a list of models", + "parameters": [ + { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/get_list_schema" + } + } + }, + "in": "query", + "name": "q" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "properties": { + "count": { + "description": "The total record count on the backend", + "type": "number" + }, + "description_columns": { + "properties": { + "column_name": { + "description": "The description for the column name. Will be translated by babel", + "example": "A Nice description for the column", + "type": "string" + } + }, + "type": "object" + }, + "ids": { + "description": "A list of item ids, useful when you don't know the column id", + "items": { + "type": "string" + }, + "type": "array" + }, + "label_columns": { + "properties": { + "column_name": { + "description": "The label for the column name. Will be translated by babel", + "example": "A Nice label for the column", + "type": "string" + } + }, + "type": "object" + }, + "list_columns": { + "description": "A list of columns", + "items": { + "type": "string" + }, + "type": "array" + }, + "list_title": { + "description": "A title to render. Will be translated by babel", + "example": "List Items", + "type": "string" + }, + "order_columns": { + "description": "A list of allowed columns to sort", + "items": { + "type": "string" + }, + "type": "array" + }, + "result": { + "description": "The result from the get list query", + "items": { + "$ref": "#/components/schemas/GroupApi.get_list" + }, + "type": "array" + } + }, + "type": "object" + }, + "example": { + "count": 1.0, + "description_columns": { + "column_name": "A Nice description for the column" + }, + "ids": [ + "string" + ], + "label_columns": { + "column_name": "A Nice label for the column" + }, + "list_columns": [ + "string" + ], + "list_title": "List Items", + "order_columns": [ + "string" + ], + "result": [ + {} + ] + } + } + }, + "description": "Items from Model" + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "422": { + "$ref": "#/components/responses/422" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [ + { + "jwt": [] + } + ], + "tags": [ + "Security Groups" + ], + "operationId": "get_security_groups", + "summary": "Get security groups", + "x-codeSamples": [ + { + "lang": "cURL", + "label": "cURL", + "source": "curl -X GET \"http://localhost:8088/api/v1/security/groups/\" \\\n -H \"Authorization: Bearer $ACCESS_TOKEN\"" + }, + { + "lang": "Python", + "label": "Python", + "source": "import requests\n\nresponse = requests.get(\n \"http://localhost:8088/api/v1/security/groups/\",\n headers={\"Authorization\": \"Bearer \" + access_token}\n)\nprint(response.json())" + }, + { + "lang": "JavaScript", + "label": "JavaScript", + "source": "const response = await fetch(\n \"http://localhost:8088/api/v1/security/groups/\",\n {\n headers: {\n \"Authorization\": `Bearer ${accessToken}`\n }\n }\n);\nconst data = await response.json();\nconsole.log(data);" + } + ] + }, + "post": { + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GroupPostSchema" + }, + "example": { + "description": "string", + "label": "string", + "name": "string", + "roles": [ + 1 + ], + "users": [ + 1 + ] + } + } + }, + "description": "Model schema", + "required": true + }, + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "properties": { + "result": { + "$ref": "#/components/schemas/GroupPostSchema" + } + }, + "type": "object" + }, + "example": { + "result": { + "description": "string", + "label": "string", + "name": "string", + "roles": [], + "users": [] + } + } + } + }, + "description": "Group created" + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "422": { + "$ref": "#/components/responses/422" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [ + { + "jwt": [] + } + ], + "tags": [ + "Security Groups" + ], + "operationId": "create_security_groups", + "summary": "Create security groups", + "x-codeSamples": [ + { + "lang": "cURL", + "label": "cURL", + "source": "curl -X POST \"http://localhost:8088/api/v1/security/groups/\" \\\n -H \"Authorization: Bearer $ACCESS_TOKEN\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\"key\": \"value\"}'" + }, + { + "lang": "Python", + "label": "Python", + "source": "import requests\n\nresponse = requests.post(\n \"http://localhost:8088/api/v1/security/groups/\",\n headers={\"Authorization\": \"Bearer \" + access_token},\n json={\"key\": \"value\"}\n)\nprint(response.json())" + }, + { + "lang": "JavaScript", + "label": "JavaScript", + "source": "const response = await fetch(\n \"http://localhost:8088/api/v1/security/groups/\",\n {\n method: \"POST\",\n headers: {\n \"Authorization\": `Bearer ${accessToken}`,\n \"Content-Type\": \"application/json\"\n },\n body: JSON.stringify({ key: \"value\" })\n }\n);\nconst data = await response.json();\nconsole.log(data);" + } + ] + } + }, + "/api/v1/security/groups/_info": { + "get": { + "description": "Get metadata information about this API resource", + "parameters": [ + { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/get_info_schema" + } + } + }, + "in": "query", + "name": "q" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "properties": { + "add_columns": { + "type": "object" + }, + "edit_columns": { + "type": "object" + }, + "filters": { + "properties": { + "column_name": { + "items": { + "properties": { + "name": { + "description": "The filter name. Will be translated by babel", + "type": "string" + }, + "operator": { + "description": "The filter operation key to use on list filters", + "type": "string" + } + }, + "type": "object" + }, + "type": "array" + } + }, + "type": "object" + }, + "permissions": { + "description": "The user permissions for this API resource", + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "example": { + "add_columns": {}, + "edit_columns": {}, + "filters": { + "column_name": [ + {} + ] + }, + "permissions": [ + "string" + ] + } + } + }, + "description": "Item from Model" + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "422": { + "$ref": "#/components/responses/422" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [ + { + "jwt": [] + } + ], + "tags": [ + "Security Groups" + ], + "operationId": "get_security_groups__info", + "summary": "Get security groups info", + "x-codeSamples": [ + { + "lang": "cURL", + "label": "cURL", + "source": "curl -X GET \"http://localhost:8088/api/v1/security/groups/_info\" \\\n -H \"Authorization: Bearer $ACCESS_TOKEN\"" + }, + { + "lang": "Python", + "label": "Python", + "source": "import requests\n\nresponse = requests.get(\n \"http://localhost:8088/api/v1/security/groups/_info\",\n headers={\"Authorization\": \"Bearer \" + access_token}\n)\nprint(response.json())" + }, + { + "lang": "JavaScript", + "label": "JavaScript", + "source": "const response = await fetch(\n \"http://localhost:8088/api/v1/security/groups/_info\",\n {\n headers: {\n \"Authorization\": `Bearer ${accessToken}`\n }\n }\n);\nconst data = await response.json();\nconsole.log(data);" + } + ] + } + }, + "/api/v1/security/groups/{pk}": { + "delete": { + "parameters": [ + { + "in": "path", + "name": "pk", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "properties": { + "message": { + "type": "string" + } + }, + "type": "object" + }, + "example": { + "message": "string" + } + } + }, + "description": "Item deleted" + }, + "404": { + "$ref": "#/components/responses/404" + }, + "422": { + "$ref": "#/components/responses/422" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [ + { + "jwt": [] + } + ], + "tags": [ + "Security Groups" + ], + "operationId": "delete_security_groups_by_pk", + "summary": "Delete security groups by pk", + "x-codeSamples": [ + { + "lang": "cURL", + "label": "cURL", + "source": "curl -X DELETE \"http://localhost:8088/api/v1/security/groups/1\" \\\n -H \"Authorization: Bearer $ACCESS_TOKEN\"" + }, + { + "lang": "Python", + "label": "Python", + "source": "import requests\n\nresponse = requests.delete(\n \"http://localhost:8088/api/v1/security/groups/1\",\n headers={\"Authorization\": \"Bearer \" + access_token}\n)\nprint(response.status_code)" + }, + { + "lang": "JavaScript", + "label": "JavaScript", + "source": "const response = await fetch(\n \"http://localhost:8088/api/v1/security/groups/1\",\n {\n method: \"DELETE\",\n headers: {\n \"Authorization\": `Bearer ${accessToken}`\n }\n }\n);\nconsole.log(response.status);" + } + ] + }, + "get": { + "description": "Get an item model", + "parameters": [ + { + "in": "path", + "name": "pk", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/get_item_schema" + } + } + }, + "in": "query", + "name": "q" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "properties": { + "description_columns": { + "properties": { + "column_name": { + "description": "The description for the column name. Will be translated by babel", + "example": "A Nice description for the column", + "type": "string" + } + }, + "type": "object" + }, + "id": { + "description": "The item id", + "type": "string" + }, + "label_columns": { + "properties": { + "column_name": { + "description": "The label for the column name. Will be translated by babel", + "example": "A Nice label for the column", + "type": "string" + } + }, + "type": "object" + }, + "result": { + "$ref": "#/components/schemas/GroupApi.get" + }, + "show_columns": { + "description": "A list of columns", + "items": { + "type": "string" + }, + "type": "array" + }, + "show_title": { + "description": "A title to render. Will be translated by babel", + "example": "Show Item Details", + "type": "string" + } + }, + "type": "object" + }, + "example": { + "description_columns": { + "column_name": "A Nice description for the column" + }, + "id": "string", + "label_columns": { + "column_name": "A Nice label for the column" + }, + "result": { + "description": "string", + "id": 1, + "label": "string", + "name": "string" + }, + "show_columns": [ + "string" + ], + "show_title": "Show Item Details" + } + } + }, + "description": "Item from Model" + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "404": { + "$ref": "#/components/responses/404" + }, + "422": { + "$ref": "#/components/responses/422" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [ + { + "jwt": [] + } + ], + "tags": [ + "Security Groups" + ], + "operationId": "get_security_groups_by_pk", + "summary": "Get security groups by pk", + "x-codeSamples": [ + { + "lang": "cURL", + "label": "cURL", + "source": "curl -X GET \"http://localhost:8088/api/v1/security/groups/1\" \\\n -H \"Authorization: Bearer $ACCESS_TOKEN\"" + }, + { + "lang": "Python", + "label": "Python", + "source": "import requests\n\nresponse = requests.get(\n \"http://localhost:8088/api/v1/security/groups/1\",\n headers={\"Authorization\": \"Bearer \" + access_token}\n)\nprint(response.json())" + }, + { + "lang": "JavaScript", + "label": "JavaScript", + "source": "const response = await fetch(\n \"http://localhost:8088/api/v1/security/groups/1\",\n {\n headers: {\n \"Authorization\": `Bearer ${accessToken}`\n }\n }\n);\nconst data = await response.json();\nconsole.log(data);" + } + ] + }, + "put": { + "parameters": [ + { + "in": "path", + "name": "pk", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GroupPutSchema" + }, + "example": { + "description": "string", + "label": "string", + "name": "string", + "roles": [ + 1 + ], + "users": [ + 1 + ] + } + } + }, + "description": "Model schema", + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "properties": { + "result": { + "$ref": "#/components/schemas/GroupPutSchema" + } + }, + "type": "object" + }, + "example": { + "result": { + "description": "string", + "label": "string", + "name": "string", + "roles": [], + "users": [] + } + } + } + }, + "description": "Group updated" + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "404": { + "$ref": "#/components/responses/404" + }, + "422": { + "$ref": "#/components/responses/422" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [ + { + "jwt": [] + } + ], + "tags": [ + "Security Groups" + ], + "operationId": "update_security_groups_by_pk", + "summary": "Update security groups by pk", + "x-codeSamples": [ + { + "lang": "cURL", + "label": "cURL", + "source": "curl -X PUT \"http://localhost:8088/api/v1/security/groups/1\" \\\n -H \"Authorization: Bearer $ACCESS_TOKEN\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\"key\": \"value\"}'" + }, + { + "lang": "Python", + "label": "Python", + "source": "import requests\n\nresponse = requests.put(\n \"http://localhost:8088/api/v1/security/groups/1\",\n headers={\"Authorization\": \"Bearer \" + access_token},\n json={\"key\": \"value\"}\n)\nprint(response.json())" + }, + { + "lang": "JavaScript", + "label": "JavaScript", + "source": "const response = await fetch(\n \"http://localhost:8088/api/v1/security/groups/1\",\n {\n method: \"PUT\",\n headers: {\n \"Authorization\": `Bearer ${accessToken}`,\n \"Content-Type\": \"application/json\"\n },\n body: JSON.stringify({ key: \"value\" })\n }\n);\nconst data = await response.json();\nconsole.log(data);" + } + ] + } + }, "/api/v1/security/guest_token/": { "post": { "requestBody": { @@ -29822,8 +31553,8 @@ "$ref": "#/components/schemas/PermissionViewMenuApi.post" }, "example": { - "permission_id": 1, - "view_menu_id": 1 + "permission_id": {}, + "view_menu_id": {} } } }, @@ -29848,8 +31579,8 @@ "example": { "id": "string", "result": { - "permission_id": 1, - "view_menu_id": 1 + "permission_id": {}, + "view_menu_id": {} } } } @@ -30237,8 +31968,8 @@ "$ref": "#/components/schemas/PermissionViewMenuApi.put" }, "example": { - "permission_id": 1, - "view_menu_id": 1 + "permission_id": {}, + "view_menu_id": {} } } }, @@ -30259,8 +31990,8 @@ }, "example": { "result": { - "permission_id": 1, - "view_menu_id": 1 + "permission_id": {}, + "view_menu_id": {} } } } @@ -32204,6 +33935,100 @@ ] } }, + "/api/v1/security/roles/{role_id}/groups": { + "put": { + "parameters": [ + { + "in": "path", + "name": "role_id", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RoleGroupPutSchema" + }, + "example": { + "group_ids": [ + 1 + ] + } + } + }, + "description": "Update role groups schema", + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "properties": { + "result": { + "$ref": "#/components/schemas/RoleGroupPutSchema" + } + }, + "type": "object" + }, + "example": { + "result": { + "group_ids": [] + } + } + } + }, + "description": "Role groups updated" + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "404": { + "$ref": "#/components/responses/404" + }, + "422": { + "$ref": "#/components/responses/422" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [ + { + "jwt": [] + } + ], + "tags": [ + "Security Roles" + ], + "operationId": "update_security_roles_by_role_id_groups", + "summary": "Update security roles by role_id groups", + "x-codeSamples": [ + { + "lang": "cURL", + "label": "cURL", + "source": "curl -X PUT \"http://localhost:8088/api/v1/security/roles/{role_id}/groups\" \\\n -H \"Authorization: Bearer $ACCESS_TOKEN\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\"key\": \"value\"}'" + }, + { + "lang": "Python", + "label": "Python", + "source": "import requests\n\nresponse = requests.put(\n \"http://localhost:8088/api/v1/security/roles/{role_id}/groups\",\n headers={\"Authorization\": \"Bearer \" + access_token},\n json={\"key\": \"value\"}\n)\nprint(response.json())" + }, + { + "lang": "JavaScript", + "label": "JavaScript", + "source": "const response = await fetch(\n \"http://localhost:8088/api/v1/security/roles/{role_id}/groups\",\n {\n method: \"PUT\",\n headers: {\n \"Authorization\": `Bearer ${accessToken}`,\n \"Content-Type\": \"application/json\"\n },\n body: JSON.stringify({ key: \"value\" })\n }\n);\nconst data = await response.json();\nconsole.log(data);" + } + ] + } + }, "/api/v1/security/roles/{role_id}/permissions": { "post": { "parameters": [ @@ -32317,17 +34142,18 @@ "schema": { "properties": { "result": { - "$ref": "#/components/schemas/RolePermissionListSchema" + "items": { + "$ref": "#/components/schemas/RolePermissionListSchema" + }, + "type": "array" } }, "type": "object" }, "example": { - "result": { - "id": 1, - "permission_name": "string", - "view_menu_name": "string" - } + "result": [ + {} + ] } } }, @@ -32472,6 +34298,807 @@ ] } }, + "/api/v1/security/user_registrations/": { + "get": { + "description": "Get a list of models", + "parameters": [ + { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/get_list_schema" + } + } + }, + "in": "query", + "name": "q" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "properties": { + "count": { + "description": "The total record count on the backend", + "type": "number" + }, + "description_columns": { + "properties": { + "column_name": { + "description": "The description for the column name. Will be translated by babel", + "example": "A Nice description for the column", + "type": "string" + } + }, + "type": "object" + }, + "ids": { + "description": "A list of item ids, useful when you don't know the column id", + "items": { + "type": "string" + }, + "type": "array" + }, + "label_columns": { + "properties": { + "column_name": { + "description": "The label for the column name. Will be translated by babel", + "example": "A Nice label for the column", + "type": "string" + } + }, + "type": "object" + }, + "list_columns": { + "description": "A list of columns", + "items": { + "type": "string" + }, + "type": "array" + }, + "list_title": { + "description": "A title to render. Will be translated by babel", + "example": "List Items", + "type": "string" + }, + "order_columns": { + "description": "A list of allowed columns to sort", + "items": { + "type": "string" + }, + "type": "array" + }, + "result": { + "description": "The result from the get list query", + "items": { + "$ref": "#/components/schemas/UserRegistrationsRestAPI.get_list" + }, + "type": "array" + } + }, + "type": "object" + }, + "example": { + "count": 1.0, + "description_columns": { + "column_name": "A Nice description for the column" + }, + "ids": [ + "string" + ], + "label_columns": { + "column_name": "A Nice label for the column" + }, + "list_columns": [ + "string" + ], + "list_title": "List Items", + "order_columns": [ + "string" + ], + "result": [ + {} + ] + } + } + }, + "description": "Items from Model" + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "422": { + "$ref": "#/components/responses/422" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [ + { + "jwt": [] + } + ], + "tags": [ + "UserRegistrationsRestAPI" + ], + "operationId": "get_security_user_registrations", + "summary": "Get security user registrations", + "x-codeSamples": [ + { + "lang": "cURL", + "label": "cURL", + "source": "curl -X GET \"http://localhost:8088/api/v1/security/user_registrations/\" \\\n -H \"Authorization: Bearer $ACCESS_TOKEN\"" + }, + { + "lang": "Python", + "label": "Python", + "source": "import requests\n\nresponse = requests.get(\n \"http://localhost:8088/api/v1/security/user_registrations/\",\n headers={\"Authorization\": \"Bearer \" + access_token}\n)\nprint(response.json())" + }, + { + "lang": "JavaScript", + "label": "JavaScript", + "source": "const response = await fetch(\n \"http://localhost:8088/api/v1/security/user_registrations/\",\n {\n headers: {\n \"Authorization\": `Bearer ${accessToken}`\n }\n }\n);\nconst data = await response.json();\nconsole.log(data);" + } + ] + }, + "post": { + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserRegistrationsRestAPI.post" + }, + "example": { + "id": 1 + } + } + }, + "description": "Model schema", + "required": true + }, + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "properties": { + "id": { + "type": "string" + }, + "result": { + "$ref": "#/components/schemas/UserRegistrationsRestAPI.post" + } + }, + "type": "object" + }, + "example": { + "id": "string", + "result": { + "id": 1 + } + } + } + }, + "description": "Item inserted" + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "422": { + "$ref": "#/components/responses/422" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [ + { + "jwt": [] + } + ], + "tags": [ + "UserRegistrationsRestAPI" + ], + "operationId": "create_security_user_registrations", + "summary": "Create security user registrations", + "x-codeSamples": [ + { + "lang": "cURL", + "label": "cURL", + "source": "curl -X POST \"http://localhost:8088/api/v1/security/user_registrations/\" \\\n -H \"Authorization: Bearer $ACCESS_TOKEN\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\"key\": \"value\"}'" + }, + { + "lang": "Python", + "label": "Python", + "source": "import requests\n\nresponse = requests.post(\n \"http://localhost:8088/api/v1/security/user_registrations/\",\n headers={\"Authorization\": \"Bearer \" + access_token},\n json={\"key\": \"value\"}\n)\nprint(response.json())" + }, + { + "lang": "JavaScript", + "label": "JavaScript", + "source": "const response = await fetch(\n \"http://localhost:8088/api/v1/security/user_registrations/\",\n {\n method: \"POST\",\n headers: {\n \"Authorization\": `Bearer ${accessToken}`,\n \"Content-Type\": \"application/json\"\n },\n body: JSON.stringify({ key: \"value\" })\n }\n);\nconst data = await response.json();\nconsole.log(data);" + } + ] + } + }, + "/api/v1/security/user_registrations/_info": { + "get": { + "description": "Get metadata information about this API resource", + "parameters": [ + { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/get_info_schema" + } + } + }, + "in": "query", + "name": "q" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "properties": { + "add_columns": { + "type": "object" + }, + "edit_columns": { + "type": "object" + }, + "filters": { + "properties": { + "column_name": { + "items": { + "properties": { + "name": { + "description": "The filter name. Will be translated by babel", + "type": "string" + }, + "operator": { + "description": "The filter operation key to use on list filters", + "type": "string" + } + }, + "type": "object" + }, + "type": "array" + } + }, + "type": "object" + }, + "permissions": { + "description": "The user permissions for this API resource", + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "example": { + "add_columns": {}, + "edit_columns": {}, + "filters": { + "column_name": [ + {} + ] + }, + "permissions": [ + "string" + ] + } + } + }, + "description": "Item from Model" + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "422": { + "$ref": "#/components/responses/422" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [ + { + "jwt": [] + } + ], + "tags": [ + "UserRegistrationsRestAPI" + ], + "operationId": "get_security_user_registrations__info", + "summary": "Get security user registrations info", + "x-codeSamples": [ + { + "lang": "cURL", + "label": "cURL", + "source": "curl -X GET \"http://localhost:8088/api/v1/security/user_registrations/_info\" \\\n -H \"Authorization: Bearer $ACCESS_TOKEN\"" + }, + { + "lang": "Python", + "label": "Python", + "source": "import requests\n\nresponse = requests.get(\n \"http://localhost:8088/api/v1/security/user_registrations/_info\",\n headers={\"Authorization\": \"Bearer \" + access_token}\n)\nprint(response.json())" + }, + { + "lang": "JavaScript", + "label": "JavaScript", + "source": "const response = await fetch(\n \"http://localhost:8088/api/v1/security/user_registrations/_info\",\n {\n headers: {\n \"Authorization\": `Bearer ${accessToken}`\n }\n }\n);\nconst data = await response.json();\nconsole.log(data);" + } + ] + } + }, + "/api/v1/security/user_registrations/distinct/{column_name}": { + "get": { + "parameters": [ + { + "in": "path", + "name": "column_name", + "required": true, + "schema": { + "type": "string" + } + }, + { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/get_related_schema" + } + } + }, + "in": "query", + "name": "q" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DistincResponseSchema" + }, + "example": { + "count": 1, + "result": [] + } + } + }, + "description": "Distinct field data" + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "404": { + "$ref": "#/components/responses/404" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [ + { + "jwt": [] + } + ], + "summary": "Get distinct values from field data (security-user-registrations-distinct-column-name)", + "tags": [ + "UserRegistrationsRestAPI" + ], + "x-codeSamples": [ + { + "lang": "cURL", + "label": "cURL", + "source": "curl -X GET \"http://localhost:8088/api/v1/security/user_registrations/distinct/{column_name}\" \\\n -H \"Authorization: Bearer $ACCESS_TOKEN\"" + }, + { + "lang": "Python", + "label": "Python", + "source": "import requests\n\nresponse = requests.get(\n \"http://localhost:8088/api/v1/security/user_registrations/distinct/{column_name}\",\n headers={\"Authorization\": \"Bearer \" + access_token}\n)\nprint(response.json())" + }, + { + "lang": "JavaScript", + "label": "JavaScript", + "source": "const response = await fetch(\n \"http://localhost:8088/api/v1/security/user_registrations/distinct/{column_name}\",\n {\n headers: {\n \"Authorization\": `Bearer ${accessToken}`\n }\n }\n);\nconst data = await response.json();\nconsole.log(data);" + } + ] + } + }, + "/api/v1/security/user_registrations/related/{column_name}": { + "get": { + "parameters": [ + { + "in": "path", + "name": "column_name", + "required": true, + "schema": { + "type": "string" + } + }, + { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/get_related_schema" + } + } + }, + "in": "query", + "name": "q" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RelatedResponseSchema" + }, + "example": { + "count": 1, + "result": [] + } + } + }, + "description": "Related column data" + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "404": { + "$ref": "#/components/responses/404" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [ + { + "jwt": [] + } + ], + "summary": "Get related fields data (security-user-registrations-related-column-name)", + "tags": [ + "UserRegistrationsRestAPI" + ], + "x-codeSamples": [ + { + "lang": "cURL", + "label": "cURL", + "source": "curl -X GET \"http://localhost:8088/api/v1/security/user_registrations/related/{column_name}\" \\\n -H \"Authorization: Bearer $ACCESS_TOKEN\"" + }, + { + "lang": "Python", + "label": "Python", + "source": "import requests\n\nresponse = requests.get(\n \"http://localhost:8088/api/v1/security/user_registrations/related/{column_name}\",\n headers={\"Authorization\": \"Bearer \" + access_token}\n)\nprint(response.json())" + }, + { + "lang": "JavaScript", + "label": "JavaScript", + "source": "const response = await fetch(\n \"http://localhost:8088/api/v1/security/user_registrations/related/{column_name}\",\n {\n headers: {\n \"Authorization\": `Bearer ${accessToken}`\n }\n }\n);\nconst data = await response.json();\nconsole.log(data);" + } + ] + } + }, + "/api/v1/security/user_registrations/{pk}": { + "delete": { + "parameters": [ + { + "in": "path", + "name": "pk", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "properties": { + "message": { + "type": "string" + } + }, + "type": "object" + }, + "example": { + "message": "string" + } + } + }, + "description": "Item deleted" + }, + "404": { + "$ref": "#/components/responses/404" + }, + "422": { + "$ref": "#/components/responses/422" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [ + { + "jwt": [] + } + ], + "tags": [ + "UserRegistrationsRestAPI" + ], + "operationId": "delete_security_user_registrations_by_pk", + "summary": "Delete security user registrations by pk", + "x-codeSamples": [ + { + "lang": "cURL", + "label": "cURL", + "source": "curl -X DELETE \"http://localhost:8088/api/v1/security/user_registrations/1\" \\\n -H \"Authorization: Bearer $ACCESS_TOKEN\"" + }, + { + "lang": "Python", + "label": "Python", + "source": "import requests\n\nresponse = requests.delete(\n \"http://localhost:8088/api/v1/security/user_registrations/1\",\n headers={\"Authorization\": \"Bearer \" + access_token}\n)\nprint(response.status_code)" + }, + { + "lang": "JavaScript", + "label": "JavaScript", + "source": "const response = await fetch(\n \"http://localhost:8088/api/v1/security/user_registrations/1\",\n {\n method: \"DELETE\",\n headers: {\n \"Authorization\": `Bearer ${accessToken}`\n }\n }\n);\nconsole.log(response.status);" + } + ] + }, + "get": { + "description": "Get an item model", + "parameters": [ + { + "in": "path", + "name": "pk", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/get_item_schema" + } + } + }, + "in": "query", + "name": "q" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "properties": { + "description_columns": { + "properties": { + "column_name": { + "description": "The description for the column name. Will be translated by babel", + "example": "A Nice description for the column", + "type": "string" + } + }, + "type": "object" + }, + "id": { + "description": "The item id", + "type": "string" + }, + "label_columns": { + "properties": { + "column_name": { + "description": "The label for the column name. Will be translated by babel", + "example": "A Nice label for the column", + "type": "string" + } + }, + "type": "object" + }, + "result": { + "$ref": "#/components/schemas/UserRegistrationsRestAPI.get" + }, + "show_columns": { + "description": "A list of columns", + "items": { + "type": "string" + }, + "type": "array" + }, + "show_title": { + "description": "A title to render. Will be translated by babel", + "example": "Show Item Details", + "type": "string" + } + }, + "type": "object" + }, + "example": { + "description_columns": { + "column_name": "A Nice description for the column" + }, + "id": "string", + "label_columns": { + "column_name": "A Nice label for the column" + }, + "result": { + "id": 1 + }, + "show_columns": [ + "string" + ], + "show_title": "Show Item Details" + } + } + }, + "description": "Item from Model" + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "404": { + "$ref": "#/components/responses/404" + }, + "422": { + "$ref": "#/components/responses/422" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [ + { + "jwt": [] + } + ], + "tags": [ + "UserRegistrationsRestAPI" + ], + "operationId": "get_security_user_registrations_by_pk", + "summary": "Get security user registrations by pk", + "x-codeSamples": [ + { + "lang": "cURL", + "label": "cURL", + "source": "curl -X GET \"http://localhost:8088/api/v1/security/user_registrations/1\" \\\n -H \"Authorization: Bearer $ACCESS_TOKEN\"" + }, + { + "lang": "Python", + "label": "Python", + "source": "import requests\n\nresponse = requests.get(\n \"http://localhost:8088/api/v1/security/user_registrations/1\",\n headers={\"Authorization\": \"Bearer \" + access_token}\n)\nprint(response.json())" + }, + { + "lang": "JavaScript", + "label": "JavaScript", + "source": "const response = await fetch(\n \"http://localhost:8088/api/v1/security/user_registrations/1\",\n {\n headers: {\n \"Authorization\": `Bearer ${accessToken}`\n }\n }\n);\nconst data = await response.json();\nconsole.log(data);" + } + ] + }, + "put": { + "parameters": [ + { + "in": "path", + "name": "pk", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserRegistrationsRestAPI.put" + }, + "example": { + "id": 1 + } + } + }, + "description": "Model schema", + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "properties": { + "result": { + "$ref": "#/components/schemas/UserRegistrationsRestAPI.put" + } + }, + "type": "object" + }, + "example": { + "result": { + "id": 1 + } + } + } + }, + "description": "Item changed" + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "404": { + "$ref": "#/components/responses/404" + }, + "422": { + "$ref": "#/components/responses/422" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [ + { + "jwt": [] + } + ], + "tags": [ + "UserRegistrationsRestAPI" + ], + "operationId": "update_security_user_registrations_by_pk", + "summary": "Update security user registrations by pk", + "x-codeSamples": [ + { + "lang": "cURL", + "label": "cURL", + "source": "curl -X PUT \"http://localhost:8088/api/v1/security/user_registrations/1\" \\\n -H \"Authorization: Bearer $ACCESS_TOKEN\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\"key\": \"value\"}'" + }, + { + "lang": "Python", + "label": "Python", + "source": "import requests\n\nresponse = requests.put(\n \"http://localhost:8088/api/v1/security/user_registrations/1\",\n headers={\"Authorization\": \"Bearer \" + access_token},\n json={\"key\": \"value\"}\n)\nprint(response.json())" + }, + { + "lang": "JavaScript", + "label": "JavaScript", + "source": "const response = await fetch(\n \"http://localhost:8088/api/v1/security/user_registrations/1\",\n {\n method: \"PUT\",\n headers: {\n \"Authorization\": `Bearer ${accessToken}`,\n \"Content-Type\": \"application/json\"\n },\n body: JSON.stringify({ key: \"value\" })\n }\n);\nconst data = await response.json();\nconsole.log(data);" + } + ] + } + }, "/api/v1/security/users/": { "get": { "description": "Get a list of models", @@ -32632,6 +35259,9 @@ "active": true, "email": "string", "first_name": "string", + "groups": [ + 1 + ], "last_name": "string", "password": "string", "roles": [ @@ -32661,6 +35291,7 @@ "active": true, "email": "string", "first_name": "string", + "groups": [], "last_name": "string", "password": "string", "roles": [], @@ -33068,6 +35699,9 @@ "active": true, "email": "string", "first_name": "string", + "groups": [ + 1 + ], "last_name": "string", "password": "string", "roles": [ @@ -33097,6 +35731,7 @@ "active": true, "email": "string", "first_name": "string", + "groups": [], "last_name": "string", "password": "string", "roles": [], @@ -33313,7 +35948,6 @@ "ctas_method": "string", "database_id": 1, "expand_data": true, - "json": true, "queryLimit": 1, "runAsync": true, "schema": "string", @@ -33496,6 +36130,88 @@ ] } }, + "/api/v1/sqllab/export_streaming/": { + "post": { + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "properties": { + "client_id": { + "description": "The SQL query result identifier", + "type": "string" + }, + "expected_rows": { + "description": "Optional expected row count for progress tracking", + "type": "integer" + }, + "filename": { + "description": "Optional filename for the export", + "type": "string" + } + }, + "type": "object" + } + } + }, + "description": "Export parameters", + "required": true + }, + "responses": { + "200": { + "content": { + "text/csv": { + "schema": { + "type": "string" + } + } + }, + "description": "Streaming CSV export" + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "404": { + "$ref": "#/components/responses/404" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [ + { + "jwt": [] + } + ], + "summary": "Export SQL query results to CSV with streaming", + "tags": [ + "SQL Lab" + ], + "x-codeSamples": [ + { + "lang": "cURL", + "label": "cURL", + "source": "curl -X POST \"http://localhost:8088/api/v1/sqllab/export_streaming/\" \\\n -H \"Authorization: Bearer $ACCESS_TOKEN\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\"key\": \"value\"}'" + }, + { + "lang": "Python", + "label": "Python", + "source": "import requests\n\nresponse = requests.post(\n \"http://localhost:8088/api/v1/sqllab/export_streaming/\",\n headers={\"Authorization\": \"Bearer \" + access_token},\n json={\"key\": \"value\"}\n)\nprint(response.json())" + }, + { + "lang": "JavaScript", + "label": "JavaScript", + "source": "const response = await fetch(\n \"http://localhost:8088/api/v1/sqllab/export_streaming/\",\n {\n method: \"POST\",\n headers: {\n \"Authorization\": `Bearer ${accessToken}`,\n \"Content-Type\": \"application/json\"\n },\n body: JSON.stringify({ key: \"value\" })\n }\n);\nconst data = await response.json();\nconsole.log(data);" + } + ] + } + }, "/api/v1/sqllab/format_sql/": { "post": { "requestBody": { @@ -33505,9 +36221,9 @@ "$ref": "#/components/schemas/FormatQueryPayloadSchema" }, "example": { - "sql": "string", - "engine": "string", "database_id": 1, + "engine": "string", + "sql": "string", "template_params": "string" } } @@ -35199,6 +37915,1255 @@ ] } }, + "/api/v1/theme/": { + "delete": { + "parameters": [ + { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/get_delete_ids_schema" + } + } + }, + "in": "query", + "name": "q" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "properties": { + "message": { + "type": "string" + } + }, + "type": "object" + }, + "example": { + "message": "string" + } + } + }, + "description": "Themes bulk delete" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "404": { + "$ref": "#/components/responses/404" + }, + "422": { + "$ref": "#/components/responses/422" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [ + { + "jwt": [] + } + ], + "summary": "Bulk delete themes", + "tags": [ + "Themes" + ], + "x-codeSamples": [ + { + "lang": "cURL", + "label": "cURL", + "source": "curl -X DELETE \"http://localhost:8088/api/v1/theme/\" \\\n -H \"Authorization: Bearer $ACCESS_TOKEN\"" + }, + { + "lang": "Python", + "label": "Python", + "source": "import requests\n\nresponse = requests.delete(\n \"http://localhost:8088/api/v1/theme/\",\n headers={\"Authorization\": \"Bearer \" + access_token}\n)\nprint(response.status_code)" + }, + { + "lang": "JavaScript", + "label": "JavaScript", + "source": "const response = await fetch(\n \"http://localhost:8088/api/v1/theme/\",\n {\n method: \"DELETE\",\n headers: {\n \"Authorization\": `Bearer ${accessToken}`\n }\n }\n);\nconsole.log(response.status);" + } + ] + }, + "get": { + "description": "Gets a list of themes, use Rison or JSON query parameters for filtering, sorting, pagination and for selecting specific columns and metadata.", + "parameters": [ + { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/get_list_schema" + } + } + }, + "in": "query", + "name": "q" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "properties": { + "count": { + "description": "The total record count on the backend", + "type": "number" + }, + "description_columns": { + "properties": { + "column_name": { + "description": "The description for the column name. Will be translated by babel", + "example": "A Nice description for the column", + "type": "string" + } + }, + "type": "object" + }, + "ids": { + "description": "A list of item ids, useful when you don't know the column id", + "items": { + "type": "string" + }, + "type": "array" + }, + "label_columns": { + "properties": { + "column_name": { + "description": "The label for the column name. Will be translated by babel", + "example": "A Nice label for the column", + "type": "string" + } + }, + "type": "object" + }, + "list_columns": { + "description": "A list of columns", + "items": { + "type": "string" + }, + "type": "array" + }, + "list_title": { + "description": "A title to render. Will be translated by babel", + "example": "List Items", + "type": "string" + }, + "order_columns": { + "description": "A list of allowed columns to sort", + "items": { + "type": "string" + }, + "type": "array" + }, + "result": { + "description": "The result from the get list query", + "items": { + "$ref": "#/components/schemas/ThemeRestApi.get_list" + }, + "type": "array" + } + }, + "type": "object" + }, + "example": { + "count": 1.0, + "description_columns": { + "column_name": "A Nice description for the column" + }, + "ids": [ + "string" + ], + "label_columns": { + "column_name": "A Nice label for the column" + }, + "list_columns": [ + "string" + ], + "list_title": "List Items", + "order_columns": [ + "string" + ], + "result": [ + {} + ] + } + } + }, + "description": "Items from Model" + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "422": { + "$ref": "#/components/responses/422" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [ + { + "jwt": [] + } + ], + "summary": "Get a list of themes", + "tags": [ + "Themes" + ], + "x-codeSamples": [ + { + "lang": "cURL", + "label": "cURL", + "source": "curl -X GET \"http://localhost:8088/api/v1/theme/\" \\\n -H \"Authorization: Bearer $ACCESS_TOKEN\"" + }, + { + "lang": "Python", + "label": "Python", + "source": "import requests\n\nresponse = requests.get(\n \"http://localhost:8088/api/v1/theme/\",\n headers={\"Authorization\": \"Bearer \" + access_token}\n)\nprint(response.json())" + }, + { + "lang": "JavaScript", + "label": "JavaScript", + "source": "const response = await fetch(\n \"http://localhost:8088/api/v1/theme/\",\n {\n headers: {\n \"Authorization\": `Bearer ${accessToken}`\n }\n }\n);\nconst data = await response.json();\nconsole.log(data);" + } + ] + }, + "post": { + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ThemeRestApi.post" + }, + "example": { + "json_data": "string", + "theme_name": "string" + } + } + }, + "description": "Theme schema", + "required": true + }, + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "properties": { + "id": { + "type": "number" + }, + "result": { + "$ref": "#/components/schemas/ThemeRestApi.post" + } + }, + "type": "object" + }, + "example": { + "id": 1.0, + "result": { + "json_data": "string", + "theme_name": "string" + } + } + } + }, + "description": "Theme created" + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "422": { + "$ref": "#/components/responses/422" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [ + { + "jwt": [] + } + ], + "summary": "Create a theme", + "tags": [ + "Themes" + ], + "x-codeSamples": [ + { + "lang": "cURL", + "label": "cURL", + "source": "curl -X POST \"http://localhost:8088/api/v1/theme/\" \\\n -H \"Authorization: Bearer $ACCESS_TOKEN\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\"key\": \"value\"}'" + }, + { + "lang": "Python", + "label": "Python", + "source": "import requests\n\nresponse = requests.post(\n \"http://localhost:8088/api/v1/theme/\",\n headers={\"Authorization\": \"Bearer \" + access_token},\n json={\"key\": \"value\"}\n)\nprint(response.json())" + }, + { + "lang": "JavaScript", + "label": "JavaScript", + "source": "const response = await fetch(\n \"http://localhost:8088/api/v1/theme/\",\n {\n method: \"POST\",\n headers: {\n \"Authorization\": `Bearer ${accessToken}`,\n \"Content-Type\": \"application/json\"\n },\n body: JSON.stringify({ key: \"value\" })\n }\n);\nconst data = await response.json();\nconsole.log(data);" + } + ] + } + }, + "/api/v1/theme/_info": { + "get": { + "description": "Get metadata information about this API resource", + "parameters": [ + { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/get_info_schema" + } + } + }, + "in": "query", + "name": "q" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "properties": { + "add_columns": { + "type": "object" + }, + "edit_columns": { + "type": "object" + }, + "filters": { + "properties": { + "column_name": { + "items": { + "properties": { + "name": { + "description": "The filter name. Will be translated by babel", + "type": "string" + }, + "operator": { + "description": "The filter operation key to use on list filters", + "type": "string" + } + }, + "type": "object" + }, + "type": "array" + } + }, + "type": "object" + }, + "permissions": { + "description": "The user permissions for this API resource", + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "example": { + "add_columns": {}, + "edit_columns": {}, + "filters": { + "column_name": [ + {} + ] + }, + "permissions": [ + "string" + ] + } + } + }, + "description": "Item from Model" + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "422": { + "$ref": "#/components/responses/422" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [ + { + "jwt": [] + } + ], + "summary": "Get metadata information about this API resource (theme--info)", + "tags": [ + "Themes" + ], + "x-codeSamples": [ + { + "lang": "cURL", + "label": "cURL", + "source": "curl -X GET \"http://localhost:8088/api/v1/theme/_info\" \\\n -H \"Authorization: Bearer $ACCESS_TOKEN\"" + }, + { + "lang": "Python", + "label": "Python", + "source": "import requests\n\nresponse = requests.get(\n \"http://localhost:8088/api/v1/theme/_info\",\n headers={\"Authorization\": \"Bearer \" + access_token}\n)\nprint(response.json())" + }, + { + "lang": "JavaScript", + "label": "JavaScript", + "source": "const response = await fetch(\n \"http://localhost:8088/api/v1/theme/_info\",\n {\n headers: {\n \"Authorization\": `Bearer ${accessToken}`\n }\n }\n);\nconst data = await response.json();\nconsole.log(data);" + } + ] + } + }, + "/api/v1/theme/export/": { + "get": { + "parameters": [ + { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/get_export_ids_schema" + } + } + }, + "in": "query", + "name": "q" + } + ], + "responses": { + "200": { + "content": { + "application/zip": { + "schema": { + "format": "binary", + "type": "string" + } + } + }, + "description": "Theme export" + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "404": { + "$ref": "#/components/responses/404" + }, + "422": { + "$ref": "#/components/responses/422" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [ + { + "jwt": [] + } + ], + "summary": "Download multiple themes as YAML files", + "tags": [ + "Themes" + ], + "x-codeSamples": [ + { + "lang": "cURL", + "label": "cURL", + "source": "curl -X GET \"http://localhost:8088/api/v1/theme/export/\" \\\n -H \"Authorization: Bearer $ACCESS_TOKEN\"" + }, + { + "lang": "Python", + "label": "Python", + "source": "import requests\n\nresponse = requests.get(\n \"http://localhost:8088/api/v1/theme/export/\",\n headers={\"Authorization\": \"Bearer \" + access_token}\n)\nprint(response.json())" + }, + { + "lang": "JavaScript", + "label": "JavaScript", + "source": "const response = await fetch(\n \"http://localhost:8088/api/v1/theme/export/\",\n {\n headers: {\n \"Authorization\": `Bearer ${accessToken}`\n }\n }\n);\nconst data = await response.json();\nconsole.log(data);" + } + ] + } + }, + "/api/v1/theme/import/": { + "post": { + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "properties": { + "formData": { + "format": "binary", + "type": "string" + }, + "overwrite": { + "type": "string" + } + }, + "type": "object" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "properties": { + "message": { + "type": "string" + } + }, + "type": "object" + }, + "example": { + "message": "string" + } + } + }, + "description": "Theme imported" + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "422": { + "$ref": "#/components/responses/422" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [ + { + "jwt": [] + } + ], + "summary": "Import themes from a ZIP file", + "tags": [ + "Themes" + ], + "x-codeSamples": [ + { + "lang": "cURL", + "label": "cURL", + "source": "curl -X POST \"http://localhost:8088/api/v1/theme/import/\" \\\n -H \"Authorization: Bearer $ACCESS_TOKEN\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\"key\": \"value\"}'" + }, + { + "lang": "Python", + "label": "Python", + "source": "import requests\n\nresponse = requests.post(\n \"http://localhost:8088/api/v1/theme/import/\",\n headers={\"Authorization\": \"Bearer \" + access_token},\n json={\"key\": \"value\"}\n)\nprint(response.json())" + }, + { + "lang": "JavaScript", + "label": "JavaScript", + "source": "const response = await fetch(\n \"http://localhost:8088/api/v1/theme/import/\",\n {\n method: \"POST\",\n headers: {\n \"Authorization\": `Bearer ${accessToken}`,\n \"Content-Type\": \"application/json\"\n },\n body: JSON.stringify({ key: \"value\" })\n }\n);\nconst data = await response.json();\nconsole.log(data);" + } + ] + } + }, + "/api/v1/theme/related/{column_name}": { + "get": { + "parameters": [ + { + "in": "path", + "name": "column_name", + "required": true, + "schema": { + "type": "string" + } + }, + { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/get_related_schema" + } + } + }, + "in": "query", + "name": "q" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RelatedResponseSchema" + }, + "example": { + "count": 1, + "result": [] + } + } + }, + "description": "Related column data" + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "404": { + "$ref": "#/components/responses/404" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [ + { + "jwt": [] + } + ], + "summary": "Get related fields data (theme-related-column-name)", + "tags": [ + "Themes" + ], + "x-codeSamples": [ + { + "lang": "cURL", + "label": "cURL", + "source": "curl -X GET \"http://localhost:8088/api/v1/theme/related/{column_name}\" \\\n -H \"Authorization: Bearer $ACCESS_TOKEN\"" + }, + { + "lang": "Python", + "label": "Python", + "source": "import requests\n\nresponse = requests.get(\n \"http://localhost:8088/api/v1/theme/related/{column_name}\",\n headers={\"Authorization\": \"Bearer \" + access_token}\n)\nprint(response.json())" + }, + { + "lang": "JavaScript", + "label": "JavaScript", + "source": "const response = await fetch(\n \"http://localhost:8088/api/v1/theme/related/{column_name}\",\n {\n headers: {\n \"Authorization\": `Bearer ${accessToken}`\n }\n }\n);\nconst data = await response.json();\nconsole.log(data);" + } + ] + } + }, + "/api/v1/theme/unset_system_dark": { + "delete": { + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "properties": { + "result": { + "type": "string" + } + }, + "type": "object" + }, + "example": { + "result": "string" + } + } + }, + "description": "System dark theme cleared" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [ + { + "jwt": [] + } + ], + "summary": "Clear the system dark theme", + "tags": [ + "Themes" + ], + "x-codeSamples": [ + { + "lang": "cURL", + "label": "cURL", + "source": "curl -X DELETE \"http://localhost:8088/api/v1/theme/unset_system_dark\" \\\n -H \"Authorization: Bearer $ACCESS_TOKEN\"" + }, + { + "lang": "Python", + "label": "Python", + "source": "import requests\n\nresponse = requests.delete(\n \"http://localhost:8088/api/v1/theme/unset_system_dark\",\n headers={\"Authorization\": \"Bearer \" + access_token}\n)\nprint(response.status_code)" + }, + { + "lang": "JavaScript", + "label": "JavaScript", + "source": "const response = await fetch(\n \"http://localhost:8088/api/v1/theme/unset_system_dark\",\n {\n method: \"DELETE\",\n headers: {\n \"Authorization\": `Bearer ${accessToken}`\n }\n }\n);\nconsole.log(response.status);" + } + ] + } + }, + "/api/v1/theme/unset_system_default": { + "delete": { + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "properties": { + "result": { + "type": "string" + } + }, + "type": "object" + }, + "example": { + "result": "string" + } + } + }, + "description": "System default theme cleared" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [ + { + "jwt": [] + } + ], + "summary": "Clear the system default theme", + "tags": [ + "Themes" + ], + "x-codeSamples": [ + { + "lang": "cURL", + "label": "cURL", + "source": "curl -X DELETE \"http://localhost:8088/api/v1/theme/unset_system_default\" \\\n -H \"Authorization: Bearer $ACCESS_TOKEN\"" + }, + { + "lang": "Python", + "label": "Python", + "source": "import requests\n\nresponse = requests.delete(\n \"http://localhost:8088/api/v1/theme/unset_system_default\",\n headers={\"Authorization\": \"Bearer \" + access_token}\n)\nprint(response.status_code)" + }, + { + "lang": "JavaScript", + "label": "JavaScript", + "source": "const response = await fetch(\n \"http://localhost:8088/api/v1/theme/unset_system_default\",\n {\n method: \"DELETE\",\n headers: {\n \"Authorization\": `Bearer ${accessToken}`\n }\n }\n);\nconsole.log(response.status);" + } + ] + } + }, + "/api/v1/theme/{pk}": { + "delete": { + "parameters": [ + { + "in": "path", + "name": "pk", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "properties": { + "message": { + "type": "string" + } + }, + "type": "object" + }, + "example": { + "message": "string" + } + } + }, + "description": "Theme deleted" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "404": { + "$ref": "#/components/responses/404" + }, + "422": { + "$ref": "#/components/responses/422" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [ + { + "jwt": [] + } + ], + "summary": "Delete a theme", + "tags": [ + "Themes" + ], + "x-codeSamples": [ + { + "lang": "cURL", + "label": "cURL", + "source": "curl -X DELETE \"http://localhost:8088/api/v1/theme/1\" \\\n -H \"Authorization: Bearer $ACCESS_TOKEN\"" + }, + { + "lang": "Python", + "label": "Python", + "source": "import requests\n\nresponse = requests.delete(\n \"http://localhost:8088/api/v1/theme/1\",\n headers={\"Authorization\": \"Bearer \" + access_token}\n)\nprint(response.status_code)" + }, + { + "lang": "JavaScript", + "label": "JavaScript", + "source": "const response = await fetch(\n \"http://localhost:8088/api/v1/theme/1\",\n {\n method: \"DELETE\",\n headers: {\n \"Authorization\": `Bearer ${accessToken}`\n }\n }\n);\nconsole.log(response.status);" + } + ] + }, + "get": { + "description": "Get an item model", + "parameters": [ + { + "in": "path", + "name": "pk", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/get_item_schema" + } + } + }, + "in": "query", + "name": "q" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "properties": { + "description_columns": { + "properties": { + "column_name": { + "description": "The description for the column name. Will be translated by babel", + "example": "A Nice description for the column", + "type": "string" + } + }, + "type": "object" + }, + "id": { + "description": "The item id", + "type": "string" + }, + "label_columns": { + "properties": { + "column_name": { + "description": "The label for the column name. Will be translated by babel", + "example": "A Nice label for the column", + "type": "string" + } + }, + "type": "object" + }, + "result": { + "$ref": "#/components/schemas/ThemeRestApi.get" + }, + "show_columns": { + "description": "A list of columns", + "items": { + "type": "string" + }, + "type": "array" + }, + "show_title": { + "description": "A title to render. Will be translated by babel", + "example": "Show Item Details", + "type": "string" + } + }, + "type": "object" + }, + "example": { + "description_columns": { + "column_name": "A Nice description for the column" + }, + "id": "string", + "label_columns": { + "column_name": "A Nice label for the column" + }, + "result": { + "changed_on_delta_humanized": {}, + "id": 1, + "is_system": true, + "is_system_dark": true, + "is_system_default": true, + "json_data": "string", + "theme_name": "string", + "uuid": "550e8400-e29b-41d4-a716-446655440000" + }, + "show_columns": [ + "string" + ], + "show_title": "Show Item Details" + } + } + }, + "description": "Item from Model" + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "404": { + "$ref": "#/components/responses/404" + }, + "422": { + "$ref": "#/components/responses/422" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [ + { + "jwt": [] + } + ], + "summary": "Get a theme", + "tags": [ + "Themes" + ], + "x-codeSamples": [ + { + "lang": "cURL", + "label": "cURL", + "source": "curl -X GET \"http://localhost:8088/api/v1/theme/1\" \\\n -H \"Authorization: Bearer $ACCESS_TOKEN\"" + }, + { + "lang": "Python", + "label": "Python", + "source": "import requests\n\nresponse = requests.get(\n \"http://localhost:8088/api/v1/theme/1\",\n headers={\"Authorization\": \"Bearer \" + access_token}\n)\nprint(response.json())" + }, + { + "lang": "JavaScript", + "label": "JavaScript", + "source": "const response = await fetch(\n \"http://localhost:8088/api/v1/theme/1\",\n {\n headers: {\n \"Authorization\": `Bearer ${accessToken}`\n }\n }\n);\nconst data = await response.json();\nconsole.log(data);" + } + ] + }, + "put": { + "parameters": [ + { + "in": "path", + "name": "pk", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ThemeRestApi.put" + }, + "example": { + "json_data": "string", + "theme_name": "string" + } + } + }, + "description": "Theme schema", + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "properties": { + "id": { + "type": "number" + }, + "result": { + "$ref": "#/components/schemas/ThemeRestApi.put" + } + }, + "type": "object" + }, + "example": { + "id": 1.0, + "result": { + "json_data": "string", + "theme_name": "string" + } + } + } + }, + "description": "Theme updated" + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "404": { + "$ref": "#/components/responses/404" + }, + "422": { + "$ref": "#/components/responses/422" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [ + { + "jwt": [] + } + ], + "summary": "Update a theme", + "tags": [ + "Themes" + ], + "x-codeSamples": [ + { + "lang": "cURL", + "label": "cURL", + "source": "curl -X PUT \"http://localhost:8088/api/v1/theme/1\" \\\n -H \"Authorization: Bearer $ACCESS_TOKEN\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\"key\": \"value\"}'" + }, + { + "lang": "Python", + "label": "Python", + "source": "import requests\n\nresponse = requests.put(\n \"http://localhost:8088/api/v1/theme/1\",\n headers={\"Authorization\": \"Bearer \" + access_token},\n json={\"key\": \"value\"}\n)\nprint(response.json())" + }, + { + "lang": "JavaScript", + "label": "JavaScript", + "source": "const response = await fetch(\n \"http://localhost:8088/api/v1/theme/1\",\n {\n method: \"PUT\",\n headers: {\n \"Authorization\": `Bearer ${accessToken}`,\n \"Content-Type\": \"application/json\"\n },\n body: JSON.stringify({ key: \"value\" })\n }\n);\nconst data = await response.json();\nconsole.log(data);" + } + ] + } + }, + "/api/v1/theme/{pk}/set_system_dark": { + "put": { + "parameters": [ + { + "description": "The theme id", + "in": "path", + "name": "pk", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "properties": { + "id": { + "type": "integer" + }, + "result": { + "type": "string" + } + }, + "type": "object" + }, + "example": { + "id": 1, + "result": "string" + } + } + }, + "description": "Theme successfully set as system dark" + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "404": { + "$ref": "#/components/responses/404" + }, + "422": { + "$ref": "#/components/responses/422" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [ + { + "jwt": [] + } + ], + "summary": "Set a theme as the system dark theme", + "tags": [ + "Themes" + ], + "x-codeSamples": [ + { + "lang": "cURL", + "label": "cURL", + "source": "curl -X PUT \"http://localhost:8088/api/v1/theme/1/set_system_dark\" \\\n -H \"Authorization: Bearer $ACCESS_TOKEN\"" + }, + { + "lang": "Python", + "label": "Python", + "source": "import requests\n\nresponse = requests.put(\n \"http://localhost:8088/api/v1/theme/1/set_system_dark\",\n headers={\"Authorization\": \"Bearer \" + access_token},\n json={\"key\": \"value\"}\n)\nprint(response.json())" + }, + { + "lang": "JavaScript", + "label": "JavaScript", + "source": "const response = await fetch(\n \"http://localhost:8088/api/v1/theme/1/set_system_dark\",\n {\n method: \"PUT\",\n headers: {\n \"Authorization\": `Bearer ${accessToken}`,\n \"Content-Type\": \"application/json\"\n },\n body: JSON.stringify({ key: \"value\" })\n }\n);\nconst data = await response.json();\nconsole.log(data);" + } + ] + } + }, + "/api/v1/theme/{pk}/set_system_default": { + "put": { + "parameters": [ + { + "description": "The theme id", + "in": "path", + "name": "pk", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "properties": { + "id": { + "type": "integer" + }, + "result": { + "type": "string" + } + }, + "type": "object" + }, + "example": { + "id": 1, + "result": "string" + } + } + }, + "description": "Theme successfully set as system default" + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "404": { + "$ref": "#/components/responses/404" + }, + "422": { + "$ref": "#/components/responses/422" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [ + { + "jwt": [] + } + ], + "summary": "Set a theme as the system default theme", + "tags": [ + "Themes" + ], + "x-codeSamples": [ + { + "lang": "cURL", + "label": "cURL", + "source": "curl -X PUT \"http://localhost:8088/api/v1/theme/1/set_system_default\" \\\n -H \"Authorization: Bearer $ACCESS_TOKEN\"" + }, + { + "lang": "Python", + "label": "Python", + "source": "import requests\n\nresponse = requests.put(\n \"http://localhost:8088/api/v1/theme/1/set_system_default\",\n headers={\"Authorization\": \"Bearer \" + access_token},\n json={\"key\": \"value\"}\n)\nprint(response.json())" + }, + { + "lang": "JavaScript", + "label": "JavaScript", + "source": "const response = await fetch(\n \"http://localhost:8088/api/v1/theme/1/set_system_default\",\n {\n method: \"PUT\",\n headers: {\n \"Authorization\": `Bearer ${accessToken}`,\n \"Content-Type\": \"application/json\"\n },\n body: JSON.stringify({ key: \"value\" })\n }\n);\nconst data = await response.json();\nconsole.log(data);" + } + ] + } + }, "/api/v1/user/{user_id}/avatar.png": { "get": { "description": "Gets the avatar URL for the user with the given ID, or returns a 401 error if the user is unauthenticated.", @@ -35339,7 +39304,7 @@ "tags": [ { "name": "Advanced Data Type", - "description": "Endpoints for advanced data type operations and conversions." + "description": "Advanced data type operations and conversions." }, { "name": "Annotation Layers", @@ -35367,7 +39332,7 @@ }, { "name": "Current User", - "description": "Get information about the currently authenticated user." + "description": "Get information about the authenticated user." }, { "name": "Dashboard Filter State", @@ -35375,7 +39340,7 @@ }, { "name": "Dashboard Permanent Link", - "description": "Create and retrieve permanent links to dashboard states." + "description": "Permanent links to dashboard states." }, { "name": "Dashboards", @@ -35407,11 +39372,11 @@ }, { "name": "Explore Permanent Link", - "description": "Create and retrieve permanent links to chart explore states." + "description": "Permanent links to chart explore states." }, { "name": "Import/export", - "description": "Import and export Superset assets (dashboards, charts, databases)." + "description": "Import and export Superset assets." }, { "name": "LogRestApi", @@ -35435,7 +39400,7 @@ }, { "name": "Row Level Security", - "description": "Manage row-level security rules for data access control." + "description": "Manage row-level security rules for data access." }, { "name": "SQL Lab", @@ -35443,19 +39408,23 @@ }, { "name": "SQL Lab Permanent Link", - "description": "Create and retrieve permanent links to SQL Lab states." + "description": "Permanent links to SQL Lab states." }, { "name": "Security", "description": "Authentication and token management." }, + { + "name": "Security Groups", + "description": "Endpoints related to Security Groups." + }, { "name": "Security Permissions", "description": "View available permissions." }, { "name": "Security Permissions on Resources (View Menus)", - "description": "Manage permission-resource mappings." + "description": "Permission-resource mappings." }, { "name": "Security Resources (View Menus)", @@ -35473,9 +39442,17 @@ "name": "Tags", "description": "Organize assets with tags." }, + { + "name": "Themes", + "description": "Manage UI themes for customizing Superset's appearance." + }, { "name": "User", "description": "User profile and preferences." + }, + { + "name": "UserRegistrationsRestAPI", + "description": "Endpoints related to UserRegistrationsRestAPI." } ] } diff --git a/docs/tsconfig.json b/docs/tsconfig.json index ff0f5ac238c..e505bfd86ae 100644 --- a/docs/tsconfig.json +++ b/docs/tsconfig.json @@ -3,6 +3,7 @@ "extends": "@docusaurus/tsconfig", "compilerOptions": { "baseUrl": ".", + "ignoreDeprecations": "6.0", "skipLibCheck": true, "noImplicitAny": false, "strict": false, diff --git a/docs/versioned_docs/version-6.0.0/configuration/configuring-superset.mdx b/docs/versioned_docs/version-6.0.0/configuration/configuring-superset.mdx index d9fb2ca41a0..a61d05ecfe4 100644 --- a/docs/versioned_docs/version-6.0.0/configuration/configuring-superset.mdx +++ b/docs/versioned_docs/version-6.0.0/configuration/configuring-superset.mdx @@ -109,6 +109,14 @@ SECRET_KEY = 'YOUR_OWN_RANDOM_GENERATED_SECRET_KEY' You can generate a strong secure key with `openssl rand -base64 42`. +Alternatively, you can set the secret key using `SUPERSET_SECRET_KEY` environment variable: + +On a Unix-based system, such as Linux or macOS, you can do so by running the following command in your terminal: + +```bash +export SUPERSET_SECRET_KEY=$(openssl rand -base64 42) +``` + :::caution Use a strong secret key This key will be used for securely signing session cookies and encrypting sensitive information stored in Superset's application metadata database. Your deployment must use a complex, unique key. diff --git a/docs/versioned_docs/version-6.0.0/faq.mdx b/docs/versioned_docs/version-6.0.0/faq.mdx index 10c6b267d1e..3a3b57f82b8 100644 --- a/docs/versioned_docs/version-6.0.0/faq.mdx +++ b/docs/versioned_docs/version-6.0.0/faq.mdx @@ -35,7 +35,7 @@ or a view. When working with tables, the solution would be to create a table that contains all the fields needed for your analysis, most likely through some scheduled batch process. -A view is a simple logical layer that abstracts an arbitrary SQL queries as a virtual table. This can +A view is a simple logical layer that abstracts an arbitrary SQL query as a virtual table. This can allow you to join and union multiple tables and to apply some transformation using arbitrary SQL expressions. The limitation there is your database performance, as Superset effectively will run a query on top of your query (view). A good practice may be to limit yourself to joining your main diff --git a/docs/yarn.lock b/docs/yarn.lock index 6f455ed271b..09f05421816 100644 --- a/docs/yarn.lock +++ b/docs/yarn.lock @@ -2,41 +2,6 @@ # yarn lockfile v1 -"@ai-sdk/gateway@1.0.34": - version "1.0.34" - resolved "https://registry.npmjs.org/@ai-sdk/gateway/-/gateway-1.0.34.tgz" - integrity sha512-Vc/4fGlAW2uGmz6DnbRB9b7kdkbk4DQYNDYNdr/NHJxoaLsdNF3CE8i172rZ47+jEU2hGibcn5yivbgvjMi8Ig== - dependencies: - "@ai-sdk/provider" "2.0.0" - "@ai-sdk/provider-utils" "3.0.10" - "@vercel/oidc" "3.0.2" - -"@ai-sdk/provider-utils@3.0.10": - version "3.0.10" - resolved "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-3.0.10.tgz" - integrity sha512-T1gZ76gEIwffep6MWI0QNy9jgoybUHE7TRaHB5k54K8mF91ciGFlbtCGxDYhMH3nCRergKwYFIDeFF0hJSIQHQ== - dependencies: - "@ai-sdk/provider" "2.0.0" - "@standard-schema/spec" "^1.0.0" - eventsource-parser "^3.0.5" - -"@ai-sdk/provider@2.0.0": - version "2.0.0" - resolved "https://registry.npmjs.org/@ai-sdk/provider/-/provider-2.0.0.tgz" - integrity sha512-6o7Y2SeO9vFKB8lArHXehNuusnpddKPk7xqL7T2/b+OvXMRIXUO1rR4wcv1hAFUAT9avGZshty3Wlua/XA7TvA== - dependencies: - json-schema "^0.4.0" - -"@ai-sdk/react@^2.0.30": - version "2.0.61" - resolved "https://registry.npmjs.org/@ai-sdk/react/-/react-2.0.61.tgz" - integrity sha512-XYqIFnfW2C4LpT3ifFzLSshvqK6gX4sf1wpJTJ/ecLiwyZ83GIGIlqmDU4NopljqEU8eZjl2/QZQnM5qrG/PFA== - dependencies: - "@ai-sdk/provider-utils" "3.0.10" - ai "5.0.61" - swr "^2.2.5" - throttleit "2.1.0" - "@algolia/abtesting@1.6.0": version "1.6.0" resolved "https://registry.npmjs.org/@algolia/abtesting/-/abtesting-1.6.0.tgz" @@ -55,6 +20,14 @@ "@algolia/autocomplete-plugin-algolia-insights" "1.19.2" "@algolia/autocomplete-shared" "1.19.2" +"@algolia/autocomplete-core@^1.19.2": + version "1.19.8" + resolved "https://registry.yarnpkg.com/@algolia/autocomplete-core/-/autocomplete-core-1.19.8.tgz#7c84c771d28643fb00d09026c05013fb97aeea23" + integrity sha512-3YEorYg44niXcm7gkft3nXYItHd44e8tmh4D33CTszPgP0QWkaLEaFywiNyJBo7UL/mqObA/G9RYuU7R8tN1IA== + dependencies: + "@algolia/autocomplete-plugin-algolia-insights" "1.19.8" + "@algolia/autocomplete-shared" "1.19.8" + "@algolia/autocomplete-plugin-algolia-insights@1.19.2": version "1.19.2" resolved "https://registry.npmjs.org/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.19.2.tgz" @@ -62,11 +35,23 @@ dependencies: "@algolia/autocomplete-shared" "1.19.2" +"@algolia/autocomplete-plugin-algolia-insights@1.19.8": + version "1.19.8" + resolved "https://registry.yarnpkg.com/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.19.8.tgz#f60d21edbe2a42e6d4e2215430733e3f51641471" + integrity sha512-ZvJWO8ZZJDpc1LNM2TTBdmQsZBLMR4rU5iNR2OYvEeFBiaf/0ESnRSSLQbryarJY4SVxtoz6A2ZtDMNM+iQEAA== + dependencies: + "@algolia/autocomplete-shared" "1.19.8" + "@algolia/autocomplete-shared@1.19.2": version "1.19.2" resolved "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.19.2.tgz" integrity sha512-jEazxZTVD2nLrC+wYlVHQgpBoBB5KPStrJxLzsIFl6Kqd1AlG9sIAGl39V5tECLpIQzB3Qa2T6ZPJ1ChkwMK/w== +"@algolia/autocomplete-shared@1.19.8": + version "1.19.8" + resolved "https://registry.yarnpkg.com/@algolia/autocomplete-shared/-/autocomplete-shared-1.19.8.tgz#5d723d8bdb448efbb1b0e1c7ff94cc18e5b1dc0e" + integrity sha512-h5hf2t8ejF6vlOgvLaZzQbWs5SyH2z4PAWygNAvvD/2RI29hdQ54ldUGwqVuj9Srs+n8XUKTPUqb7fvhBhQrnQ== + "@algolia/client-abtesting@5.40.0": version "5.40.0" resolved "https://registry.npmjs.org/@algolia/client-abtesting/-/client-abtesting-5.40.0.tgz" @@ -227,10 +212,10 @@ resolved "https://registry.npmjs.org/@ant-design/icons-svg/-/icons-svg-4.4.2.tgz" integrity sha512-vHbT+zJEVzllwP+CM+ul7reTEfBR0vgxFe7+lREAsAA7YGsYpboiq2sQNeQeRvh09GfQgs/GyFEvZpJ9cLXpXA== -"@ant-design/icons@^6.1.0": - version "6.1.0" - resolved "https://registry.npmjs.org/@ant-design/icons/-/icons-6.1.0.tgz" - integrity sha512-KrWMu1fIg3w/1F2zfn+JlfNDU8dDqILfA5Tg85iqs1lf8ooyGlbkA+TkwfOKKgqpUmAiRY1PTFpuOU2DAIgSUg== +"@ant-design/icons@^6.1.1": + version "6.1.1" + resolved "https://registry.yarnpkg.com/@ant-design/icons/-/icons-6.1.1.tgz#068963d3de44ff7034dce32c9cec3ff7d343fe6b" + integrity sha512-AMT4N2y++TZETNHiM77fs4a0uPVCJGuL5MTonk13Pvv7UN7sID1cNEZOc1qNqx6zLKAOilTEFAdAoAFKa0U//Q== dependencies: "@ant-design/colors" "^8.0.0" "@ant-design/icons-svg" "^4.4.0" @@ -260,14 +245,12 @@ resolved "https://registry.npmjs.org/@antfu/utils/-/utils-8.1.1.tgz" integrity sha512-Mex9nXf9vR6AhcXmMrlz/HVgYYZpVGJ6YlPgwl7UnaFpnshXs6EK/oa5Gpf3CzENMjkvEx2tQtntGnb7UtSTOQ== -"@apidevtools/json-schema-ref-parser@^11.5.4": - version "11.9.3" - resolved "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-11.9.3.tgz" - integrity sha512-60vepv88RwcJtSHrD6MjIL6Ta3SOYbgfnkHb+ppAVK+o9mXprRtulx7VlRl3lN3bbvysAfCS7WMVfhUYemB0IQ== +"@apidevtools/json-schema-ref-parser@^15.3.3": + version "15.3.5" + resolved "https://registry.yarnpkg.com/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-15.3.5.tgz#503726178d8d792eea7b195566272aaee6837e2f" + integrity sha512-orNOYXw3hYXxxisXMldjzjBzqqTLBPbwOtHg7ovBPvfBHDue1qM9YJENZ3W2BQuS+7z4ThogMbEzEsov57Itkg== dependencies: - "@jsdevtools/ono" "^7.1.3" - "@types/json-schema" "^7.0.15" - js-yaml "^4.1.0" + js-yaml "^4.1.1" "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.28.6": version "7.28.6" @@ -1146,7 +1129,7 @@ "@babel/plugin-transform-modules-commonjs" "^7.27.1" "@babel/plugin-transform-typescript" "^7.27.1" -"@babel/runtime-corejs3@^7.20.7", "@babel/runtime-corejs3@^7.22.15", "@babel/runtime-corejs3@^7.25.9", "@babel/runtime-corejs3@^7.26.10", "@babel/runtime-corejs3@^7.27.1": +"@babel/runtime-corejs3@^7.20.7", "@babel/runtime-corejs3@^7.22.15", "@babel/runtime-corejs3@^7.26.10", "@babel/runtime-corejs3@^7.27.1": version "7.28.3" resolved "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.28.3.tgz" integrity sha512-LKYxD2CIfocUFNREQ1yk+dW+8OH8CRqmgatBZYXb+XhuObO8wsDpEoCNri5bKld9cnj8xukqZjxSX8p1YiRF8Q== @@ -1568,28 +1551,29 @@ resolved "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz" integrity sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw== -"@docsearch/css@4.2.0": - version "4.2.0" - resolved "https://registry.npmjs.org/@docsearch/css/-/css-4.2.0.tgz" - integrity sha512-65KU9Fw5fGsPPPlgIghonMcndyx1bszzrDQYLfierN+Ha29yotMHzVS94bPkZS6On9LS8dE4qmW4P/fGjtCf/g== +"@docsearch/core@4.6.2": + version "4.6.2" + resolved "https://registry.yarnpkg.com/@docsearch/core/-/core-4.6.2.tgz#0a6fdc13b1eb12153cb19316f911479b67f7bd58" + integrity sha512-/S0e6Dj7Zcm8m9Rru49YEX49dhU11be68c+S/BCyN8zQsTTgkKzXlhRbVL5mV6lOLC2+ZRRryaTdcm070Ug2oA== -"@docsearch/react@^3.9.0 || ^4.1.0": - version "4.2.0" - resolved "https://registry.npmjs.org/@docsearch/react/-/react-4.2.0.tgz" - integrity sha512-zSN/KblmtBcerf7Z87yuKIHZQmxuXvYc6/m0+qnjyNu+Ir67AVOagTa1zBqcxkVUVkmBqUExdcyrdo9hbGbqTw== +"@docsearch/css@4.6.2": + version "4.6.2" + resolved "https://registry.yarnpkg.com/@docsearch/css/-/css-4.6.2.tgz#986776619dccbf798176c75e858cc22f5e710bb4" + integrity sha512-fH/cn8BjEEdM2nJdjNMHIvOVYupG6AIDtFVDgIZrNzdCSj4KXr9kd+hsehqsNGYjpUjObeKYKvgy/IwCb1jZYQ== + +"@docsearch/react@^3.9.0 || ^4.3.2": + version "4.6.2" + resolved "https://registry.yarnpkg.com/@docsearch/react/-/react-4.6.2.tgz#e6c65fb87fb943eefaa936debe6d31bb51b25056" + integrity sha512-/BbtGFtqVOGwZx0dw/UfhN/0/DmMQYnulY4iv0tPRhC2JCXv0ka/+izwt3Jzo1ZxXS/2eMvv9zHsBJOK1I9f/w== dependencies: - "@ai-sdk/react" "^2.0.30" "@algolia/autocomplete-core" "1.19.2" - "@docsearch/css" "4.2.0" - ai "^5.0.30" - algoliasearch "^5.28.0" - marked "^16.3.0" - zod "^4.1.8" + "@docsearch/core" "4.6.2" + "@docsearch/css" "4.6.2" -"@docusaurus/babel@3.9.2": - version "3.9.2" - resolved "https://registry.npmjs.org/@docusaurus/babel/-/babel-3.9.2.tgz" - integrity sha512-GEANdi/SgER+L7Japs25YiGil/AUDnFFHaCGPBbundxoWtCkA2lmy7/tFmgED4y1htAy6Oi4wkJEQdGssnw9MA== +"@docusaurus/babel@3.10.0": + version "3.10.0" + resolved "https://registry.yarnpkg.com/@docusaurus/babel/-/babel-3.10.0.tgz#819819f107233dfcf50b59cd51158f23fb04878a" + integrity sha512-mqCJhCZNZUDg0zgDEaPTM4DnRsisa24HdqTy/qn/MQlbwhTb4WVaZg6ZyX6yIVKqTz8fS1hBMgM+98z+BeJJDg== dependencies: "@babel/core" "^7.25.9" "@babel/generator" "^7.25.9" @@ -1599,25 +1583,24 @@ "@babel/preset-react" "^7.25.9" "@babel/preset-typescript" "^7.25.9" "@babel/runtime" "^7.25.9" - "@babel/runtime-corejs3" "^7.25.9" "@babel/traverse" "^7.25.9" - "@docusaurus/logger" "3.9.2" - "@docusaurus/utils" "3.9.2" + "@docusaurus/logger" "3.10.0" + "@docusaurus/utils" "3.10.0" babel-plugin-dynamic-import-node "^2.3.3" fs-extra "^11.1.1" tslib "^2.6.0" -"@docusaurus/bundler@3.9.2": - version "3.9.2" - resolved "https://registry.npmjs.org/@docusaurus/bundler/-/bundler-3.9.2.tgz" - integrity sha512-ZOVi6GYgTcsZcUzjblpzk3wH1Fya2VNpd5jtHoCCFcJlMQ1EYXZetfAnRHLcyiFeBABaI1ltTYbOBtH/gahGVA== +"@docusaurus/bundler@3.10.0": + version "3.10.0" + resolved "https://registry.yarnpkg.com/@docusaurus/bundler/-/bundler-3.10.0.tgz#878c4c46bfa3434671ea37a43da184238a6aae26" + integrity sha512-iONUGZGgp+lAkw/cJZH6irONcF4p8+278IsdRlq8lYhxGjkoNUs0w7F4gVXBYSNChq5KG5/JleTSsdJySShxow== dependencies: "@babel/core" "^7.25.9" - "@docusaurus/babel" "3.9.2" - "@docusaurus/cssnano-preset" "3.9.2" - "@docusaurus/logger" "3.9.2" - "@docusaurus/types" "3.9.2" - "@docusaurus/utils" "3.9.2" + "@docusaurus/babel" "3.10.0" + "@docusaurus/cssnano-preset" "3.10.0" + "@docusaurus/logger" "3.10.0" + "@docusaurus/types" "3.10.0" + "@docusaurus/utils" "3.10.0" babel-loader "^9.2.1" clean-css "^5.3.3" copy-webpack-plugin "^11.0.0" @@ -1637,18 +1620,18 @@ webpack "^5.95.0" webpackbar "^6.0.1" -"@docusaurus/core@3.9.2": - version "3.9.2" - resolved "https://registry.npmjs.org/@docusaurus/core/-/core-3.9.2.tgz" - integrity sha512-HbjwKeC+pHUFBfLMNzuSjqFE/58+rLVKmOU3lxQrpsxLBOGosYco/Q0GduBb0/jEMRiyEqjNT/01rRdOMWq5pw== +"@docusaurus/core@3.10.0", "@docusaurus/core@^3.10.0": + version "3.10.0" + resolved "https://registry.yarnpkg.com/@docusaurus/core/-/core-3.10.0.tgz#642e71a0209d62c3f5ef275ed9d74a881f40df39" + integrity sha512-mgLdQsO8xppnQZc3LPi+Mf+PkPeyxJeIx11AXAq/14fsaMefInQiMEZUUmrc7J+956G/f7MwE7tn8KZgi3iRcA== dependencies: - "@docusaurus/babel" "3.9.2" - "@docusaurus/bundler" "3.9.2" - "@docusaurus/logger" "3.9.2" - "@docusaurus/mdx-loader" "3.9.2" - "@docusaurus/utils" "3.9.2" - "@docusaurus/utils-common" "3.9.2" - "@docusaurus/utils-validation" "3.9.2" + "@docusaurus/babel" "3.10.0" + "@docusaurus/bundler" "3.10.0" + "@docusaurus/logger" "3.10.0" + "@docusaurus/mdx-loader" "3.10.0" + "@docusaurus/utils" "3.10.0" + "@docusaurus/utils-common" "3.10.0" + "@docusaurus/utils-validation" "3.10.0" boxen "^6.2.1" chalk "^4.1.2" chokidar "^3.5.3" @@ -1660,7 +1643,7 @@ escape-html "^1.0.3" eta "^2.2.0" eval "^0.1.8" - execa "5.1.1" + execa "^5.1.1" fs-extra "^11.1.1" html-tags "^3.3.1" html-webpack-plugin "^5.6.0" @@ -1671,12 +1654,12 @@ prompts "^2.4.2" react-helmet-async "npm:@slorber/react-helmet-async@1.3.0" react-loadable "npm:@docusaurus/react-loadable@6.0.0" - react-loadable-ssr-addon-v5-slorber "^1.0.1" + react-loadable-ssr-addon-v5-slorber "^1.0.3" react-router "^5.3.4" react-router-config "^5.1.1" react-router-dom "^5.3.4" semver "^7.5.4" - serve-handler "^6.1.6" + serve-handler "^6.1.7" tinypool "^1.0.2" tslib "^2.6.0" update-notifier "^6.0.2" @@ -1685,47 +1668,48 @@ webpack-dev-server "^5.2.2" webpack-merge "^6.0.1" -"@docusaurus/cssnano-preset@3.9.2": - version "3.9.2" - resolved "https://registry.npmjs.org/@docusaurus/cssnano-preset/-/cssnano-preset-3.9.2.tgz" - integrity sha512-8gBKup94aGttRduABsj7bpPFTX7kbwu+xh3K9NMCF5K4bWBqTFYW+REKHF6iBVDHRJ4grZdIPbvkiHd/XNKRMQ== +"@docusaurus/cssnano-preset@3.10.0": + version "3.10.0" + resolved "https://registry.yarnpkg.com/@docusaurus/cssnano-preset/-/cssnano-preset-3.10.0.tgz#be1b435c33df09d743473d3fadda67b4568dfae3" + integrity sha512-qzSshTO1DB3TYW+dPUal5KHM7XPc5YQfzF3Kdb2NDACJUyGbNcFtw3tGkCJlYwhNCRKbZcmwraKUS1i5dcHdGg== dependencies: cssnano-preset-advanced "^6.1.2" postcss "^8.5.4" postcss-sort-media-queries "^5.2.0" tslib "^2.6.0" -"@docusaurus/faster@^3.9.2": - version "3.9.2" - resolved "https://registry.yarnpkg.com/@docusaurus/faster/-/faster-3.9.2.tgz#47b69f39ed935fdcc8dc03896274fca29056e962" - integrity sha512-DEVIwhbrZZ4ir31X+qQNEQqDWkgCJUV6kiPPAd2MGTY8n5/n0c4B8qA5k1ipF2izwH00JEf0h6Daaut71zzkyw== +"@docusaurus/faster@^3.10.0": + version "3.10.0" + resolved "https://registry.yarnpkg.com/@docusaurus/faster/-/faster-3.10.0.tgz#0758a93196f685537aa7700bde62faf926e6c817" + integrity sha512-GNPtVH14ISjHfSwnHu3KiFGf86ICmJSQDeSv/QaanpBgiZGOtgZaslnC5q8WiguxM1EVkwcGxPuD8BXF4eggKw== dependencies: - "@docusaurus/types" "3.9.2" - "@rspack/core" "^1.5.0" + "@docusaurus/types" "3.10.0" + "@rspack/core" "^1.7.10" "@swc/core" "^1.7.39" "@swc/html" "^1.13.5" browserslist "^4.24.2" lightningcss "^1.27.0" + semver "^7.5.4" swc-loader "^0.2.6" tslib "^2.6.0" webpack "^5.95.0" -"@docusaurus/logger@3.9.2": - version "3.9.2" - resolved "https://registry.npmjs.org/@docusaurus/logger/-/logger-3.9.2.tgz" - integrity sha512-/SVCc57ByARzGSU60c50rMyQlBuMIJCjcsJlkphxY6B0GV4UH3tcA1994N8fFfbJ9kX3jIBe/xg3XP5qBtGDbA== +"@docusaurus/logger@3.10.0": + version "3.10.0" + resolved "https://registry.yarnpkg.com/@docusaurus/logger/-/logger-3.10.0.tgz#2bacbd004dd78e3da926dbe8f6fa9a930856575d" + integrity sha512-9jrZzFuBH1LDRlZ7cznAhCLmAZ3HSDqgwdrSSZdGHq9SPUOQgXXu8mnxe2ZRB9NS1PCpMTIOVUqDtZPIhMafZg== dependencies: chalk "^4.1.2" tslib "^2.6.0" -"@docusaurus/mdx-loader@3.9.2": - version "3.9.2" - resolved "https://registry.npmjs.org/@docusaurus/mdx-loader/-/mdx-loader-3.9.2.tgz" - integrity sha512-wiYoGwF9gdd6rev62xDU8AAM8JuLI/hlwOtCzMmYcspEkzecKrP8J8X+KpYnTlACBUUtXNJpSoCwFWJhLRevzQ== +"@docusaurus/mdx-loader@3.10.0": + version "3.10.0" + resolved "https://registry.yarnpkg.com/@docusaurus/mdx-loader/-/mdx-loader-3.10.0.tgz#1d4b050d751389ecf38dee48bcb61e53df8ffb82" + integrity sha512-mQQV97080AH4PYNs087l202NMDqRopZA4mg5W76ZZyTFrmWhJ3mHg+8A+drJVENxw5/Q+wHMHLgsx+9z1nEs0A== dependencies: - "@docusaurus/logger" "3.9.2" - "@docusaurus/utils" "3.9.2" - "@docusaurus/utils-validation" "3.9.2" + "@docusaurus/logger" "3.10.0" + "@docusaurus/utils" "3.10.0" + "@docusaurus/utils-validation" "3.10.0" "@mdx-js/mdx" "^3.0.0" "@slorber/remark-comment" "^1.0.0" escape-html "^1.0.3" @@ -1748,12 +1732,12 @@ vfile "^6.0.1" webpack "^5.88.1" -"@docusaurus/module-type-aliases@3.9.2", "@docusaurus/module-type-aliases@^3.9.1": - version "3.9.2" - resolved "https://registry.npmjs.org/@docusaurus/module-type-aliases/-/module-type-aliases-3.9.2.tgz" - integrity sha512-8qVe2QA9hVLzvnxP46ysuofJUIc/yYQ82tvA/rBTrnpXtCjNSFLxEZfd5U8cYZuJIVlkPxamsIgwd5tGZXfvew== +"@docusaurus/module-type-aliases@3.10.0", "@docusaurus/module-type-aliases@^3.10.0": + version "3.10.0" + resolved "https://registry.yarnpkg.com/@docusaurus/module-type-aliases/-/module-type-aliases-3.10.0.tgz#749928f104d563f11f046bf0c9ab6489a470c7c8" + integrity sha512-/1O0Zg8w3DFrYX/I6Fbss7OJrtZw1QoyjDhegiFNHVi9A9Y0gQ3jUAytVxF6ywpAWpLyLxch8nN8H/V3XfzdJQ== dependencies: - "@docusaurus/types" "3.9.2" + "@docusaurus/types" "3.10.0" "@types/history" "^4.7.11" "@types/react" "*" "@types/react-router-config" "*" @@ -1761,35 +1745,36 @@ react-helmet-async "npm:@slorber/react-helmet-async@1.3.0" react-loadable "npm:@docusaurus/react-loadable@6.0.0" -"@docusaurus/plugin-client-redirects@3.9.2": - version "3.9.2" - resolved "https://registry.npmjs.org/@docusaurus/plugin-client-redirects/-/plugin-client-redirects-3.9.2.tgz" - integrity sha512-lUgMArI9vyOYMzLRBUILcg9vcPTCyyI2aiuXq/4npcMVqOr6GfmwtmBYWSbNMlIUM0147smm4WhpXD0KFboffw== +"@docusaurus/plugin-client-redirects@^3.10.0": + version "3.10.0" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-client-redirects/-/plugin-client-redirects-3.10.0.tgz#4dd4619817fd69462d1e6d986580343aeb911111" + integrity sha512-P+VLoLoZTc74so8+IbsaPZ33/mkf2BWL1CYXQpPRkl0v1QVCN2CgfsZY/8QtbYjQnx2upXUnv45abDhNcSggNw== dependencies: - "@docusaurus/core" "3.9.2" - "@docusaurus/logger" "3.9.2" - "@docusaurus/utils" "3.9.2" - "@docusaurus/utils-common" "3.9.2" - "@docusaurus/utils-validation" "3.9.2" + "@docusaurus/core" "3.10.0" + "@docusaurus/logger" "3.10.0" + "@docusaurus/utils" "3.10.0" + "@docusaurus/utils-common" "3.10.0" + "@docusaurus/utils-validation" "3.10.0" eta "^2.2.0" fs-extra "^11.1.1" lodash "^4.17.21" tslib "^2.6.0" -"@docusaurus/plugin-content-blog@3.9.2": - version "3.9.2" - resolved "https://registry.npmjs.org/@docusaurus/plugin-content-blog/-/plugin-content-blog-3.9.2.tgz" - integrity sha512-3I2HXy3L1QcjLJLGAoTvoBnpOwa6DPUa3Q0dMK19UTY9mhPkKQg/DYhAGTiBUKcTR0f08iw7kLPqOhIgdV3eVQ== +"@docusaurus/plugin-content-blog@3.10.0": + version "3.10.0" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-blog/-/plugin-content-blog-3.10.0.tgz#10095291b637440847854ecb2c8afcd8746debd7" + integrity sha512-RuTz68DhB7CL96QO5UsFbciD7GPYq6QV+YMfF9V0+N4ZgLhJIBgpVAr8GobrKF6NRe5cyWWETU5z5T834piG9g== dependencies: - "@docusaurus/core" "3.9.2" - "@docusaurus/logger" "3.9.2" - "@docusaurus/mdx-loader" "3.9.2" - "@docusaurus/theme-common" "3.9.2" - "@docusaurus/types" "3.9.2" - "@docusaurus/utils" "3.9.2" - "@docusaurus/utils-common" "3.9.2" - "@docusaurus/utils-validation" "3.9.2" + "@docusaurus/core" "3.10.0" + "@docusaurus/logger" "3.10.0" + "@docusaurus/mdx-loader" "3.10.0" + "@docusaurus/theme-common" "3.10.0" + "@docusaurus/types" "3.10.0" + "@docusaurus/utils" "3.10.0" + "@docusaurus/utils-common" "3.10.0" + "@docusaurus/utils-validation" "3.10.0" cheerio "1.0.0-rc.12" + combine-promises "^1.1.0" feed "^4.2.2" fs-extra "^11.1.1" lodash "^4.17.21" @@ -1800,20 +1785,20 @@ utility-types "^3.10.0" webpack "^5.88.1" -"@docusaurus/plugin-content-docs@3.9.2": - version "3.9.2" - resolved "https://registry.npmjs.org/@docusaurus/plugin-content-docs/-/plugin-content-docs-3.9.2.tgz" - integrity sha512-C5wZsGuKTY8jEYsqdxhhFOe1ZDjH0uIYJ9T/jebHwkyxqnr4wW0jTkB72OMqNjsoQRcb0JN3PcSeTwFlVgzCZg== +"@docusaurus/plugin-content-docs@3.10.0": + version "3.10.0" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-docs/-/plugin-content-docs-3.10.0.tgz#9c4ea1d5a405340f28c281d2e4586c695a7c65a5" + integrity sha512-9BjHhf15ct8Z7TThTC0xRndKDVvMKmVsAGAN7W9FpNRzfMdScOGcXtLmcCWtJGvAezjOJIm6CxOYCy3Io5+RnQ== dependencies: - "@docusaurus/core" "3.9.2" - "@docusaurus/logger" "3.9.2" - "@docusaurus/mdx-loader" "3.9.2" - "@docusaurus/module-type-aliases" "3.9.2" - "@docusaurus/theme-common" "3.9.2" - "@docusaurus/types" "3.9.2" - "@docusaurus/utils" "3.9.2" - "@docusaurus/utils-common" "3.9.2" - "@docusaurus/utils-validation" "3.9.2" + "@docusaurus/core" "3.10.0" + "@docusaurus/logger" "3.10.0" + "@docusaurus/mdx-loader" "3.10.0" + "@docusaurus/module-type-aliases" "3.10.0" + "@docusaurus/theme-common" "3.10.0" + "@docusaurus/types" "3.10.0" + "@docusaurus/utils" "3.10.0" + "@docusaurus/utils-common" "3.10.0" + "@docusaurus/utils-validation" "3.10.0" "@types/react-router-config" "^5.0.7" combine-promises "^1.1.0" fs-extra "^11.1.1" @@ -1824,144 +1809,145 @@ utility-types "^3.10.0" webpack "^5.88.1" -"@docusaurus/plugin-content-pages@3.9.2": - version "3.9.2" - resolved "https://registry.npmjs.org/@docusaurus/plugin-content-pages/-/plugin-content-pages-3.9.2.tgz" - integrity sha512-s4849w/p4noXUrGpPUF0BPqIAfdAe76BLaRGAGKZ1gTDNiGxGcpsLcwJ9OTi1/V8A+AzvsmI9pkjie2zjIQZKA== +"@docusaurus/plugin-content-pages@3.10.0": + version "3.10.0" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-pages/-/plugin-content-pages-3.10.0.tgz#7670cbb3c849f434949f542bfdfded1580a13165" + integrity sha512-5amX8kEJI+nIGtuLVjYk59Y5utEJ3CHETFOPEE4cooIRLA4xM4iBsA6zFgu4ljcopeYwvBzFEWf5g2I6Yb9SkA== dependencies: - "@docusaurus/core" "3.9.2" - "@docusaurus/mdx-loader" "3.9.2" - "@docusaurus/types" "3.9.2" - "@docusaurus/utils" "3.9.2" - "@docusaurus/utils-validation" "3.9.2" + "@docusaurus/core" "3.10.0" + "@docusaurus/mdx-loader" "3.10.0" + "@docusaurus/types" "3.10.0" + "@docusaurus/utils" "3.10.0" + "@docusaurus/utils-validation" "3.10.0" fs-extra "^11.1.1" tslib "^2.6.0" webpack "^5.88.1" -"@docusaurus/plugin-css-cascade-layers@3.9.2": - version "3.9.2" - resolved "https://registry.npmjs.org/@docusaurus/plugin-css-cascade-layers/-/plugin-css-cascade-layers-3.9.2.tgz" - integrity sha512-w1s3+Ss+eOQbscGM4cfIFBlVg/QKxyYgj26k5AnakuHkKxH6004ZtuLe5awMBotIYF2bbGDoDhpgQ4r/kcj4rQ== +"@docusaurus/plugin-css-cascade-layers@3.10.0": + version "3.10.0" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-css-cascade-layers/-/plugin-css-cascade-layers-3.10.0.tgz#71e318d842be95f92be6c3dca00ceea4971d0edb" + integrity sha512-6q1vtt5FJcg5osgkHeM1euErECNqEZ5Z1j69yiNx2luEBIso+nxCkS9nqj8w+MK5X7rvKEToGhFfOFWncs51pQ== dependencies: - "@docusaurus/core" "3.9.2" - "@docusaurus/types" "3.9.2" - "@docusaurus/utils" "3.9.2" - "@docusaurus/utils-validation" "3.9.2" + "@docusaurus/core" "3.10.0" + "@docusaurus/types" "3.10.0" + "@docusaurus/utils" "3.10.0" + "@docusaurus/utils-validation" "3.10.0" tslib "^2.6.0" -"@docusaurus/plugin-debug@3.9.2": - version "3.9.2" - resolved "https://registry.npmjs.org/@docusaurus/plugin-debug/-/plugin-debug-3.9.2.tgz" - integrity sha512-j7a5hWuAFxyQAkilZwhsQ/b3T7FfHZ+0dub6j/GxKNFJp2h9qk/P1Bp7vrGASnvA9KNQBBL1ZXTe7jlh4VdPdA== +"@docusaurus/plugin-debug@3.10.0": + version "3.10.0" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-debug/-/plugin-debug-3.10.0.tgz#e77f924604e1e09d5d90fe0bdf23a3be8ea3307e" + integrity sha512-XcljKN+G+nmmK69uQA1d9BlYU3ZftG3T3zpK8/7Hf/wrOlV7TA4Ampdrdwkg0jElKdKAoSnPhCO0/U3bQGsVQQ== dependencies: - "@docusaurus/core" "3.9.2" - "@docusaurus/types" "3.9.2" - "@docusaurus/utils" "3.9.2" + "@docusaurus/core" "3.10.0" + "@docusaurus/types" "3.10.0" + "@docusaurus/utils" "3.10.0" fs-extra "^11.1.1" react-json-view-lite "^2.3.0" tslib "^2.6.0" -"@docusaurus/plugin-google-analytics@3.9.2": - version "3.9.2" - resolved "https://registry.npmjs.org/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-3.9.2.tgz" - integrity sha512-mAwwQJ1Us9jL/lVjXtErXto4p4/iaLlweC54yDUK1a97WfkC6Z2k5/769JsFgwOwOP+n5mUQGACXOEQ0XDuVUw== +"@docusaurus/plugin-google-analytics@3.10.0": + version "3.10.0" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-3.10.0.tgz#22c7e976fe4d970c7cd1c73c9723d9a5786c6e37" + integrity sha512-hTEoodatpBZnUat5nFExbuTGA1lhWGy7vZGuTew5Q3QDtGKFpSJLYmZJhdTjvCFwv1+qQ67hgAVlKdJOB8TXow== dependencies: - "@docusaurus/core" "3.9.2" - "@docusaurus/types" "3.9.2" - "@docusaurus/utils-validation" "3.9.2" + "@docusaurus/core" "3.10.0" + "@docusaurus/types" "3.10.0" + "@docusaurus/utils-validation" "3.10.0" tslib "^2.6.0" -"@docusaurus/plugin-google-gtag@3.9.2": - version "3.9.2" - resolved "https://registry.npmjs.org/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-3.9.2.tgz" - integrity sha512-YJ4lDCphabBtw19ooSlc1MnxtYGpjFV9rEdzjLsUnBCeis2djUyCozZaFhCg6NGEwOn7HDDyMh0yzcdRpnuIvA== +"@docusaurus/plugin-google-gtag@3.10.0": + version "3.10.0" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-3.10.0.tgz#c38a2ba638257851cc845b934506b80c08d47f96" + integrity sha512-iB/Zzjv/eelJRbdULZqzWCbgMgJ7ht4ONVjXtN3+BI/muil6S87gQ1OJyPwlXD+ELdKkitC7bWv5eJdYOZLhrQ== dependencies: - "@docusaurus/core" "3.9.2" - "@docusaurus/types" "3.9.2" - "@docusaurus/utils-validation" "3.9.2" - "@types/gtag.js" "^0.0.12" + "@docusaurus/core" "3.10.0" + "@docusaurus/types" "3.10.0" + "@docusaurus/utils-validation" "3.10.0" + "@types/gtag.js" "^0.0.20" tslib "^2.6.0" -"@docusaurus/plugin-google-tag-manager@3.9.2": - version "3.9.2" - resolved "https://registry.npmjs.org/@docusaurus/plugin-google-tag-manager/-/plugin-google-tag-manager-3.9.2.tgz" - integrity sha512-LJtIrkZN/tuHD8NqDAW1Tnw0ekOwRTfobWPsdO15YxcicBo2ykKF0/D6n0vVBfd3srwr9Z6rzrIWYrMzBGrvNw== +"@docusaurus/plugin-google-tag-manager@3.10.0": + version "3.10.0" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-google-tag-manager/-/plugin-google-tag-manager-3.10.0.tgz#5469c923cc1ad4608399d0b17e5fcacd8e030d56" + integrity sha512-FEjZxqKgLHa+Wez/EgKxRwvArNCWIScfyEQD95rot7jkxp6nonjI5XIbGfO/iYhM5Qinwe8aIEQHP2KZtpqVuA== dependencies: - "@docusaurus/core" "3.9.2" - "@docusaurus/types" "3.9.2" - "@docusaurus/utils-validation" "3.9.2" + "@docusaurus/core" "3.10.0" + "@docusaurus/types" "3.10.0" + "@docusaurus/utils-validation" "3.10.0" tslib "^2.6.0" -"@docusaurus/plugin-sitemap@3.9.2": - version "3.9.2" - resolved "https://registry.npmjs.org/@docusaurus/plugin-sitemap/-/plugin-sitemap-3.9.2.tgz" - integrity sha512-WLh7ymgDXjG8oPoM/T4/zUP7KcSuFYRZAUTl8vR6VzYkfc18GBM4xLhcT+AKOwun6kBivYKUJf+vlqYJkm+RHw== +"@docusaurus/plugin-sitemap@3.10.0": + version "3.10.0" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-sitemap/-/plugin-sitemap-3.10.0.tgz#35d59d46803f279f22aa64fc1bd18c048f12662b" + integrity sha512-DVTSLjB97hIjmayGnGcBfognCeI7ZuUKgEnU7Oz81JYqXtVg94mVTthDjq3QHTylYNeCUbkaW8VF0FDLcc8pPw== dependencies: - "@docusaurus/core" "3.9.2" - "@docusaurus/logger" "3.9.2" - "@docusaurus/types" "3.9.2" - "@docusaurus/utils" "3.9.2" - "@docusaurus/utils-common" "3.9.2" - "@docusaurus/utils-validation" "3.9.2" + "@docusaurus/core" "3.10.0" + "@docusaurus/logger" "3.10.0" + "@docusaurus/types" "3.10.0" + "@docusaurus/utils" "3.10.0" + "@docusaurus/utils-common" "3.10.0" + "@docusaurus/utils-validation" "3.10.0" fs-extra "^11.1.1" sitemap "^7.1.1" tslib "^2.6.0" -"@docusaurus/plugin-svgr@3.9.2": - version "3.9.2" - resolved "https://registry.npmjs.org/@docusaurus/plugin-svgr/-/plugin-svgr-3.9.2.tgz" - integrity sha512-n+1DE+5b3Lnf27TgVU5jM1d4x5tUh2oW5LTsBxJX4PsAPV0JGcmI6p3yLYtEY0LRVEIJh+8RsdQmRE66wSV8mw== +"@docusaurus/plugin-svgr@3.10.0": + version "3.10.0" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-svgr/-/plugin-svgr-3.10.0.tgz#8ada2e6dd8318d20206a9b044fc091a5794ba3f0" + integrity sha512-lNljBESaETZqVBMPqkrGchr+UPT1eZzEPLmJhz8I76BxbjqgsUnRvrq6lQJ9sYjgmgX52KB7kkgczqd2yzoswQ== dependencies: - "@docusaurus/core" "3.9.2" - "@docusaurus/types" "3.9.2" - "@docusaurus/utils" "3.9.2" - "@docusaurus/utils-validation" "3.9.2" + "@docusaurus/core" "3.10.0" + "@docusaurus/types" "3.10.0" + "@docusaurus/utils" "3.10.0" + "@docusaurus/utils-validation" "3.10.0" "@svgr/core" "8.1.0" "@svgr/webpack" "^8.1.0" tslib "^2.6.0" webpack "^5.88.1" -"@docusaurus/preset-classic@3.9.2": - version "3.9.2" - resolved "https://registry.npmjs.org/@docusaurus/preset-classic/-/preset-classic-3.9.2.tgz" - integrity sha512-IgyYO2Gvaigi21LuDIe+nvmN/dfGXAiMcV/murFqcpjnZc7jxFAxW+9LEjdPt61uZLxG4ByW/oUmX/DDK9t/8w== +"@docusaurus/preset-classic@3.10.0": + version "3.10.0" + resolved "https://registry.yarnpkg.com/@docusaurus/preset-classic/-/preset-classic-3.10.0.tgz#74b6facdaf568bcd41ec90cae9aebb7ca0ac8619" + integrity sha512-kw/Ye02Hc6xP1OdTswy8yxQEHg0fdPpyWAQRxr5b2x3h7LlG2Zgbb5BDFROnXDDMpUxB7YejlocJIE5HIEfpNA== dependencies: - "@docusaurus/core" "3.9.2" - "@docusaurus/plugin-content-blog" "3.9.2" - "@docusaurus/plugin-content-docs" "3.9.2" - "@docusaurus/plugin-content-pages" "3.9.2" - "@docusaurus/plugin-css-cascade-layers" "3.9.2" - "@docusaurus/plugin-debug" "3.9.2" - "@docusaurus/plugin-google-analytics" "3.9.2" - "@docusaurus/plugin-google-gtag" "3.9.2" - "@docusaurus/plugin-google-tag-manager" "3.9.2" - "@docusaurus/plugin-sitemap" "3.9.2" - "@docusaurus/plugin-svgr" "3.9.2" - "@docusaurus/theme-classic" "3.9.2" - "@docusaurus/theme-common" "3.9.2" - "@docusaurus/theme-search-algolia" "3.9.2" - "@docusaurus/types" "3.9.2" + "@docusaurus/core" "3.10.0" + "@docusaurus/plugin-content-blog" "3.10.0" + "@docusaurus/plugin-content-docs" "3.10.0" + "@docusaurus/plugin-content-pages" "3.10.0" + "@docusaurus/plugin-css-cascade-layers" "3.10.0" + "@docusaurus/plugin-debug" "3.10.0" + "@docusaurus/plugin-google-analytics" "3.10.0" + "@docusaurus/plugin-google-gtag" "3.10.0" + "@docusaurus/plugin-google-tag-manager" "3.10.0" + "@docusaurus/plugin-sitemap" "3.10.0" + "@docusaurus/plugin-svgr" "3.10.0" + "@docusaurus/theme-classic" "3.10.0" + "@docusaurus/theme-common" "3.10.0" + "@docusaurus/theme-search-algolia" "3.10.0" + "@docusaurus/types" "3.10.0" -"@docusaurus/theme-classic@3.9.2": - version "3.9.2" - resolved "https://registry.npmjs.org/@docusaurus/theme-classic/-/theme-classic-3.9.2.tgz" - integrity sha512-IGUsArG5hhekXd7RDb11v94ycpJpFdJPkLnt10fFQWOVxAtq5/D7hT6lzc2fhyQKaaCE62qVajOMKL7OiAFAIA== +"@docusaurus/theme-classic@3.10.0": + version "3.10.0" + resolved "https://registry.yarnpkg.com/@docusaurus/theme-classic/-/theme-classic-3.10.0.tgz#d937915c691189f27ced649c822994d839ea565b" + integrity sha512-9msCAsRdN+UG+RwPwCFb0uKy4tGoPh5YfBozXeGUtIeAgsMdn6f3G/oY861luZ3t8S2ET8S9Y/1GnpJAGWytww== dependencies: - "@docusaurus/core" "3.9.2" - "@docusaurus/logger" "3.9.2" - "@docusaurus/mdx-loader" "3.9.2" - "@docusaurus/module-type-aliases" "3.9.2" - "@docusaurus/plugin-content-blog" "3.9.2" - "@docusaurus/plugin-content-docs" "3.9.2" - "@docusaurus/plugin-content-pages" "3.9.2" - "@docusaurus/theme-common" "3.9.2" - "@docusaurus/theme-translations" "3.9.2" - "@docusaurus/types" "3.9.2" - "@docusaurus/utils" "3.9.2" - "@docusaurus/utils-common" "3.9.2" - "@docusaurus/utils-validation" "3.9.2" + "@docusaurus/core" "3.10.0" + "@docusaurus/logger" "3.10.0" + "@docusaurus/mdx-loader" "3.10.0" + "@docusaurus/module-type-aliases" "3.10.0" + "@docusaurus/plugin-content-blog" "3.10.0" + "@docusaurus/plugin-content-docs" "3.10.0" + "@docusaurus/plugin-content-pages" "3.10.0" + "@docusaurus/theme-common" "3.10.0" + "@docusaurus/theme-translations" "3.10.0" + "@docusaurus/types" "3.10.0" + "@docusaurus/utils" "3.10.0" + "@docusaurus/utils-common" "3.10.0" + "@docusaurus/utils-validation" "3.10.0" "@mdx-js/react" "^3.0.0" clsx "^2.0.0" + copy-text-to-clipboard "^3.2.0" infima "0.2.0-alpha.45" lodash "^4.17.21" nprogress "^0.2.0" @@ -1973,15 +1959,15 @@ tslib "^2.6.0" utility-types "^3.10.0" -"@docusaurus/theme-common@3.9.2": - version "3.9.2" - resolved "https://registry.npmjs.org/@docusaurus/theme-common/-/theme-common-3.9.2.tgz" - integrity sha512-6c4DAbR6n6nPbnZhY2V3tzpnKnGL+6aOsLvFL26VRqhlczli9eWG0VDUNoCQEPnGwDMhPS42UhSAnz5pThm5Ag== +"@docusaurus/theme-common@3.10.0": + version "3.10.0" + resolved "https://registry.yarnpkg.com/@docusaurus/theme-common/-/theme-common-3.10.0.tgz#70b419ccfdf62f092299354a72d1692e81be597d" + integrity sha512-Dkp1YXKn16ByCJAdIjbDIOpVb4Z66MsVD694/ilX1vAAHaVEMrVsf/NPd9VgreyFx08rJ9GqV1MtzsbTcU73Kg== dependencies: - "@docusaurus/mdx-loader" "3.9.2" - "@docusaurus/module-type-aliases" "3.9.2" - "@docusaurus/utils" "3.9.2" - "@docusaurus/utils-common" "3.9.2" + "@docusaurus/mdx-loader" "3.10.0" + "@docusaurus/module-type-aliases" "3.10.0" + "@docusaurus/utils" "3.10.0" + "@docusaurus/utils-common" "3.10.0" "@types/history" "^4.7.11" "@types/react" "*" "@types/react-router-config" "*" @@ -1991,47 +1977,48 @@ tslib "^2.6.0" utility-types "^3.10.0" -"@docusaurus/theme-live-codeblock@^3.9.2": - version "3.9.2" - resolved "https://registry.npmjs.org/@docusaurus/theme-live-codeblock/-/theme-live-codeblock-3.9.2.tgz" - integrity sha512-cgxxZh18dI5Q4iV0GLmwqXtgZbTLOnb0TYgZRiUh0mnIGbuNWFUhUYXXl5owKbDfIXFdFAiI/owJKM83howEAw== +"@docusaurus/theme-live-codeblock@^3.10.0": + version "3.10.0" + resolved "https://registry.yarnpkg.com/@docusaurus/theme-live-codeblock/-/theme-live-codeblock-3.10.0.tgz#05a38c6bfac479fd698f18f27ca06ebb126633d9" + integrity sha512-1Ycxu0dBAhEXzXPQ1dQW01aY1MNi7TCTUOBtIF0GcNrQBFj74XxhDqv/T6GxYBsaN+6QnIDs1T+D43iV2/r2hQ== dependencies: - "@docusaurus/core" "3.9.2" - "@docusaurus/theme-common" "3.9.2" - "@docusaurus/theme-translations" "3.9.2" - "@docusaurus/utils-validation" "3.9.2" + "@docusaurus/core" "3.10.0" + "@docusaurus/theme-common" "3.10.0" + "@docusaurus/theme-translations" "3.10.0" + "@docusaurus/utils-validation" "3.10.0" "@philpl/buble" "^0.19.7" clsx "^2.0.0" fs-extra "^11.1.1" react-live "^4.1.6" tslib "^2.6.0" -"@docusaurus/theme-mermaid@^3.9.2": - version "3.9.2" - resolved "https://registry.npmjs.org/@docusaurus/theme-mermaid/-/theme-mermaid-3.9.2.tgz" - integrity sha512-5vhShRDq/ntLzdInsQkTdoKWSzw8d1jB17sNPYhA/KvYYFXfuVEGHLM6nrf8MFbV8TruAHDG21Fn3W4lO8GaDw== +"@docusaurus/theme-mermaid@^3.10.0": + version "3.10.0" + resolved "https://registry.yarnpkg.com/@docusaurus/theme-mermaid/-/theme-mermaid-3.10.0.tgz#6581ccf16d27e4c02fe8c7cf15488862f27be9c8" + integrity sha512-Y2xrlwhIJ80oOZIO3PXL6A7J869splfcMI87E3NKpYsy3zJxOyV+BP1QMtGi59ajKgU868HPuyyn6J+6BZGOBg== dependencies: - "@docusaurus/core" "3.9.2" - "@docusaurus/module-type-aliases" "3.9.2" - "@docusaurus/theme-common" "3.9.2" - "@docusaurus/types" "3.9.2" - "@docusaurus/utils-validation" "3.9.2" + "@docusaurus/core" "3.10.0" + "@docusaurus/module-type-aliases" "3.10.0" + "@docusaurus/theme-common" "3.10.0" + "@docusaurus/types" "3.10.0" + "@docusaurus/utils-validation" "3.10.0" mermaid ">=11.6.0" tslib "^2.6.0" -"@docusaurus/theme-search-algolia@3.9.2": - version "3.9.2" - resolved "https://registry.npmjs.org/@docusaurus/theme-search-algolia/-/theme-search-algolia-3.9.2.tgz" - integrity sha512-GBDSFNwjnh5/LdkxCKQHkgO2pIMX1447BxYUBG2wBiajS21uj64a+gH/qlbQjDLxmGrbrllBrtJkUHxIsiwRnw== +"@docusaurus/theme-search-algolia@3.10.0": + version "3.10.0" + resolved "https://registry.yarnpkg.com/@docusaurus/theme-search-algolia/-/theme-search-algolia-3.10.0.tgz#0ff57fe58db6abde8f5ad2877e459cd2fa6e7464" + integrity sha512-f5FPKI08e3JRG63vR/o4qeuUVHUHzFzM0nnF+AkB67soAZgNsKJRf2qmUZvlQkGwlV+QFkKe4D0ANMh1jToU3g== dependencies: - "@docsearch/react" "^3.9.0 || ^4.1.0" - "@docusaurus/core" "3.9.2" - "@docusaurus/logger" "3.9.2" - "@docusaurus/plugin-content-docs" "3.9.2" - "@docusaurus/theme-common" "3.9.2" - "@docusaurus/theme-translations" "3.9.2" - "@docusaurus/utils" "3.9.2" - "@docusaurus/utils-validation" "3.9.2" + "@algolia/autocomplete-core" "^1.19.2" + "@docsearch/react" "^3.9.0 || ^4.3.2" + "@docusaurus/core" "3.10.0" + "@docusaurus/logger" "3.10.0" + "@docusaurus/plugin-content-docs" "3.10.0" + "@docusaurus/theme-common" "3.10.0" + "@docusaurus/theme-translations" "3.10.0" + "@docusaurus/utils" "3.10.0" + "@docusaurus/utils-validation" "3.10.0" algoliasearch "^5.37.0" algoliasearch-helper "^3.26.0" clsx "^2.0.0" @@ -2041,23 +2028,23 @@ tslib "^2.6.0" utility-types "^3.10.0" -"@docusaurus/theme-translations@3.9.2": - version "3.9.2" - resolved "https://registry.npmjs.org/@docusaurus/theme-translations/-/theme-translations-3.9.2.tgz" - integrity sha512-vIryvpP18ON9T9rjgMRFLr2xJVDpw1rtagEGf8Ccce4CkTrvM/fRB8N2nyWYOW5u3DdjkwKw5fBa+3tbn9P4PA== +"@docusaurus/theme-translations@3.10.0": + version "3.10.0" + resolved "https://registry.yarnpkg.com/@docusaurus/theme-translations/-/theme-translations-3.10.0.tgz#8fdc23d29bd7f907db49c36cf65e2123d96be300" + integrity sha512-L9IbFLwTc5+XdgH45iQYufLn0SVZd6BUNelDbKIFlH+E4hhjuj/XHWAFMX/w2K59rfy8wak9McOaei7BSUfRPA== dependencies: fs-extra "^11.1.1" tslib "^2.6.0" -"@docusaurus/tsconfig@^3.9.2": - version "3.9.2" - resolved "https://registry.npmjs.org/@docusaurus/tsconfig/-/tsconfig-3.9.2.tgz" - integrity sha512-j6/Fp4Rlpxsc632cnRnl5HpOWeb6ZKssDj6/XzzAzVGXXfm9Eptx3rxCC+fDzySn9fHTS+CWJjPineCR1bB5WQ== +"@docusaurus/tsconfig@^3.10.0": + version "3.10.0" + resolved "https://registry.yarnpkg.com/@docusaurus/tsconfig/-/tsconfig-3.10.0.tgz#f40a57248828f0503a5f355cf30aa59941c9baaa" + integrity sha512-TXdC3WXuPrdQAexLvjUJfnYf3YKEgEqAs5nK0Q88pRBCW7t7oN4ILvWYb3A5Z1wlSXyXGWW/mCUmLEhdWsjnDQ== -"@docusaurus/types@3.9.2": - version "3.9.2" - resolved "https://registry.npmjs.org/@docusaurus/types/-/types-3.9.2.tgz" - integrity sha512-Ux1JUNswg+EfUEmajJjyhIohKceitY/yzjRUpu04WXgvVz+fbhVC0p+R0JhvEu4ytw8zIAys2hrdpQPBHRIa8Q== +"@docusaurus/types@3.10.0": + version "3.10.0" + resolved "https://registry.yarnpkg.com/@docusaurus/types/-/types-3.10.0.tgz#a69232bba74b738fcf4671fd5f0f079366dd3d13" + integrity sha512-F0dOt3FOoO20rRaFK7whGFQZ3ggyrWEdQc/c8/UiRuzhtg4y1w9FspXH5zpCT07uMnJKBPGh+qNazbNlCQqvSw== dependencies: "@mdx-js/mdx" "^3.0.0" "@types/history" "^4.7.11" @@ -2070,38 +2057,38 @@ webpack "^5.95.0" webpack-merge "^5.9.0" -"@docusaurus/utils-common@3.9.2": - version "3.9.2" - resolved "https://registry.npmjs.org/@docusaurus/utils-common/-/utils-common-3.9.2.tgz" - integrity sha512-I53UC1QctruA6SWLvbjbhCpAw7+X7PePoe5pYcwTOEXD/PxeP8LnECAhTHHwWCblyUX5bMi4QLRkxvyZ+IT8Aw== +"@docusaurus/utils-common@3.10.0": + version "3.10.0" + resolved "https://registry.yarnpkg.com/@docusaurus/utils-common/-/utils-common-3.10.0.tgz#2a6dc76b312664fca7234d33607c085318ff1ae3" + integrity sha512-JyL7sb9QVDgYvudIS81Dv0lsWm7le0vGZSDwsztxWam1SPBqrnkvBy9UYL/amh6pbybkyYTd3CMTkO24oMlCSw== dependencies: - "@docusaurus/types" "3.9.2" + "@docusaurus/types" "3.10.0" tslib "^2.6.0" -"@docusaurus/utils-validation@3.9.2": - version "3.9.2" - resolved "https://registry.npmjs.org/@docusaurus/utils-validation/-/utils-validation-3.9.2.tgz" - integrity sha512-l7yk3X5VnNmATbwijJkexdhulNsQaNDwoagiwujXoxFbWLcxHQqNQ+c/IAlzrfMMOfa/8xSBZ7KEKDesE/2J7A== +"@docusaurus/utils-validation@3.10.0": + version "3.10.0" + resolved "https://registry.yarnpkg.com/@docusaurus/utils-validation/-/utils-validation-3.10.0.tgz#a2418d7f31980d991fd3a1f39c8aad8820b36812" + integrity sha512-c+6n2+ZPOJtWWc8Bb/EYdpSDfjYEScdCu9fB/SNjOmSCf1IdVnGf2T53o0tsz0gDRtCL90tifTL0JE/oMuP1Mw== dependencies: - "@docusaurus/logger" "3.9.2" - "@docusaurus/utils" "3.9.2" - "@docusaurus/utils-common" "3.9.2" + "@docusaurus/logger" "3.10.0" + "@docusaurus/utils" "3.10.0" + "@docusaurus/utils-common" "3.10.0" fs-extra "^11.2.0" joi "^17.9.2" js-yaml "^4.1.0" lodash "^4.17.21" tslib "^2.6.0" -"@docusaurus/utils@3.9.2": - version "3.9.2" - resolved "https://registry.npmjs.org/@docusaurus/utils/-/utils-3.9.2.tgz" - integrity sha512-lBSBiRruFurFKXr5Hbsl2thmGweAPmddhF3jb99U4EMDA5L+e5Y1rAkOS07Nvrup7HUMBDrCV45meaxZnt28nQ== +"@docusaurus/utils@3.10.0": + version "3.10.0" + resolved "https://registry.yarnpkg.com/@docusaurus/utils/-/utils-3.10.0.tgz#ea7d7b0d325b60f728decc00bb3908d00ef86faf" + integrity sha512-T3B0WTigsIthe0D4LQa2k+7bJY+c3WS+Wq2JhcznOSpn1lSN64yNtHQXboCj3QnUs1EuAZszQG1SHKu5w5ZrlA== dependencies: - "@docusaurus/logger" "3.9.2" - "@docusaurus/types" "3.9.2" - "@docusaurus/utils-common" "3.9.2" + "@docusaurus/logger" "3.10.0" + "@docusaurus/types" "3.10.0" + "@docusaurus/utils-common" "3.10.0" escape-string-regexp "^4.0.0" - execa "5.1.1" + execa "^5.1.1" file-loader "^6.2.0" fs-extra "^11.1.1" github-slugger "^1.5.0" @@ -2618,11 +2605,6 @@ "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" -"@jsdevtools/ono@^7.1.3": - version "7.1.3" - resolved "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz" - integrity sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg== - "@jsonjoy.com/base64@^1.1.2": version "1.1.2" resolved "https://registry.npmjs.org/@jsonjoy.com/base64/-/base64-1.1.2.tgz" @@ -2789,11 +2771,6 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" -"@opentelemetry/api@1.9.0": - version "1.9.0" - resolved "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz" - integrity sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg== - "@parcel/watcher-android-arm64@2.5.6": version "2.5.6" resolved "https://registry.yarnpkg.com/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.6.tgz#5f32e0dba356f4ac9a11068d2a5c134ca3ba6564" @@ -3009,23 +2986,23 @@ "@rc-component/util" "^1.2.1" clsx "^2.1.1" -"@rc-component/form@~1.7.1": - version "1.7.1" - resolved "https://registry.yarnpkg.com/@rc-component/form/-/form-1.7.1.tgz#baf18de01e649415c39e895a2c2fc9c61e1f2e23" - integrity sha512-Uhw0FPvJ+Ko4xBxhvziqmqzIuO0YvVBzVyFGNAI9fMCz4r4DfrYK6PRIN6CkFqM0vdAX9sr4JGA1/h/VzpA1cA== +"@rc-component/form@~1.8.0": + version "1.8.0" + resolved "https://registry.yarnpkg.com/@rc-component/form/-/form-1.8.0.tgz#ed565337a69ebb6cfa20d1ad0dd58e443a71313a" + integrity sha512-eUD5KKYnIZWmJwRA0vnyO/ovYUfHGU1svydY1OrqU5fw8Oz9Tdqvxvrlh0wl6xI/EW69dT7II49xpgOWzK3T5A== dependencies: "@rc-component/async-validator" "^5.1.0" "@rc-component/util" "^1.6.2" clsx "^2.1.1" -"@rc-component/image@~1.6.0": - version "1.6.0" - resolved "https://registry.npmjs.org/@rc-component/image/-/image-1.6.0.tgz" - integrity sha512-tSfn2ZE/oP082g4QIOxeehkmgnXB7R+5AFj/lIFr4k7pEuxHBdyGIq9axoCY9qea8NN0DY6p4IB/F07tLqaT5A== +"@rc-component/image@~1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@rc-component/image/-/image-1.9.0.tgz#110785d735d20336afcdbac84e8fbfd059a7a44e" + integrity sha512-khF7w7xkBH5B1bsBcI1FSUZdkyd1aqpl2eYyILCqCzzQH3XdfehGUaZTnptyaJJfs09/R5hv9jXWyazOMFIClQ== dependencies: "@rc-component/motion" "^1.0.0" "@rc-component/portal" "^2.1.2" - "@rc-component/util" "^1.3.0" + "@rc-component/util" "^1.10.1" clsx "^2.1.1" "@rc-component/input-number@~1.6.2": @@ -3075,10 +3052,10 @@ dependencies: "@babel/runtime" "^7.18.0" -"@rc-component/motion@^1.0.0", "@rc-component/motion@^1.1.3", "@rc-component/motion@^1.1.4", "@rc-component/motion@^1.3.1": - version "1.3.1" - resolved "https://registry.yarnpkg.com/@rc-component/motion/-/motion-1.3.1.tgz#1e56b06841ee677261251e6e69fedc8d73e65b22" - integrity sha512-Wo1mkd0tCcHtvYvpPOmlYJz546z16qlsiwaygmW7NPJpOZOF9GBjhGzdzZSsC2lEJ1IUkWLF4gMHlRA1aSA+Yw== +"@rc-component/motion@^1.0.0", "@rc-component/motion@^1.1.3", "@rc-component/motion@^1.1.4", "@rc-component/motion@^1.3.2": + version "1.3.2" + resolved "https://registry.yarnpkg.com/@rc-component/motion/-/motion-1.3.2.tgz#bd96e0fd16ee9d98c1d9be14198f003e367d8feb" + integrity sha512-itfd+GztzJYAb04Z4RkEub1TbJAfZc2Iuy8p44U44xD1F5+fNYFKI3897ijlbIyfvXkTmMm+KGcjkQQGMHywEQ== dependencies: "@rc-component/util" "^1.2.0" clsx "^2.1.1" @@ -3117,10 +3094,10 @@ "@rc-component/util" "^1.3.0" clsx "^2.1.1" -"@rc-component/picker@~1.9.0": - version "1.9.0" - resolved "https://registry.npmjs.org/@rc-component/picker/-/picker-1.9.0.tgz" - integrity sha512-OLisdk8AWVCG9goBU1dWzuH5QlBQk8jktmQ6p0/IyBFwdKGwyIZOSjnBYo8hooHiTdl0lU+wGf/OfMtVBw02KQ== +"@rc-component/picker@~1.9.1": + version "1.9.1" + resolved "https://registry.yarnpkg.com/@rc-component/picker/-/picker-1.9.1.tgz#7ffcb1e4d4655fe2f3d712773e1d3ab9cd5c2a5c" + integrity sha512-9FBYYsvH3HMLICaPDA/1Th5FLaDkFa7qAtangIdlhKb3ZALaR745e9PsOhheJb6asS4QXc12ffiAcjdkZ4C5/g== dependencies: "@rc-component/overflow" "^1.0.0" "@rc-component/resize-observer" "^1.0.0" @@ -3159,10 +3136,10 @@ "@rc-component/util" "^1.3.0" clsx "^2.1.1" -"@rc-component/resize-observer@^1.0.0", "@rc-component/resize-observer@^1.0.1", "@rc-component/resize-observer@^1.1.1": - version "1.1.1" - resolved "https://registry.npmjs.org/@rc-component/resize-observer/-/resize-observer-1.1.1.tgz" - integrity sha512-NfXXMmiR+SmUuKE1NwJESzEUYUFWIDUn2uXpxCTOLwiRUUakd62DRNFjRJArgzyFW8S5rsL4aX5XlyIXyC/vRA== +"@rc-component/resize-observer@^1.0.0", "@rc-component/resize-observer@^1.0.1", "@rc-component/resize-observer@^1.1.1", "@rc-component/resize-observer@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@rc-component/resize-observer/-/resize-observer-1.1.2.tgz#5897e65d7fed5c6e768dcfd8bdec181a3309a98f" + integrity sha512-t/Bb0W8uvL4PYKAB3YcChC+DlHh0Wt5kM7q/J+0qpVEUMLe7Hk5zuvc9km0hMnTFPSx5Z7Wu/fzCLN6erVLE8Q== dependencies: "@rc-component/util" "^1.2.0" @@ -3176,10 +3153,10 @@ "@rc-component/util" "^1.3.0" clsx "^2.1.1" -"@rc-component/select@~1.6.0", "@rc-component/select@~1.6.14": - version "1.6.14" - resolved "https://registry.yarnpkg.com/@rc-component/select/-/select-1.6.14.tgz#61028c0abe02d2a909935b5cb586374968196c96" - integrity sha512-T1IWeLlSas7Z/igZtPtJ/bweCxMMkXIGKQBtnigK+I/n1AVNjCs+ZdL3Fj42mq3uqm4sd1uzeQLZkdCqR26ADw== +"@rc-component/select@~1.6.0", "@rc-component/select@~1.6.15": + version "1.6.15" + resolved "https://registry.yarnpkg.com/@rc-component/select/-/select-1.6.15.tgz#de2a3c8b020834cabd600b52de573a328c061eef" + integrity sha512-SyVCWnqxCQZZcQvQJ/CxSjx2bGma6ds/HtnpkIfZVnt6RoEgbqUmHgD6vrzNarNXwbLXerwVzWwq8F3d1sst7g== dependencies: "@rc-component/overflow" "^1.0.0" "@rc-component/trigger" "^3.0.0" @@ -3273,10 +3250,10 @@ "@rc-component/util" "^1.4.0" clsx "^2.1.1" -"@rc-component/tree@~1.2.0", "@rc-component/tree@~1.2.3": - version "1.2.3" - resolved "https://registry.yarnpkg.com/@rc-component/tree/-/tree-1.2.3.tgz#a70ae847a768763f4f461375620c1feccfcc933a" - integrity sha512-mG8hF2ogQcKaEpfyxzPvMWqqkptofd7Sf+YiXOpPzuXLTLwNKfLDJtysc1/oybopbnzxNqWh2Vgwi+GYwNIb7w== +"@rc-component/tree@~1.2.0", "@rc-component/tree@~1.2.4": + version "1.2.4" + resolved "https://registry.yarnpkg.com/@rc-component/tree/-/tree-1.2.4.tgz#cb4f7d818118b3447763e74d3a82fba6454c7317" + integrity sha512-5Gli43+m4R7NhpYYz3Z61I6LOw9yI6CNChxgVtvrO6xB1qML7iE6QMLVMB3+FTjo2yF6uFdAHtqWPECz/zbX5w== dependencies: "@rc-component/motion" "^1.0.0" "@rc-component/util" "^1.8.1" @@ -3302,10 +3279,10 @@ "@rc-component/util" "^1.3.0" clsx "^2.1.1" -"@rc-component/util@^1.1.0", "@rc-component/util@^1.2.0", "@rc-component/util@^1.2.1", "@rc-component/util@^1.3.0", "@rc-component/util@^1.4.0", "@rc-component/util@^1.6.2", "@rc-component/util@^1.7.0", "@rc-component/util@^1.8.1", "@rc-component/util@^1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@rc-component/util/-/util-1.9.0.tgz#ec5fe657a98554f26ef761345ca4b745be00af0e" - integrity sha512-5uW6AfhIigCWeEQDthTozlxiT4Prn6xYQWeO0xokjcaa186OtwPRHBZJ2o0T0FhbjGhZ3vXdbkv0sx3gAYW7Vg== +"@rc-component/util@^1.1.0", "@rc-component/util@^1.10.1", "@rc-component/util@^1.2.0", "@rc-component/util@^1.2.1", "@rc-component/util@^1.3.0", "@rc-component/util@^1.4.0", "@rc-component/util@^1.6.2", "@rc-component/util@^1.7.0", "@rc-component/util@^1.8.1", "@rc-component/util@^1.9.0": + version "1.10.1" + resolved "https://registry.yarnpkg.com/@rc-component/util/-/util-1.10.1.tgz#213c84c77e8b2001095530d3b0dc47c49c34ffe3" + integrity sha512-q++9S6rUa5Idb/xIBNz6jtvumw5+O5YV5V0g4iK9mn9jWs4oGJheE3ZN1kAnE723AXyaD8v95yeOASmdk8Jnng== dependencies: is-mobile "^5.0.0" react-is "^18.2.0" @@ -3320,33 +3297,36 @@ "@rc-component/util" "^1.4.0" clsx "^2.1.1" -"@redocly/ajv@^8.11.2": - version "8.17.2" - resolved "https://registry.npmjs.org/@redocly/ajv/-/ajv-8.17.2.tgz" - integrity sha512-rcbDZOfXAgGEJeJ30aWCVVJvxV9ooevb/m1/SFblO2qHs4cqTk178gx7T/vdslf57EA4lTofrwsq5K8rxK9g+g== +"@redocly/ajv@^8.18.0": + version "8.18.3" + resolved "https://registry.yarnpkg.com/@redocly/ajv/-/ajv-8.18.3.tgz#a925753d9a33375219f1b2ba91aef320f9929577" + integrity sha512-l42u0of3hY98sN2A+M4qTX1O/KrpgGH32Hu9kP2GtHyD5Dfqq86PKFLe5dwaD8DEnNmlOlll2BAmeEtf0DaySg== dependencies: fast-deep-equal "^3.1.3" fast-uri "^3.0.1" json-schema-traverse "^1.0.0" require-from-string "^2.0.2" -"@redocly/config@^0.22.0": - version "0.22.2" - resolved "https://registry.npmjs.org/@redocly/config/-/config-0.22.2.tgz" - integrity sha512-roRDai8/zr2S9YfmzUfNhKjOF0NdcOIqF7bhf4MVC5UxpjIysDjyudvlAiVbpPHp3eDRWbdzUgtkK1a7YiDNyQ== - -"@redocly/openapi-core@^1.34.3": - version "1.34.6" - resolved "https://registry.npmjs.org/@redocly/openapi-core/-/openapi-core-1.34.6.tgz" - integrity sha512-2+O+riuIUgVSuLl3Lyh5AplWZyVMNuG2F98/o6NrutKJfW4/GTZdPpZlIphS0HGgcOHgmWcCSHj+dWFlZaGSHw== +"@redocly/config@^0.48.0": + version "0.48.0" + resolved "https://registry.yarnpkg.com/@redocly/config/-/config-0.48.0.tgz#d4ff534b0e4edfc6fc3d3fc93217f29471cfbc1e" + integrity sha512-8W3wz+Q7y4e9klJWlYOvQWK5r7P2Mo589vcjtlT5coOxsyAdt53k8Vb8iAqnRiGWExbjBQmSbL2XbuU747Nf6Q== dependencies: - "@redocly/ajv" "^8.11.2" - "@redocly/config" "^0.22.0" + json-schema-to-ts "2.7.2" + +"@redocly/openapi-core@^2.25.2": + version "2.28.1" + resolved "https://registry.yarnpkg.com/@redocly/openapi-core/-/openapi-core-2.28.1.tgz#a69fe41ef0bb4817296e96984bb191db8ab50440" + integrity sha512-PXulQY+lUJzeLWfhtJ8UPBFaMvlPDvW/dkozDhUAlYDotEYNMOaKFbJxKcrPCtRYtZ0TJsh5MohdcDLCBAJbFg== + dependencies: + "@redocly/ajv" "^8.18.0" + "@redocly/config" "^0.48.0" + ajv "npm:@redocly/ajv@8.18.0" + ajv-formats "^3.0.1" colorette "^1.2.0" - https-proxy-agent "^7.0.5" js-levenshtein "^1.1.6" js-yaml "^4.1.0" - minimatch "^5.0.1" + picomatch "^4.0.4" pluralize "^8.0.0" yaml-ast-parser "0.0.43" @@ -3362,81 +3342,81 @@ redux-thunk "^3.1.0" reselect "^5.1.0" -"@rspack/binding-darwin-arm64@1.7.6": - version "1.7.6" - resolved "https://registry.yarnpkg.com/@rspack/binding-darwin-arm64/-/binding-darwin-arm64-1.7.6.tgz#10144f3841be88ea25c71a537df6b038ae84a87a" - integrity sha512-NZ9AWtB1COLUX1tA9HQQvWpTy07NSFfKBU8A6ylWd5KH8AePZztpNgLLAVPTuNO4CZXYpwcoclf8jG/luJcQdQ== +"@rspack/binding-darwin-arm64@1.7.11": + version "1.7.11" + resolved "https://registry.yarnpkg.com/@rspack/binding-darwin-arm64/-/binding-darwin-arm64-1.7.11.tgz#ea43ac25a9ff99a9faf6c820f5d174a32974e95c" + integrity sha512-oduECiZVqbO5zlVw+q7Vy65sJFth99fWPTyucwvLJJtJkPL5n17Uiql2cYP6Ijn0pkqtf1SXgK8WjiKLG5bIig== -"@rspack/binding-darwin-x64@1.7.6": - version "1.7.6" - resolved "https://registry.yarnpkg.com/@rspack/binding-darwin-x64/-/binding-darwin-x64-1.7.6.tgz#70588e30e1fdcd3149e09fe6798eb95609ad814b" - integrity sha512-J2g6xk8ZS7uc024dNTGTHxoFzFovAZIRixUG7PiciLKTMP78svbSSWrmW6N8oAsAkzYfJWwQpVgWfFNRHvYxSw== +"@rspack/binding-darwin-x64@1.7.11": + version "1.7.11" + resolved "https://registry.yarnpkg.com/@rspack/binding-darwin-x64/-/binding-darwin-x64-1.7.11.tgz#5c724d91559d642d4a5e6aa4ed380c30bd0f64c0" + integrity sha512-a1+TtTE9ap6RalgFi7FGIgkJP6O4Vy6ctv+9WGJy53E4kuqHR0RygzaiVxCI/GMc/vBT9vY23hyrpWb3d1vtXA== -"@rspack/binding-linux-arm64-gnu@1.7.6": - version "1.7.6" - resolved "https://registry.yarnpkg.com/@rspack/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.7.6.tgz#322e7fe54fccefd57603d4730130bef311b8dd60" - integrity sha512-eQfcsaxhFrv5FmtaA7+O1F9/2yFDNIoPZzV/ZvqvFz5bBXVc4FAm/1fVpBg8Po/kX1h0chBc7Xkpry3cabFW8w== +"@rspack/binding-linux-arm64-gnu@1.7.11": + version "1.7.11" + resolved "https://registry.yarnpkg.com/@rspack/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.7.11.tgz#429119939bbe9d51a72caf99cffb8febe0f870fe" + integrity sha512-P0QrGRPbTWu6RKWfN0bDtbnEps3rXH0MWIMreZABoUrVmNQKtXR6e73J3ub6a+di5s2+K0M2LJ9Bh2/H4UsDUA== -"@rspack/binding-linux-arm64-musl@1.7.6": - version "1.7.6" - resolved "https://registry.yarnpkg.com/@rspack/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.7.6.tgz#4444dfe045323f341c7c82fdb7dce5809fea51c1" - integrity sha512-DfQXKiyPIl7i1yECHy4eAkSmlUzzsSAbOjgMuKn7pudsWf483jg0UUYutNgXSlBjc/QSUp7906Cg8oty9OfwPA== +"@rspack/binding-linux-arm64-musl@1.7.11": + version "1.7.11" + resolved "https://registry.yarnpkg.com/@rspack/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.7.11.tgz#d939b8c2c5bf35380d3c860402f7063031ef469a" + integrity sha512-6ky7R43VMjWwmx3Yx7Jl7faLBBMAgMDt+/bN35RgwjiPgsIByz65EwytUVuW9rikB43BGHvA/eqlnjLrUzNBqw== -"@rspack/binding-linux-x64-gnu@1.7.6": - version "1.7.6" - resolved "https://registry.yarnpkg.com/@rspack/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.7.6.tgz#c31bf72789c71eeee75cd3cb06d6a6e5449e1bd1" - integrity sha512-NdA+2X3lk2GGrMMnTGyYTzM3pn+zNjaqXqlgKmFBXvjfZqzSsKq3pdD1KHZCd5QHN+Fwvoszj0JFsquEVhE1og== +"@rspack/binding-linux-x64-gnu@1.7.11": + version "1.7.11" + resolved "https://registry.yarnpkg.com/@rspack/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.7.11.tgz#03567317a7e8cfc62d994dcf9683f932fd22054a" + integrity sha512-cuOJMfCOvb2Wgsry5enXJ3iT1FGUjdPqtGUBVupQlEG4ntSYsQ2PtF4wIDVasR3wdxC5nQbipOrDiN/u6fYsdQ== -"@rspack/binding-linux-x64-musl@1.7.6": - version "1.7.6" - resolved "https://registry.yarnpkg.com/@rspack/binding-linux-x64-musl/-/binding-linux-x64-musl-1.7.6.tgz#20f8ad786d581af7a7c2802220b533ce531fb4c6" - integrity sha512-rEy6MHKob02t/77YNgr6dREyJ0e0tv1X6Xsg8Z5E7rPXead06zefUbfazj4RELYySWnM38ovZyJAkPx/gOn3VA== +"@rspack/binding-linux-x64-musl@1.7.11": + version "1.7.11" + resolved "https://registry.yarnpkg.com/@rspack/binding-linux-x64-musl/-/binding-linux-x64-musl-1.7.11.tgz#d93c93ea796eae1572b2353a50d58cc6218c53b6" + integrity sha512-CoK37hva4AmHGh3VCsQXmGr40L36m1/AdnN5LEjUX6kx5rEH7/1nEBN6Ii72pejqDVvk9anEROmPDiPw10tpFg== -"@rspack/binding-wasm32-wasi@1.7.6": - version "1.7.6" - resolved "https://registry.yarnpkg.com/@rspack/binding-wasm32-wasi/-/binding-wasm32-wasi-1.7.6.tgz#76ae0f9f584a054e109b83ec02516ce75dcff8e6" - integrity sha512-YupOrz0daSG+YBbCIgpDgzfMM38YpChv+afZpaxx5Ml7xPeAZIIdgWmLHnQ2rts73N2M1NspAiBwV00Xx0N4Vg== +"@rspack/binding-wasm32-wasi@1.7.11": + version "1.7.11" + resolved "https://registry.yarnpkg.com/@rspack/binding-wasm32-wasi/-/binding-wasm32-wasi-1.7.11.tgz#c90235032fb14de50baf535592069923c1308f4e" + integrity sha512-OtrmnPUVJMxjNa3eDMfHyPdtlLRmmp/aIm0fQHlAOATbZvlGm12q7rhPW5BXTu1yh+1rQ1/uqvz+SzKEZXuJaQ== dependencies: "@napi-rs/wasm-runtime" "1.0.7" -"@rspack/binding-win32-arm64-msvc@1.7.6": - version "1.7.6" - resolved "https://registry.yarnpkg.com/@rspack/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.7.6.tgz#4e7f70338dfcc8efa3a67d72064562eb9f0662cb" - integrity sha512-INj7aVXjBvlZ84kEhSK4kJ484ub0i+BzgnjDWOWM1K+eFYDZjLdAsQSS3fGGXwVc3qKbPIssFfnftATDMTEJHQ== +"@rspack/binding-win32-arm64-msvc@1.7.11": + version "1.7.11" + resolved "https://registry.yarnpkg.com/@rspack/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.7.11.tgz#0afcfde6a77cdf6fa6a85de4f8a39b94a593aab2" + integrity sha512-lObFW6e5lCWNgTBNwT//yiEDbsxm9QG4BYUojqeXxothuzJ/L6ibXz6+gLMvbOvLGV3nKgkXmx8GvT9WDKR0mA== -"@rspack/binding-win32-ia32-msvc@1.7.6": - version "1.7.6" - resolved "https://registry.yarnpkg.com/@rspack/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.7.6.tgz#efeb807c624536361467dfec0898705213fbd997" - integrity sha512-lXGvC+z67UMcw58In12h8zCa9IyYRmuptUBMItQJzu+M278aMuD1nETyGLL7e4+OZ2lvrnnBIcjXN1hfw2yRzw== +"@rspack/binding-win32-ia32-msvc@1.7.11": + version "1.7.11" + resolved "https://registry.yarnpkg.com/@rspack/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.7.11.tgz#46606834538e84cd0f95f19089695ab122d69586" + integrity sha512-0pYGnZd8PPqNR68zQ8skamqNAXEA1sUfXuAdYcknIIRq2wsbiwFzIc0Pov1cIfHYab37G7sSIPBiOUdOWF5Ivw== -"@rspack/binding-win32-x64-msvc@1.7.6": - version "1.7.6" - resolved "https://registry.yarnpkg.com/@rspack/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.7.6.tgz#48014313c224e9fce31d02134c81525a7cb7f891" - integrity sha512-zeUxEc0ZaPpmaYlCeWcjSJUPuRRySiSHN23oJ2Xyw0jsQ01Qm4OScPdr0RhEOFuK/UE+ANyRtDo4zJsY52Hadw== +"@rspack/binding-win32-x64-msvc@1.7.11": + version "1.7.11" + resolved "https://registry.yarnpkg.com/@rspack/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.7.11.tgz#e486a33fc1227ec9cbd70439ef1b32ead1faec68" + integrity sha512-EeQXayoQk/uBkI3pdoXfQBXNIUrADq56L3s/DFyM2pJeUDrWmhfIw2UFIGkYPTMSCo8F2JcdcGM32FGJrSnU0Q== -"@rspack/binding@1.7.6": - version "1.7.6" - resolved "https://registry.yarnpkg.com/@rspack/binding/-/binding-1.7.6.tgz#d61b241c0dbb4e395f7753de74a2fe760891d4f2" - integrity sha512-/NrEcfo8Gx22hLGysanrV6gHMuqZSxToSci/3M4kzEQtF5cPjfOv5pqeLK/+B6cr56ul/OmE96cCdWcXeVnFjQ== +"@rspack/binding@1.7.11": + version "1.7.11" + resolved "https://registry.yarnpkg.com/@rspack/binding/-/binding-1.7.11.tgz#30f3e87242d9dcb3744edc22752cf24a9ceb4d61" + integrity sha512-2MGdy2s2HimsDT444Bp5XnALzNRxuBNc7y0JzyuqKbHBywd4x2NeXyhWXXoxufaCFu5PBc9Qq9jyfjW2Aeh06Q== optionalDependencies: - "@rspack/binding-darwin-arm64" "1.7.6" - "@rspack/binding-darwin-x64" "1.7.6" - "@rspack/binding-linux-arm64-gnu" "1.7.6" - "@rspack/binding-linux-arm64-musl" "1.7.6" - "@rspack/binding-linux-x64-gnu" "1.7.6" - "@rspack/binding-linux-x64-musl" "1.7.6" - "@rspack/binding-wasm32-wasi" "1.7.6" - "@rspack/binding-win32-arm64-msvc" "1.7.6" - "@rspack/binding-win32-ia32-msvc" "1.7.6" - "@rspack/binding-win32-x64-msvc" "1.7.6" + "@rspack/binding-darwin-arm64" "1.7.11" + "@rspack/binding-darwin-x64" "1.7.11" + "@rspack/binding-linux-arm64-gnu" "1.7.11" + "@rspack/binding-linux-arm64-musl" "1.7.11" + "@rspack/binding-linux-x64-gnu" "1.7.11" + "@rspack/binding-linux-x64-musl" "1.7.11" + "@rspack/binding-wasm32-wasi" "1.7.11" + "@rspack/binding-win32-arm64-msvc" "1.7.11" + "@rspack/binding-win32-ia32-msvc" "1.7.11" + "@rspack/binding-win32-x64-msvc" "1.7.11" -"@rspack/core@^1.5.0": - version "1.7.6" - resolved "https://registry.yarnpkg.com/@rspack/core/-/core-1.7.6.tgz#d43a1c4103248cbe4011902906c6d7427b80f3a6" - integrity sha512-Iax6UhrfZqJajA778c1d5DBFbSIqPOSrI34kpNIiNpWd8Jq7mFIa+Z60SQb5ZQDZuUxcCZikjz5BxinFjTkg7Q== +"@rspack/core@^1.7.10": + version "1.7.11" + resolved "https://registry.yarnpkg.com/@rspack/core/-/core-1.7.11.tgz#8d7d77db3b71332afd22a9c90904fe18a6832e2c" + integrity sha512-rsD9b+Khmot5DwCMiB3cqTQo53ioPG3M/A7BySu8+0+RS7GCxKm+Z+mtsjtG/vsu4Tn2tcqCdZtA3pgLoJB+ew== dependencies: "@module-federation/runtime-tools" "0.22.0" - "@rspack/binding" "1.7.6" + "@rspack/binding" "1.7.11" "@rspack/lite-tapable" "1.1.0" "@rspack/lite-tapable@1.1.0": @@ -3738,26 +3718,26 @@ "@svgr/plugin-jsx" "8.1.0" "@svgr/plugin-svgo" "8.1.0" -"@swagger-api/apidom-ast@^1.6.0": - version "1.6.0" - resolved "https://registry.yarnpkg.com/@swagger-api/apidom-ast/-/apidom-ast-1.6.0.tgz#c72f345023435e4033a5c67cdc064a51f75cd799" - integrity sha512-ez1KnBdAzoh5a6ijDXzu5nADkVZXlnL1RkLl8n2u2tjiNg9597xxmFdEHLVa31Vxr1yYj0WtYGLA5e2Kp0KNrQ== +"@swagger-api/apidom-ast@^1.10.2": + version "1.10.2" + resolved "https://registry.yarnpkg.com/@swagger-api/apidom-ast/-/apidom-ast-1.10.2.tgz#fd7b49929bdd8ca07c247efeaae3c47c86d7da68" + integrity sha512-vTl8gWyeZaj887/NSWYs3as4K8wXHar5wY/606XRBjR2UgmJBokBgKjq7S23LW9tsYjsT4MjQKC8idjgw17xvg== dependencies: "@babel/runtime-corejs3" "^7.26.10" - "@swagger-api/apidom-error" "^1.6.0" + "@swagger-api/apidom-error" "^1.10.2" "@types/ramda" "~0.30.0" ramda "~0.30.0" ramda-adjunct "^5.0.0" unraw "^3.0.0" -"@swagger-api/apidom-core@^1.6.0": - version "1.6.0" - resolved "https://registry.yarnpkg.com/@swagger-api/apidom-core/-/apidom-core-1.6.0.tgz#4eb1ff8955ec623d6c1a9e5a5d3fb34714667cb9" - integrity sha512-gA1MVoXe19sjFLKGkWxp5VvSw3Tk0CSChfItJjFeFHpLSGrfm+LlXp37TmNSns53Ky0F7x7TB/5kAX5I/TO4xw== +"@swagger-api/apidom-core@^1.10.2": + version "1.10.2" + resolved "https://registry.yarnpkg.com/@swagger-api/apidom-core/-/apidom-core-1.10.2.tgz#1ecf2808a1e3b1814e7f133e5c94cdd9fd5b9dcd" + integrity sha512-qryNBGHNWDvSRyK1w5rox0UOrHrVBjZOHgeXFpGHF+oBO7ntSc/H7BSiYMDR+KQESkzMcAxn4tZMLYItaBt06w== dependencies: "@babel/runtime-corejs3" "^7.26.10" - "@swagger-api/apidom-ast" "^1.6.0" - "@swagger-api/apidom-error" "^1.6.0" + "@swagger-api/apidom-ast" "^1.10.2" + "@swagger-api/apidom-error" "^1.10.2" "@types/ramda" "~0.30.0" minim "~0.23.8" ramda "~0.30.0" @@ -3765,319 +3745,319 @@ short-unique-id "^5.3.2" ts-mixer "^6.0.3" -"@swagger-api/apidom-error@^1.6.0": - version "1.6.0" - resolved "https://registry.yarnpkg.com/@swagger-api/apidom-error/-/apidom-error-1.6.0.tgz#e813c5491142bbb2518fed71133b28407e0c846a" - integrity sha512-xp/cQ1xQ/4Vd/hhQfONK7ea9oVc3JUXAYyfRzvDR0lxISly/SyD2jMcqXzHtrylBAnv7V2HSsbC1BWo7ZJDLSQ== +"@swagger-api/apidom-error@^1.10.2": + version "1.10.2" + resolved "https://registry.yarnpkg.com/@swagger-api/apidom-error/-/apidom-error-1.10.2.tgz#a017b9653d22414908d664468f12634841f37696" + integrity sha512-SWyPyL5xwTUsDzPi0A5zwTFwqPezvlwj4opEqruqjESNTYupUA7+vt4Mdj7IlDaRYRG1qyCWQgKhIBXznVUD4w== dependencies: "@babel/runtime-corejs3" "^7.20.7" -"@swagger-api/apidom-json-pointer@^1.6.0": - version "1.6.0" - resolved "https://registry.yarnpkg.com/@swagger-api/apidom-json-pointer/-/apidom-json-pointer-1.6.0.tgz#21aad5634d997a7ad8976f1307393ec4d8c1fef0" - integrity sha512-RO6P5Gt64AnthGXKeqIFjQCLVFbAJvLYAb67TkvRQ9US4lNixFtFsYJnhLCC4ymz4dTT1hacG0cmTRGcEHF9ig== +"@swagger-api/apidom-json-pointer@^1.10.2": + version "1.10.2" + resolved "https://registry.yarnpkg.com/@swagger-api/apidom-json-pointer/-/apidom-json-pointer-1.10.2.tgz#f454e11606759432ee87432216eb65703a76057f" + integrity sha512-zySHPqIXF4HZ3VWbHwTxO+H1e9dJw7mGHzoX+tZjx5wVyLQO3kZDCAAXzz3c3/TIY21Y2Zkpkez3q9hjFyuLvQ== dependencies: "@babel/runtime-corejs3" "^7.26.10" - "@swagger-api/apidom-core" "^1.6.0" - "@swagger-api/apidom-error" "^1.6.0" + "@swagger-api/apidom-core" "^1.10.2" + "@swagger-api/apidom-error" "^1.10.2" "@swaggerexpert/json-pointer" "^2.10.1" -"@swagger-api/apidom-ns-api-design-systems@^1.6.0": - version "1.6.0" - resolved "https://registry.yarnpkg.com/@swagger-api/apidom-ns-api-design-systems/-/apidom-ns-api-design-systems-1.6.0.tgz#9d1b17c3b660fa310f31e77da8b3fc53b430ad83" - integrity sha512-EYJfQ4JYuUo2J4QiiLnA/8LmM1k7AQcf1XVE+NrIpZ1160GIzqE+W5uOXkhAOImkP2Cb7EZZdE2cFE/tMYxNvw== +"@swagger-api/apidom-ns-api-design-systems@^1.10.2": + version "1.10.2" + resolved "https://registry.yarnpkg.com/@swagger-api/apidom-ns-api-design-systems/-/apidom-ns-api-design-systems-1.10.2.tgz#0bea51cbfbb86ec272890c58cd771876329368e2" + integrity sha512-MsZ4GWmWN7wkWv7G9Pwk8sHU1j0bwk7xoGeaZmNCylbTfYvGkg6jJGMHdAdQNCQXbbpfLeKt1O+3YCN//JUQ7w== dependencies: "@babel/runtime-corejs3" "^7.26.10" - "@swagger-api/apidom-core" "^1.6.0" - "@swagger-api/apidom-error" "^1.6.0" - "@swagger-api/apidom-ns-openapi-3-1" "^1.6.0" + "@swagger-api/apidom-core" "^1.10.2" + "@swagger-api/apidom-error" "^1.10.2" + "@swagger-api/apidom-ns-openapi-3-1" "^1.10.2" "@types/ramda" "~0.30.0" ramda "~0.30.0" ramda-adjunct "^5.0.0" ts-mixer "^6.0.3" -"@swagger-api/apidom-ns-arazzo-1@^1.6.0": - version "1.6.0" - resolved "https://registry.yarnpkg.com/@swagger-api/apidom-ns-arazzo-1/-/apidom-ns-arazzo-1-1.6.0.tgz#0eb9ef09046e347df120c74f67462b623637c2a5" - integrity sha512-5rF8PyBiIHh6NfC5Y0WypW11X6hQIWr88EKNOQbBuT/nnzAsOznrUCfQ99FYGLucwdOHaMIBn/b/n4ejGBto/A== +"@swagger-api/apidom-ns-arazzo-1@^1.10.2": + version "1.10.2" + resolved "https://registry.yarnpkg.com/@swagger-api/apidom-ns-arazzo-1/-/apidom-ns-arazzo-1-1.10.2.tgz#419ac738a943f4990a770dcfd521f0fa6122b08f" + integrity sha512-fQSwDlIR85tbnLXAjtV/ypSGUBfrzFcZ4NbH6BL1DSTR4uEunVxAULdD4wlhCt9gGNDl/zxZD3vQtlYDkXDFmw== dependencies: "@babel/runtime-corejs3" "^7.26.10" - "@swagger-api/apidom-core" "^1.6.0" - "@swagger-api/apidom-ns-json-schema-2020-12" "^1.6.0" + "@swagger-api/apidom-core" "^1.10.2" + "@swagger-api/apidom-ns-json-schema-2020-12" "^1.10.2" "@types/ramda" "~0.30.0" ramda "~0.30.0" ramda-adjunct "^5.0.0" ts-mixer "^6.0.3" -"@swagger-api/apidom-ns-asyncapi-2@^1.6.0": - version "1.6.0" - resolved "https://registry.yarnpkg.com/@swagger-api/apidom-ns-asyncapi-2/-/apidom-ns-asyncapi-2-1.6.0.tgz#c5a45530720429c1bc2513b1d9f7704454d4c176" - integrity sha512-tOodfX+o7lonEAnSAxet7nCayW+EqtKPegT06WXt7Llq1LS9eYZ9YzXdFgIwCm8UzfEpZdVLqtxbdLX9vuUtSg== +"@swagger-api/apidom-ns-asyncapi-2@^1.10.2": + version "1.10.2" + resolved "https://registry.yarnpkg.com/@swagger-api/apidom-ns-asyncapi-2/-/apidom-ns-asyncapi-2-1.10.2.tgz#37c5e15a847a3dbefb5c002e3033a75476b3744f" + integrity sha512-obWHe3pyAj65Nf9ISwnbtJ4C5mZ15C6mtQXxzHVW5maVZqlqt3s/YbPY87EqK9ArdNOwOZHkQt2Uth02GMmjxA== dependencies: "@babel/runtime-corejs3" "^7.26.10" - "@swagger-api/apidom-core" "^1.6.0" - "@swagger-api/apidom-ns-json-schema-draft-7" "^1.6.0" + "@swagger-api/apidom-core" "^1.10.2" + "@swagger-api/apidom-ns-json-schema-draft-7" "^1.10.2" "@types/ramda" "~0.30.0" ramda "~0.30.0" ramda-adjunct "^5.0.0" ts-mixer "^6.0.3" -"@swagger-api/apidom-ns-asyncapi-3@^1.6.0": - version "1.6.0" - resolved "https://registry.yarnpkg.com/@swagger-api/apidom-ns-asyncapi-3/-/apidom-ns-asyncapi-3-1.6.0.tgz#af66f63137479ffa298f4f4b76a58db690894853" - integrity sha512-lRMvwTdtuPcwJEYLTX/UGtECpHi9UNYeT9rmWMw3LiKZrZzYc2L8q4ipPbpWwH8t7QfsF2u0iggCODU99lXCnw== +"@swagger-api/apidom-ns-asyncapi-3@^1.10.2": + version "1.10.2" + resolved "https://registry.yarnpkg.com/@swagger-api/apidom-ns-asyncapi-3/-/apidom-ns-asyncapi-3-1.10.2.tgz#787851874b278dabe5b9583120f13ed06aafc21a" + integrity sha512-yqNmXeObF2OLAusgGEapXz2CrGjXwkcfG3DYcQDtOvgRytvGZxC2EkCUR+wEXCVNYhoJ7QpVzzTJOHs3jOvptg== dependencies: "@babel/runtime-corejs3" "^7.26.10" - "@swagger-api/apidom-core" "^1.6.0" - "@swagger-api/apidom-ns-asyncapi-2" "^1.6.0" + "@swagger-api/apidom-core" "^1.10.2" + "@swagger-api/apidom-ns-asyncapi-2" "^1.10.2" "@types/ramda" "~0.30.0" ramda "~0.30.0" ramda-adjunct "^5.0.0" ts-mixer "^6.0.3" -"@swagger-api/apidom-ns-json-schema-2019-09@^1.6.0": - version "1.6.0" - resolved "https://registry.yarnpkg.com/@swagger-api/apidom-ns-json-schema-2019-09/-/apidom-ns-json-schema-2019-09-1.6.0.tgz#439faf74e8ffa1006ae144b00a8dfb89b8cefba9" - integrity sha512-dee1i8wcAFgDEOzTsyoCzQhFLZ2JKzkK5KkRuryabvwS0hG2mKlogToFc8cO2MkkiLSpERm7DREALwSTFVHa0w== +"@swagger-api/apidom-ns-json-schema-2019-09@^1.10.2": + version "1.10.2" + resolved "https://registry.yarnpkg.com/@swagger-api/apidom-ns-json-schema-2019-09/-/apidom-ns-json-schema-2019-09-1.10.2.tgz#358652692fb3dc89dbfc5f775ec79a27791b9620" + integrity sha512-I1FaBoDFMjybF4QVsesIYl8OilkwycZ0mQ0jf1P++zfTRG27uIePB8M+Iuj6iqMsE3qpkjjJJ6ZLnrLPdKvmRw== dependencies: "@babel/runtime-corejs3" "^7.26.10" - "@swagger-api/apidom-core" "^1.6.0" - "@swagger-api/apidom-error" "^1.6.0" - "@swagger-api/apidom-ns-json-schema-draft-7" "^1.6.0" + "@swagger-api/apidom-core" "^1.10.2" + "@swagger-api/apidom-error" "^1.10.2" + "@swagger-api/apidom-ns-json-schema-draft-7" "^1.10.2" "@types/ramda" "~0.30.0" ramda "~0.30.0" ramda-adjunct "^5.0.0" ts-mixer "^6.0.4" -"@swagger-api/apidom-ns-json-schema-2020-12@^1.6.0": - version "1.6.0" - resolved "https://registry.yarnpkg.com/@swagger-api/apidom-ns-json-schema-2020-12/-/apidom-ns-json-schema-2020-12-1.6.0.tgz#d2e0e1ef89141c0eea013687aebaf2580e8a6c7c" - integrity sha512-ldTxSnnIXskwpN6yCJkasqs32pJXwoXyad95crKT0xjZZr4fTrcAXXIyzdjBubiY9tK6elSrQGQxinJcV7ivWw== +"@swagger-api/apidom-ns-json-schema-2020-12@^1.10.2": + version "1.10.2" + resolved "https://registry.yarnpkg.com/@swagger-api/apidom-ns-json-schema-2020-12/-/apidom-ns-json-schema-2020-12-1.10.2.tgz#f83dc1f6f403d1572acd2457557c37618379581d" + integrity sha512-lg9XfRlJRNoBa2EDGpEFc7HvFV39G6RG0/SbjQY0BE/WZer10wmfTCU7l3RUNJXRFGKH6/O/nsYgP7AFjTanXQ== dependencies: "@babel/runtime-corejs3" "^7.26.10" - "@swagger-api/apidom-core" "^1.6.0" - "@swagger-api/apidom-error" "^1.6.0" - "@swagger-api/apidom-ns-json-schema-2019-09" "^1.6.0" + "@swagger-api/apidom-core" "^1.10.2" + "@swagger-api/apidom-error" "^1.10.2" + "@swagger-api/apidom-ns-json-schema-2019-09" "^1.10.2" "@types/ramda" "~0.30.0" ramda "~0.30.0" ramda-adjunct "^5.0.0" ts-mixer "^6.0.4" -"@swagger-api/apidom-ns-json-schema-draft-4@^1.6.0": - version "1.6.0" - resolved "https://registry.yarnpkg.com/@swagger-api/apidom-ns-json-schema-draft-4/-/apidom-ns-json-schema-draft-4-1.6.0.tgz#57f2f0fb4c4a2b907cd096efc96e0bc9cd3594fc" - integrity sha512-t9HvHwrevEG7usosO6AdXmC8oYqje5nxHpUmODr72tUtCeAeGEGEb9lgqx7fBhjc3BYsRzOL1hX56m1gjEyCog== +"@swagger-api/apidom-ns-json-schema-draft-4@^1.10.2": + version "1.10.2" + resolved "https://registry.yarnpkg.com/@swagger-api/apidom-ns-json-schema-draft-4/-/apidom-ns-json-schema-draft-4-1.10.2.tgz#79e984a17ce86a5a768cab521ea0bd79987d705f" + integrity sha512-C50KnSKynrmHky/oOB6+hHyZVpwng78Fz5aZjay3h8X5C/PJHmm3sDJFvF3/9wkYHO3N9sPp7cpu4Xm9VJ4/wQ== dependencies: "@babel/runtime-corejs3" "^7.26.10" - "@swagger-api/apidom-ast" "^1.6.0" - "@swagger-api/apidom-core" "^1.6.0" + "@swagger-api/apidom-ast" "^1.10.2" + "@swagger-api/apidom-core" "^1.10.2" "@types/ramda" "~0.30.0" ramda "~0.30.0" ramda-adjunct "^5.0.0" ts-mixer "^6.0.4" -"@swagger-api/apidom-ns-json-schema-draft-6@^1.6.0": - version "1.6.0" - resolved "https://registry.yarnpkg.com/@swagger-api/apidom-ns-json-schema-draft-6/-/apidom-ns-json-schema-draft-6-1.6.0.tgz#6d191a7ebf8c64cb6e29002a4f06ee454b960d64" - integrity sha512-aoyvQWgAOcZGTe5OfJ3r24DvXHHbrkKtAnxTOEdZzV/uOm6/cbuT8m02+aMOqWPxei1naC3ZHW9iHrETtfgV3w== +"@swagger-api/apidom-ns-json-schema-draft-6@^1.10.2": + version "1.10.2" + resolved "https://registry.yarnpkg.com/@swagger-api/apidom-ns-json-schema-draft-6/-/apidom-ns-json-schema-draft-6-1.10.2.tgz#e55d05c44bb580e6690b1d8bdbafc29c9480c822" + integrity sha512-/wiP8+2lF8UJRrkoQ9HvKnMbnqijk2uY/hAg+/Bo73T9NGKkEa29jYVUKYNYj7gJBw4hhkUHfHFWuZUpxPC4ZA== dependencies: "@babel/runtime-corejs3" "^7.26.10" - "@swagger-api/apidom-core" "^1.6.0" - "@swagger-api/apidom-error" "^1.6.0" - "@swagger-api/apidom-ns-json-schema-draft-4" "^1.6.0" + "@swagger-api/apidom-core" "^1.10.2" + "@swagger-api/apidom-error" "^1.10.2" + "@swagger-api/apidom-ns-json-schema-draft-4" "^1.10.2" "@types/ramda" "~0.30.0" ramda "~0.30.0" ramda-adjunct "^5.0.0" ts-mixer "^6.0.4" -"@swagger-api/apidom-ns-json-schema-draft-7@^1.6.0": - version "1.6.0" - resolved "https://registry.yarnpkg.com/@swagger-api/apidom-ns-json-schema-draft-7/-/apidom-ns-json-schema-draft-7-1.6.0.tgz#5f95e9b055c9c9b1dc00c468a4f88449bdc89865" - integrity sha512-GjmC4+AHQh22fRZOmV+jSYMJTXh243XvdACfIQ//39kQu7gQsimF4PVSY2IgWSvS/I1ukWdPBYmDvOKryBPGrw== +"@swagger-api/apidom-ns-json-schema-draft-7@^1.10.2": + version "1.10.2" + resolved "https://registry.yarnpkg.com/@swagger-api/apidom-ns-json-schema-draft-7/-/apidom-ns-json-schema-draft-7-1.10.2.tgz#357b62e4df4f07912c5283ac9a214e175aa27593" + integrity sha512-firN/uvnVxQgACqcyzV3NU9qjbMvNMJkpmm3wOat3URmaFMaFBT3qjbU1pFHBGbnXI3+I9pQJZHmJSwqNzfUbQ== dependencies: "@babel/runtime-corejs3" "^7.26.10" - "@swagger-api/apidom-core" "^1.6.0" - "@swagger-api/apidom-error" "^1.6.0" - "@swagger-api/apidom-ns-json-schema-draft-6" "^1.6.0" + "@swagger-api/apidom-core" "^1.10.2" + "@swagger-api/apidom-error" "^1.10.2" + "@swagger-api/apidom-ns-json-schema-draft-6" "^1.10.2" "@types/ramda" "~0.30.0" ramda "~0.30.0" ramda-adjunct "^5.0.0" ts-mixer "^6.0.4" -"@swagger-api/apidom-ns-openapi-2@^1.6.0": - version "1.6.0" - resolved "https://registry.yarnpkg.com/@swagger-api/apidom-ns-openapi-2/-/apidom-ns-openapi-2-1.6.0.tgz#2a8fe09b6e063ffe90ad75c31023ccbda558e097" - integrity sha512-xbmYzagnB8rO7sYwNGVyxYbNBkjCWnMhlnMrxkPtfQ/2u2ANAmTnCB/S/cMswX5XofiRJbznKAjLDSKBS+mLpQ== +"@swagger-api/apidom-ns-openapi-2@^1.10.2": + version "1.10.2" + resolved "https://registry.yarnpkg.com/@swagger-api/apidom-ns-openapi-2/-/apidom-ns-openapi-2-1.10.2.tgz#9b2bec035501d40393291a70f9260e333c8d3ee5" + integrity sha512-FK5kYvo/1uwAByumRVRsynBlnKxUUImfsjPEFgRCW6yhbCGRqN47NaZ7GYFHpbhjC3OmMN5/etYj6B0jnZx7Gg== dependencies: "@babel/runtime-corejs3" "^7.26.10" - "@swagger-api/apidom-core" "^1.6.0" - "@swagger-api/apidom-error" "^1.6.0" - "@swagger-api/apidom-ns-json-schema-draft-4" "^1.6.0" + "@swagger-api/apidom-core" "^1.10.2" + "@swagger-api/apidom-error" "^1.10.2" + "@swagger-api/apidom-ns-json-schema-draft-4" "^1.10.2" "@types/ramda" "~0.30.0" ramda "~0.30.0" ramda-adjunct "^5.0.0" ts-mixer "^6.0.3" -"@swagger-api/apidom-ns-openapi-3-0@^1.6.0": - version "1.6.0" - resolved "https://registry.yarnpkg.com/@swagger-api/apidom-ns-openapi-3-0/-/apidom-ns-openapi-3-0-1.6.0.tgz#2f838c8ba216541d835793fd5947e1fed7b87445" - integrity sha512-AOvW7a2H27inepcTBAWaBMjJLrCh5IPWD4nTU+gysULC7IW6gphO8hj3iUuTmFBcGh9be89GBbvv2y/EGAfx9w== +"@swagger-api/apidom-ns-openapi-3-0@^1.10.2": + version "1.10.2" + resolved "https://registry.yarnpkg.com/@swagger-api/apidom-ns-openapi-3-0/-/apidom-ns-openapi-3-0-1.10.2.tgz#8f86fd918cd1909444bb7b7c6b0ecc06bdaa76f2" + integrity sha512-ziyv85QbJYHRdc9oTEFBy3pxwxg7BW/a9GrwH01/SmuXVQPjLjwzRb+SjCxLogJppm0yjxOkDFI2VWPp2RADFg== dependencies: "@babel/runtime-corejs3" "^7.26.10" - "@swagger-api/apidom-core" "^1.6.0" - "@swagger-api/apidom-error" "^1.6.0" - "@swagger-api/apidom-ns-json-schema-draft-4" "^1.6.0" + "@swagger-api/apidom-core" "^1.10.2" + "@swagger-api/apidom-error" "^1.10.2" + "@swagger-api/apidom-ns-json-schema-draft-4" "^1.10.2" "@types/ramda" "~0.30.0" ramda "~0.30.0" ramda-adjunct "^5.0.0" ts-mixer "^6.0.3" -"@swagger-api/apidom-ns-openapi-3-1@^1.6.0": - version "1.6.0" - resolved "https://registry.yarnpkg.com/@swagger-api/apidom-ns-openapi-3-1/-/apidom-ns-openapi-3-1-1.6.0.tgz#786698fc0a1c9162782024a66d8be2b4e0e106db" - integrity sha512-jCVypc8503zDSxAQlyV8j1vzwc75VBdWHtE2O0F+q5j9qNtGxw/ekbDkgrydYRaGBl92mf16dtPjtp5LwJD0Hw== +"@swagger-api/apidom-ns-openapi-3-1@^1.10.2": + version "1.10.2" + resolved "https://registry.yarnpkg.com/@swagger-api/apidom-ns-openapi-3-1/-/apidom-ns-openapi-3-1-1.10.2.tgz#968eaa27f98595a7bb0e820d5b805e770c886be0" + integrity sha512-ngcmO4dH77JT5hZB04OJdyTzgKnt2lNhAZQ+4wXjum/xhszjUmDhOeYfXdHw3Lm7MxsEsTesWzLYQ5LKADc41A== dependencies: "@babel/runtime-corejs3" "^7.26.10" - "@swagger-api/apidom-ast" "^1.6.0" - "@swagger-api/apidom-core" "^1.6.0" - "@swagger-api/apidom-json-pointer" "^1.6.0" - "@swagger-api/apidom-ns-json-schema-2020-12" "^1.6.0" - "@swagger-api/apidom-ns-openapi-3-0" "^1.6.0" + "@swagger-api/apidom-ast" "^1.10.2" + "@swagger-api/apidom-core" "^1.10.2" + "@swagger-api/apidom-json-pointer" "^1.10.2" + "@swagger-api/apidom-ns-json-schema-2020-12" "^1.10.2" + "@swagger-api/apidom-ns-openapi-3-0" "^1.10.2" "@types/ramda" "~0.30.0" ramda "~0.30.0" ramda-adjunct "^5.0.0" ts-mixer "^6.0.3" -"@swagger-api/apidom-ns-openapi-3-2@^1.6.0": - version "1.6.0" - resolved "https://registry.yarnpkg.com/@swagger-api/apidom-ns-openapi-3-2/-/apidom-ns-openapi-3-2-1.6.0.tgz#0ba997d1321a2af1af36ccd30f335b0c9da24753" - integrity sha512-QcFAUucaPaWiOKOEaaGqSfK3OtjeGJodWZLsuBQ0vrHaHkWyQ7jwsM1DJbc1Y8geOBeD2wIwdrdRjoulmqU1SA== +"@swagger-api/apidom-ns-openapi-3-2@^1.10.2": + version "1.10.2" + resolved "https://registry.yarnpkg.com/@swagger-api/apidom-ns-openapi-3-2/-/apidom-ns-openapi-3-2-1.10.2.tgz#b2bb8f0d8ac71660255d6744aa71cb870c76fabf" + integrity sha512-3SWJ5ipWwn+w11HTUESWex/522jy2aGLzBqqMgH36sy+Wdwx+9Mw2bgSDqkxmNC5+jpzOGUOIWoQAMuCpS/Gzg== dependencies: "@babel/runtime-corejs3" "^7.26.10" - "@swagger-api/apidom-ast" "^1.6.0" - "@swagger-api/apidom-core" "^1.6.0" - "@swagger-api/apidom-json-pointer" "^1.6.0" - "@swagger-api/apidom-ns-json-schema-2020-12" "^1.6.0" - "@swagger-api/apidom-ns-openapi-3-0" "^1.6.0" - "@swagger-api/apidom-ns-openapi-3-1" "^1.6.0" + "@swagger-api/apidom-ast" "^1.10.2" + "@swagger-api/apidom-core" "^1.10.2" + "@swagger-api/apidom-json-pointer" "^1.10.2" + "@swagger-api/apidom-ns-json-schema-2020-12" "^1.10.2" + "@swagger-api/apidom-ns-openapi-3-0" "^1.10.2" + "@swagger-api/apidom-ns-openapi-3-1" "^1.10.2" "@types/ramda" "~0.30.0" ramda "~0.30.0" ramda-adjunct "^5.0.0" ts-mixer "^6.0.3" -"@swagger-api/apidom-parser-adapter-api-design-systems-json@^1.6.0": - version "1.6.0" - resolved "https://registry.yarnpkg.com/@swagger-api/apidom-parser-adapter-api-design-systems-json/-/apidom-parser-adapter-api-design-systems-json-1.6.0.tgz#8495b058dc7962ca2c17b0528aed78dac394f654" - integrity sha512-vz/9k0X/kh6mLm+Fi+LGNk/yyFq28wxI29ZVLW+b7ulcODikv+NaDnyN2n2kLKCvIchPATzAEvqMvVMuuQwWlg== +"@swagger-api/apidom-parser-adapter-api-design-systems-json@^1.10.2": + version "1.10.2" + resolved "https://registry.yarnpkg.com/@swagger-api/apidom-parser-adapter-api-design-systems-json/-/apidom-parser-adapter-api-design-systems-json-1.10.2.tgz#8249b5abdd430624a7c7f9d1a007ecfc9e2bc3cc" + integrity sha512-kzhJUGzsJ38Uohj5xRQDkQC08rqNhatbqgD30LZ0/UWryJ9nAsjqK2ovuP9t+5WKcDE4iwcYeGSt1NA2XgEZwg== dependencies: "@babel/runtime-corejs3" "^7.26.10" - "@swagger-api/apidom-core" "^1.6.0" - "@swagger-api/apidom-ns-api-design-systems" "^1.6.0" - "@swagger-api/apidom-parser-adapter-json" "^1.6.0" + "@swagger-api/apidom-core" "^1.10.2" + "@swagger-api/apidom-ns-api-design-systems" "^1.10.2" + "@swagger-api/apidom-parser-adapter-json" "^1.10.2" "@types/ramda" "~0.30.0" ramda "~0.30.0" ramda-adjunct "^5.0.0" -"@swagger-api/apidom-parser-adapter-api-design-systems-yaml@^1.6.0": - version "1.6.0" - resolved "https://registry.yarnpkg.com/@swagger-api/apidom-parser-adapter-api-design-systems-yaml/-/apidom-parser-adapter-api-design-systems-yaml-1.6.0.tgz#a56b618504f0168b3687a42553b5d26860d85adc" - integrity sha512-QAq4H6YzRtysSpvLtlJ8WZ22/1Mht+/iarrUOijxDZQPAGfYeUoIicnCqxkVZYSea85sQl+3kiCCB3nhSH+L0g== +"@swagger-api/apidom-parser-adapter-api-design-systems-yaml@^1.10.2": + version "1.10.2" + resolved "https://registry.yarnpkg.com/@swagger-api/apidom-parser-adapter-api-design-systems-yaml/-/apidom-parser-adapter-api-design-systems-yaml-1.10.2.tgz#053cc675e02bce40b16e2773fc57a11689cceb26" + integrity sha512-i3CmSxJ/iG67ybRDAJ9xpuMrOMFvC/obX2lI36E0VZzBTb+llw4Zd5qFmBqNnImLpwdmk11Z1V7i+5HM+J7ijQ== dependencies: "@babel/runtime-corejs3" "^7.26.10" - "@swagger-api/apidom-core" "^1.6.0" - "@swagger-api/apidom-ns-api-design-systems" "^1.6.0" - "@swagger-api/apidom-parser-adapter-yaml-1-2" "^1.6.0" + "@swagger-api/apidom-core" "^1.10.2" + "@swagger-api/apidom-ns-api-design-systems" "^1.10.2" + "@swagger-api/apidom-parser-adapter-yaml-1-2" "^1.10.2" "@types/ramda" "~0.30.0" ramda "~0.30.0" ramda-adjunct "^5.0.0" -"@swagger-api/apidom-parser-adapter-arazzo-json-1@^1.6.0": - version "1.6.0" - resolved "https://registry.yarnpkg.com/@swagger-api/apidom-parser-adapter-arazzo-json-1/-/apidom-parser-adapter-arazzo-json-1-1.6.0.tgz#63790158816fc50d81bcf59631b3536e3af8d5ca" - integrity sha512-syKPG3a9IGRvlGhXIEUzWhwbEuFbj+UwwtqaKu8zu771V+DRtH+wxyOkX54vKAIlApz/FgeUbmlWA1ZtYBlSIQ== +"@swagger-api/apidom-parser-adapter-arazzo-json-1@^1.10.2": + version "1.10.2" + resolved "https://registry.yarnpkg.com/@swagger-api/apidom-parser-adapter-arazzo-json-1/-/apidom-parser-adapter-arazzo-json-1-1.10.2.tgz#988b6999d34df1b85bfbc3f5dc54f419b3b448f0" + integrity sha512-HwiUkwvo5i2hV2SS6KWrdj62BdceZGfhuXhr1il8akWekpU4jXPtr5pv4gOnKKJN7VgjAmwt/DlcCSRo1+9jVA== dependencies: "@babel/runtime-corejs3" "^7.26.10" - "@swagger-api/apidom-core" "^1.6.0" - "@swagger-api/apidom-ns-arazzo-1" "^1.6.0" - "@swagger-api/apidom-parser-adapter-json" "^1.6.0" + "@swagger-api/apidom-core" "^1.10.2" + "@swagger-api/apidom-ns-arazzo-1" "^1.10.2" + "@swagger-api/apidom-parser-adapter-json" "^1.10.2" "@types/ramda" "~0.30.0" ramda "~0.30.0" ramda-adjunct "^5.0.0" -"@swagger-api/apidom-parser-adapter-arazzo-yaml-1@^1.6.0": - version "1.6.0" - resolved "https://registry.yarnpkg.com/@swagger-api/apidom-parser-adapter-arazzo-yaml-1/-/apidom-parser-adapter-arazzo-yaml-1-1.6.0.tgz#390a74c9cabaf175f35086075e82579edcefeb40" - integrity sha512-IVVLn+a8Q1iQcQsm4tXiAPghHJuJSB1rhIlDyHe3tSQgt9HOSiVpbnJDpwE/JBxxDxSAkeT6Ovo+fi2T5AmHYg== +"@swagger-api/apidom-parser-adapter-arazzo-yaml-1@^1.10.2": + version "1.10.2" + resolved "https://registry.yarnpkg.com/@swagger-api/apidom-parser-adapter-arazzo-yaml-1/-/apidom-parser-adapter-arazzo-yaml-1-1.10.2.tgz#fe2371ff7a9cf84094b1ec1554c404c804958c28" + integrity sha512-w8VTVuE7GPbRqWxvMgRoTb726JRsMhFPMfTBf8+MJ4pQThjk78dSXPV2Zlse71b2DWBuQy2sr6zGyLUNs/3ePQ== dependencies: "@babel/runtime-corejs3" "^7.26.10" - "@swagger-api/apidom-core" "^1.6.0" - "@swagger-api/apidom-ns-arazzo-1" "^1.6.0" - "@swagger-api/apidom-parser-adapter-yaml-1-2" "^1.6.0" + "@swagger-api/apidom-core" "^1.10.2" + "@swagger-api/apidom-ns-arazzo-1" "^1.10.2" + "@swagger-api/apidom-parser-adapter-yaml-1-2" "^1.10.2" "@types/ramda" "~0.30.0" ramda "~0.30.0" ramda-adjunct "^5.0.0" -"@swagger-api/apidom-parser-adapter-asyncapi-json-2@^1.6.0": - version "1.6.0" - resolved "https://registry.yarnpkg.com/@swagger-api/apidom-parser-adapter-asyncapi-json-2/-/apidom-parser-adapter-asyncapi-json-2-1.6.0.tgz#3573506e856350ef0a24c64b517c3461eaa64fa7" - integrity sha512-aSUi22ELTDvdCLA3nIUOehuNBcHSeCqU7S7YNiHP/mwE4Q07pwQrYXijH2PROfCdjlZNNN34m6Ptakd92jliJQ== +"@swagger-api/apidom-parser-adapter-asyncapi-json-2@^1.10.2": + version "1.10.2" + resolved "https://registry.yarnpkg.com/@swagger-api/apidom-parser-adapter-asyncapi-json-2/-/apidom-parser-adapter-asyncapi-json-2-1.10.2.tgz#aca77a0e3abb7643aab18fa7dcb370334de6d264" + integrity sha512-FDNjqmn2vV1jFoVVwQDO0XPPm8R5xzmcyY/6yBLFmKZADin3smSKVZ+njYHmfRjpspXwN0AwI0drdvuH0FZLJg== dependencies: "@babel/runtime-corejs3" "^7.26.10" - "@swagger-api/apidom-core" "^1.6.0" - "@swagger-api/apidom-ns-asyncapi-2" "^1.6.0" - "@swagger-api/apidom-parser-adapter-json" "^1.6.0" + "@swagger-api/apidom-core" "^1.10.2" + "@swagger-api/apidom-ns-asyncapi-2" "^1.10.2" + "@swagger-api/apidom-parser-adapter-json" "^1.10.2" "@types/ramda" "~0.30.0" ramda "~0.30.0" ramda-adjunct "^5.0.0" -"@swagger-api/apidom-parser-adapter-asyncapi-json-3@^1.6.0": - version "1.6.0" - resolved "https://registry.yarnpkg.com/@swagger-api/apidom-parser-adapter-asyncapi-json-3/-/apidom-parser-adapter-asyncapi-json-3-1.6.0.tgz#c18fb9cb2ce69fbe82a4b4fee0bb88bd972956f2" - integrity sha512-Ic53vcFF9zniDyCXOGSwwuAdEBUn5lFEAa0m2i30R36cQFHBCCuvbzbMQjWdr+oML0Aw4XoqOwZCQgkJJICpPA== +"@swagger-api/apidom-parser-adapter-asyncapi-json-3@^1.10.2": + version "1.10.2" + resolved "https://registry.yarnpkg.com/@swagger-api/apidom-parser-adapter-asyncapi-json-3/-/apidom-parser-adapter-asyncapi-json-3-1.10.2.tgz#01ff7e0b05336288776791c9d537d6abe98ddb57" + integrity sha512-x/0vM2nDDzYzFnXr69+so/KSH+2py2TiZd1K49pWcX8cHsPV1Y4Ppih7GVOMymd8m/IOCjLYlV7qt4eWDwdldg== dependencies: "@babel/runtime-corejs3" "^7.26.10" - "@swagger-api/apidom-core" "^1.6.0" - "@swagger-api/apidom-ns-asyncapi-3" "^1.6.0" - "@swagger-api/apidom-parser-adapter-json" "^1.6.0" + "@swagger-api/apidom-core" "^1.10.2" + "@swagger-api/apidom-ns-asyncapi-3" "^1.10.2" + "@swagger-api/apidom-parser-adapter-json" "^1.10.2" "@types/ramda" "~0.30.0" ramda "~0.30.0" ramda-adjunct "^5.0.0" -"@swagger-api/apidom-parser-adapter-asyncapi-yaml-2@^1.6.0": - version "1.6.0" - resolved "https://registry.yarnpkg.com/@swagger-api/apidom-parser-adapter-asyncapi-yaml-2/-/apidom-parser-adapter-asyncapi-yaml-2-1.6.0.tgz#209e4ceefa2c70ce0bfd957bf0e786474f763fb9" - integrity sha512-d/w7X+T4vT+KPqb+8xUm6n4pbHsGB28jdxE9rNVbxhu6D3owny2uxfglwaFh4fJG6FQMavCwl/QzfB4newdoKQ== +"@swagger-api/apidom-parser-adapter-asyncapi-yaml-2@^1.10.2": + version "1.10.2" + resolved "https://registry.yarnpkg.com/@swagger-api/apidom-parser-adapter-asyncapi-yaml-2/-/apidom-parser-adapter-asyncapi-yaml-2-1.10.2.tgz#0912939a3d6f0845587bcd0ed6b67ae36c9989b0" + integrity sha512-2bVACmU9ZmAVVnqQWSc3Bs+xG0HHLU1tfZbYL8xNgSi8kw4HcnejF5mWtN+MLFzTaBmWCi2In7P7BYNR8+2Dyg== dependencies: "@babel/runtime-corejs3" "^7.26.10" - "@swagger-api/apidom-core" "^1.6.0" - "@swagger-api/apidom-ns-asyncapi-2" "^1.6.0" - "@swagger-api/apidom-parser-adapter-yaml-1-2" "^1.6.0" + "@swagger-api/apidom-core" "^1.10.2" + "@swagger-api/apidom-ns-asyncapi-2" "^1.10.2" + "@swagger-api/apidom-parser-adapter-yaml-1-2" "^1.10.2" "@types/ramda" "~0.30.0" ramda "~0.30.0" ramda-adjunct "^5.0.0" -"@swagger-api/apidom-parser-adapter-asyncapi-yaml-3@^1.6.0": - version "1.6.0" - resolved "https://registry.yarnpkg.com/@swagger-api/apidom-parser-adapter-asyncapi-yaml-3/-/apidom-parser-adapter-asyncapi-yaml-3-1.6.0.tgz#37f0c6d4562d93a910c883392f51ba3e2e004e17" - integrity sha512-Wmf0LY59TZxQhqrJU2pcnUikcChVB4IqGPgjtOFLUoqPpz8FSwYbJ/SPnSMSl+QuncxROheSFsgZ6Tupv0sPHw== +"@swagger-api/apidom-parser-adapter-asyncapi-yaml-3@^1.10.2": + version "1.10.2" + resolved "https://registry.yarnpkg.com/@swagger-api/apidom-parser-adapter-asyncapi-yaml-3/-/apidom-parser-adapter-asyncapi-yaml-3-1.10.2.tgz#b8de51551d183f74e9c3eb3d0754200bc5751ff0" + integrity sha512-oHpbf+iqBcDS3qtsipMpgCwAeckKMxg0qFKYTCRZyJdctRgupJTxVeir6t/SGo0Ny0a1iknt2LN0u5frEen0kg== dependencies: "@babel/runtime-corejs3" "^7.26.10" - "@swagger-api/apidom-core" "^1.6.0" - "@swagger-api/apidom-ns-asyncapi-3" "^1.6.0" - "@swagger-api/apidom-parser-adapter-yaml-1-2" "^1.6.0" + "@swagger-api/apidom-core" "^1.10.2" + "@swagger-api/apidom-ns-asyncapi-3" "^1.10.2" + "@swagger-api/apidom-parser-adapter-yaml-1-2" "^1.10.2" "@types/ramda" "~0.30.0" ramda "~0.30.0" ramda-adjunct "^5.0.0" -"@swagger-api/apidom-parser-adapter-json@^1.6.0": - version "1.6.0" - resolved "https://registry.yarnpkg.com/@swagger-api/apidom-parser-adapter-json/-/apidom-parser-adapter-json-1.6.0.tgz#cb950140c66cb27cc7517f604e50e9c8bb6b2753" - integrity sha512-WdAS+dBAB2t18HuUgSZy5b8JM7uXfn1RlPymJNRMUsrKYCTtPrQ/0q3YfnBjPhtjSSNCp+p1wajxHAFS7cj2VA== +"@swagger-api/apidom-parser-adapter-json@^1.10.2": + version "1.10.2" + resolved "https://registry.yarnpkg.com/@swagger-api/apidom-parser-adapter-json/-/apidom-parser-adapter-json-1.10.2.tgz#fd52f62064a2456a30f020b208d80715d4bb9ae2" + integrity sha512-VnwEkarKfsJYRF0zCI9AGiSIyBUXqS2d32KQuhVCt/HeuF1XO9sjeLjGiosA/24YVOnO0ul5TpiNFQn0pw89mA== dependencies: "@babel/runtime-corejs3" "^7.26.10" - "@swagger-api/apidom-ast" "^1.6.0" - "@swagger-api/apidom-core" "^1.6.0" - "@swagger-api/apidom-error" "^1.6.0" + "@swagger-api/apidom-ast" "^1.10.2" + "@swagger-api/apidom-core" "^1.10.2" + "@swagger-api/apidom-error" "^1.10.2" "@types/ramda" "~0.30.0" ramda "~0.30.0" ramda-adjunct "^5.0.0" @@ -4085,119 +4065,119 @@ tree-sitter-json "=0.24.8" web-tree-sitter "=0.24.5" -"@swagger-api/apidom-parser-adapter-openapi-json-2@^1.6.0": - version "1.6.0" - resolved "https://registry.yarnpkg.com/@swagger-api/apidom-parser-adapter-openapi-json-2/-/apidom-parser-adapter-openapi-json-2-1.6.0.tgz#278eac1ec8fec3b0c7e2cad6cc8f75f636ae8a3c" - integrity sha512-Q36W1FzdVaY7Oh98533dzCUghwb8k3ZMdlnV37V1H13FlUkj3tVZiWaeaCLwIakzQ7XXYaQTOP+VrRhDRjzhUA== +"@swagger-api/apidom-parser-adapter-openapi-json-2@^1.10.2": + version "1.10.2" + resolved "https://registry.yarnpkg.com/@swagger-api/apidom-parser-adapter-openapi-json-2/-/apidom-parser-adapter-openapi-json-2-1.10.2.tgz#18a604e5197f095d7633ec47263ed31c856c2b87" + integrity sha512-+d/o/8TrNBjvFzgPb0RQhrCc8gOWnrHZF+xvCO5gwp+4MUr1XP1AJIox1e6t1SO+j7IQjiF2ocx2r7eFE5QC7w== dependencies: "@babel/runtime-corejs3" "^7.26.10" - "@swagger-api/apidom-core" "^1.6.0" - "@swagger-api/apidom-ns-openapi-2" "^1.6.0" - "@swagger-api/apidom-parser-adapter-json" "^1.6.0" + "@swagger-api/apidom-core" "^1.10.2" + "@swagger-api/apidom-ns-openapi-2" "^1.10.2" + "@swagger-api/apidom-parser-adapter-json" "^1.10.2" "@types/ramda" "~0.30.0" ramda "~0.30.0" ramda-adjunct "^5.0.0" -"@swagger-api/apidom-parser-adapter-openapi-json-3-0@^1.6.0": - version "1.6.0" - resolved "https://registry.yarnpkg.com/@swagger-api/apidom-parser-adapter-openapi-json-3-0/-/apidom-parser-adapter-openapi-json-3-0-1.6.0.tgz#9dfebd27032acef17d1c48820ab6d95db6561910" - integrity sha512-UY+obOLTPHJvnXscdMY9XwZyuqcnBe6cu9TURjJgkO/QpOpPDqqZoRyurKZgRrX0Pv9B1zR3EIzhl01u/jeUaw== +"@swagger-api/apidom-parser-adapter-openapi-json-3-0@^1.10.2": + version "1.10.2" + resolved "https://registry.yarnpkg.com/@swagger-api/apidom-parser-adapter-openapi-json-3-0/-/apidom-parser-adapter-openapi-json-3-0-1.10.2.tgz#80c16b46d5ef355848d7cc5ab57aec589ada20e1" + integrity sha512-3ieUeX8/WywkUzdOO1U1QKQDNmpZFfOeTAeb4ISDd/PKOVwuEx/b0w5I8EuOu97tKAe3UUesEdii+pJlkcFlFw== dependencies: "@babel/runtime-corejs3" "^7.26.10" - "@swagger-api/apidom-core" "^1.6.0" - "@swagger-api/apidom-ns-openapi-3-0" "^1.6.0" - "@swagger-api/apidom-parser-adapter-json" "^1.6.0" + "@swagger-api/apidom-core" "^1.10.2" + "@swagger-api/apidom-ns-openapi-3-0" "^1.10.2" + "@swagger-api/apidom-parser-adapter-json" "^1.10.2" "@types/ramda" "~0.30.0" ramda "~0.30.0" ramda-adjunct "^5.0.0" -"@swagger-api/apidom-parser-adapter-openapi-json-3-1@^1.6.0": - version "1.6.0" - resolved "https://registry.yarnpkg.com/@swagger-api/apidom-parser-adapter-openapi-json-3-1/-/apidom-parser-adapter-openapi-json-3-1-1.6.0.tgz#2ede833b39831973525f8b45680010e957ead469" - integrity sha512-4ch04/96lYMXQu6odqa6H0aJmV8UefnBJKX1CPuL4qcPSPMFCurcXHGpPHrwMu1p/4Q9H+yRVlYeNQV10xvM0w== +"@swagger-api/apidom-parser-adapter-openapi-json-3-1@^1.10.2": + version "1.10.2" + resolved "https://registry.yarnpkg.com/@swagger-api/apidom-parser-adapter-openapi-json-3-1/-/apidom-parser-adapter-openapi-json-3-1-1.10.2.tgz#58e2342896bc7a270d5b6cd5951b7b2536b7502e" + integrity sha512-z0c7IgMPLSDhE+ldTb54Xlhq+yPF0w/8LHXyeHX4V6BS1VG3Utb+mM/qTVfy1Eo+p1KGNlwNDEPsBp6jIaVb5Q== dependencies: "@babel/runtime-corejs3" "^7.26.10" - "@swagger-api/apidom-core" "^1.6.0" - "@swagger-api/apidom-ns-openapi-3-1" "^1.6.0" - "@swagger-api/apidom-parser-adapter-json" "^1.6.0" + "@swagger-api/apidom-core" "^1.10.2" + "@swagger-api/apidom-ns-openapi-3-1" "^1.10.2" + "@swagger-api/apidom-parser-adapter-json" "^1.10.2" "@types/ramda" "~0.30.0" ramda "~0.30.0" ramda-adjunct "^5.0.0" -"@swagger-api/apidom-parser-adapter-openapi-json-3-2@^1.6.0": - version "1.6.0" - resolved "https://registry.yarnpkg.com/@swagger-api/apidom-parser-adapter-openapi-json-3-2/-/apidom-parser-adapter-openapi-json-3-2-1.6.0.tgz#e019e0321928affbdb7e3b581da811b6da9b3665" - integrity sha512-fWR2gjMQg00QIimcXQMSVeLnCH/2iuDD/Dx8TzVHmKV/IKlu+TnmIVosdlDfRmOB+4duwU6/yfoA79IEhFeZdw== +"@swagger-api/apidom-parser-adapter-openapi-json-3-2@^1.10.2": + version "1.10.2" + resolved "https://registry.yarnpkg.com/@swagger-api/apidom-parser-adapter-openapi-json-3-2/-/apidom-parser-adapter-openapi-json-3-2-1.10.2.tgz#ec4ed3b67067a1ba3f48432feb02316a25453dc5" + integrity sha512-bx/kEIXWtpHu+4LEiyNdt0v8ER5EVwPjhQdlpOaC5qghnRH9aUYOTawZtVHsNHAQWTIMNn9DdPKYQgttQKD0Pw== dependencies: "@babel/runtime-corejs3" "^7.26.10" - "@swagger-api/apidom-core" "^1.6.0" - "@swagger-api/apidom-ns-openapi-3-2" "^1.6.0" - "@swagger-api/apidom-parser-adapter-json" "^1.6.0" + "@swagger-api/apidom-core" "^1.10.2" + "@swagger-api/apidom-ns-openapi-3-2" "^1.10.2" + "@swagger-api/apidom-parser-adapter-json" "^1.10.2" "@types/ramda" "~0.30.0" ramda "~0.30.0" ramda-adjunct "^5.0.0" -"@swagger-api/apidom-parser-adapter-openapi-yaml-2@^1.6.0": - version "1.6.0" - resolved "https://registry.yarnpkg.com/@swagger-api/apidom-parser-adapter-openapi-yaml-2/-/apidom-parser-adapter-openapi-yaml-2-1.6.0.tgz#6ca83d9027e1adf9a2a8e7ef1f0b4599bcf6fbf0" - integrity sha512-dkEh1Rw9uvuIAOTfKjWRX2rLWP+xJ/Eqdkqeo0I0BWFKXX49YcDpHJV4XHpmd5FbsjJ9vBYr0hAmkbl32TtR4g== +"@swagger-api/apidom-parser-adapter-openapi-yaml-2@^1.10.2": + version "1.10.2" + resolved "https://registry.yarnpkg.com/@swagger-api/apidom-parser-adapter-openapi-yaml-2/-/apidom-parser-adapter-openapi-yaml-2-1.10.2.tgz#9156fd645e4c262fb7d40a5a1893c933b9c4e2ff" + integrity sha512-e86JUXHGGEVsO4/xpy/GRSvYXGN30hLt1lMUhjzCFuE95N6/K3hmQHE3rA/H7ot1ajCWUhzukW5rGQac79NIjA== dependencies: "@babel/runtime-corejs3" "^7.26.10" - "@swagger-api/apidom-core" "^1.6.0" - "@swagger-api/apidom-ns-openapi-2" "^1.6.0" - "@swagger-api/apidom-parser-adapter-yaml-1-2" "^1.6.0" + "@swagger-api/apidom-core" "^1.10.2" + "@swagger-api/apidom-ns-openapi-2" "^1.10.2" + "@swagger-api/apidom-parser-adapter-yaml-1-2" "^1.10.2" "@types/ramda" "~0.30.0" ramda "~0.30.0" ramda-adjunct "^5.0.0" -"@swagger-api/apidom-parser-adapter-openapi-yaml-3-0@^1.6.0": - version "1.6.0" - resolved "https://registry.yarnpkg.com/@swagger-api/apidom-parser-adapter-openapi-yaml-3-0/-/apidom-parser-adapter-openapi-yaml-3-0-1.6.0.tgz#bd9ad26ed181153fb69eb73bda9fca9291b4eaed" - integrity sha512-6azq5YonWdzHcO9llK9zn1a+rGxlTz2Uf8p8NWDQnl2AZ56neDLYEL3mNDlrMXAy8dSJIHw+u9VF1OOzdslIHQ== +"@swagger-api/apidom-parser-adapter-openapi-yaml-3-0@^1.10.2": + version "1.10.2" + resolved "https://registry.yarnpkg.com/@swagger-api/apidom-parser-adapter-openapi-yaml-3-0/-/apidom-parser-adapter-openapi-yaml-3-0-1.10.2.tgz#311050d1aaeae227773c8487f3210acd02b6f209" + integrity sha512-ucfc13Ai31tJ0ruAm1YiHhqENgcBuiOXL00OhoICWA56ggAcnA5WfWmvtsXVMlZsTHVbhZP3XpsH5rui2N8u5w== dependencies: "@babel/runtime-corejs3" "^7.26.10" - "@swagger-api/apidom-core" "^1.6.0" - "@swagger-api/apidom-ns-openapi-3-0" "^1.6.0" - "@swagger-api/apidom-parser-adapter-yaml-1-2" "^1.6.0" + "@swagger-api/apidom-core" "^1.10.2" + "@swagger-api/apidom-ns-openapi-3-0" "^1.10.2" + "@swagger-api/apidom-parser-adapter-yaml-1-2" "^1.10.2" "@types/ramda" "~0.30.0" ramda "~0.30.0" ramda-adjunct "^5.0.0" -"@swagger-api/apidom-parser-adapter-openapi-yaml-3-1@^1.6.0": - version "1.6.0" - resolved "https://registry.yarnpkg.com/@swagger-api/apidom-parser-adapter-openapi-yaml-3-1/-/apidom-parser-adapter-openapi-yaml-3-1-1.6.0.tgz#cb9e42504cd38564c7c444b958ccb8656314eac5" - integrity sha512-g2tGCXyIAC0IA6JjA0HVxHWyCovyfAxDQ+pMAJ6qm4PfrZHB+oXKWKZHNNmQaFiKdc/SVdMQq6Up0mXOQs7IOQ== +"@swagger-api/apidom-parser-adapter-openapi-yaml-3-1@^1.10.2": + version "1.10.2" + resolved "https://registry.yarnpkg.com/@swagger-api/apidom-parser-adapter-openapi-yaml-3-1/-/apidom-parser-adapter-openapi-yaml-3-1-1.10.2.tgz#5d50ae20d9dbb4f23edfa2042deb3ec9ea3516e6" + integrity sha512-7o8j93qgf9yYAaaJ/GpH+5sB3fC9EmvmjTCqlw5YWXp+cRgCn9q7f80Sv4+NjbracUafB6qL4i9F/m+Xs0XZaQ== dependencies: "@babel/runtime-corejs3" "^7.26.10" - "@swagger-api/apidom-core" "^1.6.0" - "@swagger-api/apidom-ns-openapi-3-1" "^1.6.0" - "@swagger-api/apidom-parser-adapter-yaml-1-2" "^1.6.0" + "@swagger-api/apidom-core" "^1.10.2" + "@swagger-api/apidom-ns-openapi-3-1" "^1.10.2" + "@swagger-api/apidom-parser-adapter-yaml-1-2" "^1.10.2" "@types/ramda" "~0.30.0" ramda "~0.30.0" ramda-adjunct "^5.0.0" -"@swagger-api/apidom-parser-adapter-openapi-yaml-3-2@^1.6.0": - version "1.6.0" - resolved "https://registry.yarnpkg.com/@swagger-api/apidom-parser-adapter-openapi-yaml-3-2/-/apidom-parser-adapter-openapi-yaml-3-2-1.6.0.tgz#5851ff4cfeae7712ffab022b347e4877859ffc17" - integrity sha512-NGkdG9X5Svi89ZBluNseyUBNdgB9MkbTTNmerVKKOmCCHaVbzIb6UFPXf1MifSFyT+wTeGZk6WZLgRIDsTAZ5Q== +"@swagger-api/apidom-parser-adapter-openapi-yaml-3-2@^1.10.2": + version "1.10.2" + resolved "https://registry.yarnpkg.com/@swagger-api/apidom-parser-adapter-openapi-yaml-3-2/-/apidom-parser-adapter-openapi-yaml-3-2-1.10.2.tgz#0b8e12e981cfb2361fb7317342897c7484fc706a" + integrity sha512-blDIeVmo8bpXYV/C+b6PYi54yS+5jPEZTFsK5jQ2NzpCPrkBPacp/KTuHBUBzJsYj4bj/ivRL3+JXGw4YovUHw== dependencies: "@babel/runtime-corejs3" "^7.26.10" - "@swagger-api/apidom-core" "^1.6.0" - "@swagger-api/apidom-ns-openapi-3-2" "^1.6.0" - "@swagger-api/apidom-parser-adapter-yaml-1-2" "^1.6.0" + "@swagger-api/apidom-core" "^1.10.2" + "@swagger-api/apidom-ns-openapi-3-2" "^1.10.2" + "@swagger-api/apidom-parser-adapter-yaml-1-2" "^1.10.2" "@types/ramda" "~0.30.0" ramda "~0.30.0" ramda-adjunct "^5.0.0" -"@swagger-api/apidom-parser-adapter-yaml-1-2@^1.6.0": - version "1.6.0" - resolved "https://registry.yarnpkg.com/@swagger-api/apidom-parser-adapter-yaml-1-2/-/apidom-parser-adapter-yaml-1-2-1.6.0.tgz#3bd4c52755f39c76a159645aadc9f96bec9156cd" - integrity sha512-UwSE5pPUJ+ag7ZCbesgx/SJ8zUD3Sx+2U4AD3/1G1EJ+0gb7FMYgihuOT8ujmBfZVGGm3HMIEIa1w3zha08v2g== +"@swagger-api/apidom-parser-adapter-yaml-1-2@^1.10.2": + version "1.10.2" + resolved "https://registry.yarnpkg.com/@swagger-api/apidom-parser-adapter-yaml-1-2/-/apidom-parser-adapter-yaml-1-2-1.10.2.tgz#d723a5f2668e5eb4aa0428f4045c3bca157b90c6" + integrity sha512-I5eCls8XS3SVEwH/cuL6T3iar1TPaFYh3gXwS/2rzP1aZQNKSHDP3y3ney7nAomKG4dFvE8Q248FL36arG7T/w== dependencies: "@babel/runtime-corejs3" "^7.26.10" - "@swagger-api/apidom-ast" "^1.6.0" - "@swagger-api/apidom-core" "^1.6.0" - "@swagger-api/apidom-error" "^1.6.0" + "@swagger-api/apidom-ast" "^1.10.2" + "@swagger-api/apidom-core" "^1.10.2" + "@swagger-api/apidom-error" "^1.10.2" "@tree-sitter-grammars/tree-sitter-yaml" "=0.7.1" "@types/ramda" "~0.30.0" ramda "~0.30.0" @@ -4205,45 +4185,45 @@ tree-sitter "=0.22.4" web-tree-sitter "=0.24.5" -"@swagger-api/apidom-reference@^1.6.0": - version "1.6.0" - resolved "https://registry.yarnpkg.com/@swagger-api/apidom-reference/-/apidom-reference-1.6.0.tgz#1060b54d135b1888850678cd35d862ffb1ac6f77" - integrity sha512-gYTDfWQM1heqrCCrCsZH+EWDyAkIGqEJnSJcVWKngwOkXJKeUwat8p1TOW4q3rkaTT+fBaYbrjTr9SkFtVbdMg== +"@swagger-api/apidom-reference@^1.10.2": + version "1.10.2" + resolved "https://registry.yarnpkg.com/@swagger-api/apidom-reference/-/apidom-reference-1.10.2.tgz#82069138540116c35ec782a5395c41ced7b20942" + integrity sha512-H5UqOmae9CXdiLJbbh1j+/hwvcECmr6ci2XtUKTQpFviemvsIDZmPV1DKUAxCfzGr2iOkDO6SZc+/OEWlETqiQ== dependencies: "@babel/runtime-corejs3" "^7.26.10" - "@swagger-api/apidom-core" "^1.6.0" - "@swagger-api/apidom-error" "^1.6.0" + "@swagger-api/apidom-core" "^1.10.2" + "@swagger-api/apidom-error" "^1.10.2" "@types/ramda" "~0.30.0" - axios "^1.12.2" + axios "^1.15.0" minimatch "^10.2.1" ramda "~0.30.0" ramda-adjunct "^5.0.0" optionalDependencies: - "@swagger-api/apidom-json-pointer" "^1.6.0" - "@swagger-api/apidom-ns-arazzo-1" "^1.6.0" - "@swagger-api/apidom-ns-asyncapi-2" "^1.6.0" - "@swagger-api/apidom-ns-openapi-2" "^1.6.0" - "@swagger-api/apidom-ns-openapi-3-0" "^1.6.0" - "@swagger-api/apidom-ns-openapi-3-1" "^1.6.0" - "@swagger-api/apidom-ns-openapi-3-2" "^1.6.0" - "@swagger-api/apidom-parser-adapter-api-design-systems-json" "^1.6.0" - "@swagger-api/apidom-parser-adapter-api-design-systems-yaml" "^1.6.0" - "@swagger-api/apidom-parser-adapter-arazzo-json-1" "^1.6.0" - "@swagger-api/apidom-parser-adapter-arazzo-yaml-1" "^1.6.0" - "@swagger-api/apidom-parser-adapter-asyncapi-json-2" "^1.6.0" - "@swagger-api/apidom-parser-adapter-asyncapi-json-3" "^1.6.0" - "@swagger-api/apidom-parser-adapter-asyncapi-yaml-2" "^1.6.0" - "@swagger-api/apidom-parser-adapter-asyncapi-yaml-3" "^1.6.0" - "@swagger-api/apidom-parser-adapter-json" "^1.6.0" - "@swagger-api/apidom-parser-adapter-openapi-json-2" "^1.6.0" - "@swagger-api/apidom-parser-adapter-openapi-json-3-0" "^1.6.0" - "@swagger-api/apidom-parser-adapter-openapi-json-3-1" "^1.6.0" - "@swagger-api/apidom-parser-adapter-openapi-json-3-2" "^1.6.0" - "@swagger-api/apidom-parser-adapter-openapi-yaml-2" "^1.6.0" - "@swagger-api/apidom-parser-adapter-openapi-yaml-3-0" "^1.6.0" - "@swagger-api/apidom-parser-adapter-openapi-yaml-3-1" "^1.6.0" - "@swagger-api/apidom-parser-adapter-openapi-yaml-3-2" "^1.6.0" - "@swagger-api/apidom-parser-adapter-yaml-1-2" "^1.6.0" + "@swagger-api/apidom-json-pointer" "^1.10.2" + "@swagger-api/apidom-ns-arazzo-1" "^1.10.2" + "@swagger-api/apidom-ns-asyncapi-2" "^1.10.2" + "@swagger-api/apidom-ns-openapi-2" "^1.10.2" + "@swagger-api/apidom-ns-openapi-3-0" "^1.10.2" + "@swagger-api/apidom-ns-openapi-3-1" "^1.10.2" + "@swagger-api/apidom-ns-openapi-3-2" "^1.10.2" + "@swagger-api/apidom-parser-adapter-api-design-systems-json" "^1.10.2" + "@swagger-api/apidom-parser-adapter-api-design-systems-yaml" "^1.10.2" + "@swagger-api/apidom-parser-adapter-arazzo-json-1" "^1.10.2" + "@swagger-api/apidom-parser-adapter-arazzo-yaml-1" "^1.10.2" + "@swagger-api/apidom-parser-adapter-asyncapi-json-2" "^1.10.2" + "@swagger-api/apidom-parser-adapter-asyncapi-json-3" "^1.10.2" + "@swagger-api/apidom-parser-adapter-asyncapi-yaml-2" "^1.10.2" + "@swagger-api/apidom-parser-adapter-asyncapi-yaml-3" "^1.10.2" + "@swagger-api/apidom-parser-adapter-json" "^1.10.2" + "@swagger-api/apidom-parser-adapter-openapi-json-2" "^1.10.2" + "@swagger-api/apidom-parser-adapter-openapi-json-3-0" "^1.10.2" + "@swagger-api/apidom-parser-adapter-openapi-json-3-1" "^1.10.2" + "@swagger-api/apidom-parser-adapter-openapi-json-3-2" "^1.10.2" + "@swagger-api/apidom-parser-adapter-openapi-yaml-2" "^1.10.2" + "@swagger-api/apidom-parser-adapter-openapi-yaml-3-0" "^1.10.2" + "@swagger-api/apidom-parser-adapter-openapi-yaml-3-1" "^1.10.2" + "@swagger-api/apidom-parser-adapter-openapi-yaml-3-2" "^1.10.2" + "@swagger-api/apidom-parser-adapter-yaml-1-2" "^1.10.2" "@swaggerexpert/cookie@^2.0.2": version "2.0.2" @@ -4259,143 +4239,86 @@ dependencies: apg-lite "^1.0.4" -"@swc/core-darwin-arm64@1.15.13": - version "1.15.13" - resolved "https://registry.yarnpkg.com/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.13.tgz#f60ff48cb93066b83405074015eb6373ea0cdd77" - integrity sha512-ztXusRuC5NV2w+a6pDhX13CGioMLq8CjX5P4XgVJ21ocqz9t19288Do0y8LklplDtwcEhYGTNdMbkmUT7+lDTg== +"@swc/core-darwin-arm64@1.15.30": + version "1.15.30" + resolved "https://registry.yarnpkg.com/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.30.tgz#23447f1c30c9155fe35602de4392b4ecfa0a54cc" + integrity sha512-VvpP+vq08HmGYewMWvrdsxh9s2lthz/808zXm8Yu5kaqeR8Yia2b0eYXleHQ3VAjoStUDk6LzTheBW9KXYQdMA== -"@swc/core-darwin-arm64@1.15.18": - version "1.15.18" - resolved "https://registry.yarnpkg.com/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.18.tgz#fb487392f7bbe3179166b9b0d128916e39a627af" - integrity sha512-+mIv7uBuSaywN3C9LNuWaX1jJJ3SKfiJuE6Lr3bd+/1Iv8oMU7oLBjYMluX1UrEPzwN2qCdY6Io0yVicABoCwQ== +"@swc/core-darwin-x64@1.15.30": + version "1.15.30" + resolved "https://registry.yarnpkg.com/@swc/core-darwin-x64/-/core-darwin-x64-1.15.30.tgz#16e6e35fff5b07c712d8af44783da59ac64ad5cf" + integrity sha512-WiJA0hiZI3nwQAO6mu5RqigtWGDtth4Hiq6rbZxAaQyhIcqKIg5IoMRc1Y071lrNJn29eEDMC86Rq58xgUxlDg== -"@swc/core-darwin-x64@1.15.13": - version "1.15.13" - resolved "https://registry.yarnpkg.com/@swc/core-darwin-x64/-/core-darwin-x64-1.15.13.tgz#3838c08653ac53a6508cdc34bfa191281da4ed26" - integrity sha512-cVifxQUKhaE7qcO/y9Mq6PEhoyvN9tSLzCnnFZ4EIabFHBuLtDDO6a+vLveOy98hAs5Qu1+bb5Nv0oa1Pihe3Q== +"@swc/core-linux-arm-gnueabihf@1.15.30": + version "1.15.30" + resolved "https://registry.yarnpkg.com/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.30.tgz#abce7de734301109a7df23c22f6b6d233e3b9de9" + integrity sha512-YANuFUo48kIT6plJgCD0keae9HFXfjxsbvsgevqc0hr/07X/p7sAWTFOGYEc2SXcASaK7UvuQqzlbW8pr7R79g== -"@swc/core-darwin-x64@1.15.18": - version "1.15.18" - resolved "https://registry.yarnpkg.com/@swc/core-darwin-x64/-/core-darwin-x64-1.15.18.tgz#0e11fb0a80ebd56cb4417138a938ffc789ead492" - integrity sha512-wZle0eaQhnzxWX5V/2kEOI6Z9vl/lTFEC6V4EWcn+5pDjhemCpQv9e/TDJ0GIoiClX8EDWRvuZwh+Z3dhL1NAg== +"@swc/core-linux-arm64-gnu@1.15.30": + version "1.15.30" + resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.30.tgz#9a4e418cdbbfe64506dd12469a553c07e1924fef" + integrity sha512-VndG8jaR4ugY6u+iVOT0Q+d2fZd7sLgjPgN8W/Le+3EbZKl+cRfFxV7Eoz4gfLqhmneZPdcIzf9T3LkgkmqNLg== -"@swc/core-linux-arm-gnueabihf@1.15.13": - version "1.15.13" - resolved "https://registry.yarnpkg.com/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.13.tgz#9308b156fae3fe5d12684b86af31ada4255066fc" - integrity sha512-t+xxEzZ48enl/wGGy7SRYd7kImWQ/+wvVFD7g5JZo234g6/QnIgZ+YdfIyjHB+ZJI3F7a2IQHS7RNjxF29UkWw== +"@swc/core-linux-arm64-musl@1.15.30": + version "1.15.30" + resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.30.tgz#4cd68ccb2af71c3ec539b15aa15c8fd304833d26" + integrity sha512-1SYGs2l0Yyyi0pR/P/NKz/x0kqxkoiw+BXeJjLUdecSk/KasncWlJrc6hOvFSgKHOBrzgM5jwuluKtlT8dnrcA== -"@swc/core-linux-arm-gnueabihf@1.15.18": - version "1.15.18" - resolved "https://registry.yarnpkg.com/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.18.tgz#e7cac4b46d66dfd6b0fedea68877a5678fcf3579" - integrity sha512-ao61HGXVqrJFHAcPtF4/DegmwEkVCo4HApnotLU8ognfmU8x589z7+tcf3hU+qBiU1WOXV5fQX6W9Nzs6hjxDw== +"@swc/core-linux-ppc64-gnu@1.15.30": + version "1.15.30" + resolved "https://registry.yarnpkg.com/@swc/core-linux-ppc64-gnu/-/core-linux-ppc64-gnu-1.15.30.tgz#561997d3c5f392db7e3473cb4bbc43e6d6b1160c" + integrity sha512-TXREtiXeRhbfDFbmhnkIsXpKfzbfT73YkV2ZF6w0sfxgjC5zI2ZAbaCOq25qxvegofj2K93DtOpm9RLaBgqR2g== -"@swc/core-linux-arm64-gnu@1.15.13": - version "1.15.13" - resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.13.tgz#b150d6091e2bb3cf97dc9712a711d3ba025811a9" - integrity sha512-VndeGvKmTXFn6AGwjy0Kg8i7HccOCE7Jt/vmZwRxGtOfNZM1RLYRQ7MfDLo6T0h1Bq6eYzps3L5Ma4zBmjOnOg== +"@swc/core-linux-s390x-gnu@1.15.30": + version "1.15.30" + resolved "https://registry.yarnpkg.com/@swc/core-linux-s390x-gnu/-/core-linux-s390x-gnu-1.15.30.tgz#d6f1d5dceca794909305584cb69f80dd91820410" + integrity sha512-DCR2YYeyd6DQE4OuDhImouuNcjXEiEdnn1Y0DyGteugPEDvVuvYk8Xddi+4o2SgWH6jiW8/I+3emZvbep1NC+g== -"@swc/core-linux-arm64-gnu@1.15.18": - version "1.15.18" - resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.18.tgz#ca888f41be89887f9f6b4afd1cc38a1a596a655d" - integrity sha512-3xnctOBLIq3kj8PxOCgPrGjBLP/kNOddr6f5gukYt/1IZxsITQaU9TDyjeX6jG+FiCIHjCuWuffsyQDL5Ew1bg== +"@swc/core-linux-x64-gnu@1.15.30": + version "1.15.30" + resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.30.tgz#c3e91c60f265a62cec60145f0d2d931feb1cf41a" + integrity sha512-5Pizw3NgfOJ5BJOBK8TIRa59xFW2avESTOBDPTAYwZYa1JNDs+KMF9lUfjJiJLM5HiMs/wPheA9eiT0q9m2AoA== -"@swc/core-linux-arm64-musl@1.15.13": - version "1.15.13" - resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.13.tgz#35dd5382554096118ecd1db2928e1f71b56aa41a" - integrity sha512-SmZ9m+XqCB35NddHCctvHFLqPZDAs5j8IgD36GoutufDJmeq2VNfgk5rQoqNqKmAK3Y7iFdEmI76QoHIWiCLyw== +"@swc/core-linux-x64-musl@1.15.30": + version "1.15.30" + resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.30.tgz#3fd112e617a951438f73930b514adf19375067fb" + integrity sha512-qyqydP/wyH8alcIP4a2hnGSjHLJjm9H7yDFup+CPy9oTahFgLLwnNcv5UHXqO2Qs3AIND+cls5f/Bb6hqpxdgA== -"@swc/core-linux-arm64-musl@1.15.18": - version "1.15.18" - resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.18.tgz#292bb894cf08be522487897f6e2a616cbdd6198a" - integrity sha512-0a+Lix+FSSHBSBOA0XznCcHo5/1nA6oLLjcnocvzXeqtdjnPb+SvchItHI+lfeiuj1sClYPDvPMLSLyXFaiIKw== +"@swc/core-win32-arm64-msvc@1.15.30": + version "1.15.30" + resolved "https://registry.yarnpkg.com/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.30.tgz#d005dce92e4ec1b0a7898667c9cf5e5215e4631c" + integrity sha512-CaQENgDHVGOg1mSF5sQVgvfFHG9kjMor2rkLMLeLOkfZYNj13ppnJ9+lfaBZLZUMMbnlGQnavCJb8PVBUOso7Q== -"@swc/core-linux-x64-gnu@1.15.13": - version "1.15.13" - resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.13.tgz#03f89bbdce8f07a1bc02a3ef1c93a5b4e81aef2e" - integrity sha512-5rij+vB9a29aNkHq72EXI2ihDZPszJb4zlApJY4aCC/q6utgqFA6CkrfTfIb+O8hxtG3zP5KERETz8mfFK6A0A== +"@swc/core-win32-ia32-msvc@1.15.30": + version "1.15.30" + resolved "https://registry.yarnpkg.com/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.30.tgz#67ebfaa22266835a3d82776014c2f428346062bd" + integrity sha512-30VdLeGk6fugiUs/kUdJ/pAg7z/zpvVbR11RH60jZ0Z42WIeIniYx0rLEWN7h/pKJ3CopqsQ3RsogCAkRKiA2g== -"@swc/core-linux-x64-gnu@1.15.18": - version "1.15.18" - resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.18.tgz#2241fd6a01d88bac32334812660d80ebae88fd12" - integrity sha512-wG9J8vReUlpaHz4KOD/5UE1AUgirimU4UFT9oZmupUDEofxJKYb1mTA/DrMj0s78bkBiNI+7Fo2EgPuvOJfuAA== +"@swc/core-win32-x64-msvc@1.15.30": + version "1.15.30" + resolved "https://registry.yarnpkg.com/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.30.tgz#cb602b53f9cdcdfb580cecdb02b536339d4b004b" + integrity sha512-4iObHPR+Q4oDY110EF5SF5eIaaVJNpMdG9C0q3Q92BsJ5y467uHz7sYQhP60WYlLFsLQ1el2YrIPUItUAQGOKg== -"@swc/core-linux-x64-musl@1.15.13": - version "1.15.13" - resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.13.tgz#27ae66914125bf35724e43bb06b856afed39770c" - integrity sha512-OlSlaOK9JplQ5qn07WiBLibkOw7iml2++ojEXhhR3rbWrNEKCD7sd8+6wSavsInyFdw4PhLA+Hy6YyDBIE23Yw== - -"@swc/core-linux-x64-musl@1.15.18": - version "1.15.18" - resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.18.tgz#7d70f02a383d9dbae18b0d2906ee8b49dfb0b533" - integrity sha512-4nwbVvCphKzicwNWRmvD5iBaZj8JYsRGa4xOxJmOyHlMDpsvvJ2OR2cODlvWyGFH6BYL1MfIAK3qph3hp0Az6g== - -"@swc/core-win32-arm64-msvc@1.15.13": - version "1.15.13" - resolved "https://registry.yarnpkg.com/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.13.tgz#39776831949a53754d8f62e4730ac9430d1671f3" - integrity sha512-zwQii5YVdsfG8Ti9gIKgBKZg8qMkRZxl+OlYWUT5D93Jl4NuNBRausP20tfEkQdAPSRrMCSUZBM6FhW7izAZRg== - -"@swc/core-win32-arm64-msvc@1.15.18": - version "1.15.18" - resolved "https://registry.yarnpkg.com/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.18.tgz#6ea2b41d224a5ac84e1addf19fbc584e49698b08" - integrity sha512-zk0RYO+LjiBCat2RTMHzAWaMky0cra9loH4oRrLKLLNuL+jarxKLFDA8xTZWEkCPLjUTwlRN7d28eDLLMgtUcQ== - -"@swc/core-win32-ia32-msvc@1.15.13": - version "1.15.13" - resolved "https://registry.yarnpkg.com/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.13.tgz#995c45c9fd86d9959bc1a7275c4e60ac4efcc12f" - integrity sha512-hYXvyVVntqRlYoAIDwNzkS3tL2ijP3rxyWQMNKaxcCxxkCDto/w3meOK/OB6rbQSkNw0qTUcBfU9k+T0ptYdfQ== - -"@swc/core-win32-ia32-msvc@1.15.18": - version "1.15.18" - resolved "https://registry.yarnpkg.com/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.18.tgz#0c498802837ef53452c744964cac1391eb889e4d" - integrity sha512-yVuTrZ0RccD5+PEkpcLOBAuPbYBXS6rslENvIXfvJGXSdX5QGi1ehC4BjAMl5FkKLiam4kJECUI0l7Hq7T1vwg== - -"@swc/core-win32-x64-msvc@1.15.13": - version "1.15.13" - resolved "https://registry.yarnpkg.com/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.13.tgz#b329aec8bb5e84bab49c8f250ebb27f6c27dc213" - integrity sha512-XTzKs7c/vYCcjmcwawnQvlHHNS1naJEAzcBckMI5OJlnrcgW8UtcX9NHFYvNjGtXuKv0/9KvqL4fuahdvlNGKw== - -"@swc/core-win32-x64-msvc@1.15.18": - version "1.15.18" - resolved "https://registry.yarnpkg.com/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.18.tgz#878b48b38225680aad1e486880a6835461519e53" - integrity sha512-7NRmE4hmUQNCbYU3Hn9Tz57mK9Qq4c97ZS+YlamlK6qG9Fb5g/BB3gPDe0iLlJkns/sYv2VWSkm8c3NmbEGjbg== - -"@swc/core@^1.15.17": - version "1.15.18" - resolved "https://registry.yarnpkg.com/@swc/core/-/core-1.15.18.tgz#9eed29c0267d2c262391d4a2e75a3978e3f9dc74" - integrity sha512-z87aF9GphWp//fnkRsqvtY+inMVPgYW3zSlXH1kJFvRT5H/wiAn+G32qW5l3oEk63KSF1x3Ov0BfHCObAmT8RA== +"@swc/core@^1.15.30", "@swc/core@^1.7.39": + version "1.15.30" + resolved "https://registry.yarnpkg.com/@swc/core/-/core-1.15.30.tgz#2f77d5ed3b0df964aac8aaa251dc43ed822100cc" + integrity sha512-R8VQbQY1BZcbIF2p3gjlTCwAQzx1A194ugWfwld5y+WgVVWqVKm7eURGGOVbQVubgKWzidP2agomBbg96rZilQ== dependencies: "@swc/counter" "^0.1.3" - "@swc/types" "^0.1.25" + "@swc/types" "^0.1.26" optionalDependencies: - "@swc/core-darwin-arm64" "1.15.18" - "@swc/core-darwin-x64" "1.15.18" - "@swc/core-linux-arm-gnueabihf" "1.15.18" - "@swc/core-linux-arm64-gnu" "1.15.18" - "@swc/core-linux-arm64-musl" "1.15.18" - "@swc/core-linux-x64-gnu" "1.15.18" - "@swc/core-linux-x64-musl" "1.15.18" - "@swc/core-win32-arm64-msvc" "1.15.18" - "@swc/core-win32-ia32-msvc" "1.15.18" - "@swc/core-win32-x64-msvc" "1.15.18" - -"@swc/core@^1.7.39": - version "1.15.13" - resolved "https://registry.yarnpkg.com/@swc/core/-/core-1.15.13.tgz#19b4117a76576f46b28199ac2f75cad5bc9cdde4" - integrity sha512-0l1gl/72PErwUZuavcRpRAQN9uSst+Nk++niC5IX6lmMWpXoScYx3oq/narT64/sKv/eRiPTaAjBFGDEQiWJIw== - dependencies: - "@swc/counter" "^0.1.3" - "@swc/types" "^0.1.25" - optionalDependencies: - "@swc/core-darwin-arm64" "1.15.13" - "@swc/core-darwin-x64" "1.15.13" - "@swc/core-linux-arm-gnueabihf" "1.15.13" - "@swc/core-linux-arm64-gnu" "1.15.13" - "@swc/core-linux-arm64-musl" "1.15.13" - "@swc/core-linux-x64-gnu" "1.15.13" - "@swc/core-linux-x64-musl" "1.15.13" - "@swc/core-win32-arm64-msvc" "1.15.13" - "@swc/core-win32-ia32-msvc" "1.15.13" - "@swc/core-win32-x64-msvc" "1.15.13" + "@swc/core-darwin-arm64" "1.15.30" + "@swc/core-darwin-x64" "1.15.30" + "@swc/core-linux-arm-gnueabihf" "1.15.30" + "@swc/core-linux-arm64-gnu" "1.15.30" + "@swc/core-linux-arm64-musl" "1.15.30" + "@swc/core-linux-ppc64-gnu" "1.15.30" + "@swc/core-linux-s390x-gnu" "1.15.30" + "@swc/core-linux-x64-gnu" "1.15.30" + "@swc/core-linux-x64-musl" "1.15.30" + "@swc/core-win32-arm64-msvc" "1.15.30" + "@swc/core-win32-ia32-msvc" "1.15.30" + "@swc/core-win32-x64-msvc" "1.15.30" "@swc/counter@^0.1.3": version "0.1.3" @@ -4470,10 +4393,10 @@ "@swc/html-win32-ia32-msvc" "1.15.13" "@swc/html-win32-x64-msvc" "1.15.13" -"@swc/types@^0.1.25": - version "0.1.25" - resolved "https://registry.npmjs.org/@swc/types/-/types-0.1.25.tgz" - integrity sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g== +"@swc/types@^0.1.26": + version "0.1.26" + resolved "https://registry.yarnpkg.com/@swc/types/-/types-0.1.26.tgz#2a976a1870caef1992316dda1464150ee36968b5" + integrity sha512-lyMwd7WGgG79RS7EERZV3T8wMdmPq3xwyg+1nmAM64kIhx5yl+juO2PYIHb7vTiPgPCj8LYjsNV2T5wiQHUEaw== dependencies: "@swc/counter" "^0.1.3" @@ -4828,10 +4751,10 @@ resolved "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz" integrity sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg== -"@types/gtag.js@^0.0.12": - version "0.0.12" - resolved "https://registry.npmjs.org/@types/gtag.js/-/gtag.js-0.0.12.tgz" - integrity sha512-YQV9bUsemkzG81Ea295/nF/5GijnD2Af7QhEofh7xu+kvCN6RdodgNwwGWXB5GMI3NoyvQo0odNctoH/qLMIpg== +"@types/gtag.js@^0.0.20": + version "0.0.20" + resolved "https://registry.yarnpkg.com/@types/gtag.js/-/gtag.js-0.0.20.tgz#e47edabb4ed5ecac90a079275958e6c929d7c08a" + integrity sha512-wwAbk3SA2QeU67unN7zPxjEHmPmlXwZXZvQEpbEUQuMCRGgKyE1m6XDuTUA9b6pCGb/GqJmdfMOY5LuDjJSbbg== "@types/hast@^2.0.0": version "2.3.10" @@ -5117,100 +5040,100 @@ dependencies: "@types/yargs-parser" "*" -"@typescript-eslint/eslint-plugin@8.57.1", "@typescript-eslint/eslint-plugin@^8.52.0": - version "8.57.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.57.1.tgz#ddfdfb30f8b5ccee7f3c21798b377c51370edd55" - integrity sha512-Gn3aqnvNl4NGc6x3/Bqk1AOn0thyTU9bqDRhiRnUWezgvr2OnhYCWCgC8zXXRVqBsIL1pSDt7T9nJUe0oM0kDQ== +"@typescript-eslint/eslint-plugin@8.59.0", "@typescript-eslint/eslint-plugin@^8.52.0": + version "8.59.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.59.0.tgz#fcbe76b693ce2412410cf4d48aefd617d345f2d9" + integrity sha512-HyAZtpdkgZwpq8Sz3FSUvCR4c+ScbuWa9AksK2Jweub7w4M3yTz4O11AqVJzLYjy/B9ZWPyc81I+mOdJU/bDQw== dependencies: "@eslint-community/regexpp" "^4.12.2" - "@typescript-eslint/scope-manager" "8.57.1" - "@typescript-eslint/type-utils" "8.57.1" - "@typescript-eslint/utils" "8.57.1" - "@typescript-eslint/visitor-keys" "8.57.1" + "@typescript-eslint/scope-manager" "8.59.0" + "@typescript-eslint/type-utils" "8.59.0" + "@typescript-eslint/utils" "8.59.0" + "@typescript-eslint/visitor-keys" "8.59.0" ignore "^7.0.5" natural-compare "^1.4.0" - ts-api-utils "^2.4.0" + ts-api-utils "^2.5.0" -"@typescript-eslint/parser@8.57.1", "@typescript-eslint/parser@^8.56.1": - version "8.57.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.57.1.tgz#d523e559b148264055c0a49a29d5f50c7de659c2" - integrity sha512-k4eNDan0EIMTT/dUKc/g+rsJ6wcHYhNPdY19VoX/EOtaAG8DLtKCykhrUnuHPYvinn5jhAPgD2Qw9hXBwrahsw== +"@typescript-eslint/parser@8.59.0", "@typescript-eslint/parser@^8.59.0": + version "8.59.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.59.0.tgz#57a138280b3ceaf07904fbd62c433d5cc1ee1573" + integrity sha512-TI1XGwKbDpo9tRW8UDIXCOeLk55qe9ZFGs8MTKU6/M08HWTw52DD/IYhfQtOEhEdPhLMT26Ka/x7p70nd3dzDg== dependencies: - "@typescript-eslint/scope-manager" "8.57.1" - "@typescript-eslint/types" "8.57.1" - "@typescript-eslint/typescript-estree" "8.57.1" - "@typescript-eslint/visitor-keys" "8.57.1" + "@typescript-eslint/scope-manager" "8.59.0" + "@typescript-eslint/types" "8.59.0" + "@typescript-eslint/typescript-estree" "8.59.0" + "@typescript-eslint/visitor-keys" "8.59.0" debug "^4.4.3" -"@typescript-eslint/project-service@8.57.1": - version "8.57.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/project-service/-/project-service-8.57.1.tgz#16af9fe16eedbd7085e4fdc29baa73715c0c55c5" - integrity sha512-vx1F37BRO1OftsYlmG9xay1TqnjNVlqALymwWVuYTdo18XuKxtBpCj1QlzNIEHlvlB27osvXFWptYiEWsVdYsg== +"@typescript-eslint/project-service@8.59.0": + version "8.59.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/project-service/-/project-service-8.59.0.tgz#914bf62069d870faa0389ffd725774a200f511bf" + integrity sha512-Lw5ITrR5s5TbC19YSvlr63ZfLaJoU6vtKTHyB0GQOpX0W7d5/Ir6vUahWi/8Sps/nOukZQ0IB3SmlxZnjaKVnw== dependencies: - "@typescript-eslint/tsconfig-utils" "^8.57.1" - "@typescript-eslint/types" "^8.57.1" + "@typescript-eslint/tsconfig-utils" "^8.59.0" + "@typescript-eslint/types" "^8.59.0" debug "^4.4.3" -"@typescript-eslint/scope-manager@8.57.1": - version "8.57.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.57.1.tgz#4524d7e7b420cb501807499684d435ae129aaf35" - integrity sha512-hs/QcpCwlwT2L5S+3fT6gp0PabyGk4Q0Rv2doJXA0435/OpnSR3VRgvrp8Xdoc3UAYSg9cyUjTeFXZEPg/3OKg== +"@typescript-eslint/scope-manager@8.59.0": + version "8.59.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.59.0.tgz#f71be268bd31da1c160815c689e4dde7c9bc9e8e" + integrity sha512-UzR16Ut8IpA3Mc4DbgAShlPPkVm8xXMWafXxB0BocaVRHs8ZGakAxGRskF7FId3sdk9lgGD73GSFaWmWFDE4dg== dependencies: - "@typescript-eslint/types" "8.57.1" - "@typescript-eslint/visitor-keys" "8.57.1" + "@typescript-eslint/types" "8.59.0" + "@typescript-eslint/visitor-keys" "8.59.0" -"@typescript-eslint/tsconfig-utils@8.57.1", "@typescript-eslint/tsconfig-utils@^8.57.1": - version "8.57.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.57.1.tgz#9233443ec716882a6f9e240fd900a73f0235f3d7" - integrity sha512-0lgOZB8cl19fHO4eI46YUx2EceQqhgkPSuCGLlGi79L2jwYY1cxeYc1Nae8Aw1xjgW3PKVDLlr3YJ6Bxx8HkWg== +"@typescript-eslint/tsconfig-utils@8.59.0", "@typescript-eslint/tsconfig-utils@^8.59.0": + version "8.59.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.59.0.tgz#1276077f5ad77e384446ea28a2474e8f8be1af41" + integrity sha512-91Sbl3s4Kb3SybliIY6muFBmHVv+pYXfybC4Oolp3dvk8BvIE3wOPc+403CWIT7mJNkfQRGtdqghzs2+Z91Tqg== -"@typescript-eslint/type-utils@8.57.1": - version "8.57.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.57.1.tgz#c49af1347b5869ca85155547a8f34f84ab386fd9" - integrity sha512-+Bwwm0ScukFdyoJsh2u6pp4S9ktegF98pYUU0hkphOOqdMB+1sNQhIz8y5E9+4pOioZijrkfNO/HUJVAFFfPKA== +"@typescript-eslint/type-utils@8.59.0": + version "8.59.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.59.0.tgz#2834ea3b179cedfc9244dcd4f74105a27751a439" + integrity sha512-3TRiZaQSltGqGeNrJzzr1+8YcEobKH9rHnqIp/1psfKFmhRQDNMGP5hBufanYTGznwShzVLs3Mz+gDN7HkWfXg== dependencies: - "@typescript-eslint/types" "8.57.1" - "@typescript-eslint/typescript-estree" "8.57.1" - "@typescript-eslint/utils" "8.57.1" + "@typescript-eslint/types" "8.59.0" + "@typescript-eslint/typescript-estree" "8.59.0" + "@typescript-eslint/utils" "8.59.0" debug "^4.4.3" - ts-api-utils "^2.4.0" + ts-api-utils "^2.5.0" -"@typescript-eslint/types@8.57.1", "@typescript-eslint/types@^8.57.1": - version "8.57.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.57.1.tgz#54b27a8a25a7b45b4f978c3f8e00c4c78f11142c" - integrity sha512-S29BOBPJSFUiblEl6RzPPjJt6w25A6XsBqRVDt53tA/tlL8q7ceQNZHTjPeONt/3S7KRI4quk+yP9jK2WjBiPQ== +"@typescript-eslint/types@8.59.0", "@typescript-eslint/types@^8.59.0": + version "8.59.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.59.0.tgz#cfcc643c6e879016479775850d86d84c14492738" + integrity sha512-nLzdsT1gdOgFxxxwrlNVUBzSNBEEHJ86bblmk4QAS6stfig7rcJzWKqCyxFy3YRRHXDWEkb2NralA1nOYkkm/A== -"@typescript-eslint/typescript-estree@8.57.1": - version "8.57.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.57.1.tgz#a9fd28d4a0ec896aa9a9a7e0cead62ea24f99e76" - integrity sha512-ybe2hS9G6pXpqGtPli9Gx9quNV0TWLOmh58ADlmZe9DguLq0tiAKVjirSbtM1szG6+QH6rVXyU6GTLQbWnMY+g== +"@typescript-eslint/typescript-estree@8.59.0": + version "8.59.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.59.0.tgz#feba58a70ab6ea7ac53a2f3ae900db28ce3454c2" + integrity sha512-O9Re9P1BmBLFJyikRbQpLku/QA3/AueZNO9WePLBwQrvkixTmDe8u76B6CYUAITRl/rHawggEqUGn5QIkVRLMw== dependencies: - "@typescript-eslint/project-service" "8.57.1" - "@typescript-eslint/tsconfig-utils" "8.57.1" - "@typescript-eslint/types" "8.57.1" - "@typescript-eslint/visitor-keys" "8.57.1" + "@typescript-eslint/project-service" "8.59.0" + "@typescript-eslint/tsconfig-utils" "8.59.0" + "@typescript-eslint/types" "8.59.0" + "@typescript-eslint/visitor-keys" "8.59.0" debug "^4.4.3" minimatch "^10.2.2" semver "^7.7.3" tinyglobby "^0.2.15" - ts-api-utils "^2.4.0" + ts-api-utils "^2.5.0" -"@typescript-eslint/utils@8.57.1": - version "8.57.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.57.1.tgz#e40f5a7fcff02fd24092a7b52bd6ec029fb50465" - integrity sha512-XUNSJ/lEVFttPMMoDVA2r2bwrl8/oPx8cURtczkSEswY5T3AeLmCy+EKWQNdL4u0MmAHOjcWrqJp2cdvgjn8dQ== +"@typescript-eslint/utils@8.59.0": + version "8.59.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.59.0.tgz#f50df9bd6967881ef64fba62230111153179ead5" + integrity sha512-I1R/K7V07XsMJ12Oaxg/O9GfrysGTmCRhvZJBv0RE0NcULMzjqVpR5kRRQjHsz3J/bElU7HwCO7zkqL+MSUz+g== dependencies: "@eslint-community/eslint-utils" "^4.9.1" - "@typescript-eslint/scope-manager" "8.57.1" - "@typescript-eslint/types" "8.57.1" - "@typescript-eslint/typescript-estree" "8.57.1" + "@typescript-eslint/scope-manager" "8.59.0" + "@typescript-eslint/types" "8.59.0" + "@typescript-eslint/typescript-estree" "8.59.0" -"@typescript-eslint/visitor-keys@8.57.1": - version "8.57.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.57.1.tgz#3af4f88118924d3be983d4b8ae84803f11fe4563" - integrity sha512-YWnmJkXbofiz9KbnbbwuA2rpGkFPLbAIetcCNO6mJ8gdhdZ/v7WDXsoGFAJuM6ikUFKTlSQnjWnVO4ux+UzS6A== +"@typescript-eslint/visitor-keys@8.59.0": + version "8.59.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.59.0.tgz#2e80de30e7e944ed4bd47d751e37dcb04db03795" + integrity sha512-/uejZt4dSere1bx12WLlPfv8GktzcaDtuJ7s42/HEZ5zGj9oxRaD4bj7qwSunXkf+pbAhFt2zjpHYUiT5lHf0Q== dependencies: - "@typescript-eslint/types" "8.57.1" + "@typescript-eslint/types" "8.59.0" eslint-visitor-keys "^5.0.0" "@ungap/structured-clone@^1.0.0": @@ -5218,11 +5141,6 @@ resolved "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz" integrity sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g== -"@vercel/oidc@3.0.2": - version "3.0.2" - resolved "https://registry.npmjs.org/@vercel/oidc/-/oidc-3.0.2.tgz" - integrity sha512-JekxQ0RApo4gS4un/iMGsIL1/k4KUBe3HmnGcDvzHuFBdQdudEJgTqcsJC7y6Ul4Yw5CeykgvQbX2XeEJd0+DA== - "@vx/responsive@^0.0.199": version "0.0.199" resolved "https://registry.npmjs.org/@vx/responsive/-/responsive-0.0.199.tgz" @@ -5415,11 +5333,6 @@ address@^1.0.1: resolved "https://registry.npmjs.org/address/-/address-1.2.2.tgz" integrity sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA== -agent-base@^7.1.2: - version "7.1.4" - resolved "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz" - integrity sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ== - aggregate-error@^3.0.0: version "3.1.0" resolved "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz" @@ -5428,16 +5341,6 @@ aggregate-error@^3.0.0: clean-stack "^2.0.0" indent-string "^4.0.0" -ai@5.0.61, ai@^5.0.30: - version "5.0.61" - resolved "https://registry.npmjs.org/ai/-/ai-5.0.61.tgz" - integrity sha512-a+NtNgxjMiGvL46/+v7iNbKHLAg8VDheNhYFe9lL3MPiL9W5DNH45wMaahteCYS4WgoFgdQa2zz4CM5lkiXuOA== - dependencies: - "@ai-sdk/gateway" "1.0.34" - "@ai-sdk/provider" "2.0.0" - "@ai-sdk/provider-utils" "3.0.10" - "@opentelemetry/api" "1.9.0" - ajv-draft-04@1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/ajv-draft-04/-/ajv-draft-04-1.0.0.tgz" @@ -5450,6 +5353,13 @@ ajv-formats@2.1.1, ajv-formats@^2.1.1: dependencies: ajv "^8.0.0" +ajv-formats@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-3.0.1.tgz#3d5dc762bca17679c3c2ea7e90ad6b7532309578" + integrity sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ== + dependencies: + ajv "^8.0.0" + ajv-keywords@^3.5.2: version "3.5.2" resolved "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz" @@ -5463,9 +5373,9 @@ ajv-keywords@^5.1.0: fast-deep-equal "^3.1.3" ajv@^6.12.4, ajv@^6.12.5: - version "6.12.6" - resolved "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz" - integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + version "6.14.0" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.14.0.tgz#fd067713e228210636ebb08c60bd3765d6dbe73a" + integrity sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw== dependencies: fast-deep-equal "^3.1.1" fast-json-stable-stringify "^2.0.0" @@ -5473,9 +5383,19 @@ ajv@^6.12.4, ajv@^6.12.5: uri-js "^4.2.2" ajv@^8.0.0, ajv@^8.11.0, ajv@^8.9.0: - version "8.17.1" - resolved "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz" - integrity sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g== + version "8.18.0" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.18.0.tgz#8864186b6738d003eb3a933172bb3833e10cefbc" + integrity sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A== + dependencies: + fast-deep-equal "^3.1.3" + fast-uri "^3.0.1" + json-schema-traverse "^1.0.0" + require-from-string "^2.0.2" + +"ajv@npm:@redocly/ajv@8.18.0": + version "8.18.0" + resolved "https://registry.yarnpkg.com/@redocly/ajv/-/ajv-8.18.0.tgz#e6c7ba549111838baa950bc31acbc84b06f0239f" + integrity sha512-F+LMD2IDIXuHxgpLJh3nkLj9+tSaEzoUWd+7fONGq5pe2169FUDjpEkOfEpoGLz1sbZni/69p07OsecNfAOpqA== dependencies: fast-deep-equal "^3.1.3" fast-uri "^3.0.1" @@ -5489,7 +5409,7 @@ algoliasearch-helper@^3.26.0: dependencies: "@algolia/events" "^4.0.1" -algoliasearch@^5.28.0, algoliasearch@^5.37.0: +algoliasearch@^5.37.0: version "5.40.0" resolved "https://registry.npmjs.org/algoliasearch/-/algoliasearch-5.40.0.tgz" integrity sha512-a9aIL2E3Z7uYUPMCmjMFFd5MWhn+ccTubEvnMy7rOTZCB62dXBJtz0R5BZ/TPuX3R9ocBsgWuAbGWQ+Ph4Fmlg== @@ -5523,13 +5443,6 @@ ansi-align@^3.0.1: dependencies: string-width "^4.1.0" -ansi-escapes@^4.3.2: - version "4.3.2" - resolved "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz" - integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== - dependencies: - type-fest "^0.21.3" - ansi-html-community@^0.0.8: version "0.0.8" resolved "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz" @@ -5564,16 +5477,21 @@ ansi-styles@^6.1.0: resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz" integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug== -antd@^6.3.2: - version "6.3.2" - resolved "https://registry.yarnpkg.com/antd/-/antd-6.3.2.tgz#ce1a33783d495fcfc77b58b73156ac6249e4fc0a" - integrity sha512-IlMoqaXlq5Bgxi0ANERhAzmDREYyGwr/U7MCVihaUQbE/ZOB3r4ArakUxjA1ULYNDA6K00dawSrB8aalGnZlLA== +ansis@^3.2.0: + version "3.17.0" + resolved "https://registry.yarnpkg.com/ansis/-/ansis-3.17.0.tgz#fa8d9c2a93fe7d1177e0c17f9eeb562a58a832d7" + integrity sha512-0qWUglt9JEqLFr3w1I1pbrChn1grhaiAR2ocX1PP/flRmxgtwTzPFFFnfIlD6aMOLQZgSuCRlidD70lvx8yhzg== + +antd@^6.3.6: + version "6.3.6" + resolved "https://registry.yarnpkg.com/antd/-/antd-6.3.6.tgz#e892b851cf45d62201d889fe9cac36f4d2412e5f" + integrity sha512-zdCYjusrTUn4gNxEg4PH8MWlfuXYbKfuGOkjgZ0Rg6DpWbIVmG/MwvsZ5yvG6z3Y6UI/gzYpaQ82iTt4KdbeaA== dependencies: "@ant-design/colors" "^8.0.1" "@ant-design/cssinjs" "^2.1.2" "@ant-design/cssinjs-utils" "^2.1.2" "@ant-design/fast-color" "^3.0.1" - "@ant-design/icons" "^6.1.0" + "@ant-design/icons" "^6.1.1" "@ant-design/react-slick" "~2.0.0" "@babel/runtime" "^7.28.4" "@rc-component/cascader" "~1.14.0" @@ -5583,23 +5501,23 @@ antd@^6.3.2: "@rc-component/dialog" "~1.8.4" "@rc-component/drawer" "~1.4.2" "@rc-component/dropdown" "~1.0.2" - "@rc-component/form" "~1.7.1" - "@rc-component/image" "~1.6.0" + "@rc-component/form" "~1.8.0" + "@rc-component/image" "~1.9.0" "@rc-component/input" "~1.1.2" "@rc-component/input-number" "~1.6.2" "@rc-component/mentions" "~1.6.0" "@rc-component/menu" "~1.2.0" - "@rc-component/motion" "^1.3.1" + "@rc-component/motion" "^1.3.2" "@rc-component/mutate-observer" "^2.0.1" "@rc-component/notification" "~1.2.0" "@rc-component/pagination" "~1.2.0" - "@rc-component/picker" "~1.9.0" + "@rc-component/picker" "~1.9.1" "@rc-component/progress" "~1.0.2" "@rc-component/qrcode" "~1.1.1" "@rc-component/rate" "~1.0.1" - "@rc-component/resize-observer" "^1.1.1" + "@rc-component/resize-observer" "^1.1.2" "@rc-component/segmented" "~1.3.0" - "@rc-component/select" "~1.6.14" + "@rc-component/select" "~1.6.15" "@rc-component/slider" "~1.0.1" "@rc-component/steps" "~1.2.2" "@rc-component/switch" "~1.0.3" @@ -5608,11 +5526,11 @@ antd@^6.3.2: "@rc-component/textarea" "~1.1.2" "@rc-component/tooltip" "~1.4.0" "@rc-component/tour" "~2.3.0" - "@rc-component/tree" "~1.2.3" + "@rc-component/tree" "~1.2.4" "@rc-component/tree-select" "~1.8.0" "@rc-component/trigger" "^3.9.0" "@rc-component/upload" "~1.1.0" - "@rc-component/util" "^1.9.0" + "@rc-component/util" "^1.10.1" clsx "^2.1.1" dayjs "^1.11.11" scroll-into-view-if-needed "^3.1.0" @@ -5799,14 +5717,14 @@ available-typed-arrays@^1.0.7: dependencies: possible-typed-array-names "^1.0.0" -axios@^1.12.2: - version "1.13.5" - resolved "https://registry.yarnpkg.com/axios/-/axios-1.13.5.tgz#5e464688fa127e11a660a2c49441c009f6567a43" - integrity sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q== +axios@^1.15.0: + version "1.15.0" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.15.0.tgz#0fcee91ef03d386514474904b27863b2c683bf4f" + integrity sha512-wWyJDlAatxk30ZJer+GeCWS209sA42X+N5jU2jy6oHTp7ufw8uzUTVFBX9+wTfAlhiJXGS0Bq7X6efruWjuK9Q== dependencies: follow-redirects "^1.15.11" form-data "^4.0.5" - proxy-from-env "^1.1.0" + proxy-from-env "^2.1.0" babel-loader@^9.2.1: version "9.2.1" @@ -5876,10 +5794,10 @@ base64-js@^1.3.1, base64-js@^1.5.1: resolved "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== -baseline-browser-mapping@^2.10.7, baseline-browser-mapping@^2.9.0, baseline-browser-mapping@^2.9.19: - version "2.10.7" - resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.10.7.tgz#2c017adffe4f7bbe93c2e55526cc1869d36f588c" - integrity sha512-1ghYO3HnxGec0TCGBXiDLVns4eCSx4zJpxnHrlqFQajmhfKMQBzUGDdkMK7fUW7PTHTeLf+j87aTuKuuwWzMGw== +baseline-browser-mapping@^2.10.20, baseline-browser-mapping@^2.9.0, baseline-browser-mapping@^2.9.19: + version "2.10.20" + resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.10.20.tgz#7c99b86d43ae9be3810cac515f4675802e1f6b87" + integrity sha512-1AaXxEPfXT+GvTBJFuy4yXVHWJBXa4OdbIebGN/wX5DlsIkU0+wzGnd2lOzokSk51d5LUmqjgBLRLlypLUqInQ== batch@0.6.1: version "0.6.1" @@ -5968,24 +5886,17 @@ boxen@^7.0.0: wrap-ansi "^8.1.0" brace-expansion@^1.1.7: - version "1.1.12" - resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz" - integrity sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg== + version "1.1.13" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.13.tgz#d37875c01dc9eff988dd49d112a57cb67b54efe6" + integrity sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w== dependencies: balanced-match "^1.0.0" concat-map "0.0.1" -brace-expansion@^2.0.1: - version "2.0.2" - resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz" - integrity sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ== - dependencies: - balanced-match "^1.0.0" - -brace-expansion@^5.0.2: - version "5.0.2" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-5.0.2.tgz#b6c16d0791087af6c2bc463f52a8142046c06b6f" - integrity sha512-Pdk8c9poy+YhOgVWw1JNN22/HcivgKWwpxKq04M/jTmHyCZn12WPJebZxdjSa5TmBqISrUSgNYU3eRORljfCCw== +brace-expansion@^5.0.5: + version "5.0.5" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-5.0.5.tgz#dcc3a37116b79f3e1b46db994ced5d570e930fdb" + integrity sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ== dependencies: balanced-match "^4.0.2" @@ -6124,10 +6035,10 @@ caniuse-api@^3.0.0: lodash.memoize "^4.1.2" lodash.uniq "^4.5.0" -caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001702, caniuse-lite@^1.0.30001759, caniuse-lite@^1.0.30001780: - version "1.0.30001780" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001780.tgz#0e413de292808868a62ed9118822683fa120a110" - integrity sha512-llngX0E7nQci5BPJDqoZSbuZ5Bcs9F5db7EtgfwBerX9XGtkkiO4NwfDDIRzHTTwcYC8vC7bmeUEPGrKlR/TkQ== +caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001702, caniuse-lite@^1.0.30001759, caniuse-lite@^1.0.30001790: + version "1.0.30001790" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001790.tgz#04660c7de15f445d86dd10ac88a8936ac0698e45" + integrity sha512-bOoxfJPyYo+ds6W0YfptaCWbFnJYjh2Y1Eow5lRv+vI2u8ganPZqNm1JwNh0t2ELQCqIWg4B3dWEusgAmsoyOw== ccount@^2.0.0: version "2.0.1" @@ -6156,6 +6067,11 @@ chalk@^5.0.1, chalk@^5.2.0: resolved "https://registry.npmjs.org/chalk/-/chalk-5.6.0.tgz" integrity sha512-46QrSQFyVSEyYAgQ22hQ+zDa60YHA4fBstHmtSApj1Y5vKtG27fWowW03jCk5KcbXEWPZUIR894aARCA/G1kfQ== +chalk@^5.6.2: + version "5.6.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.6.2.tgz#b1238b6e23ea337af71c7f8a295db5af0c158aea" + integrity sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA== + char-regex@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz" @@ -7321,7 +7237,7 @@ depd@~1.1.2: resolved "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz" integrity sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ== -dequal@^2.0.0, dequal@^2.0.3: +dequal@^2.0.0: version "2.0.3" resolved "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz" integrity sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA== @@ -7389,31 +7305,31 @@ doctrine@^2.1.0: dependencies: esutils "^2.0.2" -docusaurus-plugin-openapi-docs@^4.6.0: - version "4.7.1" - resolved "https://registry.npmjs.org/docusaurus-plugin-openapi-docs/-/docusaurus-plugin-openapi-docs-4.7.1.tgz" - integrity sha512-RpqvTEnhIfdSuTn/Fa/8bmxeufijLL9HCRb//ELD33AKqEbCw147SKR/CqWu4H4gwi50FZLUbiHKZJbPtXLt9Q== +docusaurus-plugin-openapi-docs@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/docusaurus-plugin-openapi-docs/-/docusaurus-plugin-openapi-docs-5.0.1.tgz#2fe62b58fc1af11e3d947edc2f0d60e04f1aa149" + integrity sha512-OVfoDovRdiS78DQYWmr2BjuOF2A6kVmJ43mgkQaAEZxASyHbUft4zUIhvfa7gqema6KNL9pVKejDievZdZ3wGQ== dependencies: - "@apidevtools/json-schema-ref-parser" "^11.5.4" - "@redocly/openapi-core" "^1.34.3" + "@apidevtools/json-schema-ref-parser" "^15.3.3" + "@redocly/openapi-core" "^2.25.2" allof-merge "^0.6.6" - chalk "^4.1.2" + chalk "^5.6.2" clsx "^2.1.1" fs-extra "^11.3.0" json-pointer "^0.6.2" json5 "^2.2.3" lodash "^4.17.21" mustache "^4.2.0" - openapi-to-postmanv2 "^5.0.0" + openapi-to-postmanv2 "^6.0.0" postman-collection "^5.0.2" slugify "^1.6.6" swagger2openapi "^7.0.8" xml-formatter "^3.6.6" -docusaurus-theme-openapi-docs@^4.6.0: - version "4.7.1" - resolved "https://registry.npmjs.org/docusaurus-theme-openapi-docs/-/docusaurus-theme-openapi-docs-4.7.1.tgz" - integrity sha512-OPydf11LoEY3fdxaoqCVO+qCk7LBo6l6s28UvHJ5mIN/2xu+dOOio9+xnKZ5FIPOlD+dx0gVSKzaVCi/UFTxlg== +docusaurus-theme-openapi-docs@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/docusaurus-theme-openapi-docs/-/docusaurus-theme-openapi-docs-5.0.1.tgz#a2c2c91346b6238f6d7862752cdb02611fb5396f" + integrity sha512-bVeb7hOqog9LKVrJzYXdNJ7/0N22lk0VE22QK+naAn5GuAvYo41JmpXW9hqLIPkEp2UbexTHoPW9SYVdUsyvvw== dependencies: "@hookform/error-message" "^2.0.1" "@reduxjs/toolkit" "^2.8.2" @@ -7425,6 +7341,7 @@ docusaurus-theme-openapi-docs@^4.6.0: file-saver "^2.0.5" lodash "^4.17.21" pako "^2.1.0" + path-browserify "^1.0.1" postman-code-generators "^2.0.0" postman-collection "^5.0.2" prism-react-renderer "^2.4.1" @@ -7487,10 +7404,10 @@ domhandler@^5.0.2, domhandler@^5.0.3: dependencies: domelementtype "^2.3.0" -dompurify@=3.2.6, dompurify@^3.2.5: - version "3.2.6" - resolved "https://registry.npmjs.org/dompurify/-/dompurify-3.2.6.tgz" - integrity sha512-/2GogDQlohXPZe6D6NOgQvXLPSYBqIWMnZ8zzOhn09REE4eyAzb+Hed3jhoM9OkuaJ8P6ZGTTVWQKAi8ieIzfQ== +dompurify@^3.2.5, dompurify@^3.3.2: + version "3.4.0" + resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-3.4.0.tgz#b1fc33ebdadb373241621e0a30e4ad81573dfd0b" + integrity sha512-nolgK9JcaUXMSmW+j1yaSvaEaoXYHwWyGJlkoCTghc97KgGDDSnpoU/PlEnw63Ah+TGKFOyY+X5LnxaWbCSfXg== optionalDependencies: "@types/trusted-types" "^2.0.7" @@ -8090,12 +8007,7 @@ events@^3.2.0: resolved "https://registry.npmjs.org/events/-/events-3.3.0.tgz" integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== -eventsource-parser@^3.0.5: - version "3.0.6" - resolved "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz" - integrity sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg== - -execa@5.1.1, execa@^5.1.1: +execa@^5.1.1: version "5.1.1" resolved "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz" integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== @@ -8260,13 +8172,6 @@ fetch-retry@^6.0.0: resolved "https://registry.npmjs.org/fetch-retry/-/fetch-retry-6.0.0.tgz" integrity sha512-BUFj1aMubgib37I3v4q78fYo63Po7t4HUPTpQ6/QE6yK6cIQrP+W43FYToeTEyg5m2Y7eFUtijUuAv/PDlWuag== -figures@^3.2.0: - version "3.2.0" - resolved "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz" - integrity sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg== - dependencies: - escape-string-regexp "^1.0.5" - file-entry-cache@^8.0.0: version "8.0.0" resolved "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz" @@ -8355,14 +8260,14 @@ flat@^5.0.2: integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== flatted@^3.2.9: - version "3.3.3" - resolved "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz" - integrity sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg== + version "3.4.2" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.4.2.tgz#f5c23c107f0f37de8dbdf24f13722b3b98d52726" + integrity sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA== follow-redirects@^1.0.0, follow-redirects@^1.15.11: - version "1.15.11" - resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz" - integrity sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ== + version "1.16.0" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.16.0.tgz#28474a159d3b9d11ef62050a14ed60e4df6d61bc" + integrity sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw== for-each@^0.3.3, for-each@^0.3.5: version "0.3.5" @@ -8412,16 +8317,7 @@ fresh@0.5.2, fresh@~0.5.2: resolved "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz" integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== -fs-extra@^11.1.1, fs-extra@^11.2.0: - version "11.3.1" - resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.1.tgz" - integrity sha512-eXvGGwZ5CL17ZSwHWd3bbgk7UUpF6IFHtP57NYYakPvHOs8GDgDe5KJI36jIJzDkJ6eJjuzRA8eBQb6SkKue0g== - dependencies: - graceful-fs "^4.2.0" - jsonfile "^6.0.1" - universalify "^2.0.0" - -fs-extra@^11.3.0: +fs-extra@^11.1.1, fs-extra@^11.2.0, fs-extra@^11.3.0: version "11.3.3" resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.3.tgz" integrity sha512-VWSRii4t0AFm6ixFFmLLx1t7wS1gh+ckoa84aOeapGum0h+EZd1EhEumSB+ZdDLnEPuucsVB9oB7cxJHap6Afg== @@ -8578,10 +8474,10 @@ globals@^15.14.0: resolved "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz" integrity sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg== -globals@^17.4.0: - version "17.4.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-17.4.0.tgz#33d7d297ed1536b388a0e2f4bcd0ff19c8ff91b5" - integrity sha512-hjrNztw/VajQwOLsMNT1cbJiH2muO3OROCHnbehc8eY5JyD2gqz4AcMHPqgaOR59DjgUjYAYLeH699g/eWi2jw== +globals@^17.5.0: + version "17.5.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-17.5.0.tgz#a82c641d898f8dfbe0e81f66fdff7d0de43f88c6" + integrity sha512-qoV+HK2yFl/366t2/Cb3+xxPUo5BuMynomoDmiaZBIdbs+0pYbjfZU+twLhGKp4uCZ/+NbtpVepH5bGCxRyy2g== globalthis@^1.0.4: version "1.0.4" @@ -9067,14 +8963,6 @@ http2-wrapper@^2.1.10: quick-lru "^5.1.1" resolve-alpn "^1.2.0" -https-proxy-agent@^7.0.5: - version "7.0.6" - resolved "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz" - integrity sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw== - dependencies: - agent-base "^7.1.2" - debug "4" - human-signals@^2.1.0: version "2.1.0" resolved "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz" @@ -9130,14 +9018,14 @@ immer@^11.0.0: integrity sha512-6jQTc5z0KJFtr1UgFpIL3N9XSC3saRaI9PwWtzM2pSqkNGtiNkYY2OSwkOGDK2XcTRcLb1pi/aNkKZz0nxVH4Q== immutable@^3.x.x: - version "3.8.2" - resolved "https://registry.npmjs.org/immutable/-/immutable-3.8.2.tgz" - integrity sha512-15gZoQ38eYjEjxkorfbcgBKBL6R7T459OuK+CpcWt7O3KF4uPCx2tD0uFETlUDIyo+1789crbMhTvQBSR5yBMg== + version "3.8.3" + resolved "https://registry.yarnpkg.com/immutable/-/immutable-3.8.3.tgz#0a8d2494a94d4b2d4f0e99986e74dd25d1e9a859" + integrity sha512-AUY/VyX0E5XlibOmWt10uabJzam1zlYjwiEgQSDc5+UIkFNaF9WM0JxXKaNMGf+F/ffUF+7kRKXM9A7C0xXqMg== immutable@^5.0.2: - version "5.1.4" - resolved "https://registry.npmjs.org/immutable/-/immutable-5.1.4.tgz" - integrity sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA== + version "5.1.5" + resolved "https://registry.yarnpkg.com/immutable/-/immutable-5.1.5.tgz#93ee4db5c2a9ab42a4a783069f3c5d8847d40165" + integrity sha512-t7xcm2siw+hlUM68I+UEOK+z84RzmN59as9DZ7P1l0994DKUWV7UXBMQZVxaoMSRQ+PBZbHCOoBt7a2wxOMt+A== import-fresh@^3.2.1, import-fresh@^3.3.0: version "3.3.1" @@ -9714,21 +9602,21 @@ js-yaml-loader@^1.2.2: js-yaml@4.1.0: version "4.1.0" - resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== dependencies: argparse "^2.0.1" js-yaml@=4.1.1, js-yaml@^4.1.0, js-yaml@^4.1.1: version "4.1.1" - resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.1.tgz#854c292467705b699476e1a2decc0c8a3458806b" integrity sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA== dependencies: argparse "^2.0.1" js-yaml@^3.13.1: version "3.14.2" - resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.2.tgz#77485ce1dd7f33c061fd1b16ecea23b55fcb04b0" integrity sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg== dependencies: argparse "^1.0.7" @@ -9771,7 +9659,7 @@ json-crawl@^0.5.3: resolved "https://registry.npmjs.org/json-crawl/-/json-crawl-0.5.3.tgz" integrity sha512-BEjjCw8c7SxzNK4orhlWD5cXQh8vCk2LqDr4WgQq4CV+5dvopeYwt1Tskg67SuSLKvoFH5g0yuYtg7rcfKV6YA== -json-parse-even-better-errors@^2.3.0, json-parse-even-better-errors@^2.3.1: +json-parse-even-better-errors@^2.3.0: version "2.3.1" resolved "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz" integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== @@ -9799,6 +9687,15 @@ json-schema-merge-allof@0.8.1: json-schema-compare "^0.2.2" lodash "^4.17.20" +json-schema-to-ts@2.7.2: + version "2.7.2" + resolved "https://registry.yarnpkg.com/json-schema-to-ts/-/json-schema-to-ts-2.7.2.tgz#e8df41d7153e5517f0e68dbe57be12bb3609d6d5" + integrity sha512-R1JfqKqbBR4qE8UyBR56Ms30LL62/nlhoz+1UkfI/VE7p54Awu919FZ6ZUPG8zIa3XB65usPJgr1ONVncUGSaQ== + dependencies: + "@babel/runtime" "^7.18.3" + "@types/json-schema" "^7.0.9" + ts-algebra "^1.2.0" + json-schema-traverse@^0.4.1: version "0.4.1" resolved "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz" @@ -9809,11 +9706,6 @@ json-schema-traverse@^1.0.0: resolved "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz" integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== -json-schema@^0.4.0: - version "0.4.0" - resolved "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz" - integrity sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA== - json-stable-stringify-without-jsonify@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz" @@ -10107,13 +9999,13 @@ lodash.uniq@^4.5.0: lodash@4.17.21: version "4.17.21" - resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== -lodash@^4.15.0, lodash@^4.17.10, lodash@^4.17.15, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.4: - version "4.17.23" - resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz" - integrity sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w== +lodash@^4.15.0, lodash@^4.17.10, lodash@^4.17.15, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.4, lodash@^4.18.1: + version "4.18.1" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.18.1.tgz#ff2b66c1f6326d59513de2407bf881439812771c" + integrity sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q== longest-streak@^3.0.0: version "3.1.0" @@ -10166,13 +10058,6 @@ markdown-extensions@^2.0.0: resolved "https://registry.npmjs.org/markdown-extensions/-/markdown-extensions-2.0.0.tgz" integrity sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q== -markdown-table@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/markdown-table/-/markdown-table-2.0.0.tgz" - integrity sha512-Ezda85ToJUBhM6WGaG6veasyym+Tbs3cMAw/ZhOPqXiYsr0jgocBV3j3nx+4lk47plLlIqjwuTm/ywVI+zjJ/A== - dependencies: - repeat-string "^1.0.0" - markdown-table@^3.0.0: version "3.0.4" resolved "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz" @@ -10183,11 +10068,6 @@ marked@^16.0.0: resolved "https://registry.npmjs.org/marked/-/marked-16.2.0.tgz" integrity sha512-LbbTuye+0dWRz2TS9KJ7wsnD4KAtpj0MVkWc90XvBa6AslXsT0hTBVH5k32pcSyHH1fst9XEFJunXHktVy0zlg== -marked@^16.3.0: - version "16.4.0" - resolved "https://registry.npmjs.org/marked/-/marked-16.4.0.tgz" - integrity sha512-CTPAcRBq57cn3R8n3hwc2REddc28hjR7RzDXQ+lXLmMJYqn20BaI2cGw6QjgZGIgVfp2Wdfw4aMzgNteQ6qJgQ== - math-expression-evaluator@^1.3.8: version "1.4.0" resolved "https://registry.npmjs.org/math-expression-evaluator/-/math-expression-evaluator-1.4.0.tgz" @@ -11217,33 +11097,19 @@ minimalistic-assert@^1.0.0: resolved "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz" integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== -minimatch@3.1.2, minimatch@^3.1.1, minimatch@^3.1.2: - version "3.1.2" - resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz" - integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== +minimatch@3.1.5, minimatch@^3.1.1, minimatch@^3.1.2: + version "3.1.5" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.5.tgz#580c88f8d5445f2bd6aa8f3cadefa0de79fbd69e" + integrity sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w== dependencies: brace-expansion "^1.1.7" -minimatch@^10.2.1: - version "10.2.2" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-10.2.2.tgz#361603ee323cfb83496fea2ae17cc44ea4e1f99f" - integrity sha512-+G4CpNBxa5MprY+04MbgOw1v7So6n5JY166pFi9KfYwT78fxScCeSNQSNzp6dpPSW2rONOps6Ocam1wFhCgoVw== +minimatch@^10.2.1, minimatch@^10.2.2: + version "10.2.5" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-10.2.5.tgz#bd48687a0be38ed2961399105600f832095861d1" + integrity sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg== dependencies: - brace-expansion "^5.0.2" - -minimatch@^10.2.2: - version "10.2.4" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-10.2.4.tgz#465b3accbd0218b8281f5301e27cedc697f96fde" - integrity sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg== - dependencies: - brace-expansion "^5.0.2" - -minimatch@^5.0.1: - version "5.1.6" - resolved "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz" - integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== - dependencies: - brace-expansion "^2.0.1" + brace-expansion "^5.0.5" minimist@^1.2.0: version "1.2.8" @@ -11398,9 +11264,9 @@ node-fetch@^2.6.1: whatwg-url "^5.0.0" node-forge@^1: - version "1.3.2" - resolved "https://registry.npmjs.org/node-forge/-/node-forge-1.3.2.tgz" - integrity sha512-6xKiQ+cph9KImrRh0VsjH2d8/GXA4FIMlgU4B757iI1ApvcyA9VlouP0yZJha01V+huImO+kKMU7ih+2+E14fw== + version "1.4.0" + resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.4.0.tgz#1c7b7d8bdc2d078739f58287d589d903a11b2fc2" + integrity sha512-LarFH0+6VfriEhqMMcLX2F7SwSXeWwnEAJEsYm5QKWchiVYVvJyV9v7UDvUv+w5HO23ZpQTXDv/GxdDdMyOuoQ== node-gyp-build@^4.8.0, node-gyp-build@^4.8.2, node-gyp-build@^4.8.4: version "4.8.4" @@ -11645,10 +11511,10 @@ openapi-server-url-templating@^1.3.0: dependencies: apg-lite "^1.0.4" -openapi-to-postmanv2@^5.0.0: - version "5.8.0" - resolved "https://registry.npmjs.org/openapi-to-postmanv2/-/openapi-to-postmanv2-5.8.0.tgz" - integrity sha512-7f02ypBlAx4G9z3bP/uDk8pBwRbYt97Eoso8XJLyclfyRvCC+CvERLUl0MD0x+GoumpkJYnQ0VGdib/kwtUdUw== +openapi-to-postmanv2@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/openapi-to-postmanv2/-/openapi-to-postmanv2-6.0.1.tgz#ce14def40b16cd02f5656445cfe7d039f761a602" + integrity sha512-zAjaTwXo07az6jjvZTw4d26QMQsFxZBxTqjj3LQQMDCCuO6+peATQc9bSmAq3QbzvikP+h2WEjTphMcIrcSurg== dependencies: ajv "^8.11.0" ajv-draft-04 "1.0.0" @@ -11663,11 +11529,17 @@ openapi-to-postmanv2@^5.0.0: neotraverse "0.6.15" oas-resolver-browser "2.5.6" object-hash "3.0.0" + openapi-types "^12.1.3" path-browserify "1.0.1" postman-collection "^5.0.0" swagger2openapi "7.0.8" yaml "1.10.2" +openapi-types@^12.1.3: + version "12.1.3" + resolved "https://registry.yarnpkg.com/openapi-types/-/openapi-types-12.1.3.tgz#471995eb26c4b97b7bd356aacf7b91b73e777dd3" + integrity sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw== + opener@^1.5.2: version "1.5.2" resolved "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz" @@ -11906,20 +11778,20 @@ path-parse@^1.0.7: path-to-regexp@3.3.0: version "3.3.0" - resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-3.3.0.tgz" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-3.3.0.tgz#f7f31d32e8518c2660862b644414b6d5c63a611b" integrity sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw== path-to-regexp@^1.7.0: version "1.9.0" - resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.9.0.tgz" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.9.0.tgz#5dc0753acbf8521ca2e0f137b4578b917b10cf24" integrity sha512-xIp7/apCFJuUHdDLWe8O1HIkb0kQrOMb/0u6FXQjemHn/ii5LrIzU6bdECnsiTF/GjZkMEKg1xdiZwNqDYlZ6g== dependencies: isarray "0.0.1" path-to-regexp@~0.1.12: - version "0.1.12" - resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz" - integrity sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ== + version "0.1.13" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.13.tgz#9b22ec16bc3ab88d05a0c7e369869421401ab17d" + integrity sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA== path-type@^4.0.0: version "4.0.0" @@ -11945,14 +11817,14 @@ picocolors@^1.0.0, picocolors@^1.1.1: 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.npmjs.org/picomatch/-/picomatch-2.3.1.tgz" - integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + version "2.3.2" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.2.tgz#5a942915e26b372dc0f0e6753149a16e6b1c5601" + integrity sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA== -picomatch@^4.0.3: - version "4.0.3" - resolved "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz" - integrity sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q== +picomatch@^4.0.3, picomatch@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.4.tgz#fd6f5e00a143086e074dffe4c924b8fb293b0589" + integrity sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A== pirates@^4.0.1: version "4.0.7" @@ -12612,10 +12484,10 @@ prettier-linter-helpers@^1.0.1: dependencies: fast-diff "^1.1.2" -prettier@^3.8.1: - version "3.8.1" - resolved "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz" - integrity sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg== +prettier@^3.8.3: + version "3.8.3" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.8.3.tgz#560f2de55bf01b4c0503bc629d5df99b9a1d09b0" + integrity sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw== pretty-error@^4.0.0: version "4.0.0" @@ -12700,10 +12572,10 @@ proxy-addr@~2.0.7: forwarded "0.2.0" ipaddr.js "1.9.1" -proxy-from-env@^1.1.0: - version "1.1.0" - resolved "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz" - integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== +proxy-from-env@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-2.1.0.tgz#a7487568adad577cfaaa7e88c49cab3ab3081aba" + integrity sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA== punycode@^1.4.1: version "1.4.1" @@ -12914,10 +12786,10 @@ react-live@^4.1.6, react-live@^4.1.8: sucrase "^3.35.0" use-editable "^2.3.3" -react-loadable-ssr-addon-v5-slorber@^1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/react-loadable-ssr-addon-v5-slorber/-/react-loadable-ssr-addon-v5-slorber-1.0.1.tgz" - integrity sha512-lq3Lyw1lGku8zUEJPDxsNm1AfYHBrO9Y1+olAYwpUJ2IGFBskM0DMKok97A6LWUpHm+o7IvQBOWu9MLenp9Z+A== +react-loadable-ssr-addon-v5-slorber@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/react-loadable-ssr-addon-v5-slorber/-/react-loadable-ssr-addon-v5-slorber-1.0.3.tgz#bb3791bf481222c63a5bc6b96ee23f68cb5614b9" + integrity sha512-GXfh9VLwB5ERaCsU6RULh7tkemeX15aNh6wuMEBtfdyMa7fFG8TXrhXlx1SoEK2Ty/l6XIkzzYIQmyaWW3JgdQ== dependencies: "@babel/runtime" "^7.10.3" @@ -13454,7 +13326,7 @@ renderkid@^3.0.0: lodash "^4.17.21" strip-ansi "^6.0.1" -repeat-string@^1.0.0, repeat-string@^1.5.2: +repeat-string@^1.5.2: version "1.6.1" resolved "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz" integrity sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w== @@ -13821,20 +13693,20 @@ serialize-error@^8.1.0: serialize-javascript@^6.0.0, serialize-javascript@^6.0.1: version "6.0.2" - resolved "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.2.tgz#defa1e055c83bf6d59ea805d8da862254eb6a6c2" integrity sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g== dependencies: randombytes "^2.1.0" -serve-handler@^6.1.6: - version "6.1.6" - resolved "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.6.tgz" - integrity sha512-x5RL9Y2p5+Sh3D38Fh9i/iQ5ZK+e4xuXRd/pGbM4D13tgo/MGwbttUk8emytcr1YYzBYs+apnUngBDFYfpjPuQ== +serve-handler@^6.1.7: + version "6.1.7" + resolved "https://registry.yarnpkg.com/serve-handler/-/serve-handler-6.1.7.tgz#e9bb864e87ee71e8dab874cde44d146b77e3fb78" + integrity sha512-CinAq1xWb0vR3twAv9evEU8cNWkXCb9kd5ePAHUKJBkOsUpR1wt/CvGdeca7vqumL1U5cSaeVQ6zZMxiJ3yWsg== dependencies: bytes "3.0.0" content-disposition "0.5.2" mime-types "2.1.18" - minimatch "3.1.2" + minimatch "3.1.5" path-is-inside "1.0.2" path-to-regexp "3.3.0" range-parser "1.2.0" @@ -14461,19 +14333,19 @@ svgo@^3.0.2, svgo@^3.2.0: picocolors "^1.0.0" sax "^1.5.0" -swagger-client@^3.37.0: - version "3.37.0" - resolved "https://registry.yarnpkg.com/swagger-client/-/swagger-client-3.37.0.tgz#1ac57955d650cf92105e11900ca7b640f5604cc7" - integrity sha512-pzU+B+DkUbrSwlj4/E8sGeP1w84/CFgDJAt80fHu650TxnOHbqFLGQjiE6luvpRxTPdfK2zRHJP7I6CgUkI8yA== +swagger-client@^3.37.2: + version "3.37.2" + resolved "https://registry.yarnpkg.com/swagger-client/-/swagger-client-3.37.2.tgz#19d15564b2c77c9200a3ee2b8a913e2bd2e4a596" + integrity sha512-KcB8psL1On4GWwv9Ribp1oteh50ygNnAyvQbd5MwiXMGkcB4f53rkZEdvZKPDdJO764mQjgErxQEGDVw6QBUMQ== dependencies: "@babel/runtime-corejs3" "^7.22.15" "@scarf/scarf" "=1.4.0" - "@swagger-api/apidom-core" "^1.6.0" - "@swagger-api/apidom-error" "^1.6.0" - "@swagger-api/apidom-json-pointer" "^1.6.0" - "@swagger-api/apidom-ns-openapi-3-1" "^1.6.0" - "@swagger-api/apidom-ns-openapi-3-2" "^1.6.0" - "@swagger-api/apidom-reference" "^1.6.0" + "@swagger-api/apidom-core" "^1.10.2" + "@swagger-api/apidom-error" "^1.10.2" + "@swagger-api/apidom-json-pointer" "^1.10.2" + "@swagger-api/apidom-ns-openapi-3-1" "^1.10.2" + "@swagger-api/apidom-ns-openapi-3-2" "^1.10.2" + "@swagger-api/apidom-reference" "^1.10.2" "@swaggerexpert/cookie" "^2.0.2" deepmerge "~4.3.0" fast-json-patch "^3.0.0-1" @@ -14486,10 +14358,10 @@ swagger-client@^3.37.0: ramda "^0.30.1" ramda-adjunct "^5.1.0" -swagger-ui-react@^5.32.0: - version "5.32.0" - resolved "https://registry.yarnpkg.com/swagger-ui-react/-/swagger-ui-react-5.32.0.tgz#fd764a7be746b5fdbff748fee6ef723eb05b4846" - integrity sha512-2mmrtvfp0EA90pdT8qXTMu26ex03TG2bsjvDAwXhdfCm+9foyadYJN+nEvDHM6/c6/xtXbdAsb6cVxBvbltnpw== +swagger-ui-react@^5.32.4: + version "5.32.4" + resolved "https://registry.yarnpkg.com/swagger-ui-react/-/swagger-ui-react-5.32.4.tgz#783b22213c4e826618b1470fd8ae9e68a2587c7d" + integrity sha512-OsTqKCiDT/o8/oqZbt+p1djPkrOk3unKK/7+wGvP1+WY6pOzFoDLM4D39cNFtpIArtlg9uoK6MKIz3W00WX8qw== dependencies: "@babel/runtime-corejs3" "^7.27.1" "@scarf/scarf" "=1.4.0" @@ -14498,12 +14370,12 @@ swagger-ui-react@^5.32.0: classnames "^2.5.1" css.escape "1.5.1" deep-extend "0.6.0" - dompurify "=3.2.6" + dompurify "^3.3.2" ieee754 "^1.2.1" immutable "^3.x.x" js-file-download "^0.4.12" js-yaml "=4.1.1" - lodash "^4.17.21" + lodash "^4.18.1" prop-types "^15.8.1" randexp "^0.5.3" randombytes "^2.1.0" @@ -14520,7 +14392,7 @@ swagger-ui-react@^5.32.0: reselect "^5.1.1" serialize-error "^8.1.0" sha.js "^2.4.12" - swagger-client "^3.37.0" + swagger-client "^3.37.2" url-parse "^1.5.10" xml "=1.0.1" xml-but-prettier "^1.0.1" @@ -14550,14 +14422,6 @@ swc-loader@^0.2.6, swc-loader@^0.2.7: dependencies: "@swc/counter" "^0.1.3" -swr@^2.2.5: - version "2.3.6" - resolved "https://registry.npmjs.org/swr/-/swr-2.3.6.tgz" - integrity sha512-wfHRmHWk/isGNMwlLGlZX5Gzz/uTgo0o2IRuTMcf4CPuPFJZlq0rDaKUx+ozB5nBOReNV1kiOyzMfj+MBMikLw== - dependencies: - dequal "^2.0.3" - use-sync-external-store "^1.4.0" - synckit@^0.11.12: version "0.11.12" resolved "https://registry.npmjs.org/synckit/-/synckit-0.11.12.tgz" @@ -14614,11 +14478,6 @@ throttle-debounce@^5.0.0, throttle-debounce@^5.0.2: resolved "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-5.0.2.tgz" integrity sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A== -throttleit@2.1.0: - version "2.1.0" - resolved "https://registry.npmjs.org/throttleit/-/throttleit-2.1.0.tgz" - integrity sha512-nt6AMGKW1p/70DF/hGBdJB57B8Tspmbp5gfJ8ilhLnt7kkr2ye7hzD6NVG8GGErk2HWF34igrL2CXmNIkzKqKw== - thunky@^1.0.2: version "1.1.0" resolved "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz" @@ -14737,10 +14596,15 @@ trough@^2.0.0: resolved "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz" integrity sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw== -ts-api-utils@^2.4.0: - version "2.4.0" - resolved "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz" - integrity sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA== +ts-algebra@^1.2.0: + version "1.2.2" + resolved "https://registry.yarnpkg.com/ts-algebra/-/ts-algebra-1.2.2.tgz#b75d301c28cd4126cd344760a47b43e48e2872e0" + integrity sha512-kloPhf1hq3JbCPOTYoOWDKxebWjNb2o/LKnNfkWhxVVisFFmMJPPdJeGoGmM+iRLyoXAR61e08Pb+vUXINg8aA== + +ts-api-utils@^2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-2.5.0.tgz#4acd4a155e22734990a5ed1fe9e97f113bcb37c1" + integrity sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA== ts-dedent@^2.0.0, ts-dedent@^2.2.0: version "2.2.0" @@ -14779,11 +14643,6 @@ type-fest@^0.20.2: resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz" integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== -type-fest@^0.21.3: - version "0.21.3" - resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz" - integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== - type-fest@^1.0.1: version "1.4.0" resolved "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz" @@ -14861,20 +14720,20 @@ types-ramda@^0.30.1: dependencies: ts-toolbelt "^9.6.0" -typescript-eslint@^8.57.1: - version "8.57.1" - resolved "https://registry.yarnpkg.com/typescript-eslint/-/typescript-eslint-8.57.1.tgz#573f97d3e48bbb67290b47dde1b7cb3b5d01dc4f" - integrity sha512-fLvZWf+cAGw3tqMCYzGIU6yR8K+Y9NT2z23RwOjlNFF2HwSB3KhdEFI5lSBv8tNmFkkBShSjsCjzx1vahZfISA== +typescript-eslint@^8.59.0: + version "8.59.0" + resolved "https://registry.yarnpkg.com/typescript-eslint/-/typescript-eslint-8.59.0.tgz#d1cc7c63559ce7116aeb66d35ec9dbe0063379fd" + integrity sha512-BU3ONW9X+v90EcCH9ZS6LMackcVtxRLlI3XrYyqZIwVSHIk7Qf7bFw1z0M9Q0IUxhTMZCf8piY9hTYaNEIASrw== dependencies: - "@typescript-eslint/eslint-plugin" "8.57.1" - "@typescript-eslint/parser" "8.57.1" - "@typescript-eslint/typescript-estree" "8.57.1" - "@typescript-eslint/utils" "8.57.1" + "@typescript-eslint/eslint-plugin" "8.59.0" + "@typescript-eslint/parser" "8.59.0" + "@typescript-eslint/typescript-estree" "8.59.0" + "@typescript-eslint/utils" "8.59.0" -typescript@~5.9.3: - version "5.9.3" - resolved "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz" - integrity sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw== +typescript@~6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-6.0.3.tgz#90251dc007916e972786cb94d74d15b185577d21" + integrity sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw== ufo@^1.5.4: version "1.6.1" @@ -15479,10 +15338,10 @@ webpack-virtual-modules@^0.6.2: resolved "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz" integrity sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ== -webpack@^5.105.4, webpack@^5.88.1, webpack@^5.95.0: - version "5.105.4" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.105.4.tgz#1b77fcd55a985ac7ca9de80a746caffa38220169" - integrity sha512-jTywjboN9aHxFlToqb0K0Zs9SbBoW4zRUlGzI2tYNxVYcEi/IPpn+Xi4ye5jTLvX2YeLuic/IvxNot+Q1jMoOw== +webpack@^5.106.2, webpack@^5.88.1, webpack@^5.95.0: + version "5.106.2" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.106.2.tgz#ca8174b4fd80f055cc5a45fcc5577d6db76c8ac5" + integrity sha512-wGN3qcrBQIFmQ/c0AiOAQBvrZ5lmY8vbbMv4Mxfgzqd/B6+9pXtLo73WuS1dSGXM5QYY3hZnIbvx+K1xxe6FyA== dependencies: "@types/eslint-scope" "^3.7.7" "@types/estree" "^1.0.8" @@ -15500,9 +15359,8 @@ webpack@^5.105.4, webpack@^5.88.1, webpack@^5.95.0: events "^3.2.0" glob-to-regexp "^0.4.1" graceful-fs "^4.2.11" - json-parse-even-better-errors "^2.3.1" loader-runner "^4.3.1" - mime-types "^2.1.27" + mime-db "^1.54.0" neo-async "^2.6.2" schema-utils "^4.3.3" tapable "^2.3.0" @@ -15510,19 +15368,15 @@ webpack@^5.105.4, webpack@^5.88.1, webpack@^5.95.0: watchpack "^2.5.1" webpack-sources "^3.3.4" -webpackbar@^6.0.1: - version "6.0.1" - resolved "https://registry.npmjs.org/webpackbar/-/webpackbar-6.0.1.tgz" - integrity sha512-TnErZpmuKdwWBdMoexjio3KKX6ZtoKHRVvLIU0A47R0VVBDtx3ZyOJDktgYixhoJokZTYTt1Z37OkO9pnGJa9Q== +webpackbar@^6.0.1, webpackbar@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/webpackbar/-/webpackbar-7.0.0.tgz#7228d32881af2392381b6514499ddea73cdf218a" + integrity sha512-aS9soqSO2iCHgqHoCrj4LbfGQUboDCYJPSFOAchEK+9psIjNrfSWW4Y0YEz67MKURNvMmfo0ycOg9d/+OOf9/Q== dependencies: - ansi-escapes "^4.3.2" - chalk "^4.1.2" + ansis "^3.2.0" consola "^3.2.3" - figures "^3.2.0" - markdown-table "^2.0.0" pretty-time "^1.1.0" std-env "^3.7.0" - wrap-ansi "^7.0.0" websocket-driver@>=0.5.1, websocket-driver@^0.7.4: version "0.7.4" @@ -15737,11 +15591,16 @@ yaml-ast-parser@0.0.43: resolved "https://registry.npmjs.org/yaml-ast-parser/-/yaml-ast-parser-0.0.43.tgz" integrity sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A== -yaml@1.10.2, yaml@^1.10.0: +yaml@1.10.2: version "1.10.2" - resolved "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== +yaml@^1.10.0: + version "1.10.3" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.3.tgz#76e407ed95c42684fb8e14641e5de62fe65bbcb3" + integrity sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA== + yargs-parser@^21.1.1: version "21.1.1" resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz" @@ -15775,11 +15634,6 @@ zenscroll@^4.0.2: resolved "https://registry.npmjs.org/zenscroll/-/zenscroll-4.0.2.tgz" integrity sha512-jEA1znR7b4C/NnaycInCU6h/d15ZzCd1jmsruqOKnZP6WXQSMH3W2GL+OXbkruslU4h+Tzuos0HdswzRUk/Vgg== -zod@^4.1.8: - version "4.1.12" - resolved "https://registry.npmjs.org/zod/-/zod-4.1.12.tgz" - integrity sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ== - zwitch@^2.0.0: version "2.0.4" resolved "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz" diff --git a/pyproject.toml b/pyproject.toml index 12d5224cb38..5eca094b984 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,7 +48,7 @@ dependencies = [ "cryptography>=42.0.4, <47.0.0", "deprecation>=2.1.0, <2.2.0", "flask>=2.2.5, <4.0.0", - "flask-appbuilder>=5.0.2,<6", + "flask-appbuilder>=5.2.1, <6.0.0", "flask-caching>=2.1.0, <3", "flask-compress>=1.13, <2.0", "flask-talisman>=1.0.0, <2.0", @@ -89,7 +89,7 @@ dependencies = [ "python-dateutil", "python-dotenv", # optional dependencies for Flask but required for Superset, see https://flask.palletsprojects.com/en/stable/installation/#optional-dependencies "pygeohash", - "pyarrow>=16.1.0, <19", # before upgrading pyarrow, check that all db dependencies support this, see e.g. https://github.com/apache/superset/pull/34693 + "pyarrow>=16.1.0, <21", # before upgrading pyarrow, check that all db dependencies support this, see e.g. https://github.com/apache/superset/pull/34693 "pyyaml>=6.0.0, <7.0.0", "PyJWT>=2.4.0, <3.0", "redis>=5.0.0, <6.0", @@ -183,6 +183,7 @@ risingwave = ["sqlalchemy-risingwave"] shillelagh = ["shillelagh[all]>=1.4.3, <2"] singlestore = ["sqlalchemy-singlestoredb>=1.1.1, <2"] snowflake = ["snowflake-sqlalchemy>=1.2.4, <2"] +sqlite = ["syntaqlite>=0.1.0"] spark = [ "pyhive[hive]>=0.6.5;python_version<'3.11'", "pyhive[hive_pure_sasl]>=0.7", @@ -226,6 +227,7 @@ development = [ "ruff", "sqloxide", "statsd", + "syntaqlite>=0.1.0", ] [project.urls] @@ -238,7 +240,7 @@ combine_as_imports = true include_trailing_comma = true line_length = 88 known_first_party = "superset, apache-superset-core, apache-superset-extensions-cli" -known_third_party = "alembic, apispec, backoff, celery, click, colorama, cron_descriptor, croniter, cryptography, dateutil, deprecation, flask, flask_appbuilder, flask_babel, flask_caching, flask_compress, flask_jwt_extended, flask_login, flask_migrate, flask_sqlalchemy, flask_talisman, flask_testing, flask_wtf, freezegun, geohash, geopy, holidays, humanize, isodate, jinja2, jwt, markdown, markupsafe, marshmallow, marshmallow-union, msgpack, nh3, numpy, pandas, parameterized, parsedatetime, pgsanity, polyline, prison, progress, pyarrow, sqlalchemy_bigquery, pyhive, pyparsing, pytest, pytest_mock, pytz, redis, requests, selenium, setuptools, shillelagh, simplejson, slack, sqlalchemy, sqlalchemy_utils, typing_extensions, urllib3, werkzeug, wtforms, wtforms_json, yaml" +known_third_party = "alembic, apispec, backoff, celery, click, colorama, cron_descriptor, croniter, cryptography, dateutil, deprecation, flask, flask_appbuilder, flask_babel, flask_caching, flask_compress, flask_jwt_extended, flask_login, flask_migrate, flask_sqlalchemy, flask_talisman, flask_testing, flask_wtf, freezegun, geohash, geopy, holidays, humanize, isodate, jinja2, jwt, markdown, markupsafe, marshmallow, marshmallow-union, msgpack, nh3, numpy, pandas, parameterized, parsedatetime, pgsanity, polyline, prison, progress, pyarrow, sqlalchemy_bigquery, pyhive, pyparsing, pytest, pytest_mock, pytz, redis, requests, selenium, setuptools, shillelagh, simplejson, slack, sqlalchemy, sqlalchemy_utils, syntaqlite, typing_extensions, urllib3, werkzeug, wtforms, wtforms_json, yaml" multi_line_output = 3 order_by_type = false @@ -372,6 +374,7 @@ unfixable = [] dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" [tool.ruff.lint.per-file-ignores] +"superset/mcp_service/app.py" = ["S608", "E501"] # LLM instruction text: SQL examples (S608) and long lines in multiline string (E501) "scripts/*" = ["TID251"] "setup.py" = ["TID251"] "superset/config.py" = ["TID251"] diff --git a/requirements/base.in b/requirements/base.in index 2d6100beab3..cad39dc543c 100644 --- a/requirements/base.in +++ b/requirements/base.in @@ -25,6 +25,16 @@ filelock>=3.20.3,<4.0.0 # Security: decompression bomb fix (required by aiohttp 3.13.3) brotli>=1.2.0,<2.0.0 numexpr>=2.9.0 +# Security: CVE-2026-34073 (MEDIUM) - Improper Certificate Validation +cryptography>=46.0.7,<47.0.0 +# Security: Snyk - XSS vulnerability in Mako templates +mako>=1.3.11,<2.0.0 +# Security: CVE-2024-52338 (CRITICAL) - Deserialization of untrusted data in IPC/Parquet readers +pyarrow>=20.0.0,<21.0.0 +# Security: CVE-2026-27459 - pyopenssl certificate validation +pyopenssl>=26.0.0,<27.0.0 +# Security: CVE-2026-25645 (MEDIUM) - Insecure Temporary File +requests>=2.33.0,<3.0.0 # 5.0.0 has a sensitive deprecation used in other libs # -> https://github.com/aio-libs/async-timeout/blob/master/CHANGES.rst#500-2024-10-31 diff --git a/requirements/base.txt b/requirements/base.txt index 240cf7d4eb1..5dc0423c966 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -86,8 +86,9 @@ cron-descriptor==1.4.5 # via apache-superset (pyproject.toml) croniter==6.0.0 # via apache-superset (pyproject.toml) -cryptography==46.0.5 +cryptography==46.0.7 # via + # -r requirements/base.in # apache-superset (pyproject.toml) # paramiko # pyopenssl @@ -120,7 +121,7 @@ flask==2.3.3 # flask-session # flask-sqlalchemy # flask-wtf -flask-appbuilder==5.1.0 +flask-appbuilder==5.2.1 # via # apache-superset (pyproject.toml) # apache-superset-core @@ -205,11 +206,12 @@ kombu==5.5.3 # via celery limits==5.1.0 # via flask-limiter -mako==1.3.10 +mako==1.3.11 # via + # -r requirements/base.in # apache-superset (pyproject.toml) # alembic -markdown==3.8 +markdown==3.8.1 # via apache-superset (pyproject.toml) markdown-it-py==3.0.0 # via rich @@ -279,7 +281,7 @@ parsedatetime==2.6 # via apache-superset (pyproject.toml) pgsanity==0.2.9 # via apache-superset (pyproject.toml) -pillow==11.3.0 +pillow==12.1.1 # via apache-superset (pyproject.toml) platformdirs==4.3.8 # via requests-cache @@ -291,9 +293,11 @@ prison==0.2.1 # via flask-appbuilder prompt-toolkit==3.0.51 # via click-repl -pyarrow==16.1.0 - # via apache-superset (pyproject.toml) -pyasn1==0.6.2 +pyarrow==20.0.0 + # via + # -r requirements/base.in + # apache-superset (pyproject.toml) +pyasn1==0.6.3 # via # pyasn1-modules # rsa @@ -309,9 +313,9 @@ pydantic-core==2.33.2 # via pydantic pygeohash==3.2.2 # via apache-superset (pyproject.toml) -pygments==2.19.1 +pygments==2.20.0 # via rich -pyjwt==2.10.1 +pyjwt==2.12.0 # via # apache-superset (pyproject.toml) # flask-appbuilder @@ -319,8 +323,10 @@ pyjwt==2.10.1 # redis pynacl==1.6.2 # via paramiko -pyopenssl==25.3.0 - # via shillelagh +pyopenssl==26.0.0 + # via + # -r requirements/base.in + # shillelagh pyparsing==3.2.3 # via apache-superset (pyproject.toml) pysocks==1.7.1 @@ -353,8 +359,9 @@ referencing==0.36.2 # via # jsonschema # jsonschema-specifications -requests==2.32.4 +requests==2.33.0 # via + # -r requirements/base.in # requests-cache # shillelagh requests-cache==1.2.1 diff --git a/requirements/development.txt b/requirements/development.txt index 2b9fffd474b..0cd5411b357 100644 --- a/requirements/development.txt +++ b/requirements/development.txt @@ -178,7 +178,7 @@ croniter==6.0.0 # via # -c requirements/base-constraint.txt # apache-superset -cryptography==46.0.5 +cryptography==46.0.7 # via # -c requirements/base-constraint.txt # apache-superset @@ -259,7 +259,7 @@ flask==2.3.3 # flask-sqlalchemy # flask-testing # flask-wtf -flask-appbuilder==5.1.0 +flask-appbuilder==5.2.1 # via # -c requirements/base-constraint.txt # apache-superset @@ -503,12 +503,12 @@ limits==5.1.0 # via # -c requirements/base-constraint.txt # flask-limiter -mako==1.3.10 +mako==1.3.11 # via # -c requirements/base-constraint.txt # alembic # apache-superset -markdown==3.8 +markdown==3.8.1 # via # -c requirements/base-constraint.txt # apache-superset @@ -655,7 +655,7 @@ pgsanity==0.2.9 # via # -c requirements/base-constraint.txt # apache-superset -pillow==11.3.0 +pillow==12.1.1 # via # -c requirements/base-constraint.txt # apache-superset @@ -710,13 +710,13 @@ psycopg2-binary==2.9.9 # via apache-superset py-key-value-aio==0.4.4 # via fastmcp -pyarrow==16.1.0 +pyarrow==20.0.0 # via # -c requirements/base-constraint.txt # apache-superset # db-dtypes # pandas-gbq -pyasn1==0.6.2 +pyasn1==0.6.3 # via # -c requirements/base-constraint.txt # pyasn1-modules @@ -756,7 +756,7 @@ pygeohash==3.2.2 # via # -c requirements/base-constraint.txt # apache-superset -pygments==2.19.1 +pygments==2.20.0 # via # -c requirements/base-constraint.txt # rich @@ -764,7 +764,7 @@ pyhive==0.7.0 # via apache-superset pyinstrument==4.4.0 # via apache-superset -pyjwt==2.10.1 +pyjwt==2.12.0 # via # -c requirements/base-constraint.txt # apache-superset @@ -778,7 +778,7 @@ pynacl==1.6.2 # via # -c requirements/base-constraint.txt # paramiko -pyopenssl==25.3.0 +pyopenssl==26.0.0 # via # -c requirements/base-constraint.txt # shillelagh @@ -865,7 +865,7 @@ referencing==0.36.2 # jsonschema # jsonschema-path # jsonschema-specifications -requests==2.32.4 +requests==2.33.0 # via # -c requirements/base-constraint.txt # docker @@ -992,6 +992,8 @@ starlette==0.49.1 # via mcp statsd==4.0.1 # via apache-superset +syntaqlite==0.1.0 + # via apache-superset tabulate==0.9.0 # via # -c requirements/base-constraint.txt diff --git a/superset-core/pyproject.toml b/superset-core/pyproject.toml index 57dfb231ade..1796b694153 100644 --- a/superset-core/pyproject.toml +++ b/superset-core/pyproject.toml @@ -18,20 +18,20 @@ [project] name = "apache-superset-core" -version = "0.1.0rc1" +version = "0.1.0rc2" description = "Core Python package for building Apache Superset backend extensions and integrations" readme = "README.md" authors = [ { name = "Apache Software Foundation", email = "dev@superset.apache.org" }, ] -license = { file="LICENSE.txt" } +license = "Apache-2.0" +license-files = ["LICENSE.txt"] requires-python = ">=3.10" keywords = ["superset", "apache", "analytics", "business-intelligence", "extensions", "visualization"] classifiers = [ "Development Status :: 3 - Alpha", "Environment :: Web Environment", "Intended Audience :: Developers", - "License :: OSI Approved :: Apache Software License", "Operating System :: OS Independent", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.10", diff --git a/superset-embedded-sdk/package-lock.json b/superset-embedded-sdk/package-lock.json index ffdbbeea642..4b34612ff21 100644 --- a/superset-embedded-sdk/package-lock.json +++ b/superset-embedded-sdk/package-lock.json @@ -7025,9 +7025,9 @@ "dev": true }, "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "dev": true, "engines": { "node": ">=8.6" @@ -7121,15 +7121,6 @@ } ] }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, "node_modules/react-is": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", @@ -7306,26 +7297,6 @@ "node": ">=10" } }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, "node_modules/schema-utils": { "version": "4.3.3", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", @@ -7355,15 +7326,6 @@ "semver": "bin/semver.js" } }, - "node_modules/serialize-javascript": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", - "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", - "dev": true, - "dependencies": { - "randombytes": "^2.1.0" - } - }, "node_modules/shallow-clone": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", @@ -7590,15 +7552,14 @@ } }, "node_modules/terser-webpack-plugin": { - "version": "5.3.16", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.16.tgz", - "integrity": "sha512-h9oBFCWrq78NyWWVcSwZarJkZ01c2AyGrzs1crmHZO3QUg9D61Wu4NPjBy69n7JqylFF5y+CsUZYmYEIZ3mR+Q==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.4.0.tgz", + "integrity": "sha512-Bn5vxm48flOIfkdl5CaD2+1CiUVbonWQ3KQPyP7/EuIl9Gbzq/gQFOzaMFUEgVjB1396tcK0SG8XcNJ/2kDH8g==", "dev": true, "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "jest-worker": "^27.4.5", "schema-utils": "^4.3.0", - "serialize-javascript": "^6.0.2", "terser": "^5.31.1" }, "engines": { @@ -13159,9 +13120,9 @@ "dev": true }, "picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "dev": true }, "pify": { @@ -13220,15 +13181,6 @@ "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", "dev": true }, - "randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "requires": { - "safe-buffer": "^5.1.0" - } - }, "react-is": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", @@ -13359,12 +13311,6 @@ "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==", "dev": true }, - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true - }, "schema-utils": { "version": "4.3.3", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", @@ -13383,15 +13329,6 @@ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true }, - "serialize-javascript": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", - "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", - "dev": true, - "requires": { - "randombytes": "^2.1.0" - } - }, "shallow-clone": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", @@ -13563,15 +13500,14 @@ } }, "terser-webpack-plugin": { - "version": "5.3.16", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.16.tgz", - "integrity": "sha512-h9oBFCWrq78NyWWVcSwZarJkZ01c2AyGrzs1crmHZO3QUg9D61Wu4NPjBy69n7JqylFF5y+CsUZYmYEIZ3mR+Q==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.4.0.tgz", + "integrity": "sha512-Bn5vxm48flOIfkdl5CaD2+1CiUVbonWQ3KQPyP7/EuIl9Gbzq/gQFOzaMFUEgVjB1396tcK0SG8XcNJ/2kDH8g==", "dev": true, "requires": { "@jridgewell/trace-mapping": "^0.3.25", "jest-worker": "^27.4.5", "schema-utils": "^4.3.0", - "serialize-javascript": "^6.0.2", "terser": "^5.31.1" } }, diff --git a/superset-extensions-cli/pyproject.toml b/superset-extensions-cli/pyproject.toml index 8f853a86e36..55261c841ab 100644 --- a/superset-extensions-cli/pyproject.toml +++ b/superset-extensions-cli/pyproject.toml @@ -23,14 +23,14 @@ readme = "README.md" authors = [ { name = "Apache Software Foundation", email = "dev@superset.apache.org" }, ] -license = { file="LICENSE.txt" } +license = "Apache-2.0" +license-files = ["LICENSE.txt"] requires-python = ">=3.10" keywords = ["superset", "apache", "cli", "extensions", "analytics", "business-intelligence", "development-tools"] classifiers = [ "Development Status :: 3 - Alpha", "Environment :: Console", "Intended Audience :: Developers", - "License :: OSI Approved :: Apache Software License", "Operating System :: OS Independent", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.10", diff --git a/superset-extensions-cli/src/superset_extensions_cli/cli.py b/superset-extensions-cli/src/superset_extensions_cli/cli.py index feab2ab5d8a..730420f3535 100644 --- a/superset-extensions-cli/src/superset_extensions_cli/cli.py +++ b/superset-extensions-cli/src/superset_extensions_cli/cli.py @@ -585,9 +585,9 @@ def bundle(ctx: click.Context, output: Path | None) -> None: sys.exit(1) manifest = json.loads(manifest_path.read_text()) - id_ = manifest["id"] + name = manifest["name"] version = manifest["version"] - default_filename = f"{id_}-{version}.supx" + default_filename = f"{name}-{version}.supx" if output is None: zip_path = Path(default_filename) @@ -824,7 +824,7 @@ def init( else click.confirm("Include backend?", default=True) ) - target_dir = Path.cwd() / names["id"] + target_dir = Path.cwd() / names["name"] if target_dir.exists(): click.secho(f"❌ Directory {target_dir} already exists.", fg="red") sys.exit(1) diff --git a/superset-extensions-cli/tests/test_cli_bundle.py b/superset-extensions-cli/tests/test_cli_bundle.py index 29ec4355009..b4dbacd518c 100644 --- a/superset-extensions-cli/tests/test_cli_bundle.py +++ b/superset-extensions-cli/tests/test_cli_bundle.py @@ -43,10 +43,10 @@ def test_bundle_command_creates_zip_with_default_name( result = cli_runner.invoke(app, ["bundle"]) assert result.exit_code == 0 - assert "✅ Bundle created: test-org.test-extension-1.0.0.supx" in result.output + assert "✅ Bundle created: test-extension-1.0.0.supx" in result.output # Verify zip file was created - zip_path = isolated_filesystem / "test-org.test-extension-1.0.0.supx" + zip_path = isolated_filesystem / "test-extension-1.0.0.supx" assert_file_exists(zip_path) # Verify zip contents @@ -100,7 +100,7 @@ def test_bundle_command_with_output_directory( assert result.exit_code == 0 # Verify zip file was created in output directory - expected_path = output_dir / "test-org.test-extension-1.0.0.supx" + expected_path = output_dir / "test-extension-1.0.0.supx" assert_file_exists(expected_path) assert f"✅ Bundle created: {expected_path}" in result.output @@ -193,7 +193,7 @@ def test_bundle_includes_all_files_recursively( assert result.exit_code == 0 # Verify zip file and contents - zip_path = isolated_filesystem / "complex-org.complex-extension-2.1.0.supx" + zip_path = isolated_filesystem / "complex-extension-2.1.0.supx" assert_file_exists(zip_path) with zipfile.ZipFile(zip_path, "r") as zipf: diff --git a/superset-extensions-cli/tests/test_cli_init.py b/superset-extensions-cli/tests/test_cli_init.py index 5a03a48f917..4f2b585c67b 100644 --- a/superset-extensions-cli/tests/test_cli_init.py +++ b/superset-extensions-cli/tests/test_cli_init.py @@ -48,12 +48,12 @@ def test_init_creates_extension_with_both_frontend_and_backend( ) # Verify directory structure - extension_path = isolated_filesystem / "test-org.test-extension" + extension_path = isolated_filesystem / "test-extension" assert_directory_exists(extension_path, "main extension directory") expected_structure = create_test_extension_structure( isolated_filesystem, - "test-org.test-extension", + "test-extension", include_frontend=True, include_backend=True, ) @@ -74,7 +74,7 @@ def test_init_creates_extension_with_frontend_only( assert result.exit_code == 0, f"Command failed with output: {result.output}" - extension_path = isolated_filesystem / "test-org.test-extension" + extension_path = isolated_filesystem / "test-extension" assert_directory_exists(extension_path) # Should have frontend directory and package.json @@ -97,7 +97,7 @@ def test_init_creates_extension_with_backend_only( assert result.exit_code == 0, f"Command failed with output: {result.output}" - extension_path = isolated_filesystem / "test-org.test-extension" + extension_path = isolated_filesystem / "test-extension" assert_directory_exists(extension_path) # Should have backend directory and pyproject.toml @@ -120,7 +120,7 @@ def test_init_creates_extension_with_neither_frontend_nor_backend( assert result.exit_code == 0, f"Command failed with output: {result.output}" - extension_path = isolated_filesystem / "test-org.test-extension" + extension_path = isolated_filesystem / "test-extension" assert_directory_exists(extension_path) # Should only have extension.json @@ -138,8 +138,8 @@ def test_init_accepts_valid_display_name(cli_runner, isolated_filesystem): result = cli_runner.invoke(app, ["init"], input=cli_input) assert result.exit_code == 0, f"Should accept display name: {result.output}" - assert Path("test-org.my-awesome-extension").exists(), ( - "Directory for generated composite ID should be created" + assert Path("my-awesome-extension").exists(), ( + "Directory with extension name should be created" ) @@ -152,23 +152,21 @@ def test_init_accepts_mixed_alphanumeric_name(cli_runner, isolated_filesystem): assert result.exit_code == 0, ( f"Mixed alphanumeric display name should be valid: {result.output}" ) - assert Path("test-org.tool-123").exists(), ( - "Directory for 'test-org.tool-123' should be created" - ) + assert Path("tool-123").exists(), "Directory for 'tool-123' should be created" @pytest.mark.cli @pytest.mark.parametrize( - "display_name,expected_id", + "display_name,expected_dir", [ - ("Test Extension", "test-org.test-extension"), - ("My Tool v2", "test-org.my-tool-v2"), - ("Dashboard Helper", "test-org.dashboard-helper"), - ("Chart Builder Pro", "test-org.chart-builder-pro"), + ("Test Extension", "test-extension"), + ("My Tool v2", "my-tool-v2"), + ("Dashboard Helper", "dashboard-helper"), + ("Chart Builder Pro", "chart-builder-pro"), ], ) -def test_init_with_various_display_names(cli_runner, display_name, expected_id): - """Test that init accepts various display names and generates proper IDs.""" +def test_init_with_various_display_names(cli_runner, display_name, expected_dir): + """Test that init accepts various display names and creates directory named after extension.""" with cli_runner.isolated_filesystem(): cli_input = f"{display_name}\n\ntest-org\n0.1.0\nApache-2.0\ny\ny\n" result = cli_runner.invoke(app, ["init"], input=cli_input) @@ -176,8 +174,8 @@ def test_init_with_various_display_names(cli_runner, display_name, expected_id): assert result.exit_code == 0, ( f"Valid display name '{display_name}' was rejected: {result.output}" ) - assert Path(expected_id).exists(), ( - f"Directory for '{expected_id}' was not created" + assert Path(expected_dir).exists(), ( + f"Directory '{expected_dir}' was not created" ) @@ -187,7 +185,7 @@ def test_init_fails_when_directory_already_exists( ): """Test that init fails gracefully when target directory already exists.""" # Create the directory first - existing_dir = isolated_filesystem / "test-org.test-extension" + existing_dir = isolated_filesystem / "test-extension" existing_dir.mkdir() result = cli_runner.invoke(app, ["init"], input=cli_input_both) @@ -204,7 +202,7 @@ def test_extension_json_content_is_correct( result = cli_runner.invoke(app, ["init"], input=cli_input_both) assert result.exit_code == 0 - extension_path = isolated_filesystem / "test-org.test-extension" + extension_path = isolated_filesystem / "test-extension" extension_json_path = extension_path / "extension.json" # Verify the JSON structure and values @@ -238,7 +236,7 @@ def test_frontend_package_json_content_is_correct( result = cli_runner.invoke(app, ["init"], input=cli_input_both) assert result.exit_code == 0 - extension_path = isolated_filesystem / "test-org.test-extension" + extension_path = isolated_filesystem / "test-extension" package_json_path = extension_path / "frontend" / "package.json" # Verify the package.json structure and values @@ -267,7 +265,7 @@ def test_backend_pyproject_toml_is_created( result = cli_runner.invoke(app, ["init"], input=cli_input_both) assert result.exit_code == 0 - extension_path = isolated_filesystem / "test-org.test-extension" + extension_path = isolated_filesystem / "test-extension" pyproject_path = extension_path / "backend" / "pyproject.toml" assert_file_exists(pyproject_path, "backend pyproject.toml") @@ -305,7 +303,7 @@ def test_gitignore_content_is_correct(cli_runner, isolated_filesystem, cli_input result = cli_runner.invoke(app, ["init"], input=cli_input_both) assert result.exit_code == 0 - extension_path = isolated_filesystem / "test-org.test-extension" + extension_path = isolated_filesystem / "test-extension" gitignore_path = extension_path / ".gitignore" assert_file_exists(gitignore_path, ".gitignore") @@ -330,7 +328,7 @@ def test_init_with_custom_version_and_license(cli_runner, isolated_filesystem): assert result.exit_code == 0 - extension_path = isolated_filesystem / "test-org.my-extension" + extension_path = isolated_filesystem / "my-extension" extension_json_path = extension_path / "extension.json" assert_json_content( @@ -357,10 +355,10 @@ def test_full_init_workflow_integration(cli_runner, isolated_filesystem): assert result.exit_code == 0 # Verify complete directory structure - extension_path = isolated_filesystem / "awesome-org.awesome-charts" + extension_path = isolated_filesystem / "awesome-charts" expected_structure = create_test_extension_structure( isolated_filesystem, - "awesome-org.awesome-charts", + "awesome-charts", include_frontend=True, include_backend=True, ) @@ -412,7 +410,7 @@ def test_init_non_interactive_with_all_options(cli_runner, isolated_filesystem): assert result.exit_code == 0, f"Command failed with output: {result.output}" assert "🎉 Extension My Extension (ID: my-org.my-ext) initialized" in result.output - extension_path = isolated_filesystem / "my-org.my-ext" + extension_path = isolated_filesystem / "my-ext" assert_directory_exists(extension_path) assert_directory_exists(extension_path / "frontend") assert_directory_exists(extension_path / "backend") @@ -449,7 +447,7 @@ def test_init_frontend_only_with_cli_options(cli_runner, isolated_filesystem): assert result.exit_code == 0, f"Command failed with output: {result.output}" - extension_path = isolated_filesystem / "frontend-org.frontend-ext" + extension_path = isolated_filesystem / "frontend-ext" assert_directory_exists(extension_path / "frontend") assert not (extension_path / "backend").exists() @@ -478,7 +476,7 @@ def test_init_backend_only_with_cli_options(cli_runner, isolated_filesystem): assert result.exit_code == 0, f"Command failed with output: {result.output}" - extension_path = isolated_filesystem / "backend-org.backend-ext" + extension_path = isolated_filesystem / "backend-ext" assert not (extension_path / "frontend").exists() assert_directory_exists(extension_path / "backend") @@ -505,7 +503,7 @@ def test_init_prompts_for_missing_options(cli_runner, isolated_filesystem): assert result.exit_code == 0, f"Command failed with output: {result.output}" - extension_path = isolated_filesystem / "default-org.default-ext" + extension_path = isolated_filesystem / "default-ext" extension_json = load_json_file(extension_path / "extension.json") assert extension_json["version"] == "0.1.0" assert extension_json["license"] == "Apache-2.0" diff --git a/superset-frontend/.eslintrc.js b/superset-frontend/.eslintrc.js index db01c79e74e..fcca8cbf5ae 100644 --- a/superset-frontend/.eslintrc.js +++ b/superset-frontend/.eslintrc.js @@ -127,7 +127,6 @@ module.exports = { }, plugins: [ 'import', - 'file-progress', 'lodash', 'theme-colors', 'icons', diff --git a/superset-frontend/cypress-base/cypress/e2e/chart_list/list.test.ts b/superset-frontend/cypress-base/cypress/e2e/chart_list/list.test.ts deleted file mode 100644 index 9f507bca4ab..00000000000 --- a/superset-frontend/cypress-base/cypress/e2e/chart_list/list.test.ts +++ /dev/null @@ -1,171 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -import { CHART_LIST } from 'cypress/utils/urls'; -import { setGridMode, toggleBulkSelect } from 'cypress/utils'; -import { - setFilter, - interceptBulkDelete, - interceptUpdate, - interceptDelete, - interceptFiltering, - interceptFavoriteStatus, -} from '../explore/utils'; - -function orderAlphabetical() { - setFilter('Sort', 'Alphabetical'); -} - -function openProperties() { - cy.get('[aria-label="more"]').eq(0).click(); - cy.getBySel('chart-list-edit-option').click(); -} - -function openMenu() { - cy.get('[aria-label="more"]').eq(0).click(); -} - -function confirmDelete() { - cy.getBySel('delete-modal-input').type('DELETE'); - cy.getBySel('modal-confirm-button').click(); -} - -function visitChartList() { - interceptFiltering(); - interceptFavoriteStatus(); - cy.visit(CHART_LIST); - cy.wait('@filtering'); - cy.wait('@favoriteStatus'); -} - -describe('Charts list', () => { - describe('common actions', () => { - beforeEach(() => { - visitChartList(); - }); - - it('should bulk delete correctly', () => { - cy.createSampleCharts([0, 1, 2, 3]); - - interceptBulkDelete(); - toggleBulkSelect(); - - // bulk deletes in card-view - setGridMode('card'); - orderAlphabetical(); - - cy.getBySel('skeleton-card').should('not.exist'); - cy.getBySel('styled-card').contains('1 - Sample chart').click(); - cy.getBySel('styled-card').contains('2 - Sample chart').click(); - cy.getBySel('bulk-select-action').contains('Delete').click(); - confirmDelete(); - cy.wait('@bulkDelete'); - cy.getBySel('styled-card') - .eq(1) - .should('not.contain', '1 - Sample chart'); - cy.getBySel('styled-card') - .eq(2) - .should('not.contain', '2 - Sample chart'); - - // bulk deletes in list-view - setGridMode('list'); - cy.get('.loading').should('not.exist'); - cy.getBySel('table-row').contains('3 - Sample chart').should('exist'); - cy.getBySel('table-row').contains('4 - Sample chart').should('exist'); - cy.get('[data-test="table-row"] input[type="checkbox"]').eq(0).click(); - cy.get('[data-test="table-row"] input[type="checkbox"]').eq(1).click(); - cy.getBySel('bulk-select-action').eq(0).contains('Delete').click(); - confirmDelete(); - cy.wait('@bulkDelete'); - cy.get('.loading').should('exist'); - cy.get('.loading').should('not.exist'); - cy.getBySel('table-row').eq(0).should('not.contain', '3 - Sample chart'); - cy.getBySel('table-row').eq(1).should('not.contain', '4 - Sample chart'); - }); - - it('should delete correctly in card mode', () => { - cy.createSampleCharts([0, 1]); - interceptDelete(); - - // deletes in card-view - setGridMode('card'); - orderAlphabetical(); - - cy.getBySel('styled-card').contains('1 - Sample chart'); - openMenu(); - cy.getBySel('chart-list-delete-option').click(); - confirmDelete(); - cy.wait('@delete'); - cy.getBySel('styled-card') - .contains('1 - Sample chart') - .should('not.exist'); - }); - - it('should delete correctly in list mode', () => { - cy.createSampleCharts([2, 3]); - interceptDelete(); - cy.getBySel('sort-header').contains('Name').click(); - - // Modal closes immediately without this - cy.wait(2000); - - cy.getBySel('table-row').eq(0).contains('3 - Sample chart'); - cy.getBySel('delete').eq(0).click(); - confirmDelete(); - cy.wait('@delete'); - cy.get('.loading').should('exist'); - cy.get('.loading').should('not.exist'); - cy.getBySel('table-row').eq(0).should('not.contain', '3 - Sample chart'); - }); - - it('should edit correctly', () => { - cy.createSampleCharts([0]); - interceptUpdate(); - - // edits in card-view - setGridMode('card'); - orderAlphabetical(); - cy.getBySel('skeleton-card').should('not.exist'); - cy.getBySel('styled-card').eq(0).contains('1 - Sample chart'); - - // change title - openProperties(); - cy.getBySel('properties-modal-name-input').type(' | EDITED'); - cy.get('button:contains("Save")').click(); - cy.wait('@update'); - cy.getBySel('styled-card').eq(0).contains('1 - Sample chart | EDITED'); - - // edits in list-view - setGridMode('list'); - // Wait for list view to fully render after mode change - cy.get('.loading').should('not.exist'); - cy.getBySel('table-row').should('be.visible'); - // Target the specific row by chart title to avoid flakiness from row ordering - cy.getBySel('table-row') - .contains('1 - Sample chart | EDITED') - .parents('[data-test="table-row"]') - .find('[data-test="edit-alt"]') - .click(); - cy.getBySel('properties-modal-name-input').clear(); - cy.getBySel('properties-modal-name-input').type('1 - Sample chart'); - cy.get('button:contains("Save")').click(); - cy.wait('@update'); - cy.getBySel('table-row').contains('1 - Sample chart').should('exist'); - }); - }); -}); diff --git a/superset-frontend/cypress-base/cypress/e2e/dataset/dataset_list.test.ts b/superset-frontend/cypress-base/cypress/e2e/dataset/dataset_list.test.ts deleted file mode 100644 index 6bf0419cdda..00000000000 --- a/superset-frontend/cypress-base/cypress/e2e/dataset/dataset_list.test.ts +++ /dev/null @@ -1,42 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { DATASET_LIST_PATH } from 'cypress/utils/urls'; - -describe('Dataset list', () => { - before(() => { - cy.visit(DATASET_LIST_PATH); - }); - - xit('should open Explore on dataset name click', () => { - cy.intercept('**/api/v1/explore/**').as('explore'); - cy.get('[data-test="listview-table"] [data-test="internal-link"]') - .contains('birth_names') - .click(); - cy.wait('@explore'); - cy.get('[data-test="datasource-control"] .title-select').contains( - 'birth_names', - ); - cy.get('.metric-option-label').first().contains('COUNT(*)'); - cy.get('.column-option-label').first().contains('ds'); - cy.get('[data-test="fast-viz-switcher"] > div:not([role="button"]') - .contains('Table') - .should('be.visible'); - }); -}); diff --git a/superset-frontend/cypress-base/cypress/e2e/explore/utils.ts b/superset-frontend/cypress-base/cypress/e2e/explore/utils.ts index 93dbe99baa2..5a4df482ec0 100644 --- a/superset-frontend/cypress-base/cypress/e2e/explore/utils.ts +++ b/superset-frontend/cypress-base/cypress/e2e/explore/utils.ts @@ -23,18 +23,6 @@ export function interceptFiltering() { cy.intercept('GET', `**/api/v1/chart/?q=*`).as('filtering'); } -export function interceptBulkDelete() { - cy.intercept('DELETE', `**/api/v1/chart/?q=*`).as('bulkDelete'); -} - -export function interceptDelete() { - cy.intercept('DELETE', `**/api/v1/chart/*`).as('delete'); -} - -export function interceptFavoriteStatus() { - cy.intercept('GET', '**/api/v1/chart/favorite_status/*').as('favoriteStatus'); -} - export function interceptUpdate() { cy.intercept('PUT', `**/api/v1/chart/*`).as('update'); } @@ -43,32 +31,13 @@ export const interceptV1ChartData = (alias = 'v1Data') => { cy.intercept('**/api/v1/chart/data*').as(alias); }; -export function interceptExploreJson(alias = 'getJson') { - cy.intercept('POST', `**/superset/explore_json/**`).as(alias); -} - -export const interceptFormDataKey = () => { - cy.intercept('POST', '**/api/v1/explore/form_data').as('formDataKey'); -}; - -export function interceptExploreGet() { +function interceptExploreGet() { cy.intercept({ method: 'GET', url: /.*\/api\/v1\/explore\/\?(form_data_key|dashboard_page_id|slice_id)=.*/, }).as('getExplore'); } -export function setFilter(filter: string, option: string) { - interceptFiltering(); - - cy.get(`[aria-label^="${filter}"]`).first().click(); - cy.get(`.ant-select-item-option[title="${option}"]`).first().click({ - force: true, - }); - - cy.wait('@filtering'); -} - export function saveChartToDashboard(chartName: string, dashboardName: string) { interceptDashboardGet(); interceptUpdate(); diff --git a/superset-frontend/cypress-base/cypress/e2e/sqllab/_skip.sourcePanel.index.test.js b/superset-frontend/cypress-base/cypress/e2e/sqllab/_skip.sourcePanel.index.test.js deleted file mode 100644 index 4b24c844364..00000000000 --- a/superset-frontend/cypress-base/cypress/e2e/sqllab/_skip.sourcePanel.index.test.js +++ /dev/null @@ -1,73 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -import { selectResultsTab } from './sqllab.helper'; - -describe.skip('SqlLab datasource panel', () => { - beforeEach(() => { - cy.visit('/sqllab'); - }); - - // TODO the test below is flaky, and has been disabled for the time being - // (notice the `it.skip`) - it('creates a table preview when a database, schema, and table are selected', () => { - cy.intercept('**/superset/table/**').as('tableMetadata'); - - // it should have dropdowns to select database, schema, and table - cy.get('.sql-toolbar .Select').should('have.length', 3); - - cy.get('.sql-toolbar .table-schema').should('not.exist'); - cy.get('[data-test="filterable-table-container"]').should('not.exist'); - - cy.get('.sql-toolbar .Select') - .eq(0) // database select - .within(() => { - // note: we have to set force: true because the input is invisible / cypress throws - cy.get('input').type('main{enter}', { force: true }); - }); - - cy.get('.sql-toolbar .Select') - .eq(1) // schema select - .within(() => { - cy.get('input').type('main{enter}', { force: true }); - }); - - cy.get('.sql-toolbar .Select') - .eq(2) // table select - .within(() => { - cy.get('input').type('birth_names{enter}', { force: true }); - }); - - cy.wait('@tableMetadata'); - - cy.get('.sql-toolbar .table-schema').should('have.length', 1); - selectResultsTab().should('have.length', 1); - - // add another table and check for added schema + preview - cy.get('.sql-toolbar .Select') - .eq(2) - .within(() => { - cy.get('input').type('logs{enter}', { force: true }); - }); - - cy.wait('@tableMetadata'); - - cy.get('.sql-toolbar .table-schema').should('have.length', 2); - selectResultsTab().should('have.length', 2); - }); -}); diff --git a/superset-frontend/cypress-base/cypress/e2e/sqllab/query.test.ts b/superset-frontend/cypress-base/cypress/e2e/sqllab/query.test.ts deleted file mode 100644 index 389698dcb8d..00000000000 --- a/superset-frontend/cypress-base/cypress/e2e/sqllab/query.test.ts +++ /dev/null @@ -1,192 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -import { nanoid } from 'nanoid'; -import { selectResultsTab, assertSQLLabResultsAreEqual } from './sqllab.helper'; - -function parseClockStr(node: JQuery) { - return Number.parseFloat(node.text().replace(/:/g, '')); -} - -describe('SqlLab query panel', () => { - beforeEach(() => { - cy.visit('/sqllab'); - }); - - it.skip('supports entering and running a query', () => { - // row limit has to be < ~10 for us to be able to determine how many rows - // are fetched below (because React _Virtualized_ does not render all rows) - let clockTime = 0; - - cy.intercept({ - method: 'POST', - url: '**/api/v1/sqllab/execute/', - }).as('mockSQLResponse'); - - cy.get('.TableSelector .Select:eq(0)').click(); - cy.get('.TableSelector .Select:eq(0) input[type=text]').focus(); - cy.focused().type('{enter}'); - - cy.get('#brace-editor textarea').focus(); - cy.focused().clear(); - cy.focused().type(`{selectall}{backspace}SELECT 1`); - - cy.get('#js-sql-toolbar button:eq(0)').eq(0).click(); - - // wait for 300 milliseconds - cy.wait(300); - - // started timer - cy.get('.sql-toolbar .label-success').then(node => { - clockTime = parseClockStr(node); - // should be longer than 0.2s - expect(clockTime).greaterThan(0.2); - }); - - cy.wait('@mockSQLResponse'); - - // timer is increasing - cy.get('.sql-toolbar .label-success').then(node => { - const newClockTime = parseClockStr(node); - expect(newClockTime).greaterThan(0.9); - clockTime = newClockTime; - }); - - // rerun the query - cy.get('#js-sql-toolbar button:eq(0)').eq(0).click(); - - // should restart the timer - cy.get('.sql-toolbar .label-success').contains('00:00:00'); - cy.wait('@mockSQLResponse'); - cy.get('.sql-toolbar .label-success').then(node => { - expect(parseClockStr(node)).greaterThan(0.9); - }); - }); - - it.skip('successfully saves a query', () => { - cy.intercept('**/api/v1/database/**/tables/**').as('getTables'); - - const query = - 'SELECT ds, gender, name, num FROM main.birth_names ORDER BY name LIMIT 3'; - const savedQueryTitle = `CYPRESS TEST QUERY ${nanoid()}`; - - // we will assert that the results of the query we save, and the saved query are the same - let initialResultsTable: HTMLElement | null = null; - let savedQueryResultsTable = null; - - cy.get('#brace-editor textarea').clear({ force: true }); - cy.get('#brace-editor textarea').type(`{selectall}{backspace}${query}`, { - force: true, - }); - cy.get('#brace-editor textarea').focus(); // focus => blur is required for updating the query that is to be saved - cy.focused().blur(); - - // ctrl + r also runs query - cy.get('#brace-editor textarea').type('{ctrl}r', { force: true }); - - cy.wait('@sqlLabQuery'); - - // Save results to check against below - selectResultsTab().then(resultsA => { - initialResultsTable = resultsA[0]; - }); - - cy.get('#js-sql-toolbar button') - .eq(1) // save query - .click(); - - // Enter name + save into modal - cy.get('.modal-sm input').clear({ force: true }); - cy.get('.modal-sm input').type(`{selectall}{backspace}${savedQueryTitle}`, { - force: true, - }); - - cy.get('.modal-sm .modal-body button') - .eq(0) // save - .click(); - - // first row contains most recent link, follow back to SqlLab - cy.get('table tr:first-child a[href*="savedQueryId"').click(); - - // will timeout without explicitly waiting here - cy.wait(['@getSavedQuery', '@getTables']); - - // run the saved query - cy.get('#js-sql-toolbar button') - .eq(0) // run query - .click(); - - cy.wait('@sqlLabQuery'); - - // assert the results of the saved query match the initial results - selectResultsTab().then(resultsB => { - savedQueryResultsTable = resultsB[0]; - - assertSQLLabResultsAreEqual(initialResultsTable, savedQueryResultsTable); - }); - }); - - it.skip('Create a chart from a query', () => { - cy.intercept('**/api/v1/sqllab/execute/').as('queryFinished'); - cy.intercept('**/api/v1/explore/**').as('explore'); - cy.intercept('**/api/v1/chart/**').as('chart'); - cy.intercept('**/tabstateview/**').as('tabstateview'); - - // cypress doesn't handle opening a new tab, override window.open to open in the same tab - cy.window().then(win => { - cy.stub(win, 'open', url => { - // eslint-disable-next-line no-param-reassign - win.location.href = url; - }); - }); - cy.wait('@tabstateview'); - - const query = 'SELECT gender, name FROM birth_names'; - - cy.get('.ace_text-input').focus(); - cy.focused().clear({ force: true }); - cy.focused().type(`{selectall}{backspace}${query}`, { force: true }); - cy.get('.sql-toolbar button').contains('Run').click(); - cy.wait('@queryFinished'); - - cy.get( - '.SouthPane .ant-tabs-content > .ant-tabs-tabpane-active > div button:first', - { timeout: 10000 }, - ).click(); - - cy.wait('@explore'); - cy.get('[data-test="datasource-control"] .title-select').contains(query); - cy.get('.column-option-label').first().contains('gender'); - cy.get('.column-option-label').last().contains('name'); - - cy.get( - '[data-test="all_columns"] [data-test="dnd-labels-container"] > div:first-child', - ).contains('gender'); - cy.get( - '[data-test="all_columns"] [data-test="dnd-labels-container"] > div:nth-child(2)', - ).contains('name'); - - cy.wait('@chart'); - cy.get('[data-test="slice-container"] table > thead th') - .first() - .contains('gender'); - cy.get('[data-test="slice-container"] table > thead th') - .last() - .contains('name'); - }); -}); diff --git a/superset-frontend/cypress-base/cypress/e2e/sqllab/sqllab.helper.js b/superset-frontend/cypress-base/cypress/e2e/sqllab/sqllab.helper.js deleted file mode 100644 index 45f298205d3..00000000000 --- a/superset-frontend/cypress-base/cypress/e2e/sqllab/sqllab.helper.js +++ /dev/null @@ -1,41 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -export const selectResultsTab = () => - cy.get('.SouthPane .ReactVirtualized__Table', { timeout: 10000 }); - -// this function asserts that the result set for two SQL lab table results are equal -export const assertSQLLabResultsAreEqual = (resultsA, resultsB) => { - const [headerA, bodyWrapperA] = resultsA.childNodes; - const bodyA = bodyWrapperA.childNodes[0]; - - const [headerB, bodyWrapperB] = resultsB.childNodes; - const bodyB = bodyWrapperB.childNodes[0]; - - expect(headerA.childNodes.length).to.equal(headerB.childNodes.length); - expect(bodyA.childNodes.length).to.equal(bodyB.childNodes.length); - - bodyA.childNodes.forEach((rowA, rowIndex) => { - const rowB = bodyB.childNodes[rowIndex]; - - rowA.childNodes.forEach((cellA, columnIndex) => { - const cellB = rowB.childNodes[columnIndex]; - expect(cellA.innerText).to.equal(cellB.innerText); - }); - }); -}; diff --git a/superset-frontend/cypress-base/cypress/e2e/sqllab/tabs.test.ts b/superset-frontend/cypress-base/cypress/e2e/sqllab/tabs.test.ts deleted file mode 100644 index 8ca480aab6d..00000000000 --- a/superset-frontend/cypress-base/cypress/e2e/sqllab/tabs.test.ts +++ /dev/null @@ -1,113 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -describe('SqlLab query tabs', () => { - beforeEach(() => { - cy.visit('/sqllab'); - }); - - const tablistSelector = '[data-test="sql-editor-tabs"] > [role="tablist"]'; - const tabSelector = `${tablistSelector} [role="tab"]:not([type="button"])`; - - it('allows you to create and close a tab', () => { - cy.get(tabSelector).then(tabs => { - const initialTabCount = tabs.length; - const initialUntitledCount = Math.max( - 0, - ...tabs - .map( - (i, tabItem) => - Number(tabItem.textContent?.match(/Untitled Query (\d+)/)?.[1]) || - 0, - ) - .toArray(), - ); - - // add two new tabs - cy.get('[data-test="add-tab-icon"]:visible:last').click({ force: true }); - cy.contains('[role="tab"]', `Untitled Query ${initialUntitledCount + 1}`); - cy.get(tabSelector).should('have.length', initialTabCount + 1); - - cy.get('[data-test="add-tab-icon"]:visible:last').click({ force: true }); - cy.contains('[role="tab"]', `Untitled Query ${initialUntitledCount + 2}`); - cy.get(tabSelector).should('have.length', initialTabCount + 2); - - // close the tabs - cy.get(`${tabSelector}:last [data-test="dropdown-trigger"]`).click({ - force: true, - }); - cy.get('[data-test="close-tab-menu-option"]').click(); - cy.get(tabSelector).should('have.length', initialTabCount + 1); - cy.contains('[role="tab"]', `Untitled Query ${initialUntitledCount + 1}`); - - cy.get(`${tablistSelector} [aria-label="remove"]:last`).click(); - cy.get(tabSelector).should('have.length', initialTabCount); - }); - }); - - it('opens a new tab by a button and a shortcut', () => { - const editorContent = '.ace_editor .ace_content'; - const editorInput = '.ace_editor textarea'; - const queryLimitSelector = '#js-sql-toolbar .limitDropdown'; - cy.get(tabSelector).then(tabs => { - const initialTabCount = tabs.length; - const initialUntitledCount = Math.max( - 0, - ...tabs - .map( - (i, tabItem) => - Number(tabItem.textContent?.match(/Untitled Query (\d+)/)?.[1]) || - 0, - ) - .toArray(), - ); - - // configure some editor settings - cy.get(editorInput).type('some random query string', { force: true }); - cy.get(queryLimitSelector).parent().click({ force: true }); - cy.get('.ant-dropdown-menu') - .last() - .find('.ant-dropdown-menu-item') - .first() - .click({ force: true }); - - // open a new tab by a button - cy.get('[data-test="add-tab-icon"]:visible:last').click({ force: true }); - cy.contains('[role="tab"]', `Untitled Query ${initialUntitledCount + 1}`); - cy.get(tabSelector).should('have.length', initialTabCount + 1); - cy.get(editorContent).contains('SELECT ...'); - cy.get(queryLimitSelector).contains('10'); - - // close the tab - cy.get(`${tabSelector}:last [data-test="dropdown-trigger"]`).click({ - force: true, - }); - cy.get(`${tablistSelector} [aria-label="remove"]:last`).click({ - force: true, - }); - cy.get(tabSelector).should('have.length', initialTabCount); - - // open a new tab by a shortcut - cy.get('body').type('{ctrl}t'); - cy.get(tabSelector).should('have.length', initialTabCount + 1); - cy.contains('[role="tab"]', `Untitled Query ${initialUntitledCount + 1}`); - cy.get(editorContent).contains('SELECT ...'); - cy.get(queryLimitSelector).contains('10'); - }); - }); -}); diff --git a/superset-frontend/cypress-base/cypress/utils/index.ts b/superset-frontend/cypress-base/cypress/utils/index.ts index 00ff58ab9aa..0c12c23aba7 100644 --- a/superset-frontend/cypress-base/cypress/utils/index.ts +++ b/superset-frontend/cypress-base/cypress/utils/index.ts @@ -25,28 +25,6 @@ export interface ChartSpec { viz: string; } -const viewTypeIcons = { - card: 'appstore', - list: 'unordered-list', -}; - -export function setGridMode(type: 'card' | 'list') { - const icon = viewTypeIcons[type]; - cy.get(`[aria-label="${icon}"]`).click(); -} - -export function toggleBulkSelect() { - cy.getBySel('bulk-select').click(); -} - -export function clearAllInputs() { - cy.get('body').then($body => { - if ($body.find('.ant-select-clear').length) { - cy.get('.ant-select-clear').click({ multiple: true, force: true }); - } - }); -} - const toSlicelike = ($chart: JQuery): Slice => { const chartId = $chart.attr('data-test-chart-id'); const vizType = $chart.attr('data-test-viz-type'); diff --git a/superset-frontend/cypress-base/cypress/utils/urls.ts b/superset-frontend/cypress-base/cypress/utils/urls.ts index 1a585f40479..66110bcc64b 100644 --- a/superset-frontend/cypress-base/cypress/utils/urls.ts +++ b/superset-frontend/cypress-base/cypress/utils/urls.ts @@ -25,8 +25,3 @@ export const SUPPORTED_CHARTS_DASHBOARD = '/superset/dashboard/supported_charts_dash/'; export const TABBED_DASHBOARD = '/superset/dashboard/tabbed_dash/'; export const DATABASE_LIST = '/databaseview/list'; -export const DATASET_LIST_PATH = 'tablemodelview/list'; -export const ALERT_LIST = '/alert/list/'; -export const REPORT_LIST = '/report/list/'; -export const LOGIN = '/login/'; -export const REGISTER = '/register/'; diff --git a/superset-frontend/cypress-base/package-lock.json b/superset-frontend/cypress-base/package-lock.json index b976340ae43..e6346c4d33f 100644 --- a/superset-frontend/cypress-base/package-lock.json +++ b/superset-frontend/cypress-base/package-lock.json @@ -3167,9 +3167,9 @@ "integrity": "sha512-Fc8Ne62jJlKHiG/ajlonC4Sd66Pq68fFwK4ihJGNZpGqboc324SQk+lRvMzpPRuJOmfrJefdG8/7JdWX4bzJ2Q==" }, "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", + "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -4673,9 +4673,9 @@ } }, "node_modules/flatted": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.2.tgz", - "integrity": "sha512-JaTY/wtrcSyvXJl4IMFHPKyFur1sE9AUqc0QnhOaJ0CxHtAoIV8pYDzeEfAaNEtGkOfq4gr3LBFmdXW5mOQFnA==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", "dev": true, "peer": true }, @@ -5809,9 +5809,9 @@ } }, "node_modules/lodash": { - "version": "4.17.23", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", - "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==" + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==" }, "node_modules/lodash.clonedeep": { "version": "4.5.0", @@ -6930,9 +6930,9 @@ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" }, "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "engines": { "node": ">=8.6" }, @@ -8642,9 +8642,9 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "node_modules/yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.3.tgz", + "integrity": "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==", "peer": true, "engines": { "node": ">= 6" @@ -11132,9 +11132,9 @@ "integrity": "sha512-Fc8Ne62jJlKHiG/ajlonC4Sd66Pq68fFwK4ihJGNZpGqboc324SQk+lRvMzpPRuJOmfrJefdG8/7JdWX4bzJ2Q==" }, "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", + "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -12253,9 +12253,9 @@ } }, "flatted": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.2.tgz", - "integrity": "sha512-JaTY/wtrcSyvXJl4IMFHPKyFur1sE9AUqc0QnhOaJ0CxHtAoIV8pYDzeEfAaNEtGkOfq4gr3LBFmdXW5mOQFnA==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", "dev": true, "peer": true }, @@ -13072,9 +13072,9 @@ } }, "lodash": { - "version": "4.17.23", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", - "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==" + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==" }, "lodash.clonedeep": { "version": "4.5.0", @@ -13825,9 +13825,9 @@ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" }, "picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==" + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==" }, "pify": { "version": "2.3.0", @@ -15063,9 +15063,9 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.3.tgz", + "integrity": "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==", "peer": true }, "yargs": { diff --git a/superset-frontend/oxlint.json b/superset-frontend/oxlint.json index fb4401c4bab..d2fe2bfb5ac 100644 --- a/superset-frontend/oxlint.json +++ b/superset-frontend/oxlint.json @@ -33,6 +33,33 @@ // - No FontAwesome icons (use Icons component) // - No template variables in i18n (use parameterized messages) + // === Rules carried over from ESLint that oxlint does NOT implement === + // oxlint 1.58+ errors on unknown builtin rules, so these can no longer + // be listed in this config. They were silently dropped in earlier + // oxlint versions (not actually enforced). Documented here for future + // maintainers — if/when oxlint adds them, re-enable in the relevant + // plugin section above. + // import: newline-after-import, no-extraneous-dependencies, + // no-import-module-exports, no-relative-packages, + // no-unresolved, no-useless-path-segments + // react: default-props-match-prop-types, destructuring-assignment, + // forbid-component-props, forbid-foreign-prop-types, + // forbid-prop-types, function-component-definition, + // jsx-no-bind, jsx-uses-vars, no-access-state-in-setstate, + // no-deprecated, no-did-update-set-state, no-typos, + // no-unstable-nested-components, + // no-unused-class-component-methods, no-unused-prop-types, + // no-unused-state, prefer-stateless-function, prop-types, + // require-default-props, sort-comp, static-property-placement + // (prefer-stateless-function / function-component-definition + // are represented by react/prefer-function-component below) + // jsx-a11y: interactive-supports-focus, + // no-interactive-element-to-noninteractive-role, + // no-noninteractive-element-interactions, + // no-noninteractive-element-to-interactive-role + // typescript: naming-convention + // unicorn: prevent-abbreviations + // === Core ESLint rules === // Error prevention "no-console": "warn", @@ -89,7 +116,6 @@ "no-object-constructor": "error", // === Import plugin rules === - "import/no-unresolved": "error", // TODO: Fix incorrect named imports in Storybook and other files "import/named": "warn", // TODO: Fix duplicate exports in shared-controls and other modules @@ -105,56 +131,24 @@ "import/first": "error", // TODO: Consolidate duplicate imports in DatasetList and other files "import/no-duplicates": "warn", - "import/newline-after-import": "error", "import/no-absolute-path": "error", "import/no-dynamic-require": "error", "import/no-webpack-loader-syntax": "error", "import/no-self-import": "error", "import/no-cycle": "off", - "import/no-useless-path-segments": ["error", { "commonjs": true }], "import/prefer-default-export": "off", - "import/no-relative-packages": "off", - "import/no-import-module-exports": "off", - "import/no-extraneous-dependencies": [ - "error", - { - "devDependencies": [ - "test/**", - "tests/**", - "spec/**", - "**/__tests__/**", - "**/__mocks__/**", - "*.test.{js,jsx,ts,tsx}", - "*.spec.{js,jsx,ts,tsx}", - "**/*.test.{js,jsx,ts,tsx}", - "**/*.spec.{js,jsx,ts,tsx}", - "**/jest.config.js", - "**/jest.setup.js", - "**/webpack.config.js", - "**/webpack.config.*.js", - "**/.eslintrc.js" - ], - "optionalDependencies": false - } - ], // === React plugin rules === - "react/prop-types": "off", - "react/require-default-props": "off", - "react/forbid-prop-types": "off", - "react/forbid-component-props": "warn", "react/jsx-filename-extension": [ "warn", { "extensions": [".jsx", ".tsx"] } ], "react/jsx-fragments": "error", - "react/jsx-no-bind": "off", "react/jsx-props-no-spreading": "off", "react/jsx-boolean-value": ["error", "never", { "always": [] }], "react/jsx-no-duplicate-props": "error", "react/jsx-no-undef": "error", "react/jsx-pascal-case": ["error", { "allowAllCaps": true, "ignore": [] }], - "react/jsx-uses-vars": "error", "react/jsx-no-target-blank": ["error", { "enforceDynamicLinks": "always" }], "react/jsx-no-comment-textnodes": "error", "react/jsx-no-useless-fragment": "off", @@ -165,40 +159,27 @@ "react/no-array-index-key": "off", "react/no-children-prop": "error", "react/no-danger": "error", - "react/forbid-foreign-prop-types": "error", "react/no-danger-with-children": "error", - "react/no-deprecated": "error", - "react/no-did-update-set-state": "error", "react/no-find-dom-node": "error", "react/no-is-mounted": "error", "react/no-render-return-value": "error", "react/no-string-refs": "off", "react/no-unescaped-entities": "off", "react/no-unknown-property": "off", - "react/no-unused-prop-types": "off", - "react/no-unused-state": "error", "react/no-will-update-set-state": "error", "react/prefer-es6-class": ["error", "always"], - "react/prefer-stateless-function": [ - "error", - { "ignorePureComponents": true } - ], "react/require-render-return": "error", "react/self-closing-comp": "error", "react/void-dom-elements-no-children": "error", - "react/no-access-state-in-setstate": "error", "react/no-redundant-should-component-update": "error", "react/no-this-in-sfc": "error", - "react/no-typos": "error", - "react/no-unstable-nested-components": "off", - "react/no-unused-class-component-methods": "off", - "react/destructuring-assignment": "off", - "react/sort-comp": "off", "react/state-in-constructor": "off", - "react/static-property-placement": "off", "react/react-in-jsx-scope": "off", - "react/function-component-definition": "off", - "react/default-props-match-prop-types": "off", + // Successor to the ESLint-era `react/prefer-stateless-function` and + // `react/function-component-definition` rules. Disabled because the + // codebase still contains legacy class components; flip to "error" + // once the class-to-function migration completes. + "react/prefer-function-component": "off", "react/button-has-type": [ "error", { "button": true, "submit": true, "reset": false } @@ -223,7 +204,6 @@ "jsx-a11y/html-has-lang": "error", "jsx-a11y/iframe-has-title": "error", "jsx-a11y/img-redundant-alt": "error", - "jsx-a11y/interactive-supports-focus": "error", "jsx-a11y/label-has-associated-control": "error", "jsx-a11y/lang": "error", "jsx-a11y/media-has-caption": "error", @@ -231,9 +211,6 @@ "jsx-a11y/no-access-key": "error", "jsx-a11y/no-autofocus": ["error", { "ignoreNonDOM": true }], "jsx-a11y/no-distracting-elements": "error", - "jsx-a11y/no-interactive-element-to-noninteractive-role": "error", - "jsx-a11y/no-noninteractive-element-interactions": "error", - "jsx-a11y/no-noninteractive-element-to-interactive-role": "error", "jsx-a11y/no-noninteractive-tabindex": "error", "jsx-a11y/no-redundant-roles": "error", "jsx-a11y/no-static-element-interactions": "off", @@ -253,17 +230,6 @@ "@typescript-eslint/explicit-module-boundary-types": "off", "@typescript-eslint/no-unused-vars": "warn", "@typescript-eslint/prefer-optional-chain": "error", - "@typescript-eslint/naming-convention": [ - "error", - { - "selector": "enum", - "format": ["PascalCase"] - }, - { - "selector": "enumMember", - "format": ["PascalCase"] - } - ], // === Unicorn rules (bonus coverage) === "unicorn/no-new-array": "error", @@ -279,7 +245,6 @@ "unicorn/prefer-negative-index": "error", "unicorn/prefer-math-trunc": "error", "unicorn/filename-case": "off", - "unicorn/prevent-abbreviations": "off", "unicorn/no-null": "off", "unicorn/no-array-reduce": "off", "unicorn/no-array-for-each": "off", diff --git a/superset-frontend/package-lock.json b/superset-frontend/package-lock.json index e3a986c973f..e615ac4323e 100644 --- a/superset-frontend/package-lock.json +++ b/superset-frontend/package-lock.json @@ -47,21 +47,21 @@ "@superset-ui/legacy-plugin-chart-chord": "file:./plugins/legacy-plugin-chart-chord", "@superset-ui/legacy-plugin-chart-country-map": "file:./plugins/legacy-plugin-chart-country-map", "@superset-ui/legacy-plugin-chart-horizon": "file:./plugins/legacy-plugin-chart-horizon", - "@superset-ui/legacy-plugin-chart-map-box": "file:./plugins/legacy-plugin-chart-map-box", "@superset-ui/legacy-plugin-chart-paired-t-test": "file:./plugins/legacy-plugin-chart-paired-t-test", "@superset-ui/legacy-plugin-chart-parallel-coordinates": "file:./plugins/legacy-plugin-chart-parallel-coordinates", "@superset-ui/legacy-plugin-chart-partition": "file:./plugins/legacy-plugin-chart-partition", "@superset-ui/legacy-plugin-chart-rose": "file:./plugins/legacy-plugin-chart-rose", "@superset-ui/legacy-plugin-chart-world-map": "file:./plugins/legacy-plugin-chart-world-map", - "@superset-ui/legacy-preset-chart-deckgl": "file:./plugins/legacy-preset-chart-deckgl", "@superset-ui/legacy-preset-chart-nvd3": "file:./plugins/legacy-preset-chart-nvd3", "@superset-ui/plugin-chart-ag-grid-table": "file:./plugins/plugin-chart-ag-grid-table", "@superset-ui/plugin-chart-cartodiagram": "file:./plugins/plugin-chart-cartodiagram", "@superset-ui/plugin-chart-echarts": "file:./plugins/plugin-chart-echarts", "@superset-ui/plugin-chart-handlebars": "file:./plugins/plugin-chart-handlebars", "@superset-ui/plugin-chart-pivot-table": "file:./plugins/plugin-chart-pivot-table", + "@superset-ui/plugin-chart-point-cluster-map": "file:./plugins/plugin-chart-point-cluster-map", "@superset-ui/plugin-chart-table": "file:./plugins/plugin-chart-table", "@superset-ui/plugin-chart-word-cloud": "file:./plugins/plugin-chart-word-cloud", + "@superset-ui/preset-chart-deckgl": "file:./plugins/preset-chart-deckgl", "@superset-ui/switchboard": "file:./packages/superset-ui-switchboard", "@types/d3-format": "^3.0.1", "@types/d3-selection": "^3.0.11", @@ -73,49 +73,49 @@ "@visx/scale": "^3.5.0", "@visx/tooltip": "^3.0.0", "@visx/xychart": "^3.5.1", - "ag-grid-community": "35.0.1", - "ag-grid-react": "35.0.1", + "ag-grid-community": "35.2.1", + "ag-grid-react": "35.2.1", "antd": "^5.26.0", "chrono-node": "^2.9.0", "classnames": "^2.2.5", "content-disposition": "^1.0.1", "d3-color": "^3.1.0", "d3-scale": "^4.0.2", - "dayjs": "^1.11.19", + "dayjs": "^1.11.20", "dom-to-image-more": "^3.7.2", "dom-to-pdf": "^0.3.2", "echarts": "^5.6.0", "fast-glob": "^3.3.2", - "fs-extra": "^11.3.3", - "fuse.js": "^7.1.0", - "geolib": "^3.3.4", - "geostyler": "^18.3.1", + "fs-extra": "^11.3.4", + "fuse.js": "^7.3.0", + "geolib": "^3.3.14", + "geostyler": "^18.5.0", "geostyler-data": "^1.1.0", - "geostyler-openlayers-parser": "^5.4.0", + "geostyler-openlayers-parser": "^5.7.0", "geostyler-style": "11.0.2", "geostyler-wfs-parser": "^3.0.1", - "google-auth-library": "^10.6.1", + "google-auth-library": "^10.6.2", "immer": "^11.1.4", "interweave": "^13.1.1", "jquery": "^4.0.0", "js-levenshtein": "^1.1.6", "json-bigint": "^1.0.0", "json-stringify-pretty-compact": "^2.0.0", - "lodash": "^4.17.23", - "mapbox-gl": "^3.19.0", - "markdown-to-jsx": "^9.7.6", - "match-sorter": "^6.3.4", + "lodash": "^4.18.1", + "mapbox-gl": "^3.22.0", + "markdown-to-jsx": "^9.7.16", + "match-sorter": "^8.2.0", "memoize-one": "^5.2.1", "mousetrap": "^1.6.5", "mustache": "^4.2.0", - "nanoid": "^5.1.6", + "nanoid": "^5.1.9", "ol": "^10.8.0", "pretty-ms": "^9.3.0", "query-string": "9.3.1", "re-resizable": "^6.11.2", "react": "^17.0.2", - "react-arborist": "^3.4.3", - "react-checkbox-tree": "^1.8.0", + "react-arborist": "^3.5.0", + "react-checkbox-tree": "^2.0.1", "react-diff-viewer-continued": "^4.2.0", "react-dnd": "^11.1.3", "react-dnd-html5-backend": "^11.1.3", @@ -149,7 +149,7 @@ "use-event-callback": "^0.1.0", "use-immer": "^0.11.0", "use-query-params": "^2.2.2", - "uuid": "^13.0.0", + "uuid": "^14.0.0", "xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz", "yargs": "^17.7.2" }, @@ -163,34 +163,34 @@ "@babel/plugin-transform-export-namespace-from": "^7.27.1", "@babel/plugin-transform-modules-commonjs": "^7.28.6", "@babel/plugin-transform-runtime": "^7.29.0", - "@babel/preset-env": "^7.29.0", + "@babel/preset-env": "^7.29.2", "@babel/preset-react": "^7.28.5", "@babel/preset-typescript": "^7.28.5", "@babel/register": "^7.23.7", - "@babel/runtime": "^7.28.6", - "@babel/runtime-corejs3": "^7.29.0", + "@babel/runtime": "^7.29.2", + "@babel/runtime-corejs3": "^7.29.2", "@babel/types": "^7.28.6", "@cypress/react": "^8.0.2", "@emotion/babel-plugin": "^11.13.5", "@emotion/jest": "^11.14.2", "@istanbuljs/nyc-config-typescript": "^1.0.1", "@mihkeleidast/storybook-addon-source": "^1.0.1", - "@playwright/test": "^1.58.2", + "@playwright/test": "^1.59.1", "@pmmmwh/react-refresh-webpack-plugin": "^0.6.2", - "@storybook/addon-actions": "^8.6.17", - "@storybook/addon-controls": "^8.6.17", - "@storybook/addon-essentials": "^8.6.17", - "@storybook/addon-links": "^8.6.17", - "@storybook/addon-mdx-gfm": "^8.6.17", - "@storybook/components": "^8.6.17", - "@storybook/preview-api": "^8.6.17", - "@storybook/react": "^8.6.17", - "@storybook/react-webpack5": "^8.6.17", - "@storybook/test": "^8.6.15", + "@storybook/addon-actions": "^8.6.18", + "@storybook/addon-controls": "^8.6.18", + "@storybook/addon-essentials": "^8.6.18", + "@storybook/addon-links": "^8.6.18", + "@storybook/addon-mdx-gfm": "^8.6.18", + "@storybook/components": "^8.6.18", + "@storybook/preview-api": "^8.6.18", + "@storybook/react": "^8.6.18", + "@storybook/react-webpack5": "^8.6.18", + "@storybook/test": "^8.6.18", "@storybook/test-runner": "^0.17.0", "@svgr/webpack": "^8.1.0", - "@swc/core": "^1.15.18", - "@swc/plugin-emotion": "^14.6.0", + "@swc/core": "^1.15.30", + "@swc/plugin-emotion": "^14.8.0", "@swc/plugin-transform-imports": "^12.5.0", "@testing-library/dom": "^8.20.1", "@testing-library/jest-dom": "^6.9.1", @@ -200,10 +200,11 @@ "@types/content-disposition": "^0.5.9", "@types/dom-to-image": "^2.6.7", "@types/jest": "^30.0.0", + "@types/jquery": "^4.0.0", "@types/js-levenshtein": "^1.1.3", "@types/json-bigint": "^1.0.4", "@types/mousetrap": "^1.6.15", - "@types/node": "^25.3.3", + "@types/node": "^25.6.0", "@types/react": "^17.0.83", "@types/react-dom": "^17.0.26", "@types/react-loadable": "^5.5.11", @@ -220,14 +221,14 @@ "@typescript-eslint/eslint-plugin": "^7.18.0", "@typescript-eslint/parser": "^7.18.0", "babel-jest": "^30.0.2", - "babel-loader": "^10.0.0", + "babel-loader": "^10.1.1", "babel-plugin-dynamic-import-node": "^2.3.3", "babel-plugin-jsx-remove-data-test-id": "^3.0.0", "babel-plugin-lodash": "^3.3.4", - "baseline-browser-mapping": "^2.10.7", + "baseline-browser-mapping": "^2.10.16", "cheerio": "1.2.0", "concurrently": "^9.2.1", - "copy-webpack-plugin": "^13.0.1", + "copy-webpack-plugin": "^14.0.0", "cross-env": "^10.1.0", "css-loader": "^7.1.4", "css-minimizer-webpack-plugin": "^8.0.0", @@ -236,17 +237,17 @@ "eslint-import-resolver-alias": "^1.1.2", "eslint-import-resolver-typescript": "^4.4.4", "eslint-plugin-cypress": "^3.6.0", - "eslint-plugin-file-progress": "^1.5.0", "eslint-plugin-i18n-strings": "file:eslint-rules/eslint-plugin-i18n-strings", "eslint-plugin-icons": "file:eslint-rules/eslint-plugin-icons", "eslint-plugin-import": "^2.32.0", "eslint-plugin-jest-dom": "^5.5.0", "eslint-plugin-lodash": "^7.4.0", + "eslint-plugin-no-only-tests": "^3.3.0", "eslint-plugin-prettier": "^5.5.5", "eslint-plugin-react-prefer-function-component": "^5.0.0", - "eslint-plugin-react-you-might-not-need-an-effect": "^0.9.2", + "eslint-plugin-react-you-might-not-need-an-effect": "^0.9.3", "eslint-plugin-storybook": "^0.8.0", - "eslint-plugin-testing-library": "^7.16.0", + "eslint-plugin-testing-library": "^7.16.2", "eslint-plugin-theme-colors": "file:eslint-rules/eslint-plugin-theme-colors", "fetch-mock": "^12.6.0", "fork-ts-checker-webpack-plugin": "^9.1.0", @@ -256,17 +257,17 @@ "imports-loader": "^5.0.0", "jest": "^30.3.0", "jest-environment-jsdom": "^29.7.0", - "jest-html-reporter": "^4.3.0", + "jest-html-reporter": "^4.4.0", "jest-websocket-mock": "^2.5.0", "js-yaml-loader": "^1.2.2", - "jsdom": "^28.1.0", - "lerna": "^8.2.3", + "jsdom": "^29.0.2", + "lerna": "^9.0.4", "lightningcss": "^1.32.0", - "mini-css-extract-plugin": "^2.10.1", - "open-cli": "^8.0.0", - "oxlint": "^1.53.0", + "mini-css-extract-plugin": "^2.10.2", + "open-cli": "^9.0.0", + "oxlint": "^1.61.0", "po2json": "^0.4.5", - "prettier": "3.8.1", + "prettier": "3.8.3", "prettier-plugin-packagejson": "^3.0.2", "process": "^0.11.10", "react-refresh": "^0.18.0", @@ -274,21 +275,21 @@ "redux-mock-store": "^1.5.4", "source-map": "^0.7.6", "source-map-support": "^0.5.21", - "speed-measure-webpack-plugin": "^1.5.0", - "storybook": "8.6.17", + "speed-measure-webpack-plugin": "^1.6.0", + "storybook": "8.6.18", "style-loader": "^4.0.0", "swc-loader": "^0.2.7", - "terser-webpack-plugin": "^5.3.17", + "terser-webpack-plugin": "^5.4.0", "thread-loader": "^4.0.4", - "ts-jest": "^29.4.6", + "ts-jest": "^29.4.9", "tscw-config": "^1.1.2", "tsx": "^4.21.0", "typescript": "5.4.5", "unzipper": "^0.12.3", "vm-browserify": "^1.1.2", - "wait-on": "^9.0.4", - "webpack": "^5.105.4", - "webpack-bundle-analyzer": "^5.2.0", + "wait-on": "^9.0.5", + "webpack": "^5.106.0", + "webpack-bundle-analyzer": "^5.3.0", "webpack-cli": "^6.0.1", "webpack-dev-server": "^5.2.3", "webpack-manifest-plugin": "^5.0.1", @@ -336,13 +337,6 @@ "dev": true, "license": "Apache-2.0" }, - "node_modules/@acemir/cssom": { - "version": "0.9.31", - "resolved": "https://registry.npmjs.org/@acemir/cssom/-/cssom-0.9.31.tgz", - "integrity": "sha512-ZnR3GSaH+/vJ0YlHau21FjfLYjMpYVIzTD8M8vIEQvIGxeOXyXdzCI140rrCY862p/C/BbzWsjc1dgnM9mkoTA==", - "dev": true, - "license": "MIT" - }, "node_modules/@adobe/css-tools": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.1.tgz", @@ -469,66 +463,57 @@ "link": true }, "node_modules/@asamuzakjp/css-color": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-4.1.2.tgz", - "integrity": "sha512-NfBUvBaYgKIuq6E/RBLY1m0IohzNHAYyaJGuTK79Z23uNwmz2jl1mPsC5ZxCCxylinKhT1Amn5oNTlx1wN8cQg==", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-5.1.6.tgz", + "integrity": "sha512-BXWCh8dHs9GOfpo/fWGDJtDmleta2VePN9rn6WQt3GjEbxzutVF4t0x2pmH+7dbMCLtuv3MlwqRsAuxlzFXqFg==", "dev": true, "license": "MIT", "dependencies": { - "@csstools/css-calc": "^3.0.0", - "@csstools/css-color-parser": "^4.0.1", + "@csstools/css-calc": "^3.1.1", + "@csstools/css-color-parser": "^4.0.2", "@csstools/css-parser-algorithms": "^4.0.0", - "@csstools/css-tokenizer": "^4.0.0", - "lru-cache": "^11.2.5" - } - }, - "node_modules/@asamuzakjp/css-color/node_modules/lru-cache": { - "version": "11.2.6", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz", - "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==", - "dev": true, - "license": "BlueOak-1.0.0", + "@csstools/css-tokenizer": "^4.0.0" + }, "engines": { - "node": "20 || >=22" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, "node_modules/@asamuzakjp/dom-selector": { - "version": "6.8.1", - "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-6.8.1.tgz", - "integrity": "sha512-MvRz1nCqW0fsy8Qz4dnLIvhOlMzqDVBabZx6lH+YywFDdjXhMY37SmpV1XFX3JzG5GWHn63j6HX6QPr3lZXHvQ==", + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-7.0.7.tgz", + "integrity": "sha512-d2BgqDUOS1Hfp4IzKUZqCNz+Kg3Y88AkaBvJK/ZVSQPU1f7OpPNi7nQTH6/oI47Dkdg+Z3e8Yp6ynOu4UMINAQ==", "dev": true, "license": "MIT", "dependencies": { "@asamuzakjp/nwsapi": "^2.3.9", "bidi-js": "^1.0.3", - "css-tree": "^3.1.0", - "is-potential-custom-element-name": "^1.0.1", - "lru-cache": "^11.2.6" + "css-tree": "^3.2.1", + "is-potential-custom-element-name": "^1.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, "node_modules/@asamuzakjp/dom-selector/node_modules/css-tree": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz", - "integrity": "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", + "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", "dev": true, "license": "MIT", "dependencies": { - "mdn-data": "2.12.2", - "source-map-js": "^1.0.1" + "mdn-data": "2.27.1", + "source-map-js": "^1.2.1" }, "engines": { "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" } }, - "node_modules/@asamuzakjp/dom-selector/node_modules/lru-cache": { - "version": "11.2.6", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz", - "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==", + "node_modules/@asamuzakjp/dom-selector/node_modules/mdn-data": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", + "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": "20 || >=22" - } + "license": "CC0-1.0" }, "node_modules/@asamuzakjp/nwsapi": { "version": "2.3.9", @@ -2392,9 +2377,9 @@ } }, "node_modules/@babel/preset-env": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.29.0.tgz", - "integrity": "sha512-fNEdfc0yi16lt6IZo2Qxk3knHVdfMYX33czNb4v8yWhemoBhibCpQK/uYHtSKIiO+p/zd3+8fYVXhQdOVV608w==", + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.29.2.tgz", + "integrity": "sha512-DYD23veRYGvBFhcTY1iUvJnDNpuqNd/BzBwCvzOTKUnJjKg5kpUBh3/u9585Agdkgj+QuygG7jLfOPWMa2KVNw==", "dev": true, "license": "MIT", "dependencies": { @@ -2577,18 +2562,18 @@ } }, "node_modules/@babel/runtime": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz", - "integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==", + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", + "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/runtime-corejs3": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.29.0.tgz", - "integrity": "sha512-TgUkdp71C9pIbBcHudc+gXZnihEDOjUAmXO1VO4HHGES7QLZcShR0stfKIxLSNIYx2fqhmJChOjm/wkF8wv4gA==", + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.29.2.tgz", + "integrity": "sha512-Lc94FOD5+0aXhdb0Tdg3RUtqT6yWbI/BbFWvlaSJ3gAb9Ks+99nHRDKADVqC37er4eCB0fHyWT+y+K3QOvJKbw==", "dev": true, "license": "MIT", "dependencies": { @@ -2650,6 +2635,17 @@ "dev": true, "license": "MIT" }, + "node_modules/@borewit/text-codec": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@borewit/text-codec/-/text-codec-0.2.2.tgz", + "integrity": "sha512-DDaRehssg1aNrH4+2hnj1B7vnUGEjU6OIlyRdkMd0aUdIUvKXrJfXsy8LVtXAy7DRvYVluWbMspsRhz2lcW0mQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, "node_modules/@bramus/specificity": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz", @@ -2712,9 +2708,9 @@ } }, "node_modules/@csstools/color-helpers": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.0.1.tgz", - "integrity": "sha512-NmXRccUJMk2AWA5A7e5a//3bCIMyOu2hAtdRYrhPPHjDxINuCwX1w6rnIZ4xjLcp0ayv6h8Pc3X0eJUGiAAXHQ==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.0.2.tgz", + "integrity": "sha512-LMGQLS9EuADloEFkcTBR3BwV/CGHV7zyDxVRtVDTwdI2Ca4it0CCVTT9wCkxSgokjE5Ho41hEPgb8OEUwoXr6Q==", "dev": true, "funding": [ { @@ -2756,9 +2752,9 @@ } }, "node_modules/@csstools/css-color-parser": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.0.1.tgz", - "integrity": "sha512-vYwO15eRBEkeF6xjAno/KQ61HacNhfQuuU/eGwH67DplL0zD5ZixUa563phQvUelA07yDczIXdtmYojCphKJcw==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.0.2.tgz", + "integrity": "sha512-0GEfbBLmTFf0dJlpsNU7zwxRIH0/BGEMuXLTCvFYxuL1tNhqzTbtnFICyJLTNK4a+RechKP75e7w42ClXSnJQw==", "dev": true, "funding": [ { @@ -2772,8 +2768,8 @@ ], "license": "MIT", "dependencies": { - "@csstools/color-helpers": "^6.0.1", - "@csstools/css-calc": "^3.0.0" + "@csstools/color-helpers": "^6.0.2", + "@csstools/css-calc": "^3.1.1" }, "engines": { "node": ">=20.19.0" @@ -2806,23 +2802,6 @@ "@csstools/css-tokenizer": "^4.0.0" } }, - "node_modules/@csstools/css-syntax-patches-for-csstree": { - "version": "1.0.27", - "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.0.27.tgz", - "integrity": "sha512-sxP33Jwg1bviSUXAV43cVYdmjt2TLnLXNqCWl9xmxHawWVjGz/kEbdkr7F9pxJNBN2Mh+dq0crgItbW6tQvyow==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0" - }, "node_modules/@csstools/css-tokenizer": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz", @@ -3088,13 +3067,13 @@ } }, "node_modules/@discoveryjs/json-ext": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", - "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.6.3.tgz", + "integrity": "sha512-4B4OijXeVNOPZlYA2oEwWOTkzyltLao+xbotHQeqN++Rv27Y6s818+n2Qkp8q+Fxhn0t/5lA5X1Mxktud8eayQ==", "dev": true, "license": "MIT", "engines": { - "node": ">=10.0.0" + "node": ">=14.17.0" } }, "node_modules/@dnd-kit/accessibility": { @@ -4044,6 +4023,16 @@ "url": "https://github.com/sponsors/ayuhito" } }, + "node_modules/@gar/promise-retry": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@gar/promise-retry/-/promise-retry-1.0.3.tgz", + "integrity": "sha512-GmzA9ckNokPypTg10pgpeHNQe7ph+iIKKmhKu3Ob9ANkswreCx7R3cKmY781K8QK3AqVL3xVh9A42JvIAbkkSA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, "node_modules/@googleapis/sheets": { "version": "13.0.1", "resolved": "https://registry.npmjs.org/@googleapis/sheets/-/sheets-13.0.1.tgz", @@ -4091,9 +4080,9 @@ "license": "BSD-3-Clause" }, "node_modules/@hapi/tlds": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@hapi/tlds/-/tlds-1.1.4.tgz", - "integrity": "sha512-Fq+20dxsxLaUn5jSSWrdtSRcIUba2JquuorF9UW1wIJS5cSUwxIsO2GIhaWynPRflvxSzFN+gxKte2HEW1OuoA==", + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@hapi/tlds/-/tlds-1.1.6.tgz", + "integrity": "sha512-xdi7A/4NZokvV0ewovme3aUO5kQhW9pQ2YD1hRqZGhhSi5rBv4usHYidVocXSi9eihYsznZxLtAiEYYUL6VBGw==", "dev": true, "license": "BSD-3-Clause", "engines": { @@ -4158,14 +4147,28 @@ "node": ">=6.9.0" } }, - "node_modules/@inquirer/external-editor": { + "node_modules/@inquirer/ansi": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-1.0.2.tgz", - "integrity": "sha512-yy9cOoBnx58TlsPrIxauKIFQTiyH+0MK4e97y4sV9ERbI+zDxw7i2hxHLCIEGIE/8PPvDxGhgzIOTSOWcs6/MQ==", + "resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-1.0.2.tgz", + "integrity": "sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/checkbox": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-4.3.2.tgz", + "integrity": "sha512-VXukHf0RR1doGe6Sm4F0Em7SWYLTHSsbGfJdS9Ja2bX5/D5uwVOEjr07cncLROdBvmnvCATYEWlHqYmXv2IlQA==", + "dev": true, "license": "MIT", "dependencies": { - "chardet": "^2.1.0", - "iconv-lite": "^0.7.0" + "@inquirer/ansi": "^1.0.2", + "@inquirer/core": "^10.3.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" }, "engines": { "node": ">=18" @@ -4179,11 +4182,145 @@ } } }, - "node_modules/@inquirer/external-editor/node_modules/chardet": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.1.0.tgz", - "integrity": "sha512-bNFETTG/pM5ryzQ9Ad0lJOTa6HWD/YsScAR3EnCPZRPlQh77JocYktSHOUHelyhm8IARL+o4c4F1bP5KVOjiRA==", - "license": "MIT" + "node_modules/@inquirer/confirm": { + "version": "5.1.21", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.21.tgz", + "integrity": "sha512-KR8edRkIsUayMXV+o3Gv+q4jlhENF9nMYUZs9PA2HzrXeHI8M5uDag70U7RJn9yyiMZSbtF5/UexBtAVtZGSbQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/core": { + "version": "10.3.2", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.3.2.tgz", + "integrity": "sha512-43RTuEbfP8MbKzedNqBrlhhNKVwoK//vUFNW3Q3vZ88BLcrs4kYpGg+B2mm5p2K/HfygoCxuKwJJiv8PbGmE0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^1.0.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "cli-width": "^4.1.0", + "mute-stream": "^2.0.0", + "signal-exit": "^4.1.0", + "wrap-ansi": "^6.2.0", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/core/node_modules/mute-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-2.0.0.tgz", + "integrity": "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@inquirer/core/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@inquirer/editor": { + "version": "4.2.23", + "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-4.2.23.tgz", + "integrity": "sha512-aLSROkEwirotxZ1pBaP8tugXRFCxW94gwrQLxXfrZsKkfjOYC1aRvAZuhpJOb5cu4IBTJdsCigUlf2iCOu4ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/external-editor": "^1.0.3", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/expand": { + "version": "4.0.23", + "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-4.0.23.tgz", + "integrity": "sha512-nRzdOyFYnpeYTTR2qFwEVmIWypzdAx/sIkCMeTNTcflFOovfqUk+HcFhQQVBftAh9gmGrpFj6QcGEqrDMDOiew==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/external-editor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-1.0.3.tgz", + "integrity": "sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==", + "license": "MIT", + "dependencies": { + "chardet": "^2.1.1", + "iconv-lite": "^0.7.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } }, "node_modules/@inquirer/external-editor/node_modules/iconv-lite": { "version": "0.7.0", @@ -4202,19 +4339,206 @@ } }, "node_modules/@inquirer/figures": { - "version": "1.0.13", - "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.13.tgz", - "integrity": "sha512-lGPVU3yO9ZNqA7vTYz26jny41lE7yoQansmqdMLBEfqaGsmdg7V3W9mK9Pvb5IL4EVZ9GnSDGMO/cJXud5dMaw==", + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.15.tgz", + "integrity": "sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==", "license": "MIT", - "peer": true, "engines": { "node": ">=18" } }, + "node_modules/@inquirer/input": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-4.3.1.tgz", + "integrity": "sha512-kN0pAM4yPrLjJ1XJBjDxyfDduXOuQHrBB8aLDMueuwUGn+vNpF7Gq7TvyVxx8u4SHlFFj4trmj+a2cbpG4Jn1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/number": { + "version": "3.0.23", + "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-3.0.23.tgz", + "integrity": "sha512-5Smv0OK7K0KUzUfYUXDXQc9jrf8OHo4ktlEayFlelCjwMXz0299Y8OrI+lj7i4gCBY15UObk76q0QtxjzFcFcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/password": { + "version": "4.0.23", + "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-4.0.23.tgz", + "integrity": "sha512-zREJHjhT5vJBMZX/IUbyI9zVtVfOLiTO66MrF/3GFZYZ7T4YILW5MSkEYHceSii/KtRk+4i3RE7E1CUXA2jHcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^1.0.2", + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/prompts": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-7.10.1.tgz", + "integrity": "sha512-Dx/y9bCQcXLI5ooQ5KyvA4FTgeo2jYj/7plWfV5Ak5wDPKQZgudKez2ixyfz7tKXzcJciTxqLeK7R9HItwiByg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/checkbox": "^4.3.2", + "@inquirer/confirm": "^5.1.21", + "@inquirer/editor": "^4.2.23", + "@inquirer/expand": "^4.0.23", + "@inquirer/input": "^4.3.1", + "@inquirer/number": "^3.0.23", + "@inquirer/password": "^4.0.23", + "@inquirer/rawlist": "^4.1.11", + "@inquirer/search": "^3.2.2", + "@inquirer/select": "^4.4.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/rawlist": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-4.1.11.tgz", + "integrity": "sha512-+LLQB8XGr3I5LZN/GuAHo+GpDJegQwuPARLChlMICNdwW7OwV2izlCSCxN6cqpL0sMXmbKbFcItJgdQq5EBXTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/search": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-3.2.2.tgz", + "integrity": "sha512-p2bvRfENXCZdWF/U2BXvnSI9h+tuA8iNqtUKb9UWbmLYCRQxd8WkvwWvYn+3NgYaNwdUkHytJMGG4MMLucI1kA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/select": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-4.4.2.tgz", + "integrity": "sha512-l4xMuJo55MAe+N7Qr4rX90vypFwCajSakx59qe/tMaC1aEHWLyw68wF4o0A4SLAY4E0nd+Vt+EyskeDIqu1M6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^1.0.2", + "@inquirer/core": "^10.3.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/type": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.10.tgz", + "integrity": "sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, "license": "ISC", "dependencies": { "string-width": "^5.1.2", @@ -4232,6 +4556,7 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -4244,6 +4569,7 @@ "version": "6.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -4256,6 +4582,7 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, "license": "MIT", "dependencies": { "eastasianwidth": "^0.2.0", @@ -4273,6 +4600,7 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^6.0.1" @@ -4288,6 +4616,7 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^6.1.0", @@ -4301,6 +4630,19 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@isaacs/string-locale-compare": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@isaacs/string-locale-compare/-/string-locale-compare-1.1.0.tgz", @@ -4531,9 +4873,9 @@ } }, "node_modules/@jest/console/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", "engines": { @@ -4724,9 +5066,9 @@ } }, "node_modules/@jest/core/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", "engines": { @@ -5080,9 +5422,9 @@ } }, "node_modules/@jest/expect/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", "engines": { @@ -5329,9 +5671,9 @@ } }, "node_modules/@jest/globals/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", "engines": { @@ -5486,9 +5828,9 @@ } }, "node_modules/@jest/reporters/node_modules/brace-expansion": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz", - "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", "dev": true, "license": "MIT", "dependencies": { @@ -5640,9 +5982,9 @@ } }, "node_modules/@jest/reporters/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", "engines": { @@ -5989,9 +6331,9 @@ } }, "node_modules/@jest/transform/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", "engines": { @@ -6182,690 +6524,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@lerna/create": { - "version": "8.2.4", - "resolved": "https://registry.npmjs.org/@lerna/create/-/create-8.2.4.tgz", - "integrity": "sha512-A8AlzetnS2WIuhijdAzKUyFpR5YbLLfV3luQ4lzBgIBgRfuoBDZeF+RSZPhra+7A6/zTUlrbhKZIOi/MNhqgvQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@npmcli/arborist": "7.5.4", - "@npmcli/package-json": "5.2.0", - "@npmcli/run-script": "8.1.0", - "@nx/devkit": ">=17.1.2 < 21", - "@octokit/plugin-enterprise-rest": "6.0.1", - "@octokit/rest": "20.1.2", - "aproba": "2.0.0", - "byte-size": "8.1.1", - "chalk": "4.1.0", - "clone-deep": "4.0.1", - "cmd-shim": "6.0.3", - "color-support": "1.1.3", - "columnify": "1.6.0", - "console-control-strings": "^1.1.0", - "conventional-changelog-core": "5.0.1", - "conventional-recommended-bump": "7.0.1", - "cosmiconfig": "9.0.0", - "dedent": "1.5.3", - "execa": "5.0.0", - "fs-extra": "^11.2.0", - "get-stream": "6.0.0", - "git-url-parse": "14.0.0", - "glob-parent": "6.0.2", - "graceful-fs": "4.2.11", - "has-unicode": "2.0.1", - "ini": "^1.3.8", - "init-package-json": "6.0.3", - "inquirer": "^8.2.4", - "is-ci": "3.0.1", - "is-stream": "2.0.0", - "js-yaml": "4.1.0", - "libnpmpublish": "9.0.9", - "load-json-file": "6.2.0", - "make-dir": "4.0.0", - "minimatch": "3.0.5", - "multimatch": "5.0.0", - "node-fetch": "2.6.7", - "npm-package-arg": "11.0.2", - "npm-packlist": "8.0.2", - "npm-registry-fetch": "^17.1.0", - "nx": ">=17.1.2 < 21", - "p-map": "4.0.0", - "p-map-series": "2.1.0", - "p-queue": "6.6.2", - "p-reduce": "^2.1.0", - "pacote": "^18.0.6", - "pify": "5.0.0", - "read-cmd-shim": "4.0.0", - "resolve-from": "5.0.0", - "rimraf": "^4.4.1", - "semver": "^7.3.4", - "set-blocking": "^2.0.0", - "signal-exit": "3.0.7", - "slash": "^3.0.0", - "ssri": "^10.0.6", - "string-width": "^4.2.3", - "tar": "6.2.1", - "temp-dir": "1.0.0", - "through": "2.3.8", - "tinyglobby": "0.2.12", - "upath": "2.0.1", - "uuid": "^10.0.0", - "validate-npm-package-license": "^3.0.4", - "validate-npm-package-name": "5.0.1", - "wide-align": "1.1.5", - "write-file-atomic": "5.0.1", - "write-pkg": "4.0.0", - "yargs": "17.7.2", - "yargs-parser": "21.1.1" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@lerna/create/node_modules/@octokit/auth-token": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-4.0.0.tgz", - "integrity": "sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 18" - } - }, - "node_modules/@lerna/create/node_modules/@octokit/core": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/@octokit/core/-/core-5.2.2.tgz", - "integrity": "sha512-/g2d4sW9nUDJOMz3mabVQvOGhVa4e/BN/Um7yca9Bb2XTzPPnfTWHWQg+IsEYO7M3Vx+EXvaM/I2pJWIMun1bg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/auth-token": "^4.0.0", - "@octokit/graphql": "^7.1.0", - "@octokit/request": "^8.4.1", - "@octokit/request-error": "^5.1.1", - "@octokit/types": "^13.0.0", - "before-after-hook": "^2.2.0", - "universal-user-agent": "^6.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@lerna/create/node_modules/@octokit/endpoint": { - "version": "9.0.6", - "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-9.0.6.tgz", - "integrity": "sha512-H1fNTMA57HbkFESSt3Y9+FBICv+0jFceJFPWDePYlR/iMGrwM5ph+Dd4XRQs+8X+PUFURLQgX9ChPfhJ/1uNQw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/types": "^13.1.0", - "universal-user-agent": "^6.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@lerna/create/node_modules/@octokit/graphql": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-7.1.1.tgz", - "integrity": "sha512-3mkDltSfcDUoa176nlGoA32RGjeWjl3K7F/BwHwRMJUW/IteSa4bnSV8p2ThNkcIcZU2umkZWxwETSSCJf2Q7g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/request": "^8.4.1", - "@octokit/types": "^13.0.0", - "universal-user-agent": "^6.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@lerna/create/node_modules/@octokit/openapi-types": { - "version": "24.2.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", - "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@lerna/create/node_modules/@octokit/plugin-paginate-rest": { - "version": "11.4.4-cjs.2", - "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-11.4.4-cjs.2.tgz", - "integrity": "sha512-2dK6z8fhs8lla5PaOTgqfCGBxgAv/le+EhPs27KklPhm1bKObpu6lXzwfUEQ16ajXzqNrKMujsFyo9K2eaoISw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/types": "^13.7.0" - }, - "engines": { - "node": ">= 18" - }, - "peerDependencies": { - "@octokit/core": "5" - } - }, - "node_modules/@lerna/create/node_modules/@octokit/plugin-request-log": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-4.0.1.tgz", - "integrity": "sha512-GihNqNpGHorUrO7Qa9JbAl0dbLnqJVrV8OXe2Zm5/Y4wFkZQDfTreBzVmiRfJVfE4mClXdihHnbpyyO9FSX4HA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 18" - }, - "peerDependencies": { - "@octokit/core": "5" - } - }, - "node_modules/@lerna/create/node_modules/@octokit/plugin-rest-endpoint-methods": { - "version": "13.3.2-cjs.1", - "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-13.3.2-cjs.1.tgz", - "integrity": "sha512-VUjIjOOvF2oELQmiFpWA1aOPdawpyaCUqcEBc/UOUnj3Xp6DJGrJ1+bjUIIDzdHjnFNO6q57ODMfdEZnoBkCwQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/types": "^13.8.0" - }, - "engines": { - "node": ">= 18" - }, - "peerDependencies": { - "@octokit/core": "^5" - } - }, - "node_modules/@lerna/create/node_modules/@octokit/request": { - "version": "8.4.1", - "resolved": "https://registry.npmjs.org/@octokit/request/-/request-8.4.1.tgz", - "integrity": "sha512-qnB2+SY3hkCmBxZsR/MPCybNmbJe4KAlfWErXq+rBKkQJlbjdJeS85VI9r8UqeLYLvnAenU8Q1okM/0MBsAGXw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/endpoint": "^9.0.6", - "@octokit/request-error": "^5.1.1", - "@octokit/types": "^13.1.0", - "universal-user-agent": "^6.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@lerna/create/node_modules/@octokit/request-error": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-5.1.1.tgz", - "integrity": "sha512-v9iyEQJH6ZntoENr9/yXxjuezh4My67CBSu9r6Ve/05Iu5gNgnisNWOsoJHTP6k0Rr0+HQIpnH+kyammu90q/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/types": "^13.1.0", - "deprecation": "^2.0.0", - "once": "^1.4.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@lerna/create/node_modules/@octokit/rest": { - "version": "20.1.2", - "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-20.1.2.tgz", - "integrity": "sha512-GmYiltypkHHtihFwPRxlaorG5R9VAHuk/vbszVoRTGXnAsY60wYLkh/E2XiFmdZmqrisw+9FaazS1i5SbdWYgA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/core": "^5.0.2", - "@octokit/plugin-paginate-rest": "11.4.4-cjs.2", - "@octokit/plugin-request-log": "^4.0.0", - "@octokit/plugin-rest-endpoint-methods": "13.3.2-cjs.1" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@lerna/create/node_modules/@octokit/types": { - "version": "13.10.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", - "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/openapi-types": "^24.2.0" - } - }, - "node_modules/@lerna/create/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "license": "Python-2.0" - }, - "node_modules/@lerna/create/node_modules/before-after-hook": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz", - "integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/@lerna/create/node_modules/chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@lerna/create/node_modules/cli-width": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", - "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">= 10" - } - }, - "node_modules/@lerna/create/node_modules/cosmiconfig": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", - "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", - "dev": true, - "license": "MIT", - "dependencies": { - "env-paths": "^2.2.1", - "import-fresh": "^3.3.0", - "js-yaml": "^4.1.0", - "parse-json": "^5.2.0" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/d-fischer" - }, - "peerDependencies": { - "typescript": ">=4.9.5" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@lerna/create/node_modules/dedent": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.3.tgz", - "integrity": "sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "babel-plugin-macros": "^3.1.0" - }, - "peerDependenciesMeta": { - "babel-plugin-macros": { - "optional": true - } - } - }, - "node_modules/@lerna/create/node_modules/eventemitter3": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@lerna/create/node_modules/execa": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.0.0.tgz", - "integrity": "sha512-ov6w/2LCiuyO4RLYGdpFGjkcs0wMTgGE8PrkTHikeUy5iJekXyPIKUjifk5CsE0pt7sMCrMZ3YNqoCj6idQOnQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/@lerna/create/node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/@lerna/create/node_modules/get-stream": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.0.tgz", - "integrity": "sha512-A1B3Bh1UmL0bidM/YX2NsCOTnGJePL9rO/M+Mw3m9f2gUpfokS0hi5Eah0WSUEWZdZhIZtMjkIYS7mDfOqNHbg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@lerna/create/node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/@lerna/create/node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "dev": true, - "license": "ISC" - }, - "node_modules/@lerna/create/node_modules/inquirer": { - "version": "8.2.7", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.7.tgz", - "integrity": "sha512-UjOaSel/iddGZJ5xP/Eixh6dY1XghiBw4XK13rCCIJcJfyhhoul/7KhLLUGtebEj6GDYM6Vnx/mVsjx2L/mFIA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@inquirer/external-editor": "^1.0.0", - "ansi-escapes": "^4.2.1", - "chalk": "^4.1.1", - "cli-cursor": "^3.1.0", - "cli-width": "^3.0.0", - "figures": "^3.0.0", - "lodash": "^4.17.21", - "mute-stream": "0.0.8", - "ora": "^5.4.1", - "run-async": "^2.4.0", - "rxjs": "^7.5.5", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0", - "through": "^2.3.6", - "wrap-ansi": "^6.0.1" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/@lerna/create/node_modules/inquirer/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@lerna/create/node_modules/is-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", - "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/@lerna/create/node_modules/is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@lerna/create/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/@lerna/create/node_modules/log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@lerna/create/node_modules/make-dir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", - "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^7.5.3" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@lerna/create/node_modules/minimatch": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.5.tgz", - "integrity": "sha512-tUpxzX0VAzJHjLu0xUfFv1gwVp9ba3IOuRAVH2EGuRW8a5emA2FlACLqiT/lDVtS1W+TGNwqz3sWaNyLgDJWuw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/@lerna/create/node_modules/mute-stream": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", - "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", - "dev": true, - "license": "ISC" - }, - "node_modules/@lerna/create/node_modules/ora": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", - "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "bl": "^4.1.0", - "chalk": "^4.1.0", - "cli-cursor": "^3.1.0", - "cli-spinners": "^2.5.0", - "is-interactive": "^1.0.0", - "is-unicode-supported": "^0.1.0", - "log-symbols": "^4.1.0", - "strip-ansi": "^6.0.0", - "wcwidth": "^1.0.1" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@lerna/create/node_modules/p-queue": { - "version": "6.6.2", - "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz", - "integrity": "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "eventemitter3": "^4.0.4", - "p-timeout": "^3.2.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@lerna/create/node_modules/p-timeout": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", - "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-finally": "^1.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@lerna/create/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/@lerna/create/node_modules/pify": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-5.0.0.tgz", - "integrity": "sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@lerna/create/node_modules/run-async": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", - "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/@lerna/create/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/@lerna/create/node_modules/tinyglobby": { - "version": "0.2.12", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.12.tgz", - "integrity": "sha512-qkf4trmKSIiMTs/E63cxH+ojC2unam7rJ0WrauAzpT3ECNTxGRMlaXxVbfxMUC/w0LaYk6jQ4y/nGR9uBO3tww==", - "dev": true, - "license": "MIT", - "dependencies": { - "fdir": "^6.4.3", - "picomatch": "^4.0.2" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/@lerna/create/node_modules/universal-user-agent": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.1.tgz", - "integrity": "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/@lerna/create/node_modules/uuid": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", - "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", - "dev": true, - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" - } - }, "node_modules/@loaders.gl/3d-tiles": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/@loaders.gl/3d-tiles/-/3d-tiles-4.3.4.tgz", @@ -7179,6 +6837,13 @@ "@loaders.gl/core": "^4.3.0" } }, + "node_modules/@ltd/j-toml": { + "version": "1.38.0", + "resolved": "https://registry.npmjs.org/@ltd/j-toml/-/j-toml-1.38.0.tgz", + "integrity": "sha512-lYtBcmvHustHQtg4X7TXUu1Xa/tbLC3p2wLvgQI+fWVySguVZJF60Snxijw5EiohumxZbR10kWYFFebh1zotiw==", + "dev": true, + "license": "LGPL-3.0" + }, "node_modules/@luma.gl/constants": { "version": "9.2.6", "resolved": "https://registry.npmjs.org/@luma.gl/constants/-/constants-9.2.6.tgz", @@ -7300,19 +6965,6 @@ "geojson-normalize": "geojson-normalize" } }, - "node_modules/@mapbox/geojson-rewind": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/@mapbox/geojson-rewind/-/geojson-rewind-0.5.2.tgz", - "integrity": "sha512-tJaT+RbYGJYStt7wI3cq4Nl4SXxG8W7JDG5DMJu97V25RnbNg3QtQtf+KD+VLjNpWKYsRvXDNmNrBgEETr1ifA==", - "license": "ISC", - "dependencies": { - "get-stream": "^6.0.1", - "minimist": "^1.2.6" - }, - "bin": { - "geojson-rewind": "geojson-rewind" - } - }, "node_modules/@mapbox/jsonlint-lines-primitives": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/@mapbox/jsonlint-lines-primitives/-/jsonlint-lines-primitives-2.0.2.tgz", @@ -7340,9 +6992,9 @@ "license": "ISC" }, "node_modules/@mapbox/tiny-sdf": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@mapbox/tiny-sdf/-/tiny-sdf-2.0.6.tgz", - "integrity": "sha512-qMqa27TLw+ZQz5Jk+RcwZGH7BQf5G/TrutJhspsca/3SHwmgKQ1iq+d3Jxz5oysPVYTGP6aXxCo5Lk9Er6YBAA==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@mapbox/tiny-sdf/-/tiny-sdf-2.1.0.tgz", + "integrity": "sha512-uFJhNh36BR4OCuWIEiWaEix9CA2WzT6CAIcqVjWYpnx8+QDtS+oC4QehRrx5cX4mgWs37MmKnwUejeHxVymzNg==", "license": "BSD-2-Clause" }, "node_modules/@mapbox/unitbezier": { @@ -7369,6 +7021,114 @@ "node": ">=6.0.0" } }, + "node_modules/@maplibre/geojson-vt": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@maplibre/geojson-vt/-/geojson-vt-6.0.4.tgz", + "integrity": "sha512-HYv3POhMRCdhP3UPPATM/hfcy6/WuVIf5FKboH8u/ZuFMTnAIcSVlq5nfOqroLokd925w2QtE7YwquFOIacwVQ==", + "license": "ISC", + "optional": true, + "peer": true, + "dependencies": { + "kdbush": "^4.0.2" + } + }, + "node_modules/@maplibre/maplibre-gl-style-spec": { + "version": "24.8.1", + "resolved": "https://registry.npmjs.org/@maplibre/maplibre-gl-style-spec/-/maplibre-gl-style-spec-24.8.1.tgz", + "integrity": "sha512-zxa92qF96ZNojLxeAjnaRpjVCy+swoUNJvDhtpC90k7u5F0TMr4GmvNqMKvYrMoPB8d7gRSXbMG1hBbmgESIsw==", + "license": "ISC", + "dependencies": { + "@mapbox/jsonlint-lines-primitives": "~2.0.2", + "@mapbox/unitbezier": "^0.0.1", + "json-stringify-pretty-compact": "^4.0.0", + "minimist": "^1.2.8", + "quickselect": "^3.0.0", + "rw": "^1.3.3", + "tinyqueue": "^3.0.0" + }, + "bin": { + "gl-style-format": "dist/gl-style-format.mjs", + "gl-style-migrate": "dist/gl-style-migrate.mjs", + "gl-style-validate": "dist/gl-style-validate.mjs" + } + }, + "node_modules/@maplibre/maplibre-gl-style-spec/node_modules/json-stringify-pretty-compact": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/json-stringify-pretty-compact/-/json-stringify-pretty-compact-4.0.0.tgz", + "integrity": "sha512-3CNZ2DnrpByG9Nqj6Xo8vqbjT4F6N+tb4Gb28ESAZjYZ5yqvmc56J+/kuIwkaAMOyblTQhUW7PxMkUb8Q36N3Q==", + "license": "MIT" + }, + "node_modules/@maplibre/maplibre-gl-style-spec/node_modules/rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@maplibre/mlt": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/@maplibre/mlt/-/mlt-1.1.8.tgz", + "integrity": "sha512-8vtfYGidr1rNkv5IwIoU2lfe3Oy+Wa8HluzQYcQi9cveU9K3pweAal/poQj4GJ0K/EW4bTQp2wVAs09g2yDRZg==", + "license": "(MIT OR Apache-2.0)", + "dependencies": { + "@mapbox/point-geometry": "^1.1.0" + } + }, + "node_modules/@maplibre/mlt/node_modules/@mapbox/point-geometry": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@mapbox/point-geometry/-/point-geometry-1.1.0.tgz", + "integrity": "sha512-YGcBz1cg4ATXDCM/71L9xveh4dynfGmcLDqufR+nQQy3fKwsAZsWd/x4621/6uJaeB9mwOHE6hPeDgXz9uViUQ==", + "license": "ISC" + }, + "node_modules/@maplibre/vt-pbf": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@maplibre/vt-pbf/-/vt-pbf-4.3.0.tgz", + "integrity": "sha512-jIvp8F5hQCcreqOOpEt42TJMUlsrEcpf/kI1T2v85YrQRV6PPXUcEXUg5karKtH6oh47XJZ4kHu56pUkOuqA7w==", + "license": "MIT", + "dependencies": { + "@mapbox/point-geometry": "^1.1.0", + "@mapbox/vector-tile": "^2.0.4", + "@maplibre/geojson-vt": "^5.0.4", + "@types/geojson": "^7946.0.16", + "@types/supercluster": "^7.1.3", + "pbf": "^4.0.1", + "supercluster": "^8.0.1" + } + }, + "node_modules/@maplibre/vt-pbf/node_modules/@mapbox/point-geometry": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@mapbox/point-geometry/-/point-geometry-1.1.0.tgz", + "integrity": "sha512-YGcBz1cg4ATXDCM/71L9xveh4dynfGmcLDqufR+nQQy3fKwsAZsWd/x4621/6uJaeB9mwOHE6hPeDgXz9uViUQ==", + "license": "ISC" + }, + "node_modules/@maplibre/vt-pbf/node_modules/@mapbox/vector-tile": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@mapbox/vector-tile/-/vector-tile-2.0.4.tgz", + "integrity": "sha512-AkOLcbgGTdXScosBWwmmD7cDlvOjkg/DetGva26pIRiZPdeJYjYKarIlb4uxVzi6bwHO6EWH82eZ5Nuv4T5DUg==", + "license": "BSD-3-Clause", + "dependencies": { + "@mapbox/point-geometry": "~1.1.0", + "@types/geojson": "^7946.0.16", + "pbf": "^4.0.1" + } + }, + "node_modules/@maplibre/vt-pbf/node_modules/@maplibre/geojson-vt": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@maplibre/geojson-vt/-/geojson-vt-5.0.4.tgz", + "integrity": "sha512-KGg9sma45S+stfH9vPCJk1J0lSDLWZgCT9Y8u8qWZJyjFlP8MNP1WGTxIMYJZjDvVT3PDn05kN1C95Sut1HpgQ==", + "license": "ISC" + }, + "node_modules/@maplibre/vt-pbf/node_modules/pbf": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pbf/-/pbf-4.0.1.tgz", + "integrity": "sha512-SuLdBvS42z33m8ejRbInMapQe8n0D3vN/Xd5fmWM3tufNgRQFBpaW2YVJxQZV4iPNqb0vEFvssMEo5w9c6BTIA==", + "license": "BSD-3-Clause", + "dependencies": { + "resolve-protobuf-schema": "^2.1.0" + }, + "bin": { + "pbf": "bin/pbf" + } + }, "node_modules/@math.gl/core": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/@math.gl/core/-/core-4.1.0.tgz", @@ -7519,6 +7279,18 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/@nodable/entities": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@nodable/entities/-/entities-2.1.0.tgz", + "integrity": "sha512-nyT7T3nbMyBI/lvr6L5TyWbFJAI9FTgVRakNoBqCD+PmID8DzFrrNdLLtHMwMszOtqZa8PAOV24ZqDnQrhQINA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/nodable" + } + ], + "license": "MIT" + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -7555,26 +7327,26 @@ } }, "node_modules/@npmcli/agent": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-2.2.2.tgz", - "integrity": "sha512-OrcNPXdpSl9UX7qPVRWbmWMCSXrcDa2M9DvrbOTj7ao1S4PlqVFYv9/yLKMkrJKZ/V5A/kDBC690or307i26Og==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-4.0.0.tgz", + "integrity": "sha512-kAQTcEN9E8ERLVg5AsGwLNoFb+oEG6engbqAU2P43gD4JEIkNGMHdVQ096FsOAAYpZPB0RSt0zgInKIAS1l5QA==", "dev": true, "license": "ISC", "dependencies": { "agent-base": "^7.1.0", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.1", - "lru-cache": "^10.0.1", + "lru-cache": "^11.2.1", "socks-proxy-agent": "^8.0.3" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/@npmcli/agent/node_modules/agent-base": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", - "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", "dev": true, "license": "MIT", "engines": { @@ -7610,60 +7382,61 @@ } }, "node_modules/@npmcli/agent/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "version": "11.2.7", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", + "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==", "dev": true, - "license": "ISC" + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } }, "node_modules/@npmcli/arborist": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/@npmcli/arborist/-/arborist-7.5.4.tgz", - "integrity": "sha512-nWtIc6QwwoUORCRNzKx4ypHqCk3drI+5aeYdMTQQiRCcn4lOOgfQh7WyZobGYTxXPSq1VwV53lkpN/BRlRk08g==", + "version": "9.1.6", + "resolved": "https://registry.npmjs.org/@npmcli/arborist/-/arborist-9.1.6.tgz", + "integrity": "sha512-c5Pr3EG8UP5ollkJy2x+UdEQC5sEHe3H9whYn6hb2HJimAKS4zmoJkx5acCiR/g4P38RnCSMlsYQyyHnKYeLvQ==", "dev": true, "license": "ISC", "dependencies": { "@isaacs/string-locale-compare": "^1.1.0", - "@npmcli/fs": "^3.1.1", - "@npmcli/installed-package-contents": "^2.1.0", - "@npmcli/map-workspaces": "^3.0.2", - "@npmcli/metavuln-calculator": "^7.1.1", - "@npmcli/name-from-folder": "^2.0.0", - "@npmcli/node-gyp": "^3.0.0", - "@npmcli/package-json": "^5.1.0", - "@npmcli/query": "^3.1.0", - "@npmcli/redact": "^2.0.0", - "@npmcli/run-script": "^8.1.0", - "bin-links": "^4.0.4", - "cacache": "^18.0.3", + "@npmcli/fs": "^4.0.0", + "@npmcli/installed-package-contents": "^3.0.0", + "@npmcli/map-workspaces": "^5.0.0", + "@npmcli/metavuln-calculator": "^9.0.2", + "@npmcli/name-from-folder": "^3.0.0", + "@npmcli/node-gyp": "^4.0.0", + "@npmcli/package-json": "^7.0.0", + "@npmcli/query": "^4.0.0", + "@npmcli/redact": "^3.0.0", + "@npmcli/run-script": "^10.0.0", + "bin-links": "^5.0.0", + "cacache": "^20.0.1", "common-ancestor-path": "^1.0.1", - "hosted-git-info": "^7.0.2", - "json-parse-even-better-errors": "^3.0.2", + "hosted-git-info": "^9.0.0", "json-stringify-nice": "^1.1.4", - "lru-cache": "^10.2.2", - "minimatch": "^9.0.4", - "nopt": "^7.2.1", - "npm-install-checks": "^6.2.0", - "npm-package-arg": "^11.0.2", - "npm-pick-manifest": "^9.0.1", - "npm-registry-fetch": "^17.0.1", - "pacote": "^18.0.6", - "parse-conflict-json": "^3.0.0", - "proc-log": "^4.2.0", - "proggy": "^2.0.0", + "lru-cache": "^11.2.1", + "minimatch": "^10.0.3", + "nopt": "^8.0.0", + "npm-install-checks": "^7.1.0", + "npm-package-arg": "^13.0.0", + "npm-pick-manifest": "^11.0.1", + "npm-registry-fetch": "^19.0.0", + "pacote": "^21.0.2", + "parse-conflict-json": "^4.0.0", + "proc-log": "^5.0.0", + "proggy": "^3.0.0", "promise-all-reject-late": "^1.0.0", "promise-call-limit": "^3.0.1", - "read-package-json-fast": "^3.0.2", "semver": "^7.3.7", - "ssri": "^10.0.6", + "ssri": "^12.0.0", "treeverse": "^3.0.0", - "walk-up-path": "^3.0.1" + "walk-up-path": "^4.0.0" }, "bin": { "arborist": "bin/index.js" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/@npmcli/arborist/node_modules/balanced-match": { @@ -7677,9 +7450,9 @@ } }, "node_modules/@npmcli/arborist/node_modules/brace-expansion": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz", - "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", "dev": true, "license": "MIT", "dependencies": { @@ -7689,77 +7462,177 @@ "node": "18 || 20 || >=22" } }, - "node_modules/@npmcli/arborist/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/@npmcli/arborist/node_modules/minimatch": { - "version": "9.0.7", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.7.tgz", - "integrity": "sha512-MOwgjc8tfrpn5QQEvjijjmDVtMw2oL88ugTevzxQnzRLm6l3fVEF2gzU0kYeYYKD8C66+IdGX6peJ4MyUlUnPg==", + "node_modules/@npmcli/arborist/node_modules/hosted-git-info": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-9.0.2.tgz", + "integrity": "sha512-M422h7o/BR3rmCQ8UHi7cyyMqKltdP9Uo+J2fXK+RSAY+wTcKOIRyhTuKv4qn+DJf3g+PL890AzId5KZpX+CBg==", "dev": true, "license": "ISC", + "dependencies": { + "lru-cache": "^11.1.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/arborist/node_modules/lru-cache": { + "version": "11.2.7", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", + "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@npmcli/arborist/node_modules/minimatch": { + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", + "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { "brace-expansion": "^5.0.2" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/@npmcli/arborist/node_modules/npm-bundled": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-5.0.0.tgz", + "integrity": "sha512-JLSpbzh6UUXIEoqPsYBvVNVmyrjVZ1fzEFbqxKkTJQkWBO3xFzFT+KDnSKQWwOQNbuWRwt5LSD6HOTLGIWzfrw==", + "dev": true, + "license": "ISC", + "dependencies": { + "npm-normalize-package-bin": "^5.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/arborist/node_modules/npm-normalize-package-bin": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-5.0.0.tgz", + "integrity": "sha512-CJi3OS4JLsNMmr2u07OJlhcrPxCeOeP/4xq67aWNai6TNWWbTrlNDgl8NcFKVlcBKp18GPj+EzbNIgrBfZhsag==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/arborist/node_modules/pacote": { + "version": "21.5.0", + "resolved": "https://registry.npmjs.org/pacote/-/pacote-21.5.0.tgz", + "integrity": "sha512-VtZ0SB8mb5Tzw3dXDfVAIjhyVKUHZkS/ZH9/5mpKenwC9sFOXNI0JI7kEF7IMkwOnsWMFrvAZHzx1T5fmrp9FQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@gar/promise-retry": "^1.0.0", + "@npmcli/git": "^7.0.0", + "@npmcli/installed-package-contents": "^4.0.0", + "@npmcli/package-json": "^7.0.0", + "@npmcli/promise-spawn": "^9.0.0", + "@npmcli/run-script": "^10.0.0", + "cacache": "^20.0.0", + "fs-minipass": "^3.0.0", + "minipass": "^7.0.2", + "npm-package-arg": "^13.0.0", + "npm-packlist": "^10.0.1", + "npm-pick-manifest": "^11.0.1", + "npm-registry-fetch": "^19.0.0", + "proc-log": "^6.0.0", + "sigstore": "^4.0.0", + "ssri": "^13.0.0", + "tar": "^7.4.3" + }, + "bin": { + "pacote": "bin/index.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/arborist/node_modules/pacote/node_modules/@npmcli/installed-package-contents": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-4.0.0.tgz", + "integrity": "sha512-yNyAdkBxB72gtZ4GrwXCM0ZUedo9nIbOMKfGjt6Cu6DXf0p8y1PViZAKDC8q8kv/fufx0WTjRBdSlyrvnP7hmA==", + "dev": true, + "license": "ISC", + "dependencies": { + "npm-bundled": "^5.0.0", + "npm-normalize-package-bin": "^5.0.0" + }, + "bin": { + "installed-package-contents": "bin/index.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/arborist/node_modules/pacote/node_modules/proc-log": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz", + "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/arborist/node_modules/pacote/node_modules/ssri": { + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-13.0.1.tgz", + "integrity": "sha512-QUiRf1+u9wPTL/76GTYlKttDEBWV1ga9ZXW8BG6kfdeyyM8LGPix9gROyg9V2+P0xNyF3X2Go526xKFdMZrHSQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, "node_modules/@npmcli/fs": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-3.1.1.tgz", - "integrity": "sha512-q9CRWjpHCMIh5sVyefoD1cA7PkvILqCZsnSOEUUivORLjxCO/Irmue2DprETiNgEqktDBZaM1Bi+jrarx1XdCg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-4.0.0.tgz", + "integrity": "sha512-/xGlezI6xfGO9NwuJlnwz/K14qD1kCSAGtacBHnGzeAIuJGazcp45KP5NuyARXoKb7cwulAGWVsbeSxdG/cb0Q==", "dev": true, "license": "ISC", "dependencies": { "semver": "^7.3.5" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/@npmcli/git": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-5.0.8.tgz", - "integrity": "sha512-liASfw5cqhjNW9UFd+ruwwdEf/lbOAQjLL2XY2dFW/bkJheXDYZgOyul/4gVvEV4BWkTXjYGmDqMw9uegdbJNQ==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-7.0.2.tgz", + "integrity": "sha512-oeolHDjExNAJAnlYP2qzNjMX/Xi9bmu78C9dIGr4xjobrSKbuMYCph8lTzn4vnW3NjIqVmw/f8BCfouqyJXlRg==", "dev": true, "license": "ISC", "dependencies": { - "@npmcli/promise-spawn": "^7.0.0", - "ini": "^4.1.3", - "lru-cache": "^10.0.1", - "npm-pick-manifest": "^9.0.0", - "proc-log": "^4.0.0", - "promise-inflight": "^1.0.1", - "promise-retry": "^2.0.1", + "@gar/promise-retry": "^1.0.0", + "@npmcli/promise-spawn": "^9.0.0", + "ini": "^6.0.0", + "lru-cache": "^11.2.1", + "npm-pick-manifest": "^11.0.1", + "proc-log": "^6.0.0", "semver": "^7.3.5", - "which": "^4.0.0" + "which": "^6.0.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "node_modules/@npmcli/git/node_modules/ini": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.3.tgz", - "integrity": "sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg==", - "dev": true, - "license": "ISC", - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/@npmcli/git/node_modules/isexe": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.2.tgz", - "integrity": "sha512-mIcis6w+JiQf3P7t7mg/35GKB4T1FQsBOtMIvuKw4YErj5RjtbhcTd5/I30fmkmGMwvI0WlzSNN+27K0QCMkAw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-4.0.0.tgz", + "integrity": "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==", "dev": true, "license": "BlueOak-1.0.0", "engines": { @@ -7767,59 +7640,82 @@ } }, "node_modules/@npmcli/git/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "version": "11.2.7", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", + "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==", "dev": true, - "license": "ISC" + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@npmcli/git/node_modules/proc-log": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz", + "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } }, "node_modules/@npmcli/git/node_modules/which": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", - "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/which/-/which-6.0.1.tgz", + "integrity": "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg==", "dev": true, "license": "ISC", "dependencies": { - "isexe": "^3.1.1" + "isexe": "^4.0.0" }, "bin": { "node-which": "bin/which.js" }, "engines": { - "node": "^16.13.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/@npmcli/installed-package-contents": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-2.1.0.tgz", - "integrity": "sha512-c8UuGLeZpm69BryRykLuKRyKFZYJsZSCT4aVY5ds4omyZqJ172ApzgfKJ5eV/r3HgLdUYgFVe54KSFVjKoe27w==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-3.0.0.tgz", + "integrity": "sha512-fkxoPuFGvxyrH+OQzyTkX2LUEamrF4jZSmxjAtPPHHGO0dqsQ8tTKjnIS8SAnPHdk2I03BDtSMR5K/4loKg79Q==", "dev": true, "license": "ISC", "dependencies": { - "npm-bundled": "^3.0.0", - "npm-normalize-package-bin": "^3.0.0" + "npm-bundled": "^4.0.0", + "npm-normalize-package-bin": "^4.0.0" }, "bin": { "installed-package-contents": "bin/index.js" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/@npmcli/map-workspaces": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@npmcli/map-workspaces/-/map-workspaces-3.0.6.tgz", - "integrity": "sha512-tkYs0OYnzQm6iIRdfy+LcLBjcKuQCeE5YLb8KnrIlutJfheNaPvPpgoFEyEFgbjzl5PLZ3IA/BWAwRU0eHuQDA==", + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@npmcli/map-workspaces/-/map-workspaces-5.0.3.tgz", + "integrity": "sha512-o2grssXo1e774E5OtEwwrgoszYRh0lqkJH+Pb9r78UcqdGJRDRfhpM8DvZPjzNLLNYeD/rNbjOKM3Ss5UABROw==", "dev": true, "license": "ISC", "dependencies": { - "@npmcli/name-from-folder": "^2.0.0", - "glob": "^10.2.2", - "minimatch": "^9.0.0", - "read-package-json-fast": "^3.0.0" + "@npmcli/name-from-folder": "^4.0.0", + "@npmcli/package-json": "^7.0.0", + "glob": "^13.0.0", + "minimatch": "^10.0.3" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/map-workspaces/node_modules/@npmcli/name-from-folder": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/name-from-folder/-/name-from-folder-4.0.0.tgz", + "integrity": "sha512-qfrhVlOSqmKM8i6rkNdZzABj8MKEITGFAY+4teqBziksCQAOLutiAxM1wY2BKEd8KjUSpWmWCYxvXr0y4VTlPg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/@npmcli/map-workspaces/node_modules/balanced-match": { @@ -7833,9 +7729,9 @@ } }, "node_modules/@npmcli/map-workspaces/node_modules/brace-expansion": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz", - "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", "dev": true, "license": "MIT", "dependencies": { @@ -7846,97 +7742,140 @@ } }, "node_modules/@npmcli/map-workspaces/node_modules/glob": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", - "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "version": "13.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "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" + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" }, - "bin": { - "glob": "dist/esm/bin.mjs" + "engines": { + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@npmcli/map-workspaces/node_modules/minimatch": { - "version": "9.0.7", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.7.tgz", - "integrity": "sha512-MOwgjc8tfrpn5QQEvjijjmDVtMw2oL88ugTevzxQnzRLm6l3fVEF2gzU0kYeYYKD8C66+IdGX6peJ4MyUlUnPg==", + "node_modules/@npmcli/map-workspaces/node_modules/lru-cache": { + "version": "11.2.7", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", + "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@npmcli/map-workspaces/node_modules/minimatch": { + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", + "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { "brace-expansion": "^5.0.2" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@npmcli/map-workspaces/node_modules/path-scurry": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/@npmcli/metavuln-calculator": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/@npmcli/metavuln-calculator/-/metavuln-calculator-7.1.1.tgz", - "integrity": "sha512-Nkxf96V0lAx3HCpVda7Vw4P23RILgdi/5K1fmj2tZkWIYLpXAN8k2UVVOsW16TsS5F8Ws2I7Cm+PU1/rsVF47g==", + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/@npmcli/metavuln-calculator/-/metavuln-calculator-9.0.3.tgz", + "integrity": "sha512-94GLSYhLXF2t2LAC7pDwLaM4uCARzxShyAQKsirmlNcpidH89VA4/+K1LbJmRMgz5gy65E/QBBWQdUvGLe2Frg==", "dev": true, "license": "ISC", "dependencies": { - "cacache": "^18.0.0", - "json-parse-even-better-errors": "^3.0.0", - "pacote": "^18.0.0", - "proc-log": "^4.1.0", + "cacache": "^20.0.0", + "json-parse-even-better-errors": "^5.0.0", + "pacote": "^21.0.0", + "proc-log": "^6.0.0", "semver": "^7.3.5" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/metavuln-calculator/node_modules/proc-log": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz", + "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/@npmcli/name-from-folder": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@npmcli/name-from-folder/-/name-from-folder-2.0.0.tgz", - "integrity": "sha512-pwK+BfEBZJbKdNYpHHRTNBwBoqrN/iIMO0AiGvYsp3Hoaq0WbgGSWQR6SCldZovoDpY3yje5lkFUe6gsDgJ2vg==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/name-from-folder/-/name-from-folder-3.0.0.tgz", + "integrity": "sha512-61cDL8LUc9y80fXn+lir+iVt8IS0xHqEKwPu/5jCjxQTVoSCmkXvw4vbMrzAMtmghz3/AkiBjhHkDKUH+kf7kA==", "dev": true, "license": "ISC", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/@npmcli/node-gyp": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@npmcli/node-gyp/-/node-gyp-3.0.0.tgz", - "integrity": "sha512-gp8pRXC2oOxu0DUE1/M3bYtb1b3/DbJ5aM113+XJBgfXdussRAsX0YOrOhdd8WvnAR6auDBvJomGAkLKA5ydxA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/node-gyp/-/node-gyp-4.0.0.tgz", + "integrity": "sha512-+t5DZ6mO/QFh78PByMq1fGSAub/agLJZDRfJRMeOSNCt8s9YVlTjmGpIPwPhvXTGUIJk+WszlT0rQa1W33yzNA==", "dev": true, "license": "ISC", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/@npmcli/package-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@npmcli/package-json/-/package-json-5.2.0.tgz", - "integrity": "sha512-qe/kiqqkW0AGtvBjL8TJKZk/eBBSpnJkUWvHdQ9jM2lKHXRYYJuyNpJPlJw3c8QjC2ow6NZYiLExhUaeJelbxQ==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@npmcli/package-json/-/package-json-7.0.2.tgz", + "integrity": "sha512-0ylN3U5htO1SJTmy2YI78PZZjLkKUGg7EKgukb2CRi0kzyoDr0cfjHAzi7kozVhj2V3SxN1oyKqZ2NSo40z00g==", "dev": true, "license": "ISC", "dependencies": { - "@npmcli/git": "^5.0.0", - "glob": "^10.2.2", - "hosted-git-info": "^7.0.0", - "json-parse-even-better-errors": "^3.0.0", - "normalize-package-data": "^6.0.0", - "proc-log": "^4.0.0", - "semver": "^7.5.3" + "@npmcli/git": "^7.0.0", + "glob": "^11.0.3", + "hosted-git-info": "^9.0.0", + "json-parse-even-better-errors": "^5.0.0", + "proc-log": "^6.0.0", + "semver": "^7.5.3", + "validate-npm-package-license": "^3.0.4" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/package-json/node_modules/@isaacs/cliui": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-9.0.0.tgz", + "integrity": "sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" } }, "node_modules/@npmcli/package-json/node_modules/balanced-match": { @@ -7950,9 +7889,9 @@ } }, "node_modules/@npmcli/package-json/node_modules/brace-expansion": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz", - "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", "dev": true, "license": "MIT", "dependencies": { @@ -7963,60 +7902,129 @@ } }, "node_modules/@npmcli/package-json/node_modules/glob": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", - "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.1.0.tgz", + "integrity": "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==", "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", + "foreground-child": "^3.3.1", + "jackspeak": "^4.1.1", + "minimatch": "^10.1.1", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" + "path-scurry": "^2.0.0" }, "bin": { "glob": "dist/esm/bin.mjs" }, + "engines": { + "node": "20 || >=22" + }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@npmcli/package-json/node_modules/minimatch": { - "version": "9.0.7", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.7.tgz", - "integrity": "sha512-MOwgjc8tfrpn5QQEvjijjmDVtMw2oL88ugTevzxQnzRLm6l3fVEF2gzU0kYeYYKD8C66+IdGX6peJ4MyUlUnPg==", + "node_modules/@npmcli/package-json/node_modules/hosted-git-info": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-9.0.2.tgz", + "integrity": "sha512-M422h7o/BR3rmCQ8UHi7cyyMqKltdP9Uo+J2fXK+RSAY+wTcKOIRyhTuKv4qn+DJf3g+PL890AzId5KZpX+CBg==", "dev": true, "license": "ISC", + "dependencies": { + "lru-cache": "^11.1.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/package-json/node_modules/jackspeak": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.2.3.tgz", + "integrity": "sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^9.0.0" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@npmcli/package-json/node_modules/lru-cache": { + "version": "11.2.7", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", + "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@npmcli/package-json/node_modules/minimatch": { + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", + "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { "brace-expansion": "^5.0.2" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/@npmcli/package-json/node_modules/path-scurry": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@npmcli/package-json/node_modules/proc-log": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz", + "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, "node_modules/@npmcli/promise-spawn": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-7.0.2.tgz", - "integrity": "sha512-xhfYPXoV5Dy4UkY0D+v2KkwvnDfiA/8Mt3sWCGI/hM03NsYIH8ZaG6QzS9x7pje5vHZBZJ2v6VRFVTWACnqcmQ==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-9.0.1.tgz", + "integrity": "sha512-OLUaoqBuyxeTqUvjA3FZFiXUfYC1alp3Sa99gW3EUDz3tZ3CbXDdcZ7qWKBzicrJleIgucoWamWH1saAmH/l2Q==", "dev": true, "license": "ISC", "dependencies": { - "which": "^4.0.0" + "which": "^6.0.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/@npmcli/promise-spawn/node_modules/isexe": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.2.tgz", - "integrity": "sha512-mIcis6w+JiQf3P7t7mg/35GKB4T1FQsBOtMIvuKw4YErj5RjtbhcTd5/I30fmkmGMwvI0WlzSNN+27K0QCMkAw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-4.0.0.tgz", + "integrity": "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==", "dev": true, "license": "BlueOak-1.0.0", "engines": { @@ -8024,116 +8032,162 @@ } }, "node_modules/@npmcli/promise-spawn/node_modules/which": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", - "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/which/-/which-6.0.1.tgz", + "integrity": "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg==", "dev": true, "license": "ISC", "dependencies": { - "isexe": "^3.1.1" + "isexe": "^4.0.0" }, "bin": { "node-which": "bin/which.js" }, "engines": { - "node": "^16.13.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/@npmcli/query": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@npmcli/query/-/query-3.1.0.tgz", - "integrity": "sha512-C/iR0tk7KSKGldibYIB9x8GtO/0Bd0I2mhOaDb8ucQL/bQVTmGoeREaFj64Z5+iCBRf3dQfed0CjJL7I8iTkiQ==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@npmcli/query/-/query-4.0.1.tgz", + "integrity": "sha512-4OIPFb4weUUwkDXJf4Hh1inAn8neBGq3xsH4ZsAaN6FK3ldrFkH7jSpCc7N9xesi0Sp+EBXJ9eGMDrEww2Ztqw==", "dev": true, "license": "ISC", "dependencies": { - "postcss-selector-parser": "^6.0.10" + "postcss-selector-parser": "^7.0.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@npmcli/query/node_modules/postcss-selector-parser": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", + "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" } }, "node_modules/@npmcli/redact": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@npmcli/redact/-/redact-2.0.1.tgz", - "integrity": "sha512-YgsR5jCQZhVmTJvjduTOIHph0L73pK8xwMVaDY0PatySqVM9AZj93jpoXYSJqfHFxFkN9dmqTw6OiqExsS3LPw==", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@npmcli/redact/-/redact-3.2.2.tgz", + "integrity": "sha512-7VmYAmk4csGv08QzrDKScdzn11jHPFGyqJW39FyPgPuAp3zIaUmuCo1yxw9aGs+NEJuTGQ9Gwqpt93vtJubucg==", "dev": true, "license": "ISC", "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/@npmcli/run-script": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-8.1.0.tgz", - "integrity": "sha512-y7efHHwghQfk28G2z3tlZ67pLG0XdfYbcVG26r7YIXALRsrVQcTq4/tdenSmdOrEsNahIYA/eh8aEVROWGFUDg==", + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-10.0.3.tgz", + "integrity": "sha512-ER2N6itRkzWbbtVmZ9WKaWxVlKlOeBFF1/7xx+KA5J1xKa4JjUwBdb6tDpk0v1qA+d+VDwHI9qmLcXSWcmi+Rw==", "dev": true, "license": "ISC", "dependencies": { - "@npmcli/node-gyp": "^3.0.0", - "@npmcli/package-json": "^5.0.0", - "@npmcli/promise-spawn": "^7.0.0", - "node-gyp": "^10.0.0", - "proc-log": "^4.0.0", - "which": "^4.0.0" + "@npmcli/node-gyp": "^5.0.0", + "@npmcli/package-json": "^7.0.0", + "@npmcli/promise-spawn": "^9.0.0", + "node-gyp": "^12.1.0", + "proc-log": "^6.0.0", + "which": "^6.0.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/run-script/node_modules/@npmcli/node-gyp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/node-gyp/-/node-gyp-5.0.0.tgz", + "integrity": "sha512-uuG5HZFXLfyFKqg8QypsmgLQW7smiRjVc45bqD/ofZZcR/uxEjgQU8qDPv0s9TEeMUiAAU/GC5bR6++UdTirIQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/@npmcli/run-script/node_modules/isexe": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.2.tgz", - "integrity": "sha512-mIcis6w+JiQf3P7t7mg/35GKB4T1FQsBOtMIvuKw4YErj5RjtbhcTd5/I30fmkmGMwvI0WlzSNN+27K0QCMkAw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-4.0.0.tgz", + "integrity": "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==", "dev": true, "license": "BlueOak-1.0.0", "engines": { "node": ">=20" } }, + "node_modules/@npmcli/run-script/node_modules/proc-log": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz", + "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, "node_modules/@npmcli/run-script/node_modules/which": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", - "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/which/-/which-6.0.1.tgz", + "integrity": "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg==", "dev": true, "license": "ISC", "dependencies": { - "isexe": "^3.1.1" + "isexe": "^4.0.0" }, "bin": { "node-which": "bin/which.js" }, "engines": { - "node": "^16.13.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/@nx/devkit": { - "version": "20.8.4", - "resolved": "https://registry.npmjs.org/@nx/devkit/-/devkit-20.8.4.tgz", - "integrity": "sha512-3r+6QmIXXAWL6K7m8vAbW31aniAZmZAZXeMhOhWcJoOAU7ggpCQaM8JP8/kO5ov/Bmhyf0i/SSVXI6kwiR5WNQ==", + "version": "22.6.1", + "resolved": "https://registry.npmjs.org/@nx/devkit/-/devkit-22.6.1.tgz", + "integrity": "sha512-/mwG9zWY1phsWvMKzP0yZ4pE6aH0kLH31DuCYj4eLbhuUu0STL3xSdjPPzhDHf71R4K3YnuvG97e2qiGDbG5Qw==", "dev": true, "license": "MIT", "dependencies": { + "@zkochan/js-yaml": "0.0.7", "ejs": "^3.1.7", "enquirer": "~2.3.6", - "ignore": "^5.0.4", - "minimatch": "9.0.3", - "semver": "^7.5.3", - "tmp": "~0.2.1", + "minimatch": "10.2.4", + "semver": "^7.6.3", "tslib": "^2.3.0", "yargs-parser": "21.1.1" }, "peerDependencies": { - "nx": ">= 19 <= 21" + "nx": ">= 21 <= 23 || ^22.0.0-0" + } + }, + "node_modules/@nx/devkit/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" } }, "node_modules/@nx/devkit/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0" + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" } }, "node_modules/@nx/devkit/node_modules/enquirer": { @@ -8150,25 +8204,25 @@ } }, "node_modules/@nx/devkit/node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^5.0.2" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/@nx/nx-darwin-arm64": { - "version": "20.8.4", - "resolved": "https://registry.npmjs.org/@nx/nx-darwin-arm64/-/nx-darwin-arm64-20.8.4.tgz", - "integrity": "sha512-8Y7+4wj1qoZsuDRpnuiHzSIsMt3VqtJ0su8dgd/MyGccvvi4pndan2R5yTiVw/wmbMxtBmZ6PO6Z8dgSIrMVog==", + "version": "22.6.1", + "resolved": "https://registry.npmjs.org/@nx/nx-darwin-arm64/-/nx-darwin-arm64-22.6.1.tgz", + "integrity": "sha512-lixkEBGFdEsUiqEZg9LIyjfiTv12Sg1Es/yUgrdOQUAZu+5oiUPMoybyBwrvINl+fZw+PLh66jOmB4GSP2aUMQ==", "cpu": [ "arm64" ], @@ -8177,15 +8231,12 @@ "optional": true, "os": [ "darwin" - ], - "engines": { - "node": ">= 10" - } + ] }, "node_modules/@nx/nx-darwin-x64": { - "version": "20.8.4", - "resolved": "https://registry.npmjs.org/@nx/nx-darwin-x64/-/nx-darwin-x64-20.8.4.tgz", - "integrity": "sha512-2lfuxRc56QWnAysMhcD03tpCPiRzV1+foUq0MhV2sSBIybXmgV4wHLkPZNhlBCl4FNXrWiZiN1OJ2X9AGiOdug==", + "version": "22.6.1", + "resolved": "https://registry.npmjs.org/@nx/nx-darwin-x64/-/nx-darwin-x64-22.6.1.tgz", + "integrity": "sha512-HvgtOtuWnEf0dpfWb05N0ptdFg040YgzsKFhXg6+qaBJg5Hg0e0AXPKaSgh2PCqCIDlKu40YtwVgF7KXxXAGlA==", "cpu": [ "x64" ], @@ -8194,15 +8245,12 @@ "optional": true, "os": [ "darwin" - ], - "engines": { - "node": ">= 10" - } + ] }, "node_modules/@nx/nx-freebsd-x64": { - "version": "20.8.4", - "resolved": "https://registry.npmjs.org/@nx/nx-freebsd-x64/-/nx-freebsd-x64-20.8.4.tgz", - "integrity": "sha512-99vnUXZy+OUBHU+8Yhabre2qafepKg9GKkQkhmXvJGqOmuIsepK7wirUFo2PiVM8YhS6UV2rv6hKAZcQ7skYyg==", + "version": "22.6.1", + "resolved": "https://registry.npmjs.org/@nx/nx-freebsd-x64/-/nx-freebsd-x64-22.6.1.tgz", + "integrity": "sha512-g2wUltGX+7/+mdTV5d6ODa0ylrNu/krgb9YdrsbhW6oZeXYm2LeLOAnYqIlL/Kx140NLrb5Kcz7bi7JrBAw4Ow==", "cpu": [ "x64" ], @@ -8211,15 +8259,12 @@ "optional": true, "os": [ "freebsd" - ], - "engines": { - "node": ">= 10" - } + ] }, "node_modules/@nx/nx-linux-arm-gnueabihf": { - "version": "20.8.4", - "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm-gnueabihf/-/nx-linux-arm-gnueabihf-20.8.4.tgz", - "integrity": "sha512-dht73zpnpzEUEzMHFQs4mfiwZH3WcJgQNWkD5p7WkeJewHq2Yyd0eG5Jg3kB7wnFtwPUV1eNJRM5rephgylkLA==", + "version": "22.6.1", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm-gnueabihf/-/nx-linux-arm-gnueabihf-22.6.1.tgz", + "integrity": "sha512-TTqisFPAPrj35EihvzotBbajS+0bX++PQggmRVmDmGwSTrpySRJwZnKNHYDqP6s9tigDvkNJOJftK+GkBEFRRA==", "cpu": [ "arm" ], @@ -8228,15 +8273,12 @@ "optional": true, "os": [ "linux" - ], - "engines": { - "node": ">= 10" - } + ] }, "node_modules/@nx/nx-linux-arm64-gnu": { - "version": "20.8.4", - "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm64-gnu/-/nx-linux-arm64-gnu-20.8.4.tgz", - "integrity": "sha512-syXxbJZ0yPaqzVmB28QJgUtaarSiW/PQmv/5Z2Ps8rCi7kYylISPVNjP1NNiIOcGDRWbHqoBfM0bEGPfSp0rBQ==", + "version": "22.6.1", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm64-gnu/-/nx-linux-arm64-gnu-22.6.1.tgz", + "integrity": "sha512-uIkPcanSTIcyh7/6LOoX0YpGO/7GkVhMRgyM9Mg/7ItFjCtRaeuPEPrJESsaNeB5zIVVhI4cXbGrM9NDnagiiw==", "cpu": [ "arm64" ], @@ -8245,15 +8287,12 @@ "optional": true, "os": [ "linux" - ], - "engines": { - "node": ">= 10" - } + ] }, "node_modules/@nx/nx-linux-arm64-musl": { - "version": "20.8.4", - "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm64-musl/-/nx-linux-arm64-musl-20.8.4.tgz", - "integrity": "sha512-AlZZFolS/S0FahRKG7rJ0Z9CgmIkyzHgGaoy3qNEMDEjFhR3jt2ZZSLp90W7zjgrxojOo90ajNMrg2UmtcQRDA==", + "version": "22.6.1", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm64-musl/-/nx-linux-arm64-musl-22.6.1.tgz", + "integrity": "sha512-eqkG8s/7remiRZ1Lo2zIrFLSNsQ/0x9fAj++CV1nqFE+rfykPQhC48F8pqsq6tUQpI5HqRQEfQgv4CnFNpLR+w==", "cpu": [ "arm64" ], @@ -8262,15 +8301,12 @@ "optional": true, "os": [ "linux" - ], - "engines": { - "node": ">= 10" - } + ] }, "node_modules/@nx/nx-linux-x64-gnu": { - "version": "20.8.4", - "resolved": "https://registry.npmjs.org/@nx/nx-linux-x64-gnu/-/nx-linux-x64-gnu-20.8.4.tgz", - "integrity": "sha512-MSu+xVNdR95tuuO+eL/a/ZeMlhfrZ627On5xaCZXnJ+lFxNg/S4nlKZQk0Eq5hYALCd/GKgFGasRdlRdOtvGPg==", + "version": "22.6.1", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-x64-gnu/-/nx-linux-x64-gnu-22.6.1.tgz", + "integrity": "sha512-6DhSupCcDa6BYzQ48qsMK4LIdIO+y4E+4xuUBkX2YTGOZh58gctELCv7Gi6/FhiC8rzVzM7hDcygOvHCGc30zA==", "cpu": [ "x64" ], @@ -8279,15 +8315,12 @@ "optional": true, "os": [ "linux" - ], - "engines": { - "node": ">= 10" - } + ] }, "node_modules/@nx/nx-linux-x64-musl": { - "version": "20.8.4", - "resolved": "https://registry.npmjs.org/@nx/nx-linux-x64-musl/-/nx-linux-x64-musl-20.8.4.tgz", - "integrity": "sha512-KxpQpyLCgIIHWZ4iRSUN9ohCwn1ZSDASbuFCdG3mohryzCy8WrPkuPcb+68J3wuQhmA5w//Xpp/dL0hHoit9zQ==", + "version": "22.6.1", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-x64-musl/-/nx-linux-x64-musl-22.6.1.tgz", + "integrity": "sha512-QqtfaBhdfLRKGucpP8RSv7KJ51XRWpfUcXPhkb/1dKP/b9/Z0kpaCgczGHdrAtX9m6haWw+sQXYGxnStZIg/TQ==", "cpu": [ "x64" ], @@ -8296,15 +8329,12 @@ "optional": true, "os": [ "linux" - ], - "engines": { - "node": ">= 10" - } + ] }, "node_modules/@nx/nx-win32-arm64-msvc": { - "version": "20.8.4", - "resolved": "https://registry.npmjs.org/@nx/nx-win32-arm64-msvc/-/nx-win32-arm64-msvc-20.8.4.tgz", - "integrity": "sha512-ffLBrxM9ibk+eWSY995kiFFRTSRb9HkD5T1s/uZyxV6jfxYPaZDBAWAETDneyBXps7WtaOMu+kVZlXQ3X+TfIA==", + "version": "22.6.1", + "resolved": "https://registry.npmjs.org/@nx/nx-win32-arm64-msvc/-/nx-win32-arm64-msvc-22.6.1.tgz", + "integrity": "sha512-8pTWXphY5IIgY3edZ5SfzP8yPjBqoAxRV5snAYDctF4e0OC1nDOUims70jLesMle8DTSWiHPSfbLVfp2HkU9WQ==", "cpu": [ "arm64" ], @@ -8313,15 +8343,12 @@ "optional": true, "os": [ "win32" - ], - "engines": { - "node": ">= 10" - } + ] }, "node_modules/@nx/nx-win32-x64-msvc": { - "version": "20.8.4", - "resolved": "https://registry.npmjs.org/@nx/nx-win32-x64-msvc/-/nx-win32-x64-msvc-20.8.4.tgz", - "integrity": "sha512-JxuuZc4h8EBqoYAiRHwskimpTJx70yn4lhIRFBoW5ICkxXW1Rw0yip/1UVsWRHXg/x9BxmH7VVazdfaQWmGu6A==", + "version": "22.6.1", + "resolved": "https://registry.npmjs.org/@nx/nx-win32-x64-msvc/-/nx-win32-x64-msvc-22.6.1.tgz", + "integrity": "sha512-XMYrtsR5O39uNR4fVpFs65rVB09FyLXvUM735r2rO7IUWWHxHWTAgVcc+gqQaAchBPqR9f1q+3u2i1Inub3Cdw==", "cpu": [ "x64" ], @@ -8330,70 +8357,7 @@ "optional": true, "os": [ "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@octokit/auth-token": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-5.1.2.tgz", - "integrity": "sha512-JcQDsBdg49Yky2w2ld20IHAlwr8d/d8N6NiOXbtuoPCqzbsiJgF633mVUw3x4mo0H5ypataQIX7SFu3yy44Mpw==", - "license": "MIT", - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/core": { - "version": "6.1.6", - "resolved": "https://registry.npmjs.org/@octokit/core/-/core-6.1.6.tgz", - "integrity": "sha512-kIU8SLQkYWGp3pVKiYzA5OSaNF5EE03P/R8zEmmrG6XwOg5oBjXyQVVIauQ0dgau4zYhpZEhJrvIYt6oM+zZZA==", - "license": "MIT", - "dependencies": { - "@octokit/auth-token": "^5.0.0", - "@octokit/graphql": "^8.2.2", - "@octokit/request": "^9.2.3", - "@octokit/request-error": "^6.1.8", - "@octokit/types": "^14.0.0", - "before-after-hook": "^3.0.2", - "universal-user-agent": "^7.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/endpoint": { - "version": "10.1.4", - "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-10.1.4.tgz", - "integrity": "sha512-OlYOlZIsfEVZm5HCSR8aSg02T2lbUWOsCQoPKfTXJwDzcHQBrVBGdGXb89dv2Kw2ToZaRtudp8O3ZIYoaOjKlA==", - "license": "MIT", - "dependencies": { - "@octokit/types": "^14.0.0", - "universal-user-agent": "^7.0.2" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/graphql": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-8.2.2.tgz", - "integrity": "sha512-Yi8hcoqsrXGdt0yObxbebHXFOiUA+2v3n53epuOg1QUgOB6c4XzvisBNVXJSl8RYA5KrDuSL2yq9Qmqe5N0ryA==", - "license": "MIT", - "dependencies": { - "@octokit/request": "^9.2.3", - "@octokit/types": "^14.0.0", - "universal-user-agent": "^7.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/openapi-types": { - "version": "25.1.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-25.1.0.tgz", - "integrity": "sha512-idsIggNXUKkk0+BExUn1dQ92sfysJrje03Q0bv0e+KPLrvyqZF8MnBpFz8UNfYDwB3Ie7Z0TByjWfzxt7vseaA==", - "license": "MIT" + ] }, "node_modules/@octokit/plugin-enterprise-rest": { "version": "6.0.1", @@ -8402,150 +8366,10 @@ "dev": true, "license": "MIT" }, - "node_modules/@octokit/plugin-paginate-rest": { - "version": "11.6.0", - "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-11.6.0.tgz", - "integrity": "sha512-n5KPteiF7pWKgBIBJSk8qzoZWcUkza2O6A0za97pMGVrGfPdltxrfmfF5GucHYvHGZD8BdaZmmHGz5cX/3gdpw==", - "license": "MIT", - "dependencies": { - "@octokit/types": "^13.10.0" - }, - "engines": { - "node": ">= 18" - }, - "peerDependencies": { - "@octokit/core": ">=6" - } - }, - "node_modules/@octokit/plugin-paginate-rest/node_modules/@octokit/openapi-types": { - "version": "24.2.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", - "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", - "license": "MIT" - }, - "node_modules/@octokit/plugin-paginate-rest/node_modules/@octokit/types": { - "version": "13.10.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", - "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", - "license": "MIT", - "dependencies": { - "@octokit/openapi-types": "^24.2.0" - } - }, - "node_modules/@octokit/plugin-request-log": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-5.3.1.tgz", - "integrity": "sha512-n/lNeCtq+9ofhC15xzmJCNKP2BWTv8Ih2TTy+jatNCCq/gQP/V7rK3fjIfuz0pDWDALO/o/4QY4hyOF6TQQFUw==", - "license": "MIT", - "engines": { - "node": ">= 18" - }, - "peerDependencies": { - "@octokit/core": ">=6" - } - }, - "node_modules/@octokit/plugin-rest-endpoint-methods": { - "version": "13.5.0", - "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-13.5.0.tgz", - "integrity": "sha512-9Pas60Iv9ejO3WlAX3maE1+38c5nqbJXV5GrncEfkndIpZrJ/WPMRd2xYDcPPEt5yzpxcjw9fWNoPhsSGzqKqw==", - "license": "MIT", - "dependencies": { - "@octokit/types": "^13.10.0" - }, - "engines": { - "node": ">= 18" - }, - "peerDependencies": { - "@octokit/core": ">=6" - } - }, - "node_modules/@octokit/plugin-rest-endpoint-methods/node_modules/@octokit/openapi-types": { - "version": "24.2.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", - "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", - "license": "MIT" - }, - "node_modules/@octokit/plugin-rest-endpoint-methods/node_modules/@octokit/types": { - "version": "13.10.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", - "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", - "license": "MIT", - "dependencies": { - "@octokit/openapi-types": "^24.2.0" - } - }, - "node_modules/@octokit/request": { - "version": "9.2.4", - "resolved": "https://registry.npmjs.org/@octokit/request/-/request-9.2.4.tgz", - "integrity": "sha512-q8ybdytBmxa6KogWlNa818r0k1wlqzNC+yNkcQDECHvQo8Vmstrg18JwqJHdJdUiHD2sjlwBgSm9kHkOKe2iyA==", - "license": "MIT", - "dependencies": { - "@octokit/endpoint": "^10.1.4", - "@octokit/request-error": "^6.1.8", - "@octokit/types": "^14.0.0", - "fast-content-type-parse": "^2.0.0", - "universal-user-agent": "^7.0.2" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/request-error": { - "version": "6.1.8", - "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-6.1.8.tgz", - "integrity": "sha512-WEi/R0Jmq+IJKydWlKDmryPcmdYSVjL3ekaiEL1L9eo1sUnqMJ+grqmC9cjk7CA7+b2/T397tO5d8YLOH3qYpQ==", - "license": "MIT", - "dependencies": { - "@octokit/types": "^14.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/request/node_modules/fast-content-type-parse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-2.0.1.tgz", - "integrity": "sha512-nGqtvLrj5w0naR6tDPfB4cUmYCqouzyQiz6C5y/LtcDllJdrcc6WaWW6iXyIIOErTa/XRybj28aasdn4LkVk6Q==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "MIT" - }, - "node_modules/@octokit/rest": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-21.1.1.tgz", - "integrity": "sha512-sTQV7va0IUVZcntzy1q3QqPm/r8rWtDCqpRAmb8eXXnKkjoQEtFe3Nt5GTVsHft+R6jJoHeSiVLcgcvhtue/rg==", - "license": "MIT", - "dependencies": { - "@octokit/core": "^6.1.4", - "@octokit/plugin-paginate-rest": "^11.4.2", - "@octokit/plugin-request-log": "^5.3.1", - "@octokit/plugin-rest-endpoint-methods": "^13.3.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/types": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-14.1.0.tgz", - "integrity": "sha512-1y6DgTy8Jomcpu33N+p5w58l6xyt55Ar2I91RPiIA0xCJBXyUAhXCcmZaDWSANiha7R9a6qJJ2CRomGPZ6f46g==", - "license": "MIT", - "dependencies": { - "@octokit/openapi-types": "^25.1.0" - } - }, "node_modules/@oxlint/binding-android-arm-eabi": { - "version": "1.53.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm-eabi/-/binding-android-arm-eabi-1.53.0.tgz", - "integrity": "sha512-JC89/jAx4d2zhDIbK8MC4L659FN1WiMXMBkNg7b33KXSkYpUgcbf+0nz7+EPRg+VwWiZVfaoFkNHJ7RXYb5Neg==", + "version": "1.61.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm-eabi/-/binding-android-arm-eabi-1.61.0.tgz", + "integrity": "sha512-6eZBPgiigK5txqoVgRqxbaxiom4lM8AP8CyKPPvpzKnQ3iFRFOIDc+0AapF+qsUSwjOzr5SGk4SxQDpQhkSJMQ==", "cpu": [ "arm" ], @@ -8560,9 +8384,9 @@ } }, "node_modules/@oxlint/binding-android-arm64": { - "version": "1.53.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm64/-/binding-android-arm64-1.53.0.tgz", - "integrity": "sha512-CY+pZfi+uyeU7AwFrEnjsNT+VfxYmKLMuk7bVxArd8f+09hQbJb8f7C7EpvTfNqrCK1J8zZlaYI4LltmEctgbQ==", + "version": "1.61.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm64/-/binding-android-arm64-1.61.0.tgz", + "integrity": "sha512-CkwLR69MUnyv5wjzebvbbtTSUwqLxM35CXE79bHqDIK+NtKmPEUpStTcLQRZMCo4MP0qRT6TXIQVpK0ZVScnMA==", "cpu": [ "arm64" ], @@ -8577,9 +8401,9 @@ } }, "node_modules/@oxlint/binding-darwin-arm64": { - "version": "1.53.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-darwin-arm64/-/binding-darwin-arm64-1.53.0.tgz", - "integrity": "sha512-0aqsC4HDQ94oI6kMz64iaOJ1f3bCVArxvaHJGOScBvFz6CcQedXi5b70Xg09CYjKNaHA56dW0QJfoZ/111kz1A==", + "version": "1.61.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-darwin-arm64/-/binding-darwin-arm64-1.61.0.tgz", + "integrity": "sha512-8JbefTkbmvqkqWjmQrHke+MdpgT2UghhD/ktM4FOQSpGeCgbMToJEKdl9zwhr/YWTl92i4QI1KiTwVExpcUN8A==", "cpu": [ "arm64" ], @@ -8594,9 +8418,9 @@ } }, "node_modules/@oxlint/binding-darwin-x64": { - "version": "1.53.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-darwin-x64/-/binding-darwin-x64-1.53.0.tgz", - "integrity": "sha512-e+KvuaWtnisyWojO/t5qKDbp2dvVpg+1dl4MGnTb21QpY4+4+9Y1XmZPaztcA2XNvy4BIaXFW+9JH9tMpSBqUg==", + "version": "1.61.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-darwin-x64/-/binding-darwin-x64-1.61.0.tgz", + "integrity": "sha512-uWpoxDT47hTnDLcdEh5jVbso8rlTTu5o0zuqa9J8E0JAKmIWn7kGFEIB03Pycn2hd2vKxybPGLhjURy/9We5FQ==", "cpu": [ "x64" ], @@ -8611,9 +8435,9 @@ } }, "node_modules/@oxlint/binding-freebsd-x64": { - "version": "1.53.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-freebsd-x64/-/binding-freebsd-x64-1.53.0.tgz", - "integrity": "sha512-hpU0ZHVeblFjmZDfgi9BxhhCpURh0KjoFy5V+Tvp9sg/fRcnMUEfaJrgz+jQfOX4jctlVWrAs1ANs91+5iV+zA==", + "version": "1.61.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-freebsd-x64/-/binding-freebsd-x64-1.61.0.tgz", + "integrity": "sha512-K/o4hEyW7flfMel0iBVznmMBt7VIMHGdjADocHKpK1DUF9erpWnJ+BSSWd2W0c8K3mPtpph+CuHzRU6CI3l9jQ==", "cpu": [ "x64" ], @@ -8628,9 +8452,9 @@ } }, "node_modules/@oxlint/binding-linux-arm-gnueabihf": { - "version": "1.53.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.53.0.tgz", - "integrity": "sha512-ccKxOpw+X4xa2pO+qbTOpxQ2x1+Ag3ViRQMnWt3gHp1LcpNgS1xd6GYc3OvehmHtrXqEV3YGczZ0I1qpBB4/2A==", + "version": "1.61.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.61.0.tgz", + "integrity": "sha512-P6040ZkcyweJ0Po9yEFqJCdvZnf3VNCGs1SIHgXDf8AAQNC6ID/heXQs9iSgo2FH7gKaKq32VWc59XZwL34C5Q==", "cpu": [ "arm" ], @@ -8645,9 +8469,9 @@ } }, "node_modules/@oxlint/binding-linux-arm-musleabihf": { - "version": "1.53.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-1.53.0.tgz", - "integrity": "sha512-UBkBvmzSmlyH2ZObQMDKW/TuyTmUtP/XClPUyU2YLwj0qLopZTZxnDz4VG5d3wz1HQuZXO0o1QqsnQUW1v4a6Q==", + "version": "1.61.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-1.61.0.tgz", + "integrity": "sha512-bwxrGCzTZkuB+THv2TQ1aTkVEfv5oz8sl+0XZZCpoYzErJD8OhPQOTA0ENPd1zJz8QsVdSzSrS2umKtPq4/JXg==", "cpu": [ "arm" ], @@ -8662,9 +8486,9 @@ } }, "node_modules/@oxlint/binding-linux-arm64-gnu": { - "version": "1.53.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.53.0.tgz", - "integrity": "sha512-PQJJ1izoH9p61las6rZ0BWOznAhTDMmdUPL2IEBLuXFwhy2mSloYHvRkk39PSYJ1DyG+trqU5Z9ZbtHSGH6plg==", + "version": "1.61.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.61.0.tgz", + "integrity": "sha512-vkhb9/wKguMkLlrm3FoJW/Xmdv31GgYAE+x8lxxQ+7HeOxXUySI0q36a3NTVIuQUdLzxCI1zzMGsk1o37FOe3w==", "cpu": [ "arm64" ], @@ -8679,9 +8503,9 @@ } }, "node_modules/@oxlint/binding-linux-arm64-musl": { - "version": "1.53.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.53.0.tgz", - "integrity": "sha512-GXI1o4Thn/rtnRIL38BwrDMwVcUbIHKCsOixIWf/CkU3fCG3MXFzFTtDMt+34ik0Qk452d8kcpksL0w/hUkMZA==", + "version": "1.61.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.61.0.tgz", + "integrity": "sha512-bl1dQh8LnVqsj6oOQAcxwbuOmNJkwc4p6o//HTBZhNTzJy21TLDwAviMqUFNUxDHkPGpmdKTSN4tWTjLryP8xg==", "cpu": [ "arm64" ], @@ -8696,9 +8520,9 @@ } }, "node_modules/@oxlint/binding-linux-ppc64-gnu": { - "version": "1.53.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.53.0.tgz", - "integrity": "sha512-Uahk7IVs2yBamCgeJ3XKpKT9Vh+de0pDKISFKnjEcI3c/w2CFHk1+W6Q6G3KI56HGwE9PWCp6ayhA9whXWkNIQ==", + "version": "1.61.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.61.0.tgz", + "integrity": "sha512-QoOX6KB2IiEpyOj/HKqaxi+NQHPnOgNgnr22n9N4ANJCzXkUlj1UmeAbFb4PpqdlHIzvGDM5xZ0OKtcLq9RhiQ==", "cpu": [ "ppc64" ], @@ -8713,9 +8537,9 @@ } }, "node_modules/@oxlint/binding-linux-riscv64-gnu": { - "version": "1.53.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-1.53.0.tgz", - "integrity": "sha512-sWtcU9UkrKMWsGKdFy8R6jkm9Q0VVG1VCpxVuh0HzRQQi3ENI1Nh5CkpsdfUs2MKRcOoHKbXqTscunuXjhxoxQ==", + "version": "1.61.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-1.61.0.tgz", + "integrity": "sha512-1TGcTerjY6p152wCof3oKElccq3xHljS/Mucp04gV/4ATpP6nO7YNnp7opEg6SHkv2a57/b4b8Ndm9znJ1/qAw==", "cpu": [ "riscv64" ], @@ -8730,9 +8554,9 @@ } }, "node_modules/@oxlint/binding-linux-riscv64-musl": { - "version": "1.53.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-1.53.0.tgz", - "integrity": "sha512-aXew1+HDvCdExijX/8NBVC854zJwxhKP3l9AHFSHQNo4EanlHtzDMIlIvP3raUkL0vXtFCkTFYezzU5HjstB8A==", + "version": "1.61.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-1.61.0.tgz", + "integrity": "sha512-65wXEmZIrX2ADwC8i/qFL4EWLSbeuBpAm3suuX1vu4IQkKd+wLT/HU/BOl84kp91u2SxPkPDyQgu4yrqp8vwVA==", "cpu": [ "riscv64" ], @@ -8747,9 +8571,9 @@ } }, "node_modules/@oxlint/binding-linux-s390x-gnu": { - "version": "1.53.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.53.0.tgz", - "integrity": "sha512-rVpyBSqPGou9sITcsoXqUoGBUH74bxYLYOAGUqN599Zu6BQBlBU9hh3bJQ/20D1xrhhrsbiCpVPvXpLPM5nL1w==", + "version": "1.61.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.61.0.tgz", + "integrity": "sha512-TVvhgMvor7Qa6COeXxCJ7ENOM+lcAOGsQ0iUdPSCv2hxb9qSHLQ4XF1h50S6RE1gBOJ0WV3rNukg4JJJP1LWRA==", "cpu": [ "s390x" ], @@ -8764,9 +8588,9 @@ } }, "node_modules/@oxlint/binding-linux-x64-gnu": { - "version": "1.53.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.53.0.tgz", - "integrity": "sha512-eOyeQ8qFQ2geXmlWJuXAOaek0hFhbMLlYsU457NMLKDRoC43Xf+eDPZ9Yk0n9jDaGJ5zBl/3Dy8wo41cnIXuLA==", + "version": "1.61.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.61.0.tgz", + "integrity": "sha512-SjpS5uYuFoDnDdZPwZE59ndF95AsY47R5MliuneTWR1pDm2CxGJaYXbKULI71t5TVfLQUWmrHEGRL9xvuq6dnA==", "cpu": [ "x64" ], @@ -8781,9 +8605,9 @@ } }, "node_modules/@oxlint/binding-linux-x64-musl": { - "version": "1.53.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-x64-musl/-/binding-linux-x64-musl-1.53.0.tgz", - "integrity": "sha512-S6rBArW/zD1tob8M9PwKYrRmz+j1ss1+wjbRAJCWKd7TC3JB6noDiA95pIj9zOZVVp04MIzy5qymnYusrEyXzg==", + "version": "1.61.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-x64-musl/-/binding-linux-x64-musl-1.61.0.tgz", + "integrity": "sha512-gGfAeGD4sNJGILZbc/yKcIimO9wQnPMoYp9swAaKeEtwsSQAbU+rsdQze5SBtIP6j0QDzeYd4XSSUCRCF+LIeQ==", "cpu": [ "x64" ], @@ -8798,9 +8622,9 @@ } }, "node_modules/@oxlint/binding-openharmony-arm64": { - "version": "1.53.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-openharmony-arm64/-/binding-openharmony-arm64-1.53.0.tgz", - "integrity": "sha512-sd/A0Ny5sN0D/MJtlk7w2jGY4bJQou7gToa9WZF7Sj6HTyVzvlzKJWiOHfr4SulVk4ndiFQ8rKmF9rXP0EcF3A==", + "version": "1.61.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-openharmony-arm64/-/binding-openharmony-arm64-1.61.0.tgz", + "integrity": "sha512-OlVT0LrG/ct33EVtWRyR+B/othwmDWeRxfi13wUdPeb3lAT5TgTcFDcfLfarZtzB4W1nWF/zICMgYdkggX2WmQ==", "cpu": [ "arm64" ], @@ -8815,9 +8639,9 @@ } }, "node_modules/@oxlint/binding-win32-arm64-msvc": { - "version": "1.53.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.53.0.tgz", - "integrity": "sha512-QC3q7b51Er/ZurEFcFzc7RpQ/YEoEBLJuCp3WoOzhSHHH/nkUKFy+igOxlj1z3LayhEZPDQQ7sXvv2PM2cdG3Q==", + "version": "1.61.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.61.0.tgz", + "integrity": "sha512-vI//NZPJk6DToiovPtaiwD4iQ7kO1r5ReWQD0sOOyKRtP3E2f6jxin4uvwi3OvDzHA2EFfd7DcZl5dtkQh7g1w==", "cpu": [ "arm64" ], @@ -8832,9 +8656,9 @@ } }, "node_modules/@oxlint/binding-win32-ia32-msvc": { - "version": "1.53.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.53.0.tgz", - "integrity": "sha512-3OvLgOqwd705hWHV2i8ni80pilvg6BUgpC2+xtVu++e/q28LKVohGh5J5QYJOrRMfWmxK0M/AUu43vUw62LAKQ==", + "version": "1.61.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.61.0.tgz", + "integrity": "sha512-0ySj4/4zd2XjePs3XAQq7IigIstN4LPQZgCyigX5/ERMLjdWAJfnxcTsrtxZxuij8guJW8foXuHmhGxW0H4dDA==", "cpu": [ "ia32" ], @@ -8849,9 +8673,9 @@ } }, "node_modules/@oxlint/binding-win32-x64-msvc": { - "version": "1.53.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.53.0.tgz", - "integrity": "sha512-xTiOkntexCdJytZ7ArIIgl3vGW5ujMM3sJNM7/+iqGAVJagCqjFFWn68HRWRLeyT66c95uR+CeFmQFI6mLQqDw==", + "version": "1.61.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.61.0.tgz", + "integrity": "sha512-0xgSiyeqDLDZxXoe9CVJrOx3TUVsfyoOY7cNi03JbItNcC9WCZqrSNdrAbHONxhSPaVh/lzfnDcON1RqSUMhHw==", "cpu": [ "x64" ], @@ -9034,6 +8858,7 @@ "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, "license": "MIT", "optional": true, "engines": { @@ -9054,13 +8879,13 @@ } }, "node_modules/@playwright/test": { - "version": "1.58.2", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.58.2.tgz", - "integrity": "sha512-akea+6bHYBBfA9uQqSYmlJXn61cTa+jbO87xVLCWbTqbWadRVmhxlXATaOjOgcBaWU4ePo0wB41KMFv3o35IXA==", + "version": "1.59.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.59.1.tgz", + "integrity": "sha512-PG6q63nQg5c9rIi4/Z5lR5IVF7yU5MqmKaPOe0HSc0O2cX1fPi96sUQu5j7eo4gKCkB2AnNGoWt7y4/Xx3Kcqg==", "dev": true, "license": "Apache-2.0", "dependencies": { - "playwright": "1.58.2" + "playwright": "1.59.1" }, "bin": { "playwright": "cli.js" @@ -9554,6 +9379,12 @@ "hasInstallScript": true, "license": "Apache-2.0" }, + "node_modules/@sec-ant/readable-stream": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", + "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==", + "license": "MIT" + }, "node_modules/@sideway/address": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", @@ -9586,32 +9417,32 @@ "license": "BSD-3-Clause" }, "node_modules/@sigstore/bundle": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/@sigstore/bundle/-/bundle-2.3.2.tgz", - "integrity": "sha512-wueKWDk70QixNLB363yHc2D2ItTgYiMTdPwK8D9dKQMR3ZQ0c35IxP5xnwQ8cNLoCgCRcHf14kE+CLIvNX1zmA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sigstore/bundle/-/bundle-4.0.0.tgz", + "integrity": "sha512-NwCl5Y0V6Di0NexvkTqdoVfmjTaQwoLM236r89KEojGmq/jMls8S+zb7yOwAPdXvbwfKDlP+lmXgAL4vKSQT+A==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@sigstore/protobuf-specs": "^0.3.2" + "@sigstore/protobuf-specs": "^0.5.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/@sigstore/core": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@sigstore/core/-/core-1.1.0.tgz", - "integrity": "sha512-JzBqdVIyqm2FRQCulY6nbQzMpJJpSiJ8XXWMhtOX9eKgaXXpfNOF53lzQEjIydlStnd/eFtuC1dW4VYdD93oRg==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@sigstore/core/-/core-3.2.0.tgz", + "integrity": "sha512-kxHrDQ9YgfrWUSXU0cjsQGv8JykOFZQ9ErNKbFPWzk3Hgpwu8x2hHrQ9IdA8yl+j9RTLTC3sAF3Tdq1IQCP4oA==", "dev": true, "license": "Apache-2.0", "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/@sigstore/protobuf-specs": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@sigstore/protobuf-specs/-/protobuf-specs-0.3.3.tgz", - "integrity": "sha512-RpacQhBlwpBWd7KEJsRKcBQalbV28fvkxwTOJIqhIuDysMMaJW47V4OqW30iJB9uRpqOSxxEAQFdr8tTattReQ==", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@sigstore/protobuf-specs/-/protobuf-specs-0.5.0.tgz", + "integrity": "sha512-MM8XIwUjN2bwvCg1QvrMtbBmpcSHrkhFSCu1D11NyPvDQ25HEc4oG5/OcQfd/Tlf/OxmKWERDj0zGE23jQaMwA==", "dev": true, "license": "Apache-2.0", "engines": { @@ -9619,50 +9450,166 @@ } }, "node_modules/@sigstore/sign": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/@sigstore/sign/-/sign-2.3.2.tgz", - "integrity": "sha512-5Vz5dPVuunIIvC5vBb0APwo7qKA4G9yM48kPWJT+OEERs40md5GoUR1yedwpekWZ4m0Hhw44m6zU+ObsON+iDA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@sigstore/sign/-/sign-4.1.1.tgz", + "integrity": "sha512-Hf4xglukg0XXQ2RiD5vSoLjdPe8OBUPA8XeVjUObheuDcWdYWrnH/BNmxZCzkAy68MzmNCxXLeurJvs6hcP2OQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@sigstore/bundle": "^2.3.2", - "@sigstore/core": "^1.0.0", - "@sigstore/protobuf-specs": "^0.3.2", - "make-fetch-happen": "^13.0.1", - "proc-log": "^4.2.0", - "promise-retry": "^2.0.1" + "@gar/promise-retry": "^1.0.2", + "@sigstore/bundle": "^4.0.0", + "@sigstore/core": "^3.2.0", + "@sigstore/protobuf-specs": "^0.5.0", + "make-fetch-happen": "^15.0.4", + "proc-log": "^6.1.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@sigstore/sign/node_modules/@npmcli/redact": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/redact/-/redact-4.0.0.tgz", + "integrity": "sha512-gOBg5YHMfZy+TfHArfVogwgfBeQnKbbGo3pSUyK/gSI0AVu+pEiDVcKlQb0D8Mg1LNRZILZ6XG8I5dJ4KuAd9Q==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@sigstore/sign/node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@sigstore/sign/node_modules/make-fetch-happen": { + "version": "15.0.5", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-15.0.5.tgz", + "integrity": "sha512-uCbIa8jWWmQZt4dSnEStkVC6gdakiinAm4PiGsywIkguF0eWMdcjDz0ECYhUolFU3pFLOev9VNPCEygydXnddg==", + "dev": true, + "license": "ISC", + "dependencies": { + "@gar/promise-retry": "^1.0.0", + "@npmcli/agent": "^4.0.0", + "@npmcli/redact": "^4.0.0", + "cacache": "^20.0.1", + "http-cache-semantics": "^4.1.1", + "minipass": "^7.0.2", + "minipass-fetch": "^5.0.0", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^1.0.0", + "proc-log": "^6.0.0", + "ssri": "^13.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@sigstore/sign/node_modules/minipass-fetch": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-5.0.2.tgz", + "integrity": "sha512-2d0q2a8eCi2IRg/IGubCNRJoYbA1+YPXAzQVRFmB45gdGZafyivnZ5YSEfo3JikbjGxOdntGFvBQGqaSMXlAFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.0.3", + "minipass-sized": "^2.0.0", + "minizlib": "^3.0.1" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + }, + "optionalDependencies": { + "iconv-lite": "^0.7.2" + } + }, + "node_modules/@sigstore/sign/node_modules/minipass-sized": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-2.0.0.tgz", + "integrity": "sha512-zSsHhto5BcUVM2m1LurnXY6M//cGhVaegT71OfOXoprxT6o780GZd792ea6FfrQkuU4usHZIUczAQMRUE2plzA==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@sigstore/sign/node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@sigstore/sign/node_modules/proc-log": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz", + "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@sigstore/sign/node_modules/ssri": { + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-13.0.1.tgz", + "integrity": "sha512-QUiRf1+u9wPTL/76GTYlKttDEBWV1ga9ZXW8BG6kfdeyyM8LGPix9gROyg9V2+P0xNyF3X2Go526xKFdMZrHSQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/@sigstore/tuf": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/@sigstore/tuf/-/tuf-2.3.4.tgz", - "integrity": "sha512-44vtsveTPUpqhm9NCrbU8CWLe3Vck2HO1PNLw7RIajbB7xhtn5RBPm1VNSCMwqGYHhDsBJG8gDF0q4lgydsJvw==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@sigstore/tuf/-/tuf-4.0.2.tgz", + "integrity": "sha512-TCAzTy0xzdP79EnxSjq9KQ3eaR7+FmudLC6eRKknVKZbV7ZNlGLClAAQb/HMNJ5n2OBNk2GT1tEmU0xuPr+SLQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@sigstore/protobuf-specs": "^0.3.2", - "tuf-js": "^2.2.1" + "@sigstore/protobuf-specs": "^0.5.0", + "tuf-js": "^4.1.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/@sigstore/verify": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@sigstore/verify/-/verify-1.2.1.tgz", - "integrity": "sha512-8iKx79/F73DKbGfRf7+t4dqrc0bRr0thdPrxAtCKWRm/F0tG71i6O1rvlnScncJLLBZHn3h8M3c1BSUAb9yu8g==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@sigstore/verify/-/verify-3.1.0.tgz", + "integrity": "sha512-mNe0Iigql08YupSOGv197YdHpPPr+EzDZmfCgMc7RPNaZTw5aLN01nBl6CHJOh3BGtnMIj83EeN4butBchc8Ag==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@sigstore/bundle": "^2.3.2", - "@sigstore/core": "^1.1.0", - "@sigstore/protobuf-specs": "^0.3.2" + "@sigstore/bundle": "^4.0.0", + "@sigstore/core": "^3.1.0", + "@sigstore/protobuf-specs": "^0.5.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/@sinclair/typebox": { @@ -9677,6 +9624,8 @@ "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz", "integrity": "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==", "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">=18" }, @@ -9705,16 +9654,16 @@ } }, "node_modules/@standard-schema/spec": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz", - "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", "dev": true, "license": "MIT" }, "node_modules/@storybook/addon-actions": { - "version": "8.6.17", - "resolved": "https://registry.npmjs.org/@storybook/addon-actions/-/addon-actions-8.6.17.tgz", - "integrity": "sha512-/G3Y7WIzGHMtKT6r3KCqgY/pAzfMhNHvBoRWoPfxMa27GiwmUaJlbfRPSReK/jGz6ye0Uwqix+NFbRXKiTqOJQ==", + "version": "8.6.18", + "resolved": "https://registry.npmjs.org/@storybook/addon-actions/-/addon-actions-8.6.18.tgz", + "integrity": "sha512-GcYhtE91GjIQTuZlwpTJ8jfMp6NC79nkpe1DGe0eetTpyQqLq1WUt+ACkk0Z5lqq2u8HBc09zCCGw+D8iCLpYQ==", "dev": true, "license": "MIT", "dependencies": { @@ -9729,7 +9678,7 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "8.6.17" + "storybook": "8.6.18" } }, "node_modules/@storybook/addon-actions/node_modules/uuid": { @@ -9747,9 +9696,9 @@ } }, "node_modules/@storybook/addon-controls": { - "version": "8.6.17", - "resolved": "https://registry.npmjs.org/@storybook/addon-controls/-/addon-controls-8.6.17.tgz", - "integrity": "sha512-kquUfiJyJMh8IHviTIz1A4UKqqNfvqrYFku9D3UukqkTriM1ngDOb9nryaJcUNTkco5JmIuGjYEJisva+u13lw==", + "version": "8.6.18", + "resolved": "https://registry.npmjs.org/@storybook/addon-controls/-/addon-controls-8.6.18.tgz", + "integrity": "sha512-K09dHDCfGW3cudsfuyfu0Yi49aZ2h7VYK4IXDGo1sfmtzVh4xd3HrZQQMVUeKLcfDP/NnJowT+fLVwg04CLrxQ==", "dev": true, "license": "MIT", "dependencies": { @@ -9762,25 +9711,25 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "8.6.17" + "storybook": "8.6.18" } }, "node_modules/@storybook/addon-essentials": { - "version": "8.6.17", - "resolved": "https://registry.npmjs.org/@storybook/addon-essentials/-/addon-essentials-8.6.17.tgz", - "integrity": "sha512-nyp2RUxWGpD+xhGWOo221kHOY6cZlgXsV1F11sn7WxkH+yA7YHhLLYlIPHmNKKH+hdxN0rnlcpwjbr21u0Katg==", + "version": "8.6.18", + "resolved": "https://registry.npmjs.org/@storybook/addon-essentials/-/addon-essentials-8.6.18.tgz", + "integrity": "sha512-MmH7gFb8pyfRoAth0w2RW8j7mBaEJbEWGP3juIoH03ZqTGmbMUbJXElCuRgxQhve7pyz39zLsgtE78D7G+76ew==", "dev": true, "license": "MIT", "dependencies": { - "@storybook/addon-actions": "8.6.17", - "@storybook/addon-backgrounds": "8.6.17", - "@storybook/addon-controls": "8.6.17", - "@storybook/addon-docs": "8.6.17", - "@storybook/addon-highlight": "8.6.17", - "@storybook/addon-measure": "8.6.17", - "@storybook/addon-outline": "8.6.17", - "@storybook/addon-toolbars": "8.6.17", - "@storybook/addon-viewport": "8.6.17", + "@storybook/addon-actions": "8.6.18", + "@storybook/addon-backgrounds": "8.6.18", + "@storybook/addon-controls": "8.6.18", + "@storybook/addon-docs": "8.6.18", + "@storybook/addon-highlight": "8.6.18", + "@storybook/addon-measure": "8.6.18", + "@storybook/addon-outline": "8.6.18", + "@storybook/addon-toolbars": "8.6.18", + "@storybook/addon-viewport": "8.6.18", "ts-dedent": "^2.0.0" }, "funding": { @@ -9788,13 +9737,13 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "8.6.17" + "storybook": "8.6.18" } }, "node_modules/@storybook/addon-essentials/node_modules/@storybook/addon-backgrounds": { - "version": "8.6.17", - "resolved": "https://registry.npmjs.org/@storybook/addon-backgrounds/-/addon-backgrounds-8.6.17.tgz", - "integrity": "sha512-oh7jEQUt8WhH7cBm0jsJ1dujyaujM5rVS5IXJmDgdFJ8l0pqGzOUDmgkBVX147Uo5W1U47Sx+hA69lg6TKPOMQ==", + "version": "8.6.18", + "resolved": "https://registry.npmjs.org/@storybook/addon-backgrounds/-/addon-backgrounds-8.6.18.tgz", + "integrity": "sha512-froND3WwvSCYzjEBO8QODStaWNL+aGXqxBEbrMnGYejDFST4qEFkvM2IYWMnLBkRgrgJ0yIqTeDQoyH9b9/8uQ==", "dev": true, "license": "MIT", "dependencies": { @@ -9807,20 +9756,20 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "8.6.17" + "storybook": "8.6.18" } }, "node_modules/@storybook/addon-essentials/node_modules/@storybook/addon-docs": { - "version": "8.6.17", - "resolved": "https://registry.npmjs.org/@storybook/addon-docs/-/addon-docs-8.6.17.tgz", - "integrity": "sha512-zvcSzoYvaZO4l9NxsviDr5vmuq8GVnH4Ap0v+5sSTq192yevm/iQcRnkWYBD9E/Lg5GBeyE+Ml2vjEOK+EPBEg==", + "version": "8.6.18", + "resolved": "https://registry.npmjs.org/@storybook/addon-docs/-/addon-docs-8.6.18.tgz", + "integrity": "sha512-55ADer0yNmmeR928Y3UAv3r4i7bJSd9LwywsQ+lRol/FNe0ZcwLEz31xL+jVsqQFNnDh/imsDIp8aYapGMtfEQ==", "dev": true, "license": "MIT", "dependencies": { "@mdx-js/react": "^3.0.0", - "@storybook/blocks": "8.6.17", - "@storybook/csf-plugin": "8.6.17", - "@storybook/react-dom-shim": "8.6.17", + "@storybook/blocks": "8.6.18", + "@storybook/csf-plugin": "8.6.18", + "@storybook/react-dom-shim": "8.6.18", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "ts-dedent": "^2.0.0" @@ -9830,13 +9779,13 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "8.6.17" + "storybook": "8.6.18" } }, "node_modules/@storybook/addon-essentials/node_modules/@storybook/addon-docs/node_modules/@storybook/blocks": { - "version": "8.6.17", - "resolved": "https://registry.npmjs.org/@storybook/blocks/-/blocks-8.6.17.tgz", - "integrity": "sha512-zuYHH+0egovMrjWRKwOtgVGbz6KALGowPSWBzQ8deTBu6IXfkz6Ce1hRLJPn5S6/jDqqr9xx8vuAiypnRQ98tA==", + "version": "8.6.18", + "resolved": "https://registry.npmjs.org/@storybook/blocks/-/blocks-8.6.18.tgz", + "integrity": "sha512-esZv4msPQ9LxgTb8YUIZhhxVMuI6BPi5bkXtk8c7w7sWuAsqsCe/RnVInn7ooUry2gjnD4hd9+8Eqj0b8oTVoA==", "dev": true, "license": "MIT", "dependencies": { @@ -9850,7 +9799,7 @@ "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", - "storybook": "8.6.17" + "storybook": "8.6.18" }, "peerDependenciesMeta": { "react": { @@ -9862,9 +9811,9 @@ } }, "node_modules/@storybook/addon-essentials/node_modules/@storybook/addon-docs/node_modules/@storybook/csf-plugin": { - "version": "8.6.17", - "resolved": "https://registry.npmjs.org/@storybook/csf-plugin/-/csf-plugin-8.6.17.tgz", - "integrity": "sha512-ouvF/izbKclZxpfnRUkyC5ZVDU7QA0cHhjQnXTDT4F8b0uciQUDw1LosDZy5MXf03BeIDdyBAtzd/ym3wzd+kw==", + "version": "8.6.18", + "resolved": "https://registry.npmjs.org/@storybook/csf-plugin/-/csf-plugin-8.6.18.tgz", + "integrity": "sha512-x1ioz/L0CwaelCkHci3P31YtvwayN3FBftvwQOPbvRh9qeb4Cpz5IdVDmyvSxxYwXN66uAORNoqgjTi7B4/y5Q==", "dev": true, "license": "MIT", "dependencies": { @@ -9875,13 +9824,13 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "8.6.17" + "storybook": "8.6.18" } }, "node_modules/@storybook/addon-essentials/node_modules/@storybook/addon-docs/node_modules/@storybook/react-dom-shim": { - "version": "8.6.17", - "resolved": "https://registry.npmjs.org/@storybook/react-dom-shim/-/react-dom-shim-8.6.17.tgz", - "integrity": "sha512-bHLsR9b/tiwm9lXbN8kp9XlOgkRXeg84UFwXaWBPu3pOO7vRXukk23SQUpLW+HhjKtCJ3xClSi5uMpse5MpkVQ==", + "version": "8.6.18", + "resolved": "https://registry.npmjs.org/@storybook/react-dom-shim/-/react-dom-shim-8.6.18.tgz", + "integrity": "sha512-N4xULcAWZQTUv4jy1/d346Tyb4gufuC3UaLCuU/iVSZ1brYF4OW3ANr+096btbMxY8pR/65lmtoqr5CTGwnBvA==", "dev": true, "license": "MIT", "funding": { @@ -9891,13 +9840,13 @@ "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", - "storybook": "8.6.17" + "storybook": "8.6.18" } }, "node_modules/@storybook/addon-essentials/node_modules/@storybook/addon-highlight": { - "version": "8.6.17", - "resolved": "https://registry.npmjs.org/@storybook/addon-highlight/-/addon-highlight-8.6.17.tgz", - "integrity": "sha512-Tf7DxksSg+DqH0a0rLIpB5g9bJBUHcqmEGeYGX7EPQrXBpQAtFXz/XdzuD8eYDlPC1r42iQQw4w+CQnXJCOHFw==", + "version": "8.6.18", + "resolved": "https://registry.npmjs.org/@storybook/addon-highlight/-/addon-highlight-8.6.18.tgz", + "integrity": "sha512-wTFJ1DPM0C8gK6nGTJxH75byayQj7BPAz02fME4AOmT6clrBpVl1zSTFTkXaSr+k4xOfeMR/xNUfVskaXz6T9w==", "dev": true, "license": "MIT", "dependencies": { @@ -9908,13 +9857,13 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "8.6.17" + "storybook": "8.6.18" } }, "node_modules/@storybook/addon-essentials/node_modules/@storybook/addon-measure": { - "version": "8.6.17", - "resolved": "https://registry.npmjs.org/@storybook/addon-measure/-/addon-measure-8.6.17.tgz", - "integrity": "sha512-OFGmCrz9MTWfxa0t2GP+633VXZS7W3ahIM5bRmCTeG+jF/gdiVw3S2Adq7YiIZw+nROW9VtHBjuWGvy2miZxcQ==", + "version": "8.6.18", + "resolved": "https://registry.npmjs.org/@storybook/addon-measure/-/addon-measure-8.6.18.tgz", + "integrity": "sha512-fMEOJXgPrTm6qHlWoRM+WTLE7Mr1QBIf2ei+pujBQFcWkD6Gjc2pV8zKzvh93d+EA13wD8AmwOq1DEw9J+XH+g==", "dev": true, "license": "MIT", "dependencies": { @@ -9926,13 +9875,13 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "8.6.17" + "storybook": "8.6.18" } }, "node_modules/@storybook/addon-essentials/node_modules/@storybook/addon-outline": { - "version": "8.6.17", - "resolved": "https://registry.npmjs.org/@storybook/addon-outline/-/addon-outline-8.6.17.tgz", - "integrity": "sha512-UfFThgImS8cuv9Mts5JozGO2/SgjU61uKZxn7w+YpJQu0r2UAF56ZBBMZF/Ur5IC4HaoqDWF5DksMlFqMHPBsw==", + "version": "8.6.18", + "resolved": "https://registry.npmjs.org/@storybook/addon-outline/-/addon-outline-8.6.18.tgz", + "integrity": "sha512-TErFqfCtlV2xt9B6/kskROt69TPjr6AXdHpMselaRrN1X4WEjcMk9GT9PcNP7FXqL88/VYqUb3uNMiAmpDmS/g==", "dev": true, "license": "MIT", "dependencies": { @@ -9944,13 +9893,13 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "8.6.17" + "storybook": "8.6.18" } }, "node_modules/@storybook/addon-essentials/node_modules/@storybook/addon-toolbars": { - "version": "8.6.17", - "resolved": "https://registry.npmjs.org/@storybook/addon-toolbars/-/addon-toolbars-8.6.17.tgz", - "integrity": "sha512-LoWtnMVQJWivAu+SZdgYIsaiEqIq0mZ8Ses2xSwLnQZxndCakyqPYv/7YcdLDJaX5f8DpjPvSzJ77oa75oKgFw==", + "version": "8.6.18", + "resolved": "https://registry.npmjs.org/@storybook/addon-toolbars/-/addon-toolbars-8.6.18.tgz", + "integrity": "sha512-x037KXCEcNfPISGX485DtiP+8Bw/cOT45plcQa8eiAQVrVcUwYaDoLubE9YV5b5CsSAjX8sDviGTme6ALfq7+w==", "dev": true, "license": "MIT", "funding": { @@ -9958,13 +9907,13 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "8.6.17" + "storybook": "8.6.18" } }, "node_modules/@storybook/addon-essentials/node_modules/@storybook/addon-viewport": { - "version": "8.6.17", - "resolved": "https://registry.npmjs.org/@storybook/addon-viewport/-/addon-viewport-8.6.17.tgz", - "integrity": "sha512-gVUtbFQq/mpD5CFOqXGAu5hUpGm/t5G/psg3YsTjnaRBd18dJZ/+//HzZVbX9aEW8t5qTJeJcEYaF5463BVfNA==", + "version": "8.6.18", + "resolved": "https://registry.npmjs.org/@storybook/addon-viewport/-/addon-viewport-8.6.18.tgz", + "integrity": "sha512-z9sDJSkuWQb4BP+Z1+H+y/Q0rFbPSDcw+OBBEhMfRcJPPXavdC2pNQ0GdQNVw+tDwhAXj+U7jehKnMDKaP7TyA==", "dev": true, "license": "MIT", "dependencies": { @@ -9975,13 +9924,13 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "8.6.17" + "storybook": "8.6.18" } }, "node_modules/@storybook/addon-links": { - "version": "8.6.17", - "resolved": "https://registry.npmjs.org/@storybook/addon-links/-/addon-links-8.6.17.tgz", - "integrity": "sha512-ch1GgXILEmekf81nUvmre3xyhlg5zDibRxm8+psPqj7GqzWBI5l4kAiha0XOBWGz6vQEL5xCvN/rr8rfA9kWdQ==", + "version": "8.6.18", + "resolved": "https://registry.npmjs.org/@storybook/addon-links/-/addon-links-8.6.18.tgz", + "integrity": "sha512-FFlQcPRTgXoFZr2uawtf7lNc/ceIVRhU13BkJbJZKlil3+C8ORFDO1vnREzHje9JzeOWm/rzI0ay0RVetCcXzg==", "dev": true, "license": "MIT", "dependencies": { @@ -9994,7 +9943,7 @@ }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", - "storybook": "8.6.17" + "storybook": "8.6.18" }, "peerDependenciesMeta": { "react": { @@ -10003,9 +9952,9 @@ } }, "node_modules/@storybook/addon-mdx-gfm": { - "version": "8.6.17", - "resolved": "https://registry.npmjs.org/@storybook/addon-mdx-gfm/-/addon-mdx-gfm-8.6.17.tgz", - "integrity": "sha512-6gfRnNmUVYJNHrnOjix2v2QmsiJQW1+yCWDneO0fc6FDxZKzASb/sENgOEe04AItrdzEjPeKEIdsa35vmPtqlA==", + "version": "8.6.18", + "resolved": "https://registry.npmjs.org/@storybook/addon-mdx-gfm/-/addon-mdx-gfm-8.6.18.tgz", + "integrity": "sha512-u4+6N7wAjtEfXKQrve9vUyhVsRwSTBJPQdsEScfwoVjg+amCQQDhjbwB78gsCjrxXcbHtpqNM6DXHy8yvhocOg==", "dev": true, "license": "MIT", "dependencies": { @@ -10017,7 +9966,7 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "8.6.17" + "storybook": "8.6.18" } }, "node_modules/@storybook/addon-mdx-gfm/node_modules/@types/mdast": { @@ -10887,9 +10836,9 @@ } }, "node_modules/@storybook/components": { - "version": "8.6.17", - "resolved": "https://registry.npmjs.org/@storybook/components/-/components-8.6.17.tgz", - "integrity": "sha512-0b8xkkuPCNbM8LTOzyfxuo2KdJCHIfu3+QxWBFllXap0eYNHwVeSxE5KERQ/bk2GDCiRzaUbwH9PeLorxOzJJQ==", + "version": "8.6.18", + "resolved": "https://registry.npmjs.org/@storybook/components/-/components-8.6.18.tgz", + "integrity": "sha512-55yViiZzPS/cPBuOeW4QGxGqrusjXVyxuknmbYCIwDtFyyvI/CgbjXRHdxNBaIjz+IlftxvBmmSaOqFG5+/dkA==", "dev": true, "license": "MIT", "funding": { @@ -10901,13 +10850,13 @@ } }, "node_modules/@storybook/core": { - "version": "8.6.17", - "resolved": "https://registry.npmjs.org/@storybook/core/-/core-8.6.17.tgz", - "integrity": "sha512-lndZDYIvUddWk54HmgYwE4h2B0JtWt8ztIRAzHRt6ReZZ9QQbmM5b85Qpa+ng4dyQEKc2JAtYD3Du7RRFcpHlw==", + "version": "8.6.18", + "resolved": "https://registry.npmjs.org/@storybook/core/-/core-8.6.18.tgz", + "integrity": "sha512-dRBP2TnX6fGdS0T2mXBHjkS/3Nlu1ra1huovZVFuM67CYMzrhM/3hX/zru1vWSC5rqY93ZaAhjMciPW4pK5mMQ==", "dev": true, "license": "MIT", "dependencies": { - "@storybook/theming": "8.6.17", + "@storybook/theming": "8.6.18", "better-opn": "^3.0.2", "browser-assert": "^1.2.1", "esbuild": "^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0", @@ -10992,9 +10941,9 @@ } }, "node_modules/@storybook/manager-api": { - "version": "8.6.17", - "resolved": "https://registry.npmjs.org/@storybook/manager-api/-/manager-api-8.6.17.tgz", - "integrity": "sha512-sPJytvClNrw5GgKcPletMTxDOAYcTRA8VRt9E+ncKvPSYHtPDqLfGTgWajXmt0hRsiBUN5bOgLS9bmNjNQWhrw==", + "version": "8.6.18", + "resolved": "https://registry.npmjs.org/@storybook/manager-api/-/manager-api-8.6.18.tgz", + "integrity": "sha512-BjIp12gEMgzFkEsgKpDIbZdnSWTZpm2dlws8WiPJCpgJtG+HWSxZ0/Ms30Au9yfwzQEKRSbV/5zpsKMGc2SIJw==", "dev": true, "license": "MIT", "funding": { @@ -11006,9 +10955,9 @@ } }, "node_modules/@storybook/preview-api": { - "version": "8.6.17", - "resolved": "https://registry.npmjs.org/@storybook/preview-api/-/preview-api-8.6.17.tgz", - "integrity": "sha512-vpTCTkw11wXerYnlG5Q0y4SbFqG9O6GhR0hlYgCn3Z9kcHlNjK/xuwd3h4CvwNXxRNWZGT8qYYCLn5gSSrX6fA==", + "version": "8.6.18", + "resolved": "https://registry.npmjs.org/@storybook/preview-api/-/preview-api-8.6.18.tgz", + "integrity": "sha512-joXRXh3GdVvzhbfIgmix1xs90p8Q/nja7AhEAC2egn5Pl7SKsIYZUCYI6UdrQANb2myg9P552LKXfPect8llKg==", "dev": true, "license": "MIT", "funding": { @@ -11020,18 +10969,18 @@ } }, "node_modules/@storybook/react": { - "version": "8.6.17", - "resolved": "https://registry.npmjs.org/@storybook/react/-/react-8.6.17.tgz", - "integrity": "sha512-yoOzgyZ2VXPJBmvcKS4EVoAf7SJxXbMBcLjWGvmWdDnS+hd7S9cHG/SbgQ+9/vgiLUc+uEuvQjiKrwY3iOA5rg==", + "version": "8.6.18", + "resolved": "https://registry.npmjs.org/@storybook/react/-/react-8.6.18.tgz", + "integrity": "sha512-BuLpzMkKtF+UCQCbi+lYVX9cdcAMG86Lu2dDn7UFkPi5HRNFq/zHPSvlz1XDgL0OYMtcqB1aoVzFzcyzUBhhjw==", "dev": true, "license": "MIT", "dependencies": { - "@storybook/components": "8.6.17", + "@storybook/components": "8.6.18", "@storybook/global": "^5.0.0", - "@storybook/manager-api": "8.6.17", - "@storybook/preview-api": "8.6.17", - "@storybook/react-dom-shim": "8.6.17", - "@storybook/theming": "8.6.17" + "@storybook/manager-api": "8.6.18", + "@storybook/preview-api": "8.6.18", + "@storybook/react-dom-shim": "8.6.18", + "@storybook/theming": "8.6.18" }, "engines": { "node": ">=18.0.0" @@ -11041,10 +10990,10 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "@storybook/test": "8.6.17", + "@storybook/test": "8.6.18", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", - "storybook": "8.6.17", + "storybook": "8.6.18", "typescript": ">= 4.2.x" }, "peerDependenciesMeta": { @@ -11121,15 +11070,15 @@ } }, "node_modules/@storybook/react-webpack5": { - "version": "8.6.17", - "resolved": "https://registry.npmjs.org/@storybook/react-webpack5/-/react-webpack5-8.6.17.tgz", - "integrity": "sha512-61rMF7O+Un+XgfSODkkvpQv6QToMkYB1OJBqHMidW4/VROuA+G51a2+xTWD1JrwIU11uJQU2DeqHn6w2nc9blQ==", + "version": "8.6.18", + "resolved": "https://registry.npmjs.org/@storybook/react-webpack5/-/react-webpack5-8.6.18.tgz", + "integrity": "sha512-oh7V2//Nm6O+7J5b7v4l+BTxksMq7thCmy607diwSBZHYz6G2CxcW3GhxWwZzpHoUVX6vOR5Uc94u9+wBuPi7A==", "dev": true, "license": "MIT", "dependencies": { - "@storybook/builder-webpack5": "8.6.17", - "@storybook/preset-react-webpack": "8.6.17", - "@storybook/react": "8.6.17" + "@storybook/builder-webpack5": "8.6.18", + "@storybook/preset-react-webpack": "8.6.18", + "@storybook/react": "8.6.18" }, "engines": { "node": ">=18.0.0" @@ -11141,7 +11090,7 @@ "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", - "storybook": "8.6.17", + "storybook": "8.6.18", "typescript": ">= 4.2.x" }, "peerDependenciesMeta": { @@ -11151,13 +11100,13 @@ } }, "node_modules/@storybook/react-webpack5/node_modules/@storybook/builder-webpack5": { - "version": "8.6.17", - "resolved": "https://registry.npmjs.org/@storybook/builder-webpack5/-/builder-webpack5-8.6.17.tgz", - "integrity": "sha512-QK0HuTLn/doWQNu/tBC8tP0DrQLqyZk/IeYaxYh43G3igsYHI+yTIG//lHLSRFqkJM6tFT2SIJO8xExE/MCMGQ==", + "version": "8.6.18", + "resolved": "https://registry.npmjs.org/@storybook/builder-webpack5/-/builder-webpack5-8.6.18.tgz", + "integrity": "sha512-rg73TpqIUzXc66c/AaQ4kuc8yiZ+tStvy5fb1OnFYZ9rAeYQejDD0OIIaI2rqtX5XYuxC+yQEGitMntlIMV0og==", "dev": true, "license": "MIT", "dependencies": { - "@storybook/core-webpack": "8.6.17", + "@storybook/core-webpack": "8.6.18", "@types/semver": "^7.3.4", "browser-assert": "^1.2.1", "case-sensitive-paths-webpack-plugin": "^2.4.0", @@ -11187,7 +11136,7 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "8.6.17" + "storybook": "8.6.18" }, "peerDependenciesMeta": { "typescript": { @@ -11196,9 +11145,9 @@ } }, "node_modules/@storybook/react-webpack5/node_modules/@storybook/builder-webpack5/node_modules/@storybook/core-webpack": { - "version": "8.6.17", - "resolved": "https://registry.npmjs.org/@storybook/core-webpack/-/core-webpack-8.6.17.tgz", - "integrity": "sha512-q8acHGExmDdqUyzYoPrxp52bUQ3pEskXlcZIETReb3++pATv7zlSghPVA283O9jgj9jYfz9VYyRjW3vqzIzi0A==", + "version": "8.6.18", + "resolved": "https://registry.npmjs.org/@storybook/core-webpack/-/core-webpack-8.6.18.tgz", + "integrity": "sha512-M+y/DFbiT3CJYQ90wJdXT4WxYImphof1f11StZSxJGo0u5PnCCdCze1qchXubApXRDO2T8HGxurXfhTEMqaGsA==", "dev": true, "license": "MIT", "dependencies": { @@ -11209,18 +11158,18 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "8.6.17" + "storybook": "8.6.18" } }, "node_modules/@storybook/react-webpack5/node_modules/@storybook/preset-react-webpack": { - "version": "8.6.17", - "resolved": "https://registry.npmjs.org/@storybook/preset-react-webpack/-/preset-react-webpack-8.6.17.tgz", - "integrity": "sha512-gMEc6BL8hQIXwOK6yeDc9PMgHKJO6wNM2c8Cttmk9oZeq1YzwIdrQjcLVdKYINGVaQRqLFBvLTmCzz/qPtI5qg==", + "version": "8.6.18", + "resolved": "https://registry.npmjs.org/@storybook/preset-react-webpack/-/preset-react-webpack-8.6.18.tgz", + "integrity": "sha512-UkioZsLIyKGQTAdVB3EMx4NyqwIPDRyuDTIQyCwlMcLYCJCs9Ks2ILbM1x1554/iqRIxy8Yv2IBMapK+euCwgg==", "dev": true, "license": "MIT", "dependencies": { - "@storybook/core-webpack": "8.6.17", - "@storybook/react": "8.6.17", + "@storybook/core-webpack": "8.6.18", + "@storybook/react": "8.6.18", "@storybook/react-docgen-typescript-plugin": "1.0.6--canary.9.0c3f3b7.0", "@types/semver": "^7.3.4", "find-up": "^5.0.0", @@ -11241,7 +11190,7 @@ "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", - "storybook": "8.6.17" + "storybook": "8.6.18" }, "peerDependenciesMeta": { "typescript": { @@ -11250,9 +11199,9 @@ } }, "node_modules/@storybook/react-webpack5/node_modules/@storybook/preset-react-webpack/node_modules/@storybook/core-webpack": { - "version": "8.6.17", - "resolved": "https://registry.npmjs.org/@storybook/core-webpack/-/core-webpack-8.6.17.tgz", - "integrity": "sha512-q8acHGExmDdqUyzYoPrxp52bUQ3pEskXlcZIETReb3++pATv7zlSghPVA283O9jgj9jYfz9VYyRjW3vqzIzi0A==", + "version": "8.6.18", + "resolved": "https://registry.npmjs.org/@storybook/core-webpack/-/core-webpack-8.6.18.tgz", + "integrity": "sha512-M+y/DFbiT3CJYQ90wJdXT4WxYImphof1f11StZSxJGo0u5PnCCdCze1qchXubApXRDO2T8HGxurXfhTEMqaGsA==", "dev": true, "license": "MIT", "dependencies": { @@ -11263,7 +11212,7 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "8.6.17" + "storybook": "8.6.18" } }, "node_modules/@storybook/react-webpack5/node_modules/ajv": { @@ -11451,9 +11400,9 @@ } }, "node_modules/@storybook/react-webpack5/node_modules/yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.3.tgz", + "integrity": "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==", "dev": true, "license": "ISC", "engines": { @@ -11461,9 +11410,9 @@ } }, "node_modules/@storybook/react/node_modules/@storybook/react-dom-shim": { - "version": "8.6.17", - "resolved": "https://registry.npmjs.org/@storybook/react-dom-shim/-/react-dom-shim-8.6.17.tgz", - "integrity": "sha512-bHLsR9b/tiwm9lXbN8kp9XlOgkRXeg84UFwXaWBPu3pOO7vRXukk23SQUpLW+HhjKtCJ3xClSi5uMpse5MpkVQ==", + "version": "8.6.18", + "resolved": "https://registry.npmjs.org/@storybook/react-dom-shim/-/react-dom-shim-8.6.18.tgz", + "integrity": "sha512-N4xULcAWZQTUv4jy1/d346Tyb4gufuC3UaLCuU/iVSZ1brYF4OW3ANr+096btbMxY8pR/65lmtoqr5CTGwnBvA==", "dev": true, "license": "MIT", "funding": { @@ -11473,18 +11422,18 @@ "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", - "storybook": "8.6.17" + "storybook": "8.6.18" } }, "node_modules/@storybook/test": { - "version": "8.6.17", - "resolved": "https://registry.npmjs.org/@storybook/test/-/test-8.6.17.tgz", - "integrity": "sha512-VTuCylXGQrFDZXqZ29+yvJ+A4TZ69jG72rLjiic8hI0SOt87AC/8X1NaYvd2NS4TY0G0PwqtxmKeig8qRDrhNg==", + "version": "8.6.18", + "resolved": "https://registry.npmjs.org/@storybook/test/-/test-8.6.18.tgz", + "integrity": "sha512-u/RwfWMyHcH0N2hqfMTw2CoZ58IXdeED3b8NmcHc8bmERB3byI5vVAkwYbcD7+WeRHIiym38ZHi0SRn+IpkO3Q==", "dev": true, "license": "MIT", "dependencies": { "@storybook/global": "^5.0.0", - "@storybook/instrumenter": "8.6.17", + "@storybook/instrumenter": "8.6.18", "@testing-library/dom": "10.4.0", "@testing-library/jest-dom": "6.5.0", "@testing-library/user-event": "14.5.2", @@ -11496,7 +11445,7 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "8.6.17" + "storybook": "8.6.18" } }, "node_modules/@storybook/test-runner": { @@ -12845,9 +12794,9 @@ } }, "node_modules/@storybook/test/node_modules/@storybook/instrumenter": { - "version": "8.6.17", - "resolved": "https://registry.npmjs.org/@storybook/instrumenter/-/instrumenter-8.6.17.tgz", - "integrity": "sha512-uPqC0sPY2tYGkEVi1x+L4hvhkTwxT16B/LB8xIXh68co3gR8vY6wVskoBp2tM7LSUGl08U2ksZWxyTo1DaQY5Q==", + "version": "8.6.18", + "resolved": "https://registry.npmjs.org/@storybook/instrumenter/-/instrumenter-8.6.18.tgz", + "integrity": "sha512-viEC1BGlYyjAzi1Tv3LZjByh7Y3Oh04u6QKsujxdeUbr5rUOH4pa/wCKmxXmY6yWrD4WjcNtojmUvQZN/66FXQ==", "dev": true, "license": "MIT", "dependencies": { @@ -12859,7 +12808,7 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "8.6.17" + "storybook": "8.6.18" } }, "node_modules/@storybook/test/node_modules/@testing-library/dom": { @@ -12952,9 +12901,9 @@ } }, "node_modules/@storybook/theming": { - "version": "8.6.17", - "resolved": "https://registry.npmjs.org/@storybook/theming/-/theming-8.6.17.tgz", - "integrity": "sha512-IttFvRqozpuzN5MlQEWGOzUA2rZg86688Dyv1d+bjpYcFHtY1X4XyTCGwv1BPTaTsB959oM8R2yoNYWQkABbBA==", + "version": "8.6.18", + "resolved": "https://registry.npmjs.org/@storybook/theming/-/theming-8.6.18.tgz", + "integrity": "sha512-n6OEjEtHupa2PdTwWzRepr7cO8NkDd4rgF6BKLitRbujOspLxzMBEqdphs+QLcuiCIgf33SqmEA64QWnbSMhPw==", "dev": true, "license": "MIT", "funding": { @@ -12993,10 +12942,6 @@ "resolved": "plugins/legacy-plugin-chart-horizon", "link": true }, - "node_modules/@superset-ui/legacy-plugin-chart-map-box": { - "resolved": "plugins/legacy-plugin-chart-map-box", - "link": true - }, "node_modules/@superset-ui/legacy-plugin-chart-paired-t-test": { "resolved": "plugins/legacy-plugin-chart-paired-t-test", "link": true @@ -13017,10 +12962,6 @@ "resolved": "plugins/legacy-plugin-chart-world-map", "link": true }, - "node_modules/@superset-ui/legacy-preset-chart-deckgl": { - "resolved": "plugins/legacy-preset-chart-deckgl", - "link": true - }, "node_modules/@superset-ui/legacy-preset-chart-nvd3": { "resolved": "plugins/legacy-preset-chart-nvd3", "link": true @@ -13045,6 +12986,10 @@ "resolved": "plugins/plugin-chart-pivot-table", "link": true }, + "node_modules/@superset-ui/plugin-chart-point-cluster-map": { + "resolved": "plugins/plugin-chart-point-cluster-map", + "link": true + }, "node_modules/@superset-ui/plugin-chart-table": { "resolved": "plugins/plugin-chart-table", "link": true @@ -13053,6 +12998,10 @@ "resolved": "plugins/plugin-chart-word-cloud", "link": true }, + "node_modules/@superset-ui/preset-chart-deckgl": { + "resolved": "plugins/preset-chart-deckgl", + "link": true + }, "node_modules/@superset-ui/switchboard": { "resolved": "packages/superset-ui-switchboard", "link": true @@ -13329,15 +13278,15 @@ } }, "node_modules/@swc/core": { - "version": "1.15.18", - "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.15.18.tgz", - "integrity": "sha512-z87aF9GphWp//fnkRsqvtY+inMVPgYW3zSlXH1kJFvRT5H/wiAn+G32qW5l3oEk63KSF1x3Ov0BfHCObAmT8RA==", + "version": "1.15.30", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.15.30.tgz", + "integrity": "sha512-R8VQbQY1BZcbIF2p3gjlTCwAQzx1A194ugWfwld5y+WgVVWqVKm7eURGGOVbQVubgKWzidP2agomBbg96rZilQ==", "devOptional": true, "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { "@swc/counter": "^0.1.3", - "@swc/types": "^0.1.25" + "@swc/types": "^0.1.26" }, "engines": { "node": ">=10" @@ -13347,16 +13296,18 @@ "url": "https://opencollective.com/swc" }, "optionalDependencies": { - "@swc/core-darwin-arm64": "1.15.18", - "@swc/core-darwin-x64": "1.15.18", - "@swc/core-linux-arm-gnueabihf": "1.15.18", - "@swc/core-linux-arm64-gnu": "1.15.18", - "@swc/core-linux-arm64-musl": "1.15.18", - "@swc/core-linux-x64-gnu": "1.15.18", - "@swc/core-linux-x64-musl": "1.15.18", - "@swc/core-win32-arm64-msvc": "1.15.18", - "@swc/core-win32-ia32-msvc": "1.15.18", - "@swc/core-win32-x64-msvc": "1.15.18" + "@swc/core-darwin-arm64": "1.15.30", + "@swc/core-darwin-x64": "1.15.30", + "@swc/core-linux-arm-gnueabihf": "1.15.30", + "@swc/core-linux-arm64-gnu": "1.15.30", + "@swc/core-linux-arm64-musl": "1.15.30", + "@swc/core-linux-ppc64-gnu": "1.15.30", + "@swc/core-linux-s390x-gnu": "1.15.30", + "@swc/core-linux-x64-gnu": "1.15.30", + "@swc/core-linux-x64-musl": "1.15.30", + "@swc/core-win32-arm64-msvc": "1.15.30", + "@swc/core-win32-ia32-msvc": "1.15.30", + "@swc/core-win32-x64-msvc": "1.15.30" }, "peerDependencies": { "@swc/helpers": ">=0.5.17" @@ -13368,9 +13319,9 @@ } }, "node_modules/@swc/core-darwin-arm64": { - "version": "1.15.18", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.18.tgz", - "integrity": "sha512-+mIv7uBuSaywN3C9LNuWaX1jJJ3SKfiJuE6Lr3bd+/1Iv8oMU7oLBjYMluX1UrEPzwN2qCdY6Io0yVicABoCwQ==", + "version": "1.15.30", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.30.tgz", + "integrity": "sha512-VvpP+vq08HmGYewMWvrdsxh9s2lthz/808zXm8Yu5kaqeR8Yia2b0eYXleHQ3VAjoStUDk6LzTheBW9KXYQdMA==", "cpu": [ "arm64" ], @@ -13384,9 +13335,9 @@ } }, "node_modules/@swc/core-darwin-x64": { - "version": "1.15.18", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.15.18.tgz", - "integrity": "sha512-wZle0eaQhnzxWX5V/2kEOI6Z9vl/lTFEC6V4EWcn+5pDjhemCpQv9e/TDJ0GIoiClX8EDWRvuZwh+Z3dhL1NAg==", + "version": "1.15.30", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.15.30.tgz", + "integrity": "sha512-WiJA0hiZI3nwQAO6mu5RqigtWGDtth4Hiq6rbZxAaQyhIcqKIg5IoMRc1Y071lrNJn29eEDMC86Rq58xgUxlDg==", "cpu": [ "x64" ], @@ -13400,9 +13351,9 @@ } }, "node_modules/@swc/core-linux-arm-gnueabihf": { - "version": "1.15.18", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.18.tgz", - "integrity": "sha512-ao61HGXVqrJFHAcPtF4/DegmwEkVCo4HApnotLU8ognfmU8x589z7+tcf3hU+qBiU1WOXV5fQX6W9Nzs6hjxDw==", + "version": "1.15.30", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.30.tgz", + "integrity": "sha512-YANuFUo48kIT6plJgCD0keae9HFXfjxsbvsgevqc0hr/07X/p7sAWTFOGYEc2SXcASaK7UvuQqzlbW8pr7R79g==", "cpu": [ "arm" ], @@ -13416,9 +13367,9 @@ } }, "node_modules/@swc/core-linux-arm64-gnu": { - "version": "1.15.18", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.18.tgz", - "integrity": "sha512-3xnctOBLIq3kj8PxOCgPrGjBLP/kNOddr6f5gukYt/1IZxsITQaU9TDyjeX6jG+FiCIHjCuWuffsyQDL5Ew1bg==", + "version": "1.15.30", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.30.tgz", + "integrity": "sha512-VndG8jaR4ugY6u+iVOT0Q+d2fZd7sLgjPgN8W/Le+3EbZKl+cRfFxV7Eoz4gfLqhmneZPdcIzf9T3LkgkmqNLg==", "cpu": [ "arm64" ], @@ -13432,9 +13383,9 @@ } }, "node_modules/@swc/core-linux-arm64-musl": { - "version": "1.15.18", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.18.tgz", - "integrity": "sha512-0a+Lix+FSSHBSBOA0XznCcHo5/1nA6oLLjcnocvzXeqtdjnPb+SvchItHI+lfeiuj1sClYPDvPMLSLyXFaiIKw==", + "version": "1.15.30", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.30.tgz", + "integrity": "sha512-1SYGs2l0Yyyi0pR/P/NKz/x0kqxkoiw+BXeJjLUdecSk/KasncWlJrc6hOvFSgKHOBrzgM5jwuluKtlT8dnrcA==", "cpu": [ "arm64" ], @@ -13447,10 +13398,42 @@ "node": ">=10" } }, + "node_modules/@swc/core-linux-ppc64-gnu": { + "version": "1.15.30", + "resolved": "https://registry.npmjs.org/@swc/core-linux-ppc64-gnu/-/core-linux-ppc64-gnu-1.15.30.tgz", + "integrity": "sha512-TXREtiXeRhbfDFbmhnkIsXpKfzbfT73YkV2ZF6w0sfxgjC5zI2ZAbaCOq25qxvegofj2K93DtOpm9RLaBgqR2g==", + "cpu": [ + "ppc64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-s390x-gnu": { + "version": "1.15.30", + "resolved": "https://registry.npmjs.org/@swc/core-linux-s390x-gnu/-/core-linux-s390x-gnu-1.15.30.tgz", + "integrity": "sha512-DCR2YYeyd6DQE4OuDhImouuNcjXEiEdnn1Y0DyGteugPEDvVuvYk8Xddi+4o2SgWH6jiW8/I+3emZvbep1NC+g==", + "cpu": [ + "s390x" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, "node_modules/@swc/core-linux-x64-gnu": { - "version": "1.15.18", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.18.tgz", - "integrity": "sha512-wG9J8vReUlpaHz4KOD/5UE1AUgirimU4UFT9oZmupUDEofxJKYb1mTA/DrMj0s78bkBiNI+7Fo2EgPuvOJfuAA==", + "version": "1.15.30", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.30.tgz", + "integrity": "sha512-5Pizw3NgfOJ5BJOBK8TIRa59xFW2avESTOBDPTAYwZYa1JNDs+KMF9lUfjJiJLM5HiMs/wPheA9eiT0q9m2AoA==", "cpu": [ "x64" ], @@ -13464,9 +13447,9 @@ } }, "node_modules/@swc/core-linux-x64-musl": { - "version": "1.15.18", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.18.tgz", - "integrity": "sha512-4nwbVvCphKzicwNWRmvD5iBaZj8JYsRGa4xOxJmOyHlMDpsvvJ2OR2cODlvWyGFH6BYL1MfIAK3qph3hp0Az6g==", + "version": "1.15.30", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.30.tgz", + "integrity": "sha512-qyqydP/wyH8alcIP4a2hnGSjHLJjm9H7yDFup+CPy9oTahFgLLwnNcv5UHXqO2Qs3AIND+cls5f/Bb6hqpxdgA==", "cpu": [ "x64" ], @@ -13480,9 +13463,9 @@ } }, "node_modules/@swc/core-win32-arm64-msvc": { - "version": "1.15.18", - "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.18.tgz", - "integrity": "sha512-zk0RYO+LjiBCat2RTMHzAWaMky0cra9loH4oRrLKLLNuL+jarxKLFDA8xTZWEkCPLjUTwlRN7d28eDLLMgtUcQ==", + "version": "1.15.30", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.30.tgz", + "integrity": "sha512-CaQENgDHVGOg1mSF5sQVgvfFHG9kjMor2rkLMLeLOkfZYNj13ppnJ9+lfaBZLZUMMbnlGQnavCJb8PVBUOso7Q==", "cpu": [ "arm64" ], @@ -13496,9 +13479,9 @@ } }, "node_modules/@swc/core-win32-ia32-msvc": { - "version": "1.15.18", - "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.18.tgz", - "integrity": "sha512-yVuTrZ0RccD5+PEkpcLOBAuPbYBXS6rslENvIXfvJGXSdX5QGi1ehC4BjAMl5FkKLiam4kJECUI0l7Hq7T1vwg==", + "version": "1.15.30", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.30.tgz", + "integrity": "sha512-30VdLeGk6fugiUs/kUdJ/pAg7z/zpvVbR11RH60jZ0Z42WIeIniYx0rLEWN7h/pKJ3CopqsQ3RsogCAkRKiA2g==", "cpu": [ "ia32" ], @@ -13512,9 +13495,9 @@ } }, "node_modules/@swc/core-win32-x64-msvc": { - "version": "1.15.18", - "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.18.tgz", - "integrity": "sha512-7NRmE4hmUQNCbYU3Hn9Tz57mK9Qq4c97ZS+YlamlK6qG9Fb5g/BB3gPDe0iLlJkns/sYv2VWSkm8c3NmbEGjbg==", + "version": "1.15.30", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.30.tgz", + "integrity": "sha512-4iObHPR+Q4oDY110EF5SF5eIaaVJNpMdG9C0q3Q92BsJ5y467uHz7sYQhP60WYlLFsLQ1el2YrIPUItUAQGOKg==", "cpu": [ "x64" ], @@ -13553,9 +13536,9 @@ } }, "node_modules/@swc/plugin-emotion": { - "version": "14.6.0", - "resolved": "https://registry.npmjs.org/@swc/plugin-emotion/-/plugin-emotion-14.6.0.tgz", - "integrity": "sha512-YyUalzWUeKgnm5SFxR6ZmIY06J6ffok/XyZjlzrLi5GdiGfSJVtmVhJxqNYNnT4CFa6pbo6Pp8yXeLfjNnSJzQ==", + "version": "14.8.0", + "resolved": "https://registry.npmjs.org/@swc/plugin-emotion/-/plugin-emotion-14.8.0.tgz", + "integrity": "sha512-otFM4JfEE9uyH6HxhD5Dmw6WUY773d2Ln44kEobc89HoVbdGkO3DZv9r2h5znFy7wORyl894V3nYd/mhqc4dIQ==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -13573,9 +13556,9 @@ } }, "node_modules/@swc/types": { - "version": "0.1.25", - "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.25.tgz", - "integrity": "sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g==", + "version": "0.1.26", + "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.26.tgz", + "integrity": "sha512-lyMwd7WGgG79RS7EERZV3T8wMdmPq3xwyg+1nmAM64kIhx5yl+juO2PYIHb7vTiPgPCj8LYjsNV2T5wiQHUEaw==", "devOptional": true, "license": "Apache-2.0", "dependencies": { @@ -13722,6 +13705,24 @@ "@testing-library/dom": ">=7.21.4" } }, + "node_modules/@tokenizer/inflate": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.4.1.tgz", + "integrity": "sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "token-types": "^6.1.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, "node_modules/@tokenizer/token": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", @@ -13774,17 +13775,17 @@ } }, "node_modules/@tufjs/models": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@tufjs/models/-/models-2.0.1.tgz", - "integrity": "sha512-92F7/SFyufn4DXsha9+QfKnN03JGqtMFMXgSHbZOo8JG59WkTni7UzAouNQDf7AuP9OAMxVOPQcqG3sB7w+kkg==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@tufjs/models/-/models-4.1.0.tgz", + "integrity": "sha512-Y8cK9aggNRsqJVaKUlEYs4s7CvQ1b1ta2DVPyAimb0I2qhzjNk+A+mxvll/klL0RlfuIUei8BF7YWiua4kQqww==", "dev": true, "license": "MIT", "dependencies": { "@tufjs/canonical-json": "2.0.0", - "minimatch": "^9.0.4" + "minimatch": "^10.1.1" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/@tufjs/models/node_modules/balanced-match": { @@ -13798,9 +13799,9 @@ } }, "node_modules/@tufjs/models/node_modules/brace-expansion": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz", - "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", "dev": true, "license": "MIT", "dependencies": { @@ -13811,16 +13812,16 @@ } }, "node_modules/@tufjs/models/node_modules/minimatch": { - "version": "9.0.7", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.7.tgz", - "integrity": "sha512-MOwgjc8tfrpn5QQEvjijjmDVtMw2oL88ugTevzxQnzRLm6l3fVEF2gzU0kYeYYKD8C66+IdGX6peJ4MyUlUnPg==", + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { "brace-expansion": "^5.0.2" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -14304,12 +14305,6 @@ "@types/node": "*" } }, - "node_modules/@types/hammerjs": { - "version": "2.0.46", - "resolved": "https://registry.npmjs.org/@types/hammerjs/-/hammerjs-2.0.46.tgz", - "integrity": "sha512-ynRvcq6wvqexJ9brDMS4BnBLzmr0e14d6ZJTEShTBWKymQiHwlAyGu0ZPEFI2Fh1U53F7tN9ufClWM5KvqkKOw==", - "license": "MIT" - }, "node_modules/@types/hast": { "version": "2.3.10", "resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.10.tgz", @@ -14583,9 +14578,9 @@ } }, "node_modules/@types/jest/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", "engines": { @@ -14634,14 +14629,11 @@ } }, "node_modules/@types/jquery": { - "version": "3.5.33", - "resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.33.tgz", - "integrity": "sha512-SeyVJXlCZpEki5F0ghuYe+L+PprQta6nRZqhONt9F13dWBtR/ftoaIbdRQ7cis7womE+X2LKhsDdDtkkDhJS6g==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-4.0.0.tgz", + "integrity": "sha512-Z+to+A2VkaHq1DfI2oSwsoCdhCHMpTSgjWzNcbNlRGYzksDBpPUgEcAL+RQjOBJRaLoEAOHXxqDGBVP+BblBwg==", "dev": true, - "license": "MIT", - "dependencies": { - "@types/sizzle": "*" - } + "license": "MIT" }, "node_modules/@types/js-levenshtein": { "version": "1.1.3", @@ -14706,25 +14698,6 @@ "@types/geojson": "*" } }, - "node_modules/@types/mapbox__point-geometry": { - "version": "1.0.87", - "resolved": "https://registry.npmjs.org/@types/mapbox__point-geometry/-/mapbox__point-geometry-1.0.87.tgz", - "integrity": "sha512-32whuSWD/aDBHpRrZmEMlRjxR0L3sSYUaRnVmw0XwyVMvuU5LsRqrFwYud7gXjqmUE0MSoJB91+IJMqCyYyxag==", - "deprecated": "This is a stub types definition. @mapbox/point-geometry provides its own type definitions, so you do not need this installed.", - "license": "MIT", - "dependencies": { - "@mapbox/point-geometry": "*" - } - }, - "node_modules/@types/mapbox-gl": { - "version": "2.7.21", - "resolved": "https://registry.npmjs.org/@types/mapbox-gl/-/mapbox-gl-2.7.21.tgz", - "integrity": "sha512-Dx9MuF2kKgT/N22LsMUB4b3acFZh9clVqz9zv1fomoiPoBrJolwYxpWA/9LPO/2N0xWbKi4V+pkjTaFkkx/4wA==", - "license": "MIT", - "dependencies": { - "@types/geojson": "*" - } - }, "node_modules/@types/mdast": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", @@ -14748,13 +14721,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/minimatch": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz", - "integrity": "sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/minimist": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.5.tgz", @@ -14783,12 +14749,12 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "25.3.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.3.3.tgz", - "integrity": "sha512-DpzbrH7wIcBaJibpKo9nnSQL0MTRdnWttGyE5haGwK86xgMOkFLp7vEyfQPGLOJh5wNYiJ3V9PmUMDhV9u8kkQ==", + "version": "25.6.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.0.tgz", + "integrity": "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==", "license": "MIT", "dependencies": { - "undici-types": "~7.18.0" + "undici-types": "~7.19.0" } }, "node_modules/@types/normalize-package-data": { @@ -14825,7 +14791,6 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/picomatch/-/picomatch-4.0.2.tgz", "integrity": "sha512-qHHxQ+P9PysNEGbALT8f8YOSHW0KJu6l2xU8DYY0fu/EmGxXdVnuTLvFUvBgPJMSqXq29SYHveejeAha+4AYgA==", - "dev": true, "license": "MIT" }, "node_modules/@types/prismjs": { @@ -15115,7 +15080,8 @@ "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.9.tgz", "integrity": "sha512-xzLEyKB50yqCUPUJkIsrVvoWNfFUbIZI+RspLWt8u+tIW/BetMBZtgV2LY/2o+tYH8dRvQ+eoPf3NdhQCcLE2w==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@types/sockjs": { "version": "0.3.36", @@ -15448,9 +15414,9 @@ } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/brace-expansion": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz", - "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", "dev": true, "license": "MIT", "dependencies": { @@ -15608,9 +15574,9 @@ } }, "node_modules/@typescript-eslint/parser/node_modules/brace-expansion": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz", - "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", "dev": true, "license": "MIT", "dependencies": { @@ -15861,9 +15827,9 @@ } }, "node_modules/@typescript-eslint/type-utils/node_modules/brace-expansion": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz", - "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", "dev": true, "license": "MIT", "dependencies": { @@ -16296,6 +16262,72 @@ "win32" ] }, + "node_modules/@vis.gl/react-mapbox": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@vis.gl/react-mapbox/-/react-mapbox-8.1.0.tgz", + "integrity": "sha512-FwvH822oxEjWYOr+pP2L8hpv+7cZB2UsQbHHHT0ryrkvvqzmTgt7qHDhamv0EobKw86e1I+B4ojENdJ5G5BkyQ==", + "license": "MIT", + "peerDependencies": { + "mapbox-gl": ">=3.5.0", + "react": ">=16.3.0", + "react-dom": ">=16.3.0" + }, + "peerDependenciesMeta": { + "mapbox-gl": { + "optional": true + } + } + }, + "node_modules/@vis.gl/react-maplibre": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@vis.gl/react-maplibre/-/react-maplibre-8.1.0.tgz", + "integrity": "sha512-PkAK/gp3mUfhCLhUuc+4gc3PN9zCtVGxTF2hB6R5R5yYUw+hdg84OZ770U5MU4tPMTCG6fbduExuIW6RRKN6qQ==", + "license": "MIT", + "dependencies": { + "@maplibre/maplibre-gl-style-spec": "^19.2.1" + }, + "peerDependencies": { + "maplibre-gl": ">=4.0.0", + "react": ">=16.3.0", + "react-dom": ">=16.3.0" + }, + "peerDependenciesMeta": { + "maplibre-gl": { + "optional": true + } + } + }, + "node_modules/@vis.gl/react-maplibre/node_modules/@maplibre/maplibre-gl-style-spec": { + "version": "19.3.3", + "resolved": "https://registry.npmjs.org/@maplibre/maplibre-gl-style-spec/-/maplibre-gl-style-spec-19.3.3.tgz", + "integrity": "sha512-cOZZOVhDSulgK0meTsTkmNXb1ahVvmTmWmfx9gRBwc6hq98wS9JP35ESIoNq3xqEan+UN+gn8187Z6E4NKhLsw==", + "license": "ISC", + "dependencies": { + "@mapbox/jsonlint-lines-primitives": "~2.0.2", + "@mapbox/unitbezier": "^0.0.1", + "json-stringify-pretty-compact": "^3.0.0", + "minimist": "^1.2.8", + "rw": "^1.3.3", + "sort-object": "^3.0.3" + }, + "bin": { + "gl-style-format": "dist/gl-style-format.mjs", + "gl-style-migrate": "dist/gl-style-migrate.mjs", + "gl-style-validate": "dist/gl-style-validate.mjs" + } + }, + "node_modules/@vis.gl/react-maplibre/node_modules/json-stringify-pretty-compact": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/json-stringify-pretty-compact/-/json-stringify-pretty-compact-3.0.0.tgz", + "integrity": "sha512-Rc2suX5meI0S3bfdZuA7JMFBGkJ875ApfVyq2WHELjBiiG22My/l7/8zPpH/CfFVQHuVLd8NLR0nv6vi0BYYKA==", + "license": "MIT" + }, + "node_modules/@vis.gl/react-maplibre/node_modules/rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==", + "license": "BSD-3-Clause" + }, "node_modules/@visx/annotation": { "version": "3.12.0", "resolved": "https://registry.npmjs.org/@visx/annotation/-/annotation-3.12.0.tgz", @@ -17339,13 +17371,13 @@ "license": "BSD-3-Clause" }, "node_modules/abbrev": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz", - "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-3.0.1.tgz", + "integrity": "sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg==", "dev": true, "license": "ISC", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/accepts": { @@ -17390,6 +17422,30 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/acorn-walk": { + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.5.tgz", + "integrity": "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==", + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk/node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/add-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/add-stream/-/add-stream-1.0.0.tgz", @@ -17398,27 +17454,27 @@ "license": "MIT" }, "node_modules/ag-charts-types": { - "version": "13.0.1", - "resolved": "https://registry.npmjs.org/ag-charts-types/-/ag-charts-types-13.0.1.tgz", - "integrity": "sha512-qg9adyiAaeUaDtWZEEPF45dv55kdJTe6Ghi0EQXCS79h/7KvOd6dxhqGZjPL1zeFl/L9qEXuYgb+LkGStq4mgQ==", + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/ag-charts-types/-/ag-charts-types-13.2.1.tgz", + "integrity": "sha512-r7veb3QqJtIKlXmeUsLR4/oDPwmHxFI2tmbZra/203mdaz3uwQUrrgYNg628nrK+7L2YxXnwGc6L05tWjLLjNQ==", "license": "MIT" }, "node_modules/ag-grid-community": { - "version": "35.0.1", - "resolved": "https://registry.npmjs.org/ag-grid-community/-/ag-grid-community-35.0.1.tgz", - "integrity": "sha512-fYYZdymWKsN9/tZv+R6uZRnuiWYEQr+GHl85ZrB0ixbFcE8opYK4NJI29NnMc9ShYiCBnAO9hj54IFa+FI4aDA==", + "version": "35.2.1", + "resolved": "https://registry.npmjs.org/ag-grid-community/-/ag-grid-community-35.2.1.tgz", + "integrity": "sha512-ycmGI+1EbUT7i3eg/Kgi1owwnkdHXRufo10Xm6cfSsVPM3TMpvlbLgi28KIPt9DGHZWHq9fOBn7nxMNdv1Yaow==", "license": "MIT", "dependencies": { - "ag-charts-types": "13.0.1" + "ag-charts-types": "13.2.1" } }, "node_modules/ag-grid-react": { - "version": "35.0.1", - "resolved": "https://registry.npmjs.org/ag-grid-react/-/ag-grid-react-35.0.1.tgz", - "integrity": "sha512-NvrrgWUm+DsnsZVFho16srlyBNKSl9nqeSadk63HpHYerq4S4vN/2JCxdNtfqns7SpbYx7GbasbMFCXKYj7Qaw==", + "version": "35.2.1", + "resolved": "https://registry.npmjs.org/ag-grid-react/-/ag-grid-react-35.2.1.tgz", + "integrity": "sha512-UzdU15R6fyGJB+lBKEC458xacGoZged3Ra6Plqa7LvrJ/Mg0tWn1NH01UnuKyGEKPWMEAGvdXruOtOUywsPElA==", "license": "MIT", "dependencies": { - "ag-grid-community": "35.0.1", + "ag-grid-community": "35.2.1", "prop-types": "^15.8.1" }, "peerDependencies": { @@ -17745,6 +17801,15 @@ "deep-equal": "^2.0.5" } }, + "node_modules/arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/array-buffer-byte-length": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", @@ -17761,16 +17826,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/array-differ": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-3.0.0.tgz", - "integrity": "sha512-THtfYS6KtME/yIAhKjZ2ul7XI96lQGHRputJQHO80LAWQnuGP4iCIN8vdMRboGbIEYBwU33q8Tch1os2+X0kMg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", @@ -17978,6 +18033,15 @@ "node": ">=12" } }, + "node_modules/assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/ast-types": { "version": "0.16.1", "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.16.1.tgz", @@ -18070,23 +18134,26 @@ "peer": true }, "node_modules/axios": { - "version": "1.13.5", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.5.tgz", - "integrity": "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==", + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.15.0.tgz", + "integrity": "sha512-wWyJDlAatxk30ZJer+GeCWS209sA42X+N5jU2jy6oHTp7ufw8uzUTVFBX9+wTfAlhiJXGS0Bq7X6efruWjuK9Q==", "dev": true, "license": "MIT", "dependencies": { "follow-redirects": "^1.15.11", "form-data": "^4.0.5", - "proxy-from-env": "^1.1.0" + "proxy-from-env": "^2.1.0" } }, "node_modules/axios/node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=10" + } }, "node_modules/b4a": { "version": "1.6.7", @@ -18144,9 +18211,9 @@ } }, "node_modules/babel-loader": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-10.0.0.tgz", - "integrity": "sha512-z8jt+EdS61AMw22nSfoNJAZ0vrtmhPRVi6ghL3rCeRZI8cdNYFiV5xeV3HbE7rlZZNmGH8BVccwWt8/ED0QOHA==", + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-10.1.1.tgz", + "integrity": "sha512-JwKSzk2kjIe7mgPK+/lyZ2QAaJcpahNAdM+hgR2HI8D0OJVkdj8Rl6J3kaLYki9pwF7P2iWnD8qVv80Lq1ABtg==", "dev": true, "license": "MIT", "dependencies": { @@ -18156,8 +18223,17 @@ "node": "^18.20.0 || ^20.10.0 || >=22.0.0" }, "peerDependencies": { - "@babel/core": "^7.12.0", + "@babel/core": "^7.12.0 || ^8.0.0-beta.1", + "@rspack/core": "^1.0.0 || ^2.0.0-0", "webpack": ">=5.61.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } } }, "node_modules/babel-plugin-dynamic-import-node": { @@ -18259,9 +18335,9 @@ } }, "node_modules/babel-plugin-macros/node_modules/yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.3.tgz", + "integrity": "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==", "license": "ISC", "engines": { "node": ">= 6" @@ -18417,9 +18493,9 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.10.7", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.7.tgz", - "integrity": "sha512-1ghYO3HnxGec0TCGBXiDLVns4eCSx4zJpxnHrlqFQajmhfKMQBzUGDdkMK7fUW7PTHTeLf+j87aTuKuuwWzMGw==", + "version": "2.10.16", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.16.tgz", + "integrity": "sha512-Lyf3aK28zpsD1yQMiiHD4RvVb6UdMoo8xzG2XzFIfR9luPzOpcBlAsT/qfB1XWS1bxWT+UtE4WmQgsp297FYOA==", "dev": true, "license": "Apache-2.0", "bin": { @@ -18467,12 +18543,6 @@ "tweetnacl": "^0.14.3" } }, - "node_modules/before-after-hook": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-3.0.2.tgz", - "integrity": "sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A==", - "license": "Apache-2.0" - }, "node_modules/better-opn": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/better-opn/-/better-opn-3.0.2.tgz", @@ -18516,19 +18586,67 @@ } }, "node_modules/bin-links": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/bin-links/-/bin-links-4.0.4.tgz", - "integrity": "sha512-cMtq4W5ZsEwcutJrVId+a/tjt8GSbS+h0oNkdl6+6rBuEv8Ot33Bevj5KPm40t309zuhVic8NjpuL42QCiJWWA==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/bin-links/-/bin-links-5.0.0.tgz", + "integrity": "sha512-sdleLVfCjBtgO5cNjA2HVRvWBJAHs4zwenaCPMNJAJU0yNxpzj80IpjOIimkpkr+mhlA+how5poQtt53PygbHA==", "dev": true, "license": "ISC", "dependencies": { - "cmd-shim": "^6.0.0", - "npm-normalize-package-bin": "^3.0.0", - "read-cmd-shim": "^4.0.0", - "write-file-atomic": "^5.0.0" + "cmd-shim": "^7.0.0", + "npm-normalize-package-bin": "^4.0.0", + "proc-log": "^5.0.0", + "read-cmd-shim": "^5.0.0", + "write-file-atomic": "^6.0.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/bin-links/node_modules/cmd-shim": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/cmd-shim/-/cmd-shim-7.0.0.tgz", + "integrity": "sha512-rtpaCbr164TPPh+zFdkWpCyZuKkjpAzODfaZCf/SVJZzJN+4bHQb/LP3Jzq5/+84um3XXY8r548XiWKSborwVw==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/bin-links/node_modules/read-cmd-shim": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/read-cmd-shim/-/read-cmd-shim-5.0.0.tgz", + "integrity": "sha512-SEbJV7tohp3DAAILbEMPXavBjAnMN0tVnh4+9G8ihV4Pq3HYF9h8QNez9zkJ1ILkv9G2BjdzwctznGZXgu/HGw==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/bin-links/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/bin-links/node_modules/write-file-atomic": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-6.0.0.tgz", + "integrity": "sha512-GmqrO8WJ1NuzJ2DrziEI2o57jKAVIQNf8a18W3nCYU3H7PNWqCCVTeH6/NQE93CIllIgQS98rrmVkYgTX9fFJQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/binary-extensions": { @@ -18702,9 +18820,9 @@ "license": "MIT" }, "node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", + "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -18922,28 +19040,58 @@ "node": ">=6.0.0" } }, + "node_modules/bytewise": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/bytewise/-/bytewise-1.1.0.tgz", + "integrity": "sha512-rHuuseJ9iQ0na6UDhnrRVDh8YnWVlU6xM3VH6q/+yHDeUH2zIhUzP+2/h3LIrhLDBtTqzWpE3p3tP/boefskKQ==", + "license": "MIT", + "dependencies": { + "bytewise-core": "^1.2.2", + "typewise": "^1.0.3" + } + }, + "node_modules/bytewise-core": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/bytewise-core/-/bytewise-core-1.2.3.tgz", + "integrity": "sha512-nZD//kc78OOxeYtRlVk8/zXqTB4gf/nlguL1ggWA8FuchMyOxcyHR4QPQZMUmA7czC+YnaBrPUCubqAWe50DaA==", + "license": "MIT", + "dependencies": { + "typewise-core": "^1.2" + } + }, "node_modules/cacache": { - "version": "18.0.4", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-18.0.4.tgz", - "integrity": "sha512-B+L5iIa9mgcjLbliir2th36yEwPftrzteHYujzsx3dFP/31GCHcIeS8f5MGd80odLOjaOvSpU3EEAmRQptkxLQ==", + "version": "20.0.4", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-20.0.4.tgz", + "integrity": "sha512-M3Lab8NPYlZU2exsL3bMVvMrMqgwCnMWfdZbK28bn3pK6APT/Te/I8hjRPNu1uwORY9a1eEQoifXbKPQMfMTOA==", "dev": true, "license": "ISC", "dependencies": { - "@npmcli/fs": "^3.1.0", + "@npmcli/fs": "^5.0.0", "fs-minipass": "^3.0.0", - "glob": "^10.2.2", - "lru-cache": "^10.0.1", + "glob": "^13.0.0", + "lru-cache": "^11.1.0", "minipass": "^7.0.3", "minipass-collect": "^2.0.1", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", - "p-map": "^4.0.0", - "ssri": "^10.0.0", - "tar": "^6.1.11", - "unique-filename": "^3.0.0" + "p-map": "^7.0.2", + "ssri": "^13.0.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/cacache/node_modules/@npmcli/fs": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-5.0.0.tgz", + "integrity": "sha512-7OsC1gNORBEawOa5+j2pXN9vsicaIOH5cPXxoR6fJOmH6/EXpJB2CajXOu1fPRFun2m1lktEFX11+P89hqO/og==", + "dev": true, + "license": "ISC", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/cacache/node_modules/balanced-match": { @@ -18957,9 +19105,9 @@ } }, "node_modules/cacache/node_modules/brace-expansion": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz", - "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", "dev": true, "license": "MIT", "dependencies": { @@ -18970,49 +19118,92 @@ } }, "node_modules/cacache/node_modules/glob": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", - "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "version": "13.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "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" + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" }, - "bin": { - "glob": "dist/esm/bin.mjs" + "engines": { + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/cacache/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "version": "11.2.7", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", + "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==", "dev": true, - "license": "ISC" + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } }, "node_modules/cacache/node_modules/minimatch": { - "version": "9.0.7", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.7.tgz", - "integrity": "sha512-MOwgjc8tfrpn5QQEvjijjmDVtMw2oL88ugTevzxQnzRLm6l3fVEF2gzU0kYeYYKD8C66+IdGX6peJ4MyUlUnPg==", + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { "brace-expansion": "^5.0.2" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/cacache/node_modules/p-map": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.4.tgz", + "integrity": "sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cacache/node_modules/path-scurry": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/cacache/node_modules/ssri": { + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-13.0.1.tgz", + "integrity": "sha512-QUiRf1+u9wPTL/76GTYlKttDEBWV1ga9ZXW8BG6kfdeyyM8LGPix9gROyg9V2+P0xNyF3X2Go526xKFdMZrHSQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, "node_modules/cachedir": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/cachedir/-/cachedir-2.4.0.tgz", @@ -19343,6 +19534,12 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/chardet": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.1.1.tgz", + "integrity": "sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==", + "license": "MIT" + }, "node_modules/charenc": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", @@ -19496,13 +19693,13 @@ } }, "node_modules/chownr": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "engines": { - "node": ">=10" + "node": ">=18" } }, "node_modules/chroma-js": { @@ -19531,9 +19728,9 @@ } }, "node_modules/ci-info": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.2.0.tgz", - "integrity": "sha512-cYY9mypksY8NRqgDB1XD1RiJL338v/551niynFTGkZOO2LHuB2OmOYxDIe/ttN9AHwrqdum1360G3ald0W9kCg==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.1.tgz", + "integrity": "sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA==", "dev": true, "funding": [ { @@ -19656,7 +19853,6 @@ "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", "license": "ISC", - "peer": true, "engines": { "node": ">= 12" } @@ -19675,18 +19871,6 @@ "node": ">=12" } }, - "node_modules/cliui/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/cliui/node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", @@ -19732,7 +19916,8 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", "integrity": "sha512-au6ydSpg6nsrigcZ4m8Bc9hxjeW+GJ8xh5G3BJCMt4WXe1H10UNaVOamqQTmrx1kjVuxAHIQSNU6hY4Nsn9/ag==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/clsx": { "version": "2.1.1", @@ -19850,19 +20035,6 @@ "node": ">=8.0.0" } }, - "node_modules/columnify/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -20399,20 +20571,20 @@ } }, "node_modules/copy-webpack-plugin": { - "version": "13.0.1", - "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-13.0.1.tgz", - "integrity": "sha512-J+YV3WfhY6W/Xf9h+J1znYuqTye2xkBUIGyTPWuBAT27qajBa5mR4f8WBmfDY3YjRftT2kqZZiLi1qf0H+UOFw==", + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-14.0.0.tgz", + "integrity": "sha512-3JLW90aBGeaTLpM7mYQKpnVdgsUZRExY55giiZgLuX/xTQRUs1dOCwbBnWnvY6Q6rfZoXMNwzOQJCSZPppfqXA==", "dev": true, "license": "MIT", "dependencies": { "glob-parent": "^6.0.1", "normalize-path": "^3.0.0", "schema-utils": "^4.2.0", - "serialize-javascript": "^6.0.2", + "serialize-javascript": "^7.0.3", "tinyglobby": "^0.2.12" }, "engines": { - "node": ">= 18.12.0" + "node": ">= 20.9.0" }, "funding": { "type": "opencollective", @@ -21567,9 +21739,9 @@ } }, "node_modules/css-minimizer-webpack-plugin/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", "engines": { @@ -21579,16 +21751,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/css-minimizer-webpack-plugin/node_modules/serialize-javascript": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-7.0.4.tgz", - "integrity": "sha512-DuGdB+Po43Q5Jxwpzt1lhyFSYKryqoNjQSA9M92tyw0lyHIOur+XCalOUe0KTJpyqzT8+fQ5A0Jf7vCx/NKmIg==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=20.0.0" - } - }, "node_modules/css-minimizer-webpack-plugin/node_modules/supports-color": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", @@ -21802,46 +21964,6 @@ "dev": true, "license": "CC0-1.0" }, - "node_modules/cssstyle": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-6.0.1.tgz", - "integrity": "sha512-IoJs7La+oFp/AB033wBStxNOJt4+9hHMxsXUPANcoXL2b3W4DZKghlJ2cI/eyeRZIQ9ysvYEorVhjrcYctWbog==", - "dev": true, - "license": "MIT", - "dependencies": { - "@asamuzakjp/css-color": "^4.1.2", - "@csstools/css-syntax-patches-for-csstree": "^1.0.26", - "css-tree": "^3.1.0", - "lru-cache": "^11.2.5" - }, - "engines": { - "node": ">=20" - } - }, - "node_modules/cssstyle/node_modules/css-tree": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz", - "integrity": "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "mdn-data": "2.12.2", - "source-map-js": "^1.0.1" - }, - "engines": { - "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" - } - }, - "node_modules/cssstyle/node_modules/lru-cache": { - "version": "11.2.6", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz", - "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": "20 || >=22" - } - }, "node_modules/csstype": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", @@ -22420,9 +22542,9 @@ } }, "node_modules/dayjs": { - "version": "1.11.19", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.19.tgz", - "integrity": "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==", + "version": "1.11.20", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.20.tgz", + "integrity": "sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ==", "license": "MIT" }, "node_modules/debounce": { @@ -22611,9 +22733,9 @@ } }, "node_modules/default-browser": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.2.1.tgz", - "integrity": "sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.5.0.tgz", + "integrity": "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==", "dev": true, "license": "MIT", "dependencies": { @@ -23099,9 +23221,9 @@ } }, "node_modules/dompurify": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.3.tgz", - "integrity": "sha512-Oj6pzI2+RqBfFG+qOaOLbFXLQ90ARpcGG6UePL82bJLtdsa6CYJD7nmiU8MW9nQNOtCHV3lZ/Bzq1X0QYbBZCA==", + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.0.tgz", + "integrity": "sha512-nolgK9JcaUXMSmW+j1yaSvaEaoXYHwWyGJlkoCTghc97KgGDDSnpoU/PlEnw63Ah+TGKFOyY+X5LnxaWbCSfXg==", "license": "(MPL-2.0 OR Apache-2.0)", "optionalDependencies": { "@types/trusted-types": "^2.0.7" @@ -23377,6 +23499,7 @@ "version": "3.1.10", "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "devOptional": true, "license": "Apache-2.0", "dependencies": { "jake": "^10.8.5" @@ -23412,6 +23535,7 @@ "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, "license": "MIT" }, "node_modules/emojis-list": { @@ -23508,20 +23632,6 @@ "node": ">=8.6" } }, - "node_modules/enquirer/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/entities": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", @@ -24167,20 +24277,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/eslint-plugin-file-progress": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-file-progress/-/eslint-plugin-file-progress-1.5.0.tgz", - "integrity": "sha512-get8oNfacIagP+igSzrEZhepPgodtdwACVeKQsE1fVvTL15tZvgCv8K4B2lKT4FZOZOyhxSkQGnWyjEOx1uoIw==", - "dev": true, - "license": "MIT", - "dependencies": { - "nanospinner": "^1.1.0", - "picocolors": "^1.0.1" - }, - "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0" - } - }, "node_modules/eslint-plugin-i18n-strings": { "resolved": "eslint-rules/eslint-plugin-i18n-strings", "link": true @@ -24333,6 +24429,16 @@ "eslint": ">=2" } }, + "node_modules/eslint-plugin-no-only-tests": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-no-only-tests/-/eslint-plugin-no-only-tests-3.3.0.tgz", + "integrity": "sha512-brcKcxGnISN2CcVhXJ/kEQlNa0MEfGRtwKtWA16SkqXHKitaKIMrfemJKLKX1YqDU5C/5JY3PvZXd5jEW04e0Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=5.0.0" + } + }, "node_modules/eslint-plugin-prettier": { "version": "5.5.5", "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.5.tgz", @@ -24372,9 +24478,9 @@ "license": "MIT" }, "node_modules/eslint-plugin-react-you-might-not-need-an-effect": { - "version": "0.9.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-you-might-not-need-an-effect/-/eslint-plugin-react-you-might-not-need-an-effect-0.9.2.tgz", - "integrity": "sha512-VplJMf2kAYI4bF1KSCOygQ9BHzOqM/0P3cqpnBTylnSVv9aNxVrz2RDMs8bKJtITcp2CV9kuAUkzjUP0zgxbSw==", + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-you-might-not-need-an-effect/-/eslint-plugin-react-you-might-not-need-an-effect-0.9.3.tgz", + "integrity": "sha512-44cce7LndBnpDRWBTQ8p7ircIdl2rJBP5+V9Ik64E935UB47uA9ZMU1Uv160lAMhtvoPYqXBjQ+tojr5JF3mFQ==", "dev": true, "license": "MIT", "dependencies": { @@ -24430,9 +24536,9 @@ } }, "node_modules/eslint-plugin-testing-library": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-testing-library/-/eslint-plugin-testing-library-7.16.0.tgz", - "integrity": "sha512-lHZI6/Olb2oZqxd1+s1nOLCtL2PXKrc1ERz6oDbUKS0xZAMFH3Fy6wJo75z3pXTop3BV6+loPi2MSjIYt3vpAg==", + "version": "7.16.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-testing-library/-/eslint-plugin-testing-library-7.16.2.tgz", + "integrity": "sha512-8gleGnQXK2ZA3hHwjCwpYTZvM+9VsrJ+/9kDI8CjqAQGAdMQOdn/rJNu7ZySENuiWlGKQWyZJ4ZjEg2zamaRHw==", "dev": true, "license": "MIT", "dependencies": { @@ -24559,9 +24665,9 @@ } }, "node_modules/eslint-plugin-testing-library/node_modules/brace-expansion": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz", - "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", "dev": true, "license": "MIT", "dependencies": { @@ -24771,19 +24877,6 @@ "dev": true, "license": "MIT" }, - "node_modules/eslint/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/eslint/node_modules/type-fest": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", @@ -25135,6 +25228,18 @@ "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", "license": "MIT" }, + "node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "license": "MIT", + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/extract-zip": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", @@ -25211,6 +25316,15 @@ "dev": true, "license": "Apache-2.0" }, + "node_modules/fast-equals": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-6.0.0.tgz", + "integrity": "sha512-PFhhIGgdM79r5Uztdj9Zb6Tt1zKafqVfdMGwVca1z5z6fbX7DmsySSuJd8HiP6I1j505DCS83cLxo5rmSNeVEA==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/fast-fifo": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", @@ -25299,21 +25413,24 @@ "license": "BSD-3-Clause" }, "node_modules/fast-xml-builder": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.0.0.tgz", - "integrity": "sha512-fpZuDogrAgnyt9oDDz+5DBz0zgPdPZz6D4IR7iESxRXElrlGTRkHJ9eEt+SACRJwT0FNFrt71DFQIUFBJfX/uQ==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.1.5.tgz", + "integrity": "sha512-4TJn/8FKLeslLAH3dnohXqE3QSoxkhvaMzepOIZytwJXZO69Bfz0HBdDHzOTOon6G59Zrk6VQ2bEiv1t61rfkA==", "funding": [ { "type": "github", "url": "https://github.com/sponsors/NaturalIntelligence" } ], - "license": "MIT" + "license": "MIT", + "dependencies": { + "path-expression-matcher": "^1.1.3" + } }, "node_modules/fast-xml-parser": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.5.4.tgz", - "integrity": "sha512-jE8ugADnYOBsu1uaoayVl1tVKAMNOXyjwvv2U6udEA2ORBhDooJDWoGxTkhd4Qn4yh59JVVt/pKXtjPwx9OguQ==", + "version": "4.5.5", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.5.5.tgz", + "integrity": "sha512-cK9c5I/DwIOI7/Q7AlGN3DuTdwN61gwSfL8rvuVPK+0mcCNHHGxRrpiFtaZZRfRMJL3Gl8B2AFlBG6qXf03w9A==", "funding": [ { "type": "github", @@ -25491,18 +25608,19 @@ "license": "MIT" }, "node_modules/file-type": { - "version": "18.7.0", - "resolved": "https://registry.npmjs.org/file-type/-/file-type-18.7.0.tgz", - "integrity": "sha512-ihHtXRzXEziMrQ56VSgU7wkxh55iNchFkosu7Y9/S+tXHdKyrGjVK0ujbqNnsxzea+78MaLhN6PGmfYSAv1ACw==", + "version": "21.3.4", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-21.3.4.tgz", + "integrity": "sha512-Ievi/yy8DS3ygGvT47PjSfdFoX+2isQueoYP1cntFW1JLYAuS4GD7NUPGg4zv2iZfV52uDyk5w5Z0TdpRS6Q1g==", "dev": true, "license": "MIT", "dependencies": { - "readable-web-to-node-stream": "^3.0.2", - "strtok3": "^7.0.0", - "token-types": "^5.0.1" + "@tokenizer/inflate": "^0.4.1", + "strtok3": "^10.3.4", + "token-types": "^6.1.1", + "uint8array-extras": "^1.4.0" }, "engines": { - "node": ">=14.16" + "node": ">=20" }, "funding": { "url": "https://github.com/sindresorhus/file-type?sponsor=1" @@ -25518,9 +25636,9 @@ } }, "node_modules/filelist/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz", + "integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==", "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" @@ -25784,18 +25902,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/find-up-simple": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/find-up-simple/-/find-up-simple-1.0.0.tgz", - "integrity": "sha512-q7Us7kcjj2VMePAa02hDAF6d+MzsdsAWEwYyOpwUtlerRBkOEPBCRZrAV4XfcSN8fHAgaD0hP7miwoay6DCprw==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/first-chunk-stream": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/first-chunk-stream/-/first-chunk-stream-5.0.0.tgz", @@ -25852,16 +25958,16 @@ } }, "node_modules/flatted": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.1.tgz", - "integrity": "sha512-IxfVbRFVlV8V/yRaGzk0UVIcsKKHMSfYw66T/u4nTwlWteQePsxe//LjudR1AMX4tZW3WFCh3Zqa/sjlqpbURQ==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", "dev": true, "license": "ISC" }, "node_modules/follow-redirects": { - "version": "1.15.11", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", - "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", "dev": true, "funding": [ { @@ -25895,12 +26001,13 @@ } }, "node_modules/foreground-child": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", - "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, "license": "ISC", "dependencies": { - "cross-spawn": "^7.0.0", + "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" }, "engines": { @@ -25914,6 +26021,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, "license": "ISC", "engines": { "node": ">=14" @@ -26182,9 +26290,9 @@ } }, "node_modules/fs-extra": { - "version": "11.3.3", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.3.tgz", - "integrity": "sha512-VWSRii4t0AFm6ixFFmLLx1t7wS1gh+ckoa84aOeapGum0h+EZd1EhEumSB+ZdDLnEPuucsVB9oB7cxJHap6Afg==", + "version": "11.3.4", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.4.tgz", + "integrity": "sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==", "license": "MIT", "dependencies": { "graceful-fs": "^4.2.0", @@ -26282,24 +26390,27 @@ } }, "node_modules/fuse.js": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-7.1.0.tgz", - "integrity": "sha512-trLf4SzuuUxfusZADLINj+dE8clK1frKdmqiJNb1Es75fmI5oY6X2mxLVUciLLjxqw/xr72Dhy+lER6dGd02FQ==", + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-7.3.0.tgz", + "integrity": "sha512-plz8RVjfcDedTGfVngWH1jmJvBvAwi1v2jecfDerbEnMcmOYUEEwKFTHbNoCiYyzaK2Ws8lABkTCcRSqCY1q4w==", "license": "Apache-2.0", "engines": { "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/krisk" } }, "node_modules/gaxios": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.3.tgz", - "integrity": "sha512-YGGyuEdVIjqxkxVH1pUTMY/XtmmsApXrCVv5EU25iX6inEPbV+VakJfLealkBtJN69AQmh1eGOdCl9Sm1UP6XQ==", + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.4.tgz", + "integrity": "sha512-bTIgTsM2bWn3XklZISBTQX7ZSddGW+IO3bMdGaemHZ3tbqExMENHLx6kKZ/KlejgrMtj8q7wBItt51yegqalrA==", "license": "Apache-2.0", "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", - "node-fetch": "^3.3.2", - "rimraf": "^5.0.1" + "node-fetch": "^3.3.2" }, "engines": { "node": ">=18" @@ -26314,15 +26425,6 @@ "node": ">= 14" } }, - "node_modules/gaxios/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, "node_modules/gaxios/node_modules/data-uri-to-buffer": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", @@ -26332,27 +26434,6 @@ "node": ">= 12" } }, - "node_modules/gaxios/node_modules/glob": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", - "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/gaxios/node_modules/https-proxy-agent": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", @@ -26366,21 +26447,6 @@ "node": ">= 14" } }, - "node_modules/gaxios/node_modules/minimatch": { - "version": "9.0.9", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", - "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.2" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/gaxios/node_modules/node-fetch": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", @@ -26399,21 +26465,6 @@ "url": "https://opencollective.com/node-fetch" } }, - "node_modules/gaxios/node_modules/rimraf": { - "version": "5.0.10", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz", - "integrity": "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==", - "license": "ISC", - "dependencies": { - "glob": "^10.3.7" - }, - "bin": { - "rimraf": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/gcp-metadata": { "version": "8.1.2", "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-8.1.2.tgz", @@ -26466,15 +26517,15 @@ "license": "ISC" }, "node_modules/geolib": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/geolib/-/geolib-3.3.4.tgz", - "integrity": "sha512-EicrlLLL3S42gE9/wde+11uiaYAaeSVDwCUIv2uMIoRBfNJCn8EsSI+6nS3r4TCKDO6+RQNM9ayLq2at+oZQWQ==", + "version": "3.3.14", + "resolved": "https://registry.npmjs.org/geolib/-/geolib-3.3.14.tgz", + "integrity": "sha512-uQ1772h3OjhWvL/HhSRZTMjBKIKoc4wFksLDqzqOkuG/2TgBbTwFamU0Disx3sNFk/BOweHyhKoVaYDuLIpsxQ==", "license": "MIT" }, "node_modules/geostyler": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/geostyler/-/geostyler-18.3.1.tgz", - "integrity": "sha512-ZEZDwdAtP6Z4ANtlMfYtMwymdFMrr6n0kD4q8dMANA54taXgbeJKU2quC93MQlXlSo8Ka8Y1xI3qAliRasvWcw==", + "version": "18.5.0", + "resolved": "https://registry.npmjs.org/geostyler/-/geostyler-18.5.0.tgz", + "integrity": "sha512-azjLMEhrTQot+pU3phfSrUZI7CdetyAl7JNAnxrGaPA/E/5mmyoPQugZso3CfIuIBwOtFLmfB36SLE/FeGFakA==", "license": "BSD-2-Clause", "dependencies": { "@ant-design/icons": "^5.5.1", @@ -26497,15 +26548,15 @@ "geostyler-data": "^1.1.0", "geostyler-geojson-parser": "^2.0.0", "geostyler-mapbox-parser": "^6.1.1", - "geostyler-openlayers-parser": "^5.4.0", + "geostyler-openlayers-parser": "^5.7.0", "geostyler-qgis-parser": "^4.0.2", - "geostyler-sld-parser": "^8.3.0", - "geostyler-style": "^11.0.2", + "geostyler-sld-parser": "^8.4.2", + "geostyler-style": "^11.1.0", "geostyler-wfs-parser": "^3.0.1", "lodash-es": "^4.17.21", "monaco-editor": "^0.52.0", "proj4": "^2.11.0", - "typescript-json-schema": "^0.65.0" + "typescript-json-schema": "^0.67.0" }, "engines": { "node": ">=20.6.0" @@ -26618,14 +26669,13 @@ } }, "node_modules/geostyler-openlayers-parser": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/geostyler-openlayers-parser/-/geostyler-openlayers-parser-5.4.0.tgz", - "integrity": "sha512-BdlG10/wvok7KCOdtzM/NKfiUfkx+uqLDgesC3lNZnviz02S+zIQRtbAkb/Abin6/2boMFRL3i7YIt4D98xMNw==", + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/geostyler-openlayers-parser/-/geostyler-openlayers-parser-5.7.0.tgz", + "integrity": "sha512-FRNTNPLoKJzKYnWas+E4hb4h38SGaK3KeNPZmLUqO5EcTootJjAJyTbCy/Cuv9afk56HYIBpM2gHh6q/fLwqsg==", "license": "BSD-2-Clause", "dependencies": { "css-font-parser": "^2.0.0", - "geostyler-style": "^11.0.1", - "lodash": "^4.17.21" + "geostyler-style": "^11.1.0" }, "engines": { "node": ">=20.6.0" @@ -26637,6 +26687,19 @@ "ol": ">=7.4" } }, + "node_modules/geostyler-openlayers-parser/node_modules/geostyler-style": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/geostyler-style/-/geostyler-style-11.1.0.tgz", + "integrity": "sha512-PixhZL0yR6XVSxCGdm3/ti4DjclXTGOSm4xc2TpuymfAxOhQZ3t8u392o9Ne4l4fM4nIzRRKACj7Kr0nO9H1Vg==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=20.6.0", + "npm": ">=10.0.0" + }, + "funding": { + "url": "https://opencollective.com/geostyler" + } + }, "node_modules/geostyler-qgis-parser": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/geostyler-qgis-parser/-/geostyler-qgis-parser-4.1.0.tgz", @@ -26694,14 +26757,13 @@ } }, "node_modules/geostyler-sld-parser": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/geostyler-sld-parser/-/geostyler-sld-parser-8.4.0.tgz", - "integrity": "sha512-XR7o5L1n/+xBk/W1WVobX7ftoeoRy486o7qfI1D24PYcRFK3EBr5Dh1TZgGI2U7VXDWx2/S4ipl0hztqbjYnxw==", + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/geostyler-sld-parser/-/geostyler-sld-parser-8.4.2.tgz", + "integrity": "sha512-NPlTC1VoxgsApUgs/KhEAkGuMhWrUr2slJj8gSyXhd+oJUaYaPOfeGEFiaV+MGk1va+g3XJ1e5gkRD6aHjx8Eg==", "license": "BSD-2-Clause", "dependencies": { "fast-xml-parser": "^5.2.3", - "geostyler-style": "^11.0.2", - "lodash": "^4.17.21" + "geostyler-style": "^11.0.2" }, "engines": { "node": ">=20.6.0" @@ -26711,9 +26773,9 @@ } }, "node_modules/geostyler-sld-parser/node_modules/fast-xml-parser": { - "version": "5.4.2", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.4.2.tgz", - "integrity": "sha512-pw/6pIl4k0CSpElPEJhDppLzaixDEuWui2CUQQBH/ECDf7+y6YwA4Gf7Tyb0Rfe4DIMuZipYj4AEL0nACKglvQ==", + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.7.1.tgz", + "integrity": "sha512-8Cc3f8GUGUULg34pBch/KGyPLglS+OFs05deyOlY7fL2MTagYPKrVQNmR1fLF/yJ9PH5ZSTd3YDF6pnmeZU+zA==", "funding": [ { "type": "github", @@ -26722,17 +26784,19 @@ ], "license": "MIT", "dependencies": { - "fast-xml-builder": "^1.0.0", - "strnum": "^2.1.2" + "@nodable/entities": "^2.1.0", + "fast-xml-builder": "^1.1.5", + "path-expression-matcher": "^1.5.0", + "strnum": "^2.2.3" }, "bin": { "fxparser": "src/cli/cli.js" } }, "node_modules/geostyler-sld-parser/node_modules/strnum": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.2.0.tgz", - "integrity": "sha512-Y7Bj8XyJxnPAORMZj/xltsfo55uOiyHcU2tnAVzHUnSJR/KsEX+9RoDeXEnsXtl/CX4fAcrt64gZ13aGaWPeBg==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.2.3.tgz", + "integrity": "sha512-oKx6RUCuHfT3oyVjtnrmn19H1SiCqgJSg+54XqURKp5aCMbrXrhLjRN9TjuwMjiYstZ0MzDrHqkGZ5dFTKd+zg==", "funding": [ { "type": "github", @@ -26781,6 +26845,19 @@ "react": ">=16.8.0" } }, + "node_modules/geostyler/node_modules/geostyler-style": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/geostyler-style/-/geostyler-style-11.1.0.tgz", + "integrity": "sha512-PixhZL0yR6XVSxCGdm3/ti4DjclXTGOSm4xc2TpuymfAxOhQZ3t8u392o9Ne4l4fM4nIzRRKACj7Kr0nO9H1Vg==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=20.6.0", + "npm": ">=10.0.0" + }, + "funding": { + "url": "https://opencollective.com/geostyler" + } + }, "node_modules/geotiff": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/geotiff/-/geotiff-3.0.3.tgz", @@ -26985,19 +27062,6 @@ "node": ">=10" } }, - "node_modules/get-port": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/get-port/-/get-port-5.1.1.tgz", - "integrity": "sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/get-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", @@ -27011,23 +27075,11 @@ "node": ">= 0.4" } }, - "node_modules/get-stdin": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-9.0.0.tgz", - "integrity": "sha512-dVKBjfWisLAicarI2Sf+JuBE/DghV4UzNAVe9yhEJuzeREd3JhOTE9cUaJTeSa77fsbQUK3pcOpJfM59+VKZaA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/get-stream": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -27066,6 +27118,15 @@ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, + "node_modules/get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/getos": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/getos/-/getos-3.2.1.tgz", @@ -27112,6 +27173,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-3.0.0.tgz", "integrity": "sha512-b5OHmZ3vAgGrDn/X0kS+9qCfNKWe4K/jFnhwzVWWg0/k5eLa3060tZShrRg8Dja5kPc+YjS0Gc6y7cRr44Lpjw==", + "deprecated": "This package is no longer maintained. For the JavaScript API, please use @conventional-changelog/git-client instead.", "dev": true, "license": "MIT", "dependencies": { @@ -27144,6 +27206,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/git-semver-tags/-/git-semver-tags-5.0.1.tgz", "integrity": "sha512-hIvOeZwRbQ+7YEUmCkHqo8FOLQZCEn18yevLHADlFPZY02KJGsu5FZt9YW/lybfK2uhWFI7Qg/07LekJiTv7iA==", + "deprecated": "This package is no longer maintained. For the JavaScript API, please use @conventional-changelog/git-client instead.", "dev": true, "license": "MIT", "dependencies": { @@ -27195,21 +27258,6 @@ "dev": true, "license": "ISC" }, - "node_modules/github-username": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/github-username/-/github-username-9.0.0.tgz", - "integrity": "sha512-lY7+mymwQUEhRwWTLxieKkxcZkVNnUh8iAGnl30DMB1ZtYODHkMAckZk8Jx5dLQs1YKPYM2ibnzQu02aCLFcYQ==", - "license": "MIT", - "dependencies": { - "@octokit/rest": "^21.1.1" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/gl-matrix": { "version": "3.4.4", "resolved": "https://registry.npmjs.org/gl-matrix/-/gl-matrix-3.4.4.tgz", @@ -27402,14 +27450,14 @@ } }, "node_modules/google-auth-library": { - "version": "10.6.1", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.6.1.tgz", - "integrity": "sha512-5awwuLrzNol+pFDmKJd0dKtZ0fPLAtoA5p7YO4ODsDu6ONJUVqbYwvv8y2ZBO5MBNp9TJXigB19710kYpBPdtA==", + "version": "10.6.2", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.6.2.tgz", + "integrity": "sha512-e27Z6EThmVNNvtYASwQxose/G57rkRuaRbQyxM2bvYLLX/GqWZ5chWq2EBoUchJbCc57eC9ArzO5wMsEmWftCw==", "license": "Apache-2.0", "dependencies": { "base64-js": "^1.3.0", "ecdsa-sig-formatter": "^1.0.11", - "gaxios": "7.1.3", + "gaxios": "^7.1.4", "gcp-metadata": "8.1.2", "google-logging-utils": "1.1.3", "jws": "^4.0.0" @@ -27485,15 +27533,6 @@ "yarn": ">=1.3.0" } }, - "node_modules/hammerjs": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/hammerjs/-/hammerjs-2.0.8.tgz", - "integrity": "sha512-tSQXBXS/MWQOn/RKckawJ61vvsDpCom87JgxiYdGwHdOa0ht0vzUWDlfioofFCRU0L+6NGDt6XzbgoJvZkMeRQ==", - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, "node_modules/handle-thing": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", @@ -27502,9 +27541,9 @@ "license": "MIT" }, "node_modules/handlebars": { - "version": "4.7.8", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", - "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "version": "4.7.9", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.9.tgz", + "integrity": "sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ==", "license": "MIT", "dependencies": { "minimist": "^1.2.5", @@ -28019,24 +28058,6 @@ "node": ">=0.10.0" } }, - "node_modules/hosted-git-info": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", - "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==", - "license": "ISC", - "dependencies": { - "lru-cache": "^10.0.1" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "node_modules/hosted-git-info/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "license": "ISC" - }, "node_modules/hpack.js": { "version": "2.1.6", "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", @@ -28293,9 +28314,9 @@ } }, "node_modules/http-cache-semantics": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", - "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", "dev": true, "license": "BSD-2-Clause" }, @@ -28583,22 +28604,23 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "devOptional": true, "license": "MIT", "engines": { "node": ">= 4" } }, "node_modules/ignore-walk": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-6.0.5.tgz", - "integrity": "sha512-VuuG0wCnjhnylG1ABXT3dAuIpTNDs/G8jlpmwXY03fXoXy/8ZK8/T+hMzt8L4WnrLCJgdybqgPagnF/f97cg3A==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-8.0.0.tgz", + "integrity": "sha512-FCeMZT4NiRQGh+YkeKMtWrOmBgWjHjMJ26WQWrRQyoyzqevdaGSakUaJW5xQYmjLlUVk2qUnCjYVBax9EKKg8A==", "dev": true, "license": "ISC", "dependencies": { - "minimatch": "^9.0.0" + "minimatch": "^10.0.3" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/ignore-walk/node_modules/balanced-match": { @@ -28612,9 +28634,9 @@ } }, "node_modules/ignore-walk/node_modules/brace-expansion": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz", - "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", "dev": true, "license": "MIT", "dependencies": { @@ -28625,16 +28647,16 @@ } }, "node_modules/ignore-walk/node_modules/minimatch": { - "version": "9.0.7", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.7.tgz", - "integrity": "sha512-MOwgjc8tfrpn5QQEvjijjmDVtMw2oL88ugTevzxQnzRLm6l3fVEF2gzU0kYeYYKD8C66+IdGX6peJ4MyUlUnPg==", + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { "brace-expansion": "^5.0.2" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -28734,16 +28756,6 @@ "webpack": "^5.0.0" } }, - "node_modules/imports-loader/node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -28763,18 +28775,6 @@ "node": ">=8" } }, - "node_modules/index-to-position": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/index-to-position/-/index-to-position-0.1.2.tgz", - "integrity": "sha512-MWDKS3AS1bGCHLBA2VLImJz42f7bJh8wQsTGCzI3j519/CASStoDONUBVz2I/VID0MpiX3SGSnbOD2xUalbE5g==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -28792,23 +28792,33 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "license": "ISC" }, + "node_modules/ini": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-6.0.0.tgz", + "integrity": "sha512-IBTdIkzZNOpqm7q3dRqJvMaldXjDHWkEDfrwGEQTs5eaQMWV+djAhR+wahyNNMAa+qpbDUhBMVt4ZKNwpPm7xQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, "node_modules/init-package-json": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/init-package-json/-/init-package-json-6.0.3.tgz", - "integrity": "sha512-Zfeb5ol+H+eqJWHTaGca9BovufyGeIfr4zaaBorPmJBMrJ+KBnN+kQx2ZtXdsotUTgldHmHQV44xvUWOUA7E2w==", + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/init-package-json/-/init-package-json-8.2.2.tgz", + "integrity": "sha512-pXVMn67Jdw2hPKLCuJZj62NC9B2OIDd1R3JwZXTHXuEnfN3Uq5kJbKOSld6YEU+KOGfMD82EzxFTYz5o0SSJoA==", "dev": true, "license": "ISC", "dependencies": { - "@npmcli/package-json": "^5.0.0", - "npm-package-arg": "^11.0.0", - "promzard": "^1.0.0", - "read": "^3.0.1", - "semver": "^7.3.5", + "@npmcli/package-json": "^7.0.0", + "npm-package-arg": "^13.0.0", + "promzard": "^2.0.0", + "read": "^4.0.0", + "semver": "^7.7.2", "validate-npm-package-license": "^3.0.4", - "validate-npm-package-name": "^5.0.0" + "validate-npm-package-name": "^6.0.2" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/inline-style-parser": { @@ -28912,19 +28922,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/inquirer/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", - "peer": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/internal-slot": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", @@ -28990,26 +28987,15 @@ "license": "MIT" }, "node_modules/ip-address": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", - "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", + "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", "dev": true, "license": "MIT", - "dependencies": { - "jsbn": "1.1.0", - "sprintf-js": "^1.1.3" - }, "engines": { "node": ">= 12" } }, - "node_modules/ip-address/node_modules/sprintf-js": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", - "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", - "dev": true, - "license": "BSD-3-Clause" - }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -29300,6 +29286,15 @@ "integrity": "sha512-IOQqts/aHWbiisY5DuPJQ0gcbvaLFCa7fBa9xoLfxBZvQ+ZI/Zh9xoI7Gk+G64N0FdK4AbibytHht2tWgpJWLg==", "license": "MIT" }, + "node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -29383,6 +29378,19 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/is-in-ssh": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-in-ssh/-/is-in-ssh-1.0.0.tgz", + "integrity": "sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-inside-container": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", @@ -29445,13 +29453,6 @@ "node": ">=8" } }, - "node_modules/is-lambda": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", - "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", - "dev": true, - "license": "MIT" - }, "node_modules/is-map": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", @@ -29464,6 +29465,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-mobile": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-mobile/-/is-mobile-5.0.0.tgz", + "integrity": "sha512-Tz/yndySvLAEXh+Uk8liFCxOwVH6YutuR74utvOcu7I9Di+DwM0mtdPVZNaVvvBUM2OXxne/NhOs1zAO7riusQ==", + "license": "MIT" + }, "node_modules/is-negative-zero": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", @@ -29548,7 +29555,6 @@ "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==", - "dev": true, "license": "MIT", "dependencies": { "isobject": "^3.0.1" @@ -29705,7 +29711,6 @@ "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=18" }, @@ -29803,6 +29808,8 @@ "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-5.0.3.tgz", "integrity": "sha512-VR4gNjFaDP8csJQvzInG20JvBj8MaHYLxNOMXysxRbGM7tcsHZwCjhch3FubFtZBkuDbN55i4dUukGeIrzF+6g==", "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">= 18.0.0" }, @@ -29820,7 +29827,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -30006,6 +30012,7 @@ "version": "3.4.3", "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, "license": "BlueOak-1.0.0", "dependencies": { "@isaacs/cliui": "^8.0.2" @@ -30174,9 +30181,9 @@ } }, "node_modules/jest-changed-files/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", "engines": { @@ -30430,9 +30437,9 @@ } }, "node_modules/jest-circus/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", "engines": { @@ -30588,9 +30595,9 @@ } }, "node_modules/jest-cli/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", "engines": { @@ -30691,9 +30698,9 @@ "license": "MIT" }, "node_modules/jest-config/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz", + "integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==", "dev": true, "license": "MIT", "dependencies": { @@ -30774,9 +30781,9 @@ } }, "node_modules/jest-config/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", "engines": { @@ -30990,9 +30997,9 @@ } }, "node_modules/jest-each/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", "engines": { @@ -31082,19 +31089,6 @@ "acorn-walk": "^8.0.2" } }, - "node_modules/jest-environment-jsdom/node_modules/acorn-walk": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", - "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "acorn": "^8.11.0" - }, - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/jest-environment-jsdom/node_modules/cssom": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz", @@ -31493,9 +31487,9 @@ } }, "node_modules/jest-environment-node/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", "engines": { @@ -31686,9 +31680,9 @@ } }, "node_modules/jest-haste-map/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", "engines": { @@ -31699,9 +31693,9 @@ } }, "node_modules/jest-html-reporter": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/jest-html-reporter/-/jest-html-reporter-4.3.0.tgz", - "integrity": "sha512-lq4Zx35yc6Ehw513CXJ1ok3wUmkSiOImWcyLAmylfzrz7DAqtrhDF9V73F4qfstmGxlr8X0QrEjWsl/oqhf4sQ==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/jest-html-reporter/-/jest-html-reporter-4.4.0.tgz", + "integrity": "sha512-8aC5pzPOgsbiPwlvE686Gt3ZkUGHpafHtS0ffhCmKqTYdNwnrNX1WpmF7lbb3+3/TvZ9+UlACM811abivu5SWw==", "dev": true, "license": "MIT", "dependencies": { @@ -31717,8 +31711,7 @@ "node": ">=14.0.0" }, "peerDependencies": { - "jest": "19.x - 30.x", - "typescript": "^3.7.x || ^4.3.x || ^5.x" + "jest": "19.x - 30.x" } }, "node_modules/jest-html-reporter/node_modules/@jest/schemas": { @@ -31777,19 +31770,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/jest-html-reporter/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/jest-junit": { "version": "16.0.0", "resolved": "https://registry.npmjs.org/jest-junit/-/jest-junit-16.0.0.tgz", @@ -31806,19 +31786,6 @@ "node": ">=10.12.0" } }, - "node_modules/jest-junit/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/jest-junit/node_modules/uuid": { "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", @@ -32278,9 +32245,9 @@ } }, "node_modules/jest-resolve/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", "engines": { @@ -32522,9 +32489,9 @@ } }, "node_modules/jest-runner/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", "engines": { @@ -32711,9 +32678,9 @@ } }, "node_modules/jest-runtime/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz", + "integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==", "dev": true, "license": "MIT", "dependencies": { @@ -32837,9 +32804,9 @@ } }, "node_modules/jest-runtime/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", "engines": { @@ -33114,9 +33081,9 @@ } }, "node_modules/jest-snapshot/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", "engines": { @@ -33412,9 +33379,9 @@ } }, "node_modules/jest-watcher/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", "engines": { @@ -33524,9 +33491,9 @@ } }, "node_modules/joi": { - "version": "18.0.2", - "resolved": "https://registry.npmjs.org/joi/-/joi-18.0.2.tgz", - "integrity": "sha512-RuCOQMIt78LWnktPoeBL0GErkNaJPTBGcYuyaBvUOQSpcpcLfWrHPPihYdOGbV5pam9VTWbeoF7TsGiHugcjGA==", + "version": "18.1.2", + "resolved": "https://registry.npmjs.org/joi/-/joi-18.1.2.tgz", + "integrity": "sha512-rF5MAmps5esSlhCA+N1b6IYHDw9j/btzGaqfgie522jS02Ju/HXBxamlXVlKEHAxoMKQL77HWI8jlqWsFuekZA==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -33536,7 +33503,7 @@ "@hapi/pinpoint": "^2.0.1", "@hapi/tlds": "^1.1.1", "@hapi/topo": "^6.0.2", - "@standard-schema/spec": "^1.0.0" + "@standard-schema/spec": "^1.1.0" }, "engines": { "node": ">= 20" @@ -33617,13 +33584,6 @@ "node": ">=4.0.0" } }, - "node_modules/jsbn": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", - "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", - "dev": true, - "license": "MIT" - }, "node_modules/jsdoc-type-pratt-parser": { "version": "4.8.0", "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-4.8.0.tgz", @@ -33635,36 +33595,36 @@ } }, "node_modules/jsdom": { - "version": "28.1.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-28.1.0.tgz", - "integrity": "sha512-0+MoQNYyr2rBHqO1xilltfDjV9G7ymYGlAUazgcDLQaUf8JDHbuGwsxN6U9qWaElZ4w1B2r7yEGIL3GdeW3Rug==", + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-29.0.2.tgz", + "integrity": "sha512-9VnGEBosc/ZpwyOsJBCQ/3I5p7Q5ngOY14a9bf5btenAORmZfDse1ZEheMiWcJ3h81+Fv7HmJFdS0szo/waF2w==", "dev": true, "license": "MIT", "dependencies": { - "@acemir/cssom": "^0.9.31", - "@asamuzakjp/dom-selector": "^6.8.1", + "@asamuzakjp/css-color": "^5.1.5", + "@asamuzakjp/dom-selector": "^7.0.6", "@bramus/specificity": "^2.4.2", - "@exodus/bytes": "^1.11.0", - "cssstyle": "^6.0.1", + "@csstools/css-syntax-patches-for-csstree": "^1.1.1", + "@exodus/bytes": "^1.15.0", + "css-tree": "^3.2.1", "data-urls": "^7.0.0", "decimal.js": "^10.6.0", "html-encoding-sniffer": "^6.0.0", - "http-proxy-agent": "^7.0.2", - "https-proxy-agent": "^7.0.6", "is-potential-custom-element-name": "^1.0.1", + "lru-cache": "^11.2.7", "parse5": "^8.0.0", "saxes": "^6.0.0", "symbol-tree": "^3.2.4", - "tough-cookie": "^6.0.0", - "undici": "^7.21.0", + "tough-cookie": "^6.0.1", + "undici": "^7.24.5", "w3c-xmlserializer": "^5.0.0", "webidl-conversions": "^8.0.1", "whatwg-mimetype": "^5.0.0", - "whatwg-url": "^16.0.0", + "whatwg-url": "^16.0.1", "xml-name-validator": "^5.0.0" }, "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + "node": "^20.19.0 || ^22.13.0 || >=24.0.0" }, "peerDependencies": { "canvas": "^3.0.0" @@ -33675,10 +33635,35 @@ } } }, + "node_modules/jsdom/node_modules/@csstools/css-syntax-patches-for-csstree": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.2.tgz", + "integrity": "sha512-5GkLzz4prTIpoyeUiIu3iV6CSG3Plo7xRVOFPKI7FVEJ3mZ0A8SwK0XU3Gl7xAkiQ+mDyam+NNp875/C5y+jSA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "peerDependencies": { + "css-tree": "^3.2.1" + }, + "peerDependenciesMeta": { + "css-tree": { + "optional": true + } + } + }, "node_modules/jsdom/node_modules/@exodus/bytes": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.11.0.tgz", - "integrity": "sha512-wO3vd8nsEHdumsXrjGO/v4p6irbg7hy9kvIeR6i2AwylZSk4HJdWgL0FNaVquW1+AweJcdvU1IEpuIWk/WaPnA==", + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.15.0.tgz", + "integrity": "sha512-UY0nlA+feH81UGSHv92sLEPLCeZFjXOuHhrIo0HQydScuQc8s0A7kL/UdgwgDq8g8ilksmuoF35YVTNphV2aBQ==", "dev": true, "license": "MIT", "engines": { @@ -33694,28 +33679,32 @@ } }, "node_modules/jsdom/node_modules/@noble/hashes": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", - "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.0.1.tgz", + "integrity": "sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw==", "dev": true, "license": "MIT", "optional": true, "peer": true, "engines": { - "node": "^14.21.3 || >=16" + "node": ">= 20.19.0" }, "funding": { "url": "https://paulmillr.com/funding/" } }, - "node_modules/jsdom/node_modules/agent-base": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", - "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", + "node_modules/jsdom/node_modules/css-tree": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", + "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", "dev": true, "license": "MIT", + "dependencies": { + "mdn-data": "2.27.1", + "source-map-js": "^1.2.1" + }, "engines": { - "node": ">= 14" + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" } }, "node_modules/jsdom/node_modules/entities": { @@ -33731,33 +33720,22 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, - "node_modules/jsdom/node_modules/http-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", - "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "node_modules/jsdom/node_modules/lru-cache": { + "version": "11.2.7", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", + "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==", "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" - }, + "license": "BlueOak-1.0.0", "engines": { - "node": ">= 14" + "node": "20 || >=22" } }, - "node_modules/jsdom/node_modules/https-proxy-agent": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", - "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "node_modules/jsdom/node_modules/mdn-data": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", + "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" - } + "license": "CC0-1.0" }, "node_modules/jsdom/node_modules/parse5": { "version": "8.0.0", @@ -33773,29 +33751,29 @@ } }, "node_modules/jsdom/node_modules/tldts": { - "version": "7.0.16", - "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.16.tgz", - "integrity": "sha512-5bdPHSwbKTeHmXrgecID4Ljff8rQjv7g8zKQPkCozRo2HWWni+p310FSn5ImI+9kWw9kK4lzOB5q/a6iv0IJsw==", + "version": "7.0.27", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.27.tgz", + "integrity": "sha512-I4FZcVFcqCRuT0ph6dCDpPuO4Xgzvh+spkcTr1gK7peIvxWauoloVO0vuy1FQnijT63ss6AsHB6+OIM4aXHbPg==", "dev": true, "license": "MIT", "dependencies": { - "tldts-core": "^7.0.16" + "tldts-core": "^7.0.27" }, "bin": { "tldts": "bin/cli.js" } }, "node_modules/jsdom/node_modules/tldts-core": { - "version": "7.0.16", - "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.16.tgz", - "integrity": "sha512-XHhPmHxphLi+LGbH0G/O7dmUH9V65OY20R7vH8gETHsp5AZCjBk9l8sqmRKLaGOxnETU7XNSDUPtewAy/K6jbA==", + "version": "7.0.27", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.27.tgz", + "integrity": "sha512-YQ7uPjgWUibIK6DW5lrKujGwUKhLevU4hcGbP5O6TcIUb+oTjJYJVWPS4nZsIHrEEEG6myk/oqAJUEQmpZrHsg==", "dev": true, "license": "MIT" }, "node_modules/jsdom/node_modules/tough-cookie": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.0.tgz", - "integrity": "sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.1.tgz", + "integrity": "sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -33861,20 +33839,22 @@ "license": "MIT" }, "node_modules/json-parse-even-better-errors": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.2.tgz", - "integrity": "sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-5.0.0.tgz", + "integrity": "sha512-ZF1nxZ28VhQouRWhUcVlUIN3qwSgPuswK05s/HIaoetAoE/9tngVmCHjSxmSQPav1nd+lPtTL0YZ/2AFdR/iYQ==", "dev": true, "license": "MIT", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/json-schema": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", - "license": "(AFL-2.1 OR BSD-3-Clause)" + "dev": true, + "license": "(AFL-2.1 OR BSD-3-Clause)", + "peer": true }, "node_modules/json-schema-compare": { "version": "0.2.2", @@ -34013,9 +33993,9 @@ } }, "node_modules/jspdf": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/jspdf/-/jspdf-4.2.0.tgz", - "integrity": "sha512-hR/hnRevAXXlrjeqU5oahOE+Ln9ORJUB5brLHHqH67A+RBQZuFr5GkbI9XQI8OUFSEezKegsi45QRpc4bGj75Q==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/jspdf/-/jspdf-4.2.1.tgz", + "integrity": "sha512-YyAXyvnmjTbR4bHQRLzex3CuINCDlQnBqoSYyjJwTP2x9jDLuKDzy7aKUl0hgx3uhcl7xzg32agn5vlie6HIlQ==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.28.6", @@ -34215,23 +34195,22 @@ "license": "Apache-2.0" }, "node_modules/lerna": { - "version": "8.2.4", - "resolved": "https://registry.npmjs.org/lerna/-/lerna-8.2.4.tgz", - "integrity": "sha512-0gaVWDIVT7fLfprfwpYcQajb7dBJv3EGavjG7zvJ+TmGx3/wovl5GklnSwM2/WeE0Z2wrIz7ndWhBcDUHVjOcQ==", + "version": "9.0.7", + "resolved": "https://registry.npmjs.org/lerna/-/lerna-9.0.7.tgz", + "integrity": "sha512-PMjbSWYfwL1yZ5c1D2PZuFyzmtYhLdn0f76uG8L25g6eYy34j+2jPb4Q6USx1UJvxVtxkdVEeAAWS/WxgJ8VZA==", "dev": true, "license": "MIT", "dependencies": { - "@lerna/create": "8.2.4", - "@npmcli/arborist": "7.5.4", - "@npmcli/package-json": "5.2.0", - "@npmcli/run-script": "8.1.0", - "@nx/devkit": ">=17.1.2 < 21", + "@npmcli/arborist": "9.1.6", + "@npmcli/package-json": "7.0.2", + "@npmcli/run-script": "10.0.3", + "@nx/devkit": ">=21.5.2 < 23.0.0", "@octokit/plugin-enterprise-rest": "6.0.1", "@octokit/rest": "20.1.2", "aproba": "2.0.0", "byte-size": "8.1.1", "chalk": "4.1.0", - "clone-deep": "4.0.1", + "ci-info": "4.3.1", "cmd-shim": "6.0.3", "color-support": "1.1.3", "columnify": "1.6.0", @@ -34244,60 +34223,48 @@ "envinfo": "7.13.0", "execa": "5.0.0", "fs-extra": "^11.2.0", - "get-port": "5.1.1", "get-stream": "6.0.0", "git-url-parse": "14.0.0", "glob-parent": "6.0.2", - "graceful-fs": "4.2.11", "has-unicode": "2.0.1", "import-local": "3.1.0", "ini": "^1.3.8", - "init-package-json": "6.0.3", - "inquirer": "^8.2.4", + "init-package-json": "8.2.2", + "inquirer": "12.9.6", "is-ci": "3.0.1", - "is-stream": "2.0.0", - "jest-diff": ">=29.4.3 < 30", - "js-yaml": "4.1.0", - "libnpmaccess": "8.0.6", - "libnpmpublish": "9.0.9", + "jest-diff": ">=30.0.0 < 31", + "js-yaml": "4.1.1", + "libnpmaccess": "10.0.3", + "libnpmpublish": "11.1.2", "load-json-file": "6.2.0", - "make-dir": "4.0.0", - "minimatch": "3.0.5", - "multimatch": "5.0.0", - "node-fetch": "2.6.7", - "npm-package-arg": "11.0.2", - "npm-packlist": "8.0.2", - "npm-registry-fetch": "^17.1.0", - "nx": ">=17.1.2 < 21", + "make-fetch-happen": "15.0.2", + "minimatch": "3.1.4", + "npm-package-arg": "13.0.1", + "npm-packlist": "10.0.3", + "npm-registry-fetch": "19.1.0", + "nx": ">=21.5.3 < 23.0.0", "p-map": "4.0.0", "p-map-series": "2.1.0", "p-pipe": "3.1.0", "p-queue": "6.6.2", "p-reduce": "2.1.0", "p-waterfall": "2.1.1", - "pacote": "^18.0.6", - "pify": "5.0.0", + "pacote": "21.0.1", "read-cmd-shim": "4.0.0", - "resolve-from": "5.0.0", - "rimraf": "^4.4.1", - "semver": "^7.3.8", - "set-blocking": "^2.0.0", + "semver": "7.7.2", "signal-exit": "3.0.7", "slash": "3.0.0", - "ssri": "^10.0.6", + "ssri": "12.0.0", "string-width": "^4.2.3", - "tar": "6.2.1", - "temp-dir": "1.0.0", + "tar": "7.5.11", "through": "2.3.8", "tinyglobby": "0.2.12", "typescript": ">=3 < 6", "upath": "2.0.1", - "uuid": "^10.0.0", "validate-npm-package-license": "3.0.4", - "validate-npm-package-name": "5.0.1", + "validate-npm-package-name": "6.0.2", "wide-align": "1.1.5", "write-file-atomic": "5.0.1", - "write-pkg": "4.0.0", "yargs": "17.7.2", "yargs-parser": "21.1.1" }, @@ -34305,7 +34272,30 @@ "lerna": "dist/cli.js" }, "engines": { - "node": ">=18.0.0" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/lerna/node_modules/@jest/diff-sequences": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.3.0.tgz", + "integrity": "sha512-cG51MVnLq1ecVUaQ3fr6YuuAOitHK1S4WUJHnsPFE/quQr33ADUx1FfrTCpMCRxvy0Yr9BThKpDjSlcTi91tMA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/lerna/node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/lerna/node_modules/@octokit/auth-token": { @@ -34475,6 +34465,13 @@ "@octokit/openapi-types": "^24.2.0" } }, + "node_modules/lerna/node_modules/@sinclair/typebox": { + "version": "0.34.48", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.48.tgz", + "integrity": "sha512-kKJTNuK3AQOrgjjotVxMrCn1sUJwM76wMszfq1kdU4uYVJjvEWuFQ6HgvLt4Xz3fSmZlTOxJ/Ie13KnIcWQXFA==", + "dev": true, + "license": "MIT" + }, "node_modules/lerna/node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -34506,16 +34503,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/lerna/node_modules/cli-width": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", - "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">= 10" - } - }, "node_modules/lerna/node_modules/cosmiconfig": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", @@ -34661,33 +34648,49 @@ "license": "ISC" }, "node_modules/lerna/node_modules/inquirer": { - "version": "8.2.7", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.7.tgz", - "integrity": "sha512-UjOaSel/iddGZJ5xP/Eixh6dY1XghiBw4XK13rCCIJcJfyhhoul/7KhLLUGtebEj6GDYM6Vnx/mVsjx2L/mFIA==", + "version": "12.9.6", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-12.9.6.tgz", + "integrity": "sha512-603xXOgyfxhuis4nfnWaZrMaotNT0Km9XwwBNWUKbIDqeCY89jGr2F9YPEMiNhU6XjIP4VoWISMBFfcc5NgrTw==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/external-editor": "^1.0.0", - "ansi-escapes": "^4.2.1", - "chalk": "^4.1.1", - "cli-cursor": "^3.1.0", - "cli-width": "^3.0.0", - "figures": "^3.0.0", - "lodash": "^4.17.21", - "mute-stream": "0.0.8", - "ora": "^5.4.1", - "run-async": "^2.4.0", - "rxjs": "^7.5.5", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0", - "through": "^2.3.6", - "wrap-ansi": "^6.0.1" + "@inquirer/ansi": "^1.0.0", + "@inquirer/core": "^10.2.2", + "@inquirer/prompts": "^7.8.6", + "@inquirer/type": "^3.0.8", + "mute-stream": "^2.0.0", + "run-async": "^4.0.5", + "rxjs": "^7.8.2" }, "engines": { - "node": ">=12.0.0" + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, - "node_modules/lerna/node_modules/inquirer/node_modules/chalk": { + "node_modules/lerna/node_modules/jest-diff": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.3.0.tgz", + "integrity": "sha512-n3q4PDQjS4LrKxfWB3Z5KNk1XjXtZTBwQp71OP0Jo03Z6V60x++K5L8k6ZrW8MY8pOFylZvHM0zsjS1RqlHJZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/diff-sequences": "30.3.0", + "@jest/get-type": "30.1.0", + "chalk": "^4.1.2", + "pretty-format": "30.3.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/lerna/node_modules/jest-diff/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", @@ -34704,33 +34707,10 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/lerna/node_modules/is-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", - "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/lerna/node_modules/is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/lerna/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "dev": true, "license": "MIT", "dependencies": { @@ -34740,81 +34720,14 @@ "js-yaml": "bin/js-yaml.js" } }, - "node_modules/lerna/node_modules/log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lerna/node_modules/make-dir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", - "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^7.5.3" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lerna/node_modules/minimatch": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.5.tgz", - "integrity": "sha512-tUpxzX0VAzJHjLu0xUfFv1gwVp9ba3IOuRAVH2EGuRW8a5emA2FlACLqiT/lDVtS1W+TGNwqz3sWaNyLgDJWuw==", + "node_modules/lerna/node_modules/mute-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-2.0.0.tgz", + "integrity": "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==", "dev": true, "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, "engines": { - "node": "*" - } - }, - "node_modules/lerna/node_modules/mute-stream": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", - "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", - "dev": true, - "license": "ISC" - }, - "node_modules/lerna/node_modules/ora": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", - "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "bl": "^4.1.0", - "chalk": "^4.1.0", - "cli-cursor": "^3.1.0", - "cli-spinners": "^2.5.0", - "is-interactive": "^1.0.0", - "is-unicode-supported": "^0.1.0", - "log-symbols": "^4.1.0", - "strip-ansi": "^6.0.0", - "wcwidth": "^1.0.1" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/lerna/node_modules/p-queue": { @@ -34848,9 +34761,9 @@ } }, "node_modules/lerna/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", "engines": { @@ -34860,29 +34773,57 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/lerna/node_modules/pify": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-5.0.0.tgz", - "integrity": "sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA==", + "node_modules/lerna/node_modules/pretty-format": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.3.0.tgz", + "integrity": "sha512-oG4T3wCbfeuvljnyAzhBvpN45E8iOTXCU/TD3zXW80HA3dQ4ahdqMkWGiPWZvjpQwlbyHrPTWUAqUzGzv4l1JQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "30.0.5", + "ansi-styles": "^5.2.0", + "react-is": "^18.3.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/lerna/node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, "node_modules/lerna/node_modules/run-async": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", - "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-4.0.6.tgz", + "integrity": "sha512-IoDlSLTs3Yq593mb3ZoKWKXMNu3UpObxhgA/Xuid5p4bbfi2jdY1Hj0m1K+0/tEuQTxIGMhQDqGjKb7RuxGpAQ==", "dev": true, "license": "MIT", "engines": { "node": ">=0.12.0" } }, + "node_modules/lerna/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/lerna/node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -34893,19 +34834,6 @@ "node": ">=8" } }, - "node_modules/lerna/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/lerna/node_modules/tinyglobby": { "version": "0.2.12", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.12.tgz", @@ -34930,20 +34858,6 @@ "dev": true, "license": "ISC" }, - "node_modules/lerna/node_modules/uuid": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", - "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", - "dev": true, - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" - } - }, "node_modules/leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", @@ -34969,37 +34883,37 @@ } }, "node_modules/libnpmaccess": { - "version": "8.0.6", - "resolved": "https://registry.npmjs.org/libnpmaccess/-/libnpmaccess-8.0.6.tgz", - "integrity": "sha512-uM8DHDEfYG6G5gVivVl+yQd4pH3uRclHC59lzIbSvy7b5FEwR+mU49Zq1jEyRtRFv7+M99mUW9S0wL/4laT4lw==", + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/libnpmaccess/-/libnpmaccess-10.0.3.tgz", + "integrity": "sha512-JPHTfWJxIK+NVPdNMNGnkz4XGX56iijPbe0qFWbdt68HL+kIvSzh+euBL8npLZvl2fpaxo+1eZSdoG15f5YdIQ==", "dev": true, "license": "ISC", "dependencies": { - "npm-package-arg": "^11.0.2", - "npm-registry-fetch": "^17.0.1" + "npm-package-arg": "^13.0.0", + "npm-registry-fetch": "^19.0.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/libnpmpublish": { - "version": "9.0.9", - "resolved": "https://registry.npmjs.org/libnpmpublish/-/libnpmpublish-9.0.9.tgz", - "integrity": "sha512-26zzwoBNAvX9AWOPiqqF6FG4HrSCPsHFkQm7nT+xU1ggAujL/eae81RnCv4CJ2In9q9fh10B88sYSzKCUh/Ghg==", + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/libnpmpublish/-/libnpmpublish-11.1.2.tgz", + "integrity": "sha512-tNcU3cLH7toloAzhOOrBDhjzgbxpyuYvkf+BPPnnJCdc5EIcdJ8JcT+SglvCQKKyZ6m9dVXtCVlJcA6csxKdEA==", "dev": true, "license": "ISC", "dependencies": { + "@npmcli/package-json": "^7.0.0", "ci-info": "^4.0.0", - "normalize-package-data": "^6.0.1", - "npm-package-arg": "^11.0.2", - "npm-registry-fetch": "^17.0.1", - "proc-log": "^4.2.0", + "npm-package-arg": "^13.0.0", + "npm-registry-fetch": "^19.0.0", + "proc-log": "^5.0.0", "semver": "^7.3.7", - "sigstore": "^2.2.0", - "ssri": "^10.0.6" + "sigstore": "^4.0.0", + "ssri": "^12.0.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/lie": { @@ -35400,15 +35314,15 @@ } }, "node_modules/lodash": { - "version": "4.17.23", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", - "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", "license": "MIT" }, "node_modules/lodash-es": { - "version": "4.17.23", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.23.tgz", - "integrity": "sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==", + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.18.1.tgz", + "integrity": "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==", "license": "MIT" }, "node_modules/lodash.debounce": { @@ -35455,7 +35369,6 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", - "dev": true, "license": "MIT" }, "node_modules/lodash.merge": { @@ -35695,27 +35608,36 @@ "license": "ISC" }, "node_modules/make-fetch-happen": { - "version": "13.0.1", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-13.0.1.tgz", - "integrity": "sha512-cKTUFc/rbKUd/9meOvgrpJ2WrNzymt6jfRDdwg5UCnVzv9dTpEj9JS5m3wtziXVCjluIXyL8pcaukYqezIzZQA==", + "version": "15.0.2", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-15.0.2.tgz", + "integrity": "sha512-sI1NY4lWlXBAfjmCtVWIIpBypbBdhHtcjnwnv+gtCnsaOffyFil3aidszGC8hgzJe+fT1qix05sWxmD/Bmf/oQ==", "dev": true, "license": "ISC", "dependencies": { - "@npmcli/agent": "^2.0.0", - "cacache": "^18.0.0", + "@npmcli/agent": "^4.0.0", + "cacache": "^20.0.1", "http-cache-semantics": "^4.1.1", - "is-lambda": "^1.0.1", "minipass": "^7.0.2", - "minipass-fetch": "^3.0.0", + "minipass-fetch": "^4.0.0", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", - "negotiator": "^0.6.3", - "proc-log": "^4.2.0", + "negotiator": "^1.0.0", + "proc-log": "^5.0.0", "promise-retry": "^2.0.1", - "ssri": "^10.0.0" + "ssri": "^12.0.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/make-fetch-happen/node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" } }, "node_modules/makeerror": { @@ -35749,18 +35671,18 @@ "license": "MIT" }, "node_modules/mapbox-gl": { - "version": "3.19.0", - "resolved": "https://registry.npmjs.org/mapbox-gl/-/mapbox-gl-3.19.0.tgz", - "integrity": "sha512-SFObIgdxN0b6hZNsRxSUmQWdVW9q9GM2gw4McgFbycyhekew7BZIh8V57pEERDWlI9x/5SxxraTit5Cf0hm9OA==", + "version": "3.22.0", + "resolved": "https://registry.npmjs.org/mapbox-gl/-/mapbox-gl-3.22.0.tgz", + "integrity": "sha512-ZIpF+oAMcQoDlvABmiRkHoydyBR9zI6CyDeVRa2/iyua0/B2+rPuIzoCV/CgN14P5F0HVk53GIZw220WSqJPyA==", "license": "SEE LICENSE IN LICENSE.txt", "workspaces": [ "src/style-spec", + "packages/pmtiles-provider", "test/build/vite", "test/build/webpack", "test/build/typings" ], "dependencies": { - "@mapbox/jsonlint-lines-primitives": "^2.0.2", "@mapbox/mapbox-gl-supported": "^3.0.0", "@mapbox/point-geometry": "^1.1.0", "@mapbox/tiny-sdf": "^2.0.6", @@ -35768,7 +35690,6 @@ "@mapbox/vector-tile": "^2.0.4", "@types/geojson": "^7946.0.16", "@types/geojson-vt": "^3.2.5", - "@types/mapbox__point-geometry": "^1.0.87", "@types/pbf": "^3.0.5", "@types/supercluster": "^7.1.3", "cheap-ruler": "^4.0.0", @@ -35822,11 +35743,84 @@ "pbf": "bin/pbf" } }, - "node_modules/mapbox-gl/node_modules/quickselect": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-3.0.0.tgz", - "integrity": "sha512-XdjUArbK4Bm5fLLvlm5KpTFOiOThgfWWI4axAZDWg4E/0mKdZyI9tNEfds27qCi1ze/vwTR16kvmmGhRra3c2g==", - "license": "ISC" + "node_modules/maplibre-gl": { + "version": "5.22.0", + "resolved": "https://registry.npmjs.org/maplibre-gl/-/maplibre-gl-5.22.0.tgz", + "integrity": "sha512-nc8YA+YSEioMZg5W0cb6Cf3wQ8aJge66dsttyBgpOArOnlmFJO1Kc5G32kYVPeUYhLpBja83T99uanmJvYAIyQ==", + "license": "BSD-3-Clause", + "optional": true, + "peer": true, + "dependencies": { + "@mapbox/jsonlint-lines-primitives": "^2.0.2", + "@mapbox/point-geometry": "^1.1.0", + "@mapbox/tiny-sdf": "^2.0.7", + "@mapbox/unitbezier": "^0.0.1", + "@mapbox/vector-tile": "^2.0.4", + "@mapbox/whoots-js": "^3.1.0", + "@maplibre/geojson-vt": "^6.0.4", + "@maplibre/maplibre-gl-style-spec": "^24.8.1", + "@maplibre/mlt": "^1.1.8", + "@maplibre/vt-pbf": "^4.3.0", + "@types/geojson": "^7946.0.16", + "earcut": "^3.0.2", + "gl-matrix": "^3.4.4", + "kdbush": "^4.0.2", + "murmurhash-js": "^1.0.0", + "pbf": "^4.0.1", + "potpack": "^2.1.0", + "quickselect": "^3.0.0", + "tinyqueue": "^3.0.0" + }, + "engines": { + "node": ">=16.14.0", + "npm": ">=8.1.0" + }, + "funding": { + "url": "https://github.com/maplibre/maplibre-gl-js?sponsor=1" + } + }, + "node_modules/maplibre-gl/node_modules/@mapbox/point-geometry": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@mapbox/point-geometry/-/point-geometry-1.1.0.tgz", + "integrity": "sha512-YGcBz1cg4ATXDCM/71L9xveh4dynfGmcLDqufR+nQQy3fKwsAZsWd/x4621/6uJaeB9mwOHE6hPeDgXz9uViUQ==", + "license": "ISC", + "optional": true, + "peer": true + }, + "node_modules/maplibre-gl/node_modules/@mapbox/vector-tile": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@mapbox/vector-tile/-/vector-tile-2.0.4.tgz", + "integrity": "sha512-AkOLcbgGTdXScosBWwmmD7cDlvOjkg/DetGva26pIRiZPdeJYjYKarIlb4uxVzi6bwHO6EWH82eZ5Nuv4T5DUg==", + "license": "BSD-3-Clause", + "optional": true, + "peer": true, + "dependencies": { + "@mapbox/point-geometry": "~1.1.0", + "@types/geojson": "^7946.0.16", + "pbf": "^4.0.1" + } + }, + "node_modules/maplibre-gl/node_modules/earcut": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/earcut/-/earcut-3.0.2.tgz", + "integrity": "sha512-X7hshQbLyMJ/3RPhyObLARM2sNxxmRALLKx1+NVFFnQ9gKzmCrxm9+uLIAdBcvc8FNLpctqlQ2V6AE92Ol9UDQ==", + "license": "ISC", + "optional": true, + "peer": true + }, + "node_modules/maplibre-gl/node_modules/pbf": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pbf/-/pbf-4.0.1.tgz", + "integrity": "sha512-SuLdBvS42z33m8ejRbInMapQe8n0D3vN/Xd5fmWM3tufNgRQFBpaW2YVJxQZV4iPNqb0vEFvssMEo5w9c6BTIA==", + "license": "BSD-3-Clause", + "optional": true, + "peer": true, + "dependencies": { + "resolve-protobuf-schema": "^2.1.0" + }, + "bin": { + "pbf": "bin/pbf" + } }, "node_modules/markdown-table": { "version": "3.0.4", @@ -35839,9 +35833,9 @@ } }, "node_modules/markdown-to-jsx": { - "version": "9.7.6", - "resolved": "https://registry.npmjs.org/markdown-to-jsx/-/markdown-to-jsx-9.7.6.tgz", - "integrity": "sha512-oPckbBhWv/d2HmYzSv68g2UBTONmrFYlqUd+juolxTplJImhDEKFgAEcnxSTeZ1HISKzKxm+mVeUYP7OUhglJQ==", + "version": "9.7.16", + "resolved": "https://registry.npmjs.org/markdown-to-jsx/-/markdown-to-jsx-9.7.16.tgz", + "integrity": "sha512-+LEgOlYfUEB9i2Oaxasec9H2HytB1+SQcgwFmQiNTKwe8cQ2E9bDNgePGq6ChIycMxtpcEY0g44aQ3uJoMw8eg==", "license": "MIT", "engines": { "node": ">= 18" @@ -35884,9 +35878,9 @@ "license": "Unlicense" }, "node_modules/match-sorter": { - "version": "6.3.4", - "resolved": "https://registry.npmjs.org/match-sorter/-/match-sorter-6.3.4.tgz", - "integrity": "sha512-jfZW7cWS5y/1xswZo8VBOdudUiSd9nifYRWphc9M5D/ee4w4AoXLgBEdRbgVaxbMuagBPeUC5y2Hi8DO6o9aDg==", + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/match-sorter/-/match-sorter-8.2.0.tgz", + "integrity": "sha512-qRVB7wYMJXizAWR4TKo5UYwgW7oAVzA8V9jve0wGzRvV91ou9dcqL+/2gJtD0PZ/Pm2Fq6cVT4VHXHmDFVMGRA==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.23.8", @@ -36066,6 +36060,8 @@ "resolved": "https://registry.npmjs.org/mem-fs-editor/-/mem-fs-editor-11.1.4.tgz", "integrity": "sha512-Z4QX14Ev6eOVTuVSayS5rdiOua6C3gHcFw+n9Qc7WiaVTbC+H8b99c32MYGmbQN9UFHJeI/p3lf3LAxiIzwEmA==", "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "@types/ejs": "^3.1.4", "@types/node": ">=18", @@ -36096,6 +36092,8 @@ "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-4.0.0.tgz", "integrity": "sha512-Q6VPTLMsmXZ47ENG3V+wQyZS1ZxXMxFyYzA+Z/GMrJ6yIutAIEf9wTyroTzmGjNfox9/h3GdGBCVh43GVFx4Uw==", "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, @@ -36108,6 +36106,8 @@ "resolved": "https://registry.npmjs.org/array-union/-/array-union-3.0.1.tgz", "integrity": "sha512-1OvF9IbWwaeiM9VhzYXVQacMibxpXOMYVNIvMtKRyX9SImBXpKcFr8XvFDeEslCyuH/t6KRt7HEO94AlP8Iatw==", "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">=12" }, @@ -36120,15 +36120,19 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": "18 || 20 || >=22" } }, "node_modules/mem-fs-editor/node_modules/brace-expansion": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz", - "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "balanced-match": "^4.0.2" }, @@ -36141,6 +36145,8 @@ "resolved": "https://registry.npmjs.org/globby/-/globby-14.0.2.tgz", "integrity": "sha512-s3Fq41ZVh7vbbe2PN3nrW7yC7U7MFVc5c98/iTl9c2GawNMKx/J648KQRW6WKkuU8GIbbh2IXfIRQjOZnXcTnw==", "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "@sindresorhus/merge-streams": "^2.1.0", "fast-glob": "^3.3.2", @@ -36161,6 +36167,8 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.7.tgz", "integrity": "sha512-MOwgjc8tfrpn5QQEvjijjmDVtMw2oL88ugTevzxQnzRLm6l3fVEF2gzU0kYeYYKD8C66+IdGX6peJ4MyUlUnPg==", "license": "ISC", + "optional": true, + "peer": true, "dependencies": { "brace-expansion": "^5.0.2" }, @@ -36176,6 +36184,8 @@ "resolved": "https://registry.npmjs.org/multimatch/-/multimatch-7.0.0.tgz", "integrity": "sha512-SYU3HBAdF4psHEL/+jXDKHO95/m5P2RvboHT2Y0WtTttvJLP4H/2WS9WlQPFvF6C8d6SpLw8vjCnQOnVIVOSJQ==", "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "array-differ": "^4.0.0", "array-union": "^3.0.1", @@ -36193,6 +36203,8 @@ "resolved": "https://registry.npmjs.org/path-type/-/path-type-5.0.0.tgz", "integrity": "sha512-5HviZNaZcfqP95rwpv+1HDgUamezbqdSYTyzjTvwtJSnIH+3vnbmWsItli8OFEndS984VT55M3jduxZbX351gg==", "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">=12" }, @@ -36205,6 +36217,8 @@ "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">=14.16" }, @@ -36511,6 +36525,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, "license": "MIT" }, "node_modules/merge2": { @@ -36708,9 +36723,9 @@ } }, "node_modules/mini-css-extract-plugin": { - "version": "2.10.1", - "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.10.1.tgz", - "integrity": "sha512-k7G3Y5QOegl380tXmZ68foBRRjE9Ljavx835ObdvmZjQ639izvZD8CS7BkWw1qKPPzHsGL/JDhl0uyU1zc2rJw==", + "version": "2.10.2", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.10.2.tgz", + "integrity": "sha512-AOSS0IdEB95ayVkxn5oGzNQwqAi2J0Jb/kKm43t7H73s8+f5873g0yuj0PNvK4dO75mu5DHg4nlgp4k6Kga8eg==", "dev": true, "license": "MIT", "dependencies": { @@ -36772,10 +36787,11 @@ } }, "node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "license": "ISC", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", "engines": { "node": ">=16 || 14 >=14.17" } @@ -36794,56 +36810,36 @@ } }, "node_modules/minipass-fetch": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-3.0.5.tgz", - "integrity": "sha512-2N8elDQAtSnFV0Dk7gt15KHsS0Fyz6CbYZ360h0WTYV1Ty46li3rAXVOQj1THMNLdmrD9Vt5pBPtWtVkpwGBqg==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-4.0.1.tgz", + "integrity": "sha512-j7U11C5HXigVuutxebFadoYBbd7VSdZWggSe64NVdvWNBqGAiXPL2QVCehjmw7lY1oF9gOllYbORh+hiNgfPgQ==", "dev": true, "license": "MIT", "dependencies": { "minipass": "^7.0.3", "minipass-sized": "^1.0.3", - "minizlib": "^2.1.2" + "minizlib": "^3.0.1" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" }, "optionalDependencies": { "encoding": "^0.1.13" } }, "node_modules/minipass-flush": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", - "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.6.tgz", + "integrity": "sha512-7Uf5gMJZ2kTkFisE3toGxT991s+cg+vMh42nbZGM2bNxfYVpkpqRudf1QrcOy72a3PwcL4JYqL+4NY7t0Hdd0A==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { - "minipass": "^3.0.0" + "minipass": "^7.1.3" }, "engines": { - "node": ">= 8" + "node": ">=16 || 14 >=14.17" } }, - "node_modules/minipass-flush/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minipass-flush/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true, - "license": "ISC" - }, "node_modules/minipass-pipeline": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", @@ -36911,39 +36907,18 @@ "license": "ISC" }, "node_modules/minizlib": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", - "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", + "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", "dev": true, "license": "MIT", "dependencies": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" + "minipass": "^7.1.2" }, "engines": { - "node": ">= 8" + "node": ">= 18" } }, - "node_modules/minizlib/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minizlib/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true, - "license": "ISC" - }, "node_modules/mitt": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mitt/-/mitt-2.1.0.tgz", @@ -37050,36 +37025,6 @@ "multicast-dns": "cli.js" } }, - "node_modules/multimatch": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/multimatch/-/multimatch-5.0.0.tgz", - "integrity": "sha512-ypMKuglUrZUD99Tk2bUQ+xNQj43lPEfAeX2o9cTteAmShXy2VHDJpuwu1o0xqoKCt9jLVAvwyFKdLTPXKAfJyA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/minimatch": "^3.0.3", - "array-differ": "^3.0.0", - "array-union": "^2.1.0", - "arrify": "^2.0.1", - "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/multimatch/node_modules/arrify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", - "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/murmurhash-js": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/murmurhash-js/-/murmurhash-js-1.0.0.tgz", @@ -37100,14 +37045,15 @@ "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz", "integrity": "sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==", "license": "ISC", + "peer": true, "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, "node_modules/nanoid": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.6.tgz", - "integrity": "sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg==", + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.9.tgz", + "integrity": "sha512-ZUvP7KeBLe3OZ1ypw6dI/TzYJuvHP77IM4Ry73waSQTLn8/g8rpdjfyVAh7t1/+FjBtG4lCP42MEbDxOsRpBMw==", "funding": [ { "type": "github", @@ -37122,16 +37068,6 @@ "node": "^18 || >=20" } }, - "node_modules/nanospinner": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/nanospinner/-/nanospinner-1.2.2.tgz", - "integrity": "sha512-Zt/AmG6qRU3e+WnzGGLuMCEAO/dAu45stNbHY223tUxldaDAeE+FxSPsd9Q+j+paejmm0ZbrNVs5Sraqy3dRxA==", - "dev": true, - "license": "MIT", - "dependencies": { - "picocolors": "^1.1.1" - } - }, "node_modules/napi-postinstall": { "version": "0.2.5", "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.2.5.tgz", @@ -37239,162 +37175,91 @@ "semver": "bin/semver" } }, - "node_modules/node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, - "node_modules/node-fetch/node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "dev": true, - "license": "MIT" - }, - "node_modules/node-fetch/node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "dev": true, - "license": "BSD-2-Clause" - }, - "node_modules/node-fetch/node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "dev": true, - "license": "MIT", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, "node_modules/node-gyp": { - "version": "10.3.1", - "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-10.3.1.tgz", - "integrity": "sha512-Pp3nFHBThHzVtNY7U6JfPjvT/DTE8+o/4xKsLQtBoU+j2HLsGlhcfzflAoUreaJbNmYnX+LlLi0qjV8kpyO6xQ==", + "version": "12.2.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-12.2.0.tgz", + "integrity": "sha512-q23WdzrQv48KozXlr0U1v9dwO/k59NHeSzn6loGcasyf0UnSrtzs8kRxM+mfwJSf0DkX0s43hcqgnSO4/VNthQ==", "dev": true, "license": "MIT", "dependencies": { "env-paths": "^2.2.0", "exponential-backoff": "^3.1.1", - "glob": "^10.3.10", "graceful-fs": "^4.2.6", - "make-fetch-happen": "^13.0.0", - "nopt": "^7.0.0", - "proc-log": "^4.1.0", + "make-fetch-happen": "^15.0.0", + "nopt": "^9.0.0", + "proc-log": "^6.0.0", "semver": "^7.3.5", - "tar": "^6.2.1", - "which": "^4.0.0" + "tar": "^7.5.4", + "tinyglobby": "^0.2.12", + "which": "^6.0.0" }, "bin": { "node-gyp": "bin/node-gyp.js" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/node-gyp/node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/node-gyp/node_modules/brace-expansion": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz", - "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/node-gyp/node_modules/glob": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", - "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "node_modules/node-gyp/node_modules/abbrev": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-4.0.0.tgz", + "integrity": "sha512-a1wflyaL0tHtJSmLSOVybYhy22vRih4eduhhrkcjgrWGnRfrZtovJ2FRjxuTtkkj47O/baf0R86QU5OuYpz8fA==", "dev": true, "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "engines": { + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/node-gyp/node_modules/isexe": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.2.tgz", - "integrity": "sha512-mIcis6w+JiQf3P7t7mg/35GKB4T1FQsBOtMIvuKw4YErj5RjtbhcTd5/I30fmkmGMwvI0WlzSNN+27K0QCMkAw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-4.0.0.tgz", + "integrity": "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==", "dev": true, "license": "BlueOak-1.0.0", "engines": { "node": ">=20" } }, - "node_modules/node-gyp/node_modules/minimatch": { - "version": "9.0.7", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.7.tgz", - "integrity": "sha512-MOwgjc8tfrpn5QQEvjijjmDVtMw2oL88ugTevzxQnzRLm6l3fVEF2gzU0kYeYYKD8C66+IdGX6peJ4MyUlUnPg==", + "node_modules/node-gyp/node_modules/nopt": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-9.0.0.tgz", + "integrity": "sha512-Zhq3a+yFKrYwSBluL4H9XP3m3y5uvQkB/09CwDruCiRmR/UJYnn9W4R48ry0uGC70aeTPKLynBtscP9efFFcPw==", "dev": true, "license": "ISC", "dependencies": { - "brace-expansion": "^5.0.2" + "abbrev": "^4.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" }, "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/node-gyp/node_modules/proc-log": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz", + "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/node-gyp/node_modules/which": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", - "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/which/-/which-6.0.1.tgz", + "integrity": "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg==", "dev": true, "license": "ISC", "dependencies": { - "isexe": "^3.1.1" + "isexe": "^4.0.0" }, "bin": { "node-which": "bin/which.js" }, "engines": { - "node": "^16.13.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/node-int64": { @@ -37404,13 +37269,6 @@ "dev": true, "license": "MIT" }, - "node_modules/node-machine-id": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/node-machine-id/-/node-machine-id-1.1.12.tgz", - "integrity": "sha512-QNABxbrPa3qEIfrE6GOJ7BYIuignnJw7iQ2YPbc3Nla1HzRJjXzZOiikfF8m7eAMfichLt3M4VgLOetqgDmgGQ==", - "dev": true, - "license": "MIT" - }, "node_modules/node-preload": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz", @@ -37481,33 +37339,19 @@ } }, "node_modules/nopt": { - "version": "7.2.1", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.1.tgz", - "integrity": "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-8.1.0.tgz", + "integrity": "sha512-ieGu42u/Qsa4TFktmaKEwM6MQH0pOWnaB3htzh0JRtx84+Mebc0cbZYN5bC+6WTZ4+77xrL9Pn5m7CV6VIkV7A==", "dev": true, "license": "ISC", "dependencies": { - "abbrev": "^2.0.0" + "abbrev": "^3.0.0" }, "bin": { "nopt": "bin/nopt.js" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/normalize-package-data": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.2.tgz", - "integrity": "sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g==", - "license": "BSD-2-Clause", - "dependencies": { - "hosted-git-info": "^7.0.0", - "semver": "^7.3.5", - "validate-npm-package-license": "^3.0.4" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/normalize-path": { @@ -37520,104 +37364,161 @@ } }, "node_modules/npm-bundled": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-3.0.1.tgz", - "integrity": "sha512-+AvaheE/ww1JEwRHOrn4WHNzOxGtVp+adrg2AeZS/7KuxGUYFuBta98wYpfHBbJp6Tg6j1NKSEVHNcfZzJHQwQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-4.0.0.tgz", + "integrity": "sha512-IxaQZDMsqfQ2Lz37VvyyEtKLe8FsRZuysmedy/N06TU1RyVppYKXrO4xIhR0F+7ubIBox6Q7nir6fQI3ej39iA==", "dev": true, "license": "ISC", "dependencies": { - "npm-normalize-package-bin": "^3.0.0" + "npm-normalize-package-bin": "^4.0.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/npm-install-checks": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-6.3.0.tgz", - "integrity": "sha512-W29RiK/xtpCGqn6f3ixfRYGk+zRyr+Ew9F2E20BfXxT5/euLdA/Nm7fO7OeTGuAmTs30cpgInyJ0cYe708YTZw==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-7.1.2.tgz", + "integrity": "sha512-z9HJBCYw9Zr8BqXcllKIs5nI+QggAImbBdHphOzVYrz2CB4iQ6FzWyKmlqDZua+51nAu7FcemlbTc9VgQN5XDQ==", "dev": true, "license": "BSD-2-Clause", "dependencies": { "semver": "^7.1.1" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/npm-normalize-package-bin": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-3.0.1.tgz", - "integrity": "sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-4.0.0.tgz", + "integrity": "sha512-TZKxPvItzai9kN9H/TkmCtx/ZN/hvr3vUycjlfmH0ootY9yFBzNOpiXAdIn1Iteqsvk4lQn6B5PTrt+n6h8k/w==", "dev": true, "license": "ISC", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/npm-package-arg": { - "version": "11.0.2", - "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-11.0.2.tgz", - "integrity": "sha512-IGN0IAwmhDJwy13Wc8k+4PEbTPhpJnMtfR53ZbOyjkvmEcLS4nCwp6mvMWjS5sUjeiW3mpx6cHmuhKEu9XmcQw==", + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-13.0.1.tgz", + "integrity": "sha512-6zqls5xFvJbgFjB1B2U6yITtyGBjDBORB7suI4zA4T/sZ1OmkMFlaQSNB/4K0LtXNA1t4OprAFxPisadK5O2ag==", "dev": true, "license": "ISC", "dependencies": { - "hosted-git-info": "^7.0.0", - "proc-log": "^4.0.0", + "hosted-git-info": "^9.0.0", + "proc-log": "^5.0.0", "semver": "^7.3.5", - "validate-npm-package-name": "^5.0.0" + "validate-npm-package-name": "^6.0.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm-package-arg/node_modules/hosted-git-info": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-9.0.2.tgz", + "integrity": "sha512-M422h7o/BR3rmCQ8UHi7cyyMqKltdP9Uo+J2fXK+RSAY+wTcKOIRyhTuKv4qn+DJf3g+PL890AzId5KZpX+CBg==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^11.1.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm-package-arg/node_modules/lru-cache": { + "version": "11.2.7", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", + "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" } }, "node_modules/npm-packlist": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-8.0.2.tgz", - "integrity": "sha512-shYrPFIS/JLP4oQmAwDyk5HcyysKW8/JLTEA32S0Z5TzvpaeeX2yMFfoK1fjEBnCBvVyIB/Jj/GBFdm0wsgzbA==", + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-10.0.3.tgz", + "integrity": "sha512-zPukTwJMOu5X5uvm0fztwS5Zxyvmk38H/LfidkOMt3gbZVCyro2cD/ETzwzVPcWZA3JOyPznfUN/nkyFiyUbxg==", "dev": true, "license": "ISC", "dependencies": { - "ignore-walk": "^6.0.4" + "ignore-walk": "^8.0.0", + "proc-log": "^6.0.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm-packlist/node_modules/proc-log": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz", + "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm-pick-manifest": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-9.1.0.tgz", - "integrity": "sha512-nkc+3pIIhqHVQr085X9d2JzPzLyjzQS96zbruppqC9aZRm/x8xx6xhI98gHtsfELP2bE+loHq8ZaHFHhe+NauA==", + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-11.0.3.tgz", + "integrity": "sha512-buzyCfeoGY/PxKqmBqn1IUJrZnUi1VVJTdSSRPGI60tJdUhUoSQFhs0zycJokDdOznQentgrpf8LayEHyyYlqQ==", "dev": true, "license": "ISC", "dependencies": { - "npm-install-checks": "^6.0.0", - "npm-normalize-package-bin": "^3.0.0", - "npm-package-arg": "^11.0.0", + "npm-install-checks": "^8.0.0", + "npm-normalize-package-bin": "^5.0.0", + "npm-package-arg": "^13.0.0", "semver": "^7.3.5" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm-pick-manifest/node_modules/npm-install-checks": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-8.0.0.tgz", + "integrity": "sha512-ScAUdMpyzkbpxoNekQ3tNRdFI8SJ86wgKZSQZdUxT+bj0wVFpsEMWnkXP0twVe1gJyNF5apBWDJhhIbgrIViRA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "semver": "^7.1.1" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm-pick-manifest/node_modules/npm-normalize-package-bin": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-5.0.0.tgz", + "integrity": "sha512-CJi3OS4JLsNMmr2u07OJlhcrPxCeOeP/4xq67aWNai6TNWWbTrlNDgl8NcFKVlcBKp18GPj+EzbNIgrBfZhsag==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm-registry-fetch": { - "version": "17.1.0", - "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-17.1.0.tgz", - "integrity": "sha512-5+bKQRH0J1xG1uZ1zMNvxW0VEyoNWgJpY9UDuluPFLKDfJ9u2JmmjmTJV1srBGQOROfdBMiVvnH2Zvpbm+xkVA==", + "version": "19.1.0", + "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-19.1.0.tgz", + "integrity": "sha512-xyZLfs7TxPu/WKjHUs0jZOPinzBAI32kEUel6za0vH+JUTnFZ5zbHI1ZoGZRDm6oMjADtrli6FxtMlk/5ABPNw==", "dev": true, "license": "ISC", "dependencies": { - "@npmcli/redact": "^2.0.0", + "@npmcli/redact": "^3.0.0", "jsonparse": "^1.3.1", - "make-fetch-happen": "^13.0.0", + "make-fetch-happen": "^15.0.0", "minipass": "^7.0.2", - "minipass-fetch": "^3.0.0", - "minizlib": "^2.1.2", - "npm-package-arg": "^11.0.0", - "proc-log": "^4.0.0" + "minipass-fetch": "^4.0.0", + "minizlib": "^3.0.1", + "npm-package-arg": "^13.0.0", + "proc-log": "^5.0.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm-run-path": { @@ -37672,42 +37573,44 @@ "license": "MIT" }, "node_modules/nx": { - "version": "20.8.4", - "resolved": "https://registry.npmjs.org/nx/-/nx-20.8.4.tgz", - "integrity": "sha512-/++x0OM3/UTmDR+wmPeV13tSxeTr+QGzj3flgtH9DiOPmQnn2CjHWAMZiOhcSh/hHoE/V3ySL4757InQUsVtjQ==", + "version": "22.6.1", + "resolved": "https://registry.npmjs.org/nx/-/nx-22.6.1.tgz", + "integrity": "sha512-b4eo52o5aCVt3oG6LPYvD2Cul3JFBMgr2p9OjMBIo6oU6QfSR693H2/UuUMepLtO6jcIniPKOcIrf6Ue8aXAww==", "dev": true, "hasInstallScript": true, "license": "MIT", "dependencies": { + "@ltd/j-toml": "^1.38.0", "@napi-rs/wasm-runtime": "0.2.4", "@yarnpkg/lockfile": "^1.1.0", "@yarnpkg/parsers": "3.0.2", "@zkochan/js-yaml": "0.0.7", - "axios": "^1.8.3", - "chalk": "^4.1.0", + "axios": "^1.12.0", "cli-cursor": "3.1.0", "cli-spinners": "2.6.1", "cliui": "^8.0.1", "dotenv": "~16.4.5", "dotenv-expand": "~11.0.6", + "ejs": "^3.1.7", "enquirer": "~2.3.6", "figures": "3.2.0", "flat": "^5.0.2", "front-matter": "^4.0.2", - "ignore": "^5.0.4", - "jest-diff": "^29.4.1", + "ignore": "^7.0.5", + "jest-diff": "^30.0.2", "jsonc-parser": "3.2.0", "lines-and-columns": "2.0.3", - "minimatch": "9.0.3", - "node-machine-id": "1.1.12", + "minimatch": "10.2.4", "npm-run-path": "^4.0.1", "open": "^8.4.0", "ora": "5.3.0", + "picocolors": "^1.1.0", "resolve.exports": "2.0.3", - "semver": "^7.5.3", + "semver": "^7.6.3", "string-width": "^4.2.3", "tar-stream": "~2.2.0", "tmp": "~0.2.1", + "tree-kill": "^1.2.2", "tsconfig-paths": "^4.1.2", "tslib": "^2.3.0", "yaml": "^2.6.0", @@ -37719,20 +37622,20 @@ "nx-cloud": "bin/nx-cloud.js" }, "optionalDependencies": { - "@nx/nx-darwin-arm64": "20.8.4", - "@nx/nx-darwin-x64": "20.8.4", - "@nx/nx-freebsd-x64": "20.8.4", - "@nx/nx-linux-arm-gnueabihf": "20.8.4", - "@nx/nx-linux-arm64-gnu": "20.8.4", - "@nx/nx-linux-arm64-musl": "20.8.4", - "@nx/nx-linux-x64-gnu": "20.8.4", - "@nx/nx-linux-x64-musl": "20.8.4", - "@nx/nx-win32-arm64-msvc": "20.8.4", - "@nx/nx-win32-x64-msvc": "20.8.4" + "@nx/nx-darwin-arm64": "22.6.1", + "@nx/nx-darwin-x64": "22.6.1", + "@nx/nx-freebsd-x64": "22.6.1", + "@nx/nx-linux-arm-gnueabihf": "22.6.1", + "@nx/nx-linux-arm64-gnu": "22.6.1", + "@nx/nx-linux-arm64-musl": "22.6.1", + "@nx/nx-linux-x64-gnu": "22.6.1", + "@nx/nx-linux-x64-musl": "22.6.1", + "@nx/nx-win32-arm64-msvc": "22.6.1", + "@nx/nx-win32-x64-msvc": "22.6.1" }, "peerDependencies": { - "@swc-node/register": "^1.8.0", - "@swc/core": "^1.3.85" + "@swc-node/register": "^1.11.1", + "@swc/core": "^1.15.8" }, "peerDependenciesMeta": { "@swc-node/register": { @@ -37743,14 +37646,57 @@ } } }, - "node_modules/nx/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "node_modules/nx/node_modules/@jest/diff-sequences": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.3.0.tgz", + "integrity": "sha512-cG51MVnLq1ecVUaQ3fr6YuuAOitHK1S4WUJHnsPFE/quQr33ADUx1FfrTCpMCRxvy0Yr9BThKpDjSlcTi91tMA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/nx/node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0" + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/nx/node_modules/@sinclair/typebox": { + "version": "0.34.48", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.48.tgz", + "integrity": "sha512-kKJTNuK3AQOrgjjotVxMrCn1sUJwM76wMszfq1kdU4uYVJjvEWuFQ6HgvLt4Xz3fSmZlTOxJ/Ie13KnIcWQXFA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nx/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/nx/node_modules/brace-expansion": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" } }, "node_modules/nx/node_modules/chalk": { @@ -37783,73 +37729,74 @@ "node": ">=8.6" } }, - "node_modules/nx/node_modules/is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "node_modules/nx/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", "dev": true, "license": "MIT", "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 4" } }, - "node_modules/nx/node_modules/log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "node_modules/nx/node_modules/jest-diff": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.3.0.tgz", + "integrity": "sha512-n3q4PDQjS4LrKxfWB3Z5KNk1XjXtZTBwQp71OP0Jo03Z6V60x++K5L8k6ZrW8MY8pOFylZvHM0zsjS1RqlHJZQ==", "dev": true, "license": "MIT", "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" + "@jest/diff-sequences": "30.3.0", + "@jest/get-type": "30.1.0", + "chalk": "^4.1.2", + "pretty-format": "30.3.0" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/nx/node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^5.0.2" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/nx/node_modules/ora": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/ora/-/ora-5.3.0.tgz", - "integrity": "sha512-zAKMgGXUim0Jyd6CXK9lraBnD3H5yPGBPPOkC23a2BG6hsm4Zu6OQSjQuEtV0BHDf4aKHcUFvJiGRrFuW3MG8g==", + "node_modules/nx/node_modules/pretty-format": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.3.0.tgz", + "integrity": "sha512-oG4T3wCbfeuvljnyAzhBvpN45E8iOTXCU/TD3zXW80HA3dQ4ahdqMkWGiPWZvjpQwlbyHrPTWUAqUzGzv4l1JQ==", "dev": true, "license": "MIT", "dependencies": { - "bl": "^4.0.3", - "chalk": "^4.1.0", - "cli-cursor": "^3.1.0", - "cli-spinners": "^2.5.0", - "is-interactive": "^1.0.0", - "log-symbols": "^4.0.0", - "strip-ansi": "^6.0.0", - "wcwidth": "^1.0.1" + "@jest/schemas": "30.0.5", + "ansi-styles": "^5.2.0", + "react-is": "^18.3.1" }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/nx/node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, "node_modules/nyc": { @@ -38370,23 +38317,22 @@ } }, "node_modules/open-cli": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/open-cli/-/open-cli-8.0.0.tgz", - "integrity": "sha512-3muD3BbfLyzl+aMVSEfn2FfOqGdPYR0O4KNnxXsLEPE2q9OSjBfJAaB6XKbrUzLgymoSMejvb5jpXJfru/Ko2A==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/open-cli/-/open-cli-9.0.0.tgz", + "integrity": "sha512-4UHkLVm4tUM/ardg66uY3x1icgfCnunks5eFVFBzASO3b13Ow2Md3xs9YT7yXWFjXOBpauIeh/N9fvbziU1wkg==", "dev": true, "license": "MIT", "dependencies": { - "file-type": "^18.7.0", - "get-stdin": "^9.0.0", - "meow": "^12.1.1", - "open": "^10.0.0", - "tempy": "^3.1.0" + "file-type": "^21.3.4", + "meow": "^14.1.0", + "open": "^11.0.0", + "tempy": "^3.2.0" }, "bin": { "open-cli": "cli.js" }, "engines": { - "node": ">=18" + "node": ">=22" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -38405,49 +38351,35 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/open-cli/node_modules/is-wsl": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", - "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-inside-container": "^1.0.0" - }, - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/open-cli/node_modules/meow": { - "version": "12.1.1", - "resolved": "https://registry.npmjs.org/meow/-/meow-12.1.1.tgz", - "integrity": "sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw==", + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-14.1.0.tgz", + "integrity": "sha512-EDYo6VlmtnumlcBCbh1gLJ//9jvM/ndXHfVXIFrZVr6fGcwTUyCTFNTLCKuY3ffbK8L/+3Mzqnd58RojiZqHVw==", "dev": true, "license": "MIT", "engines": { - "node": ">=16.10" + "node": ">=20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/open-cli/node_modules/open": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/open/-/open-10.1.0.tgz", - "integrity": "sha512-mnkeQ1qP5Ue2wd+aivTD3NHd/lZ96Lu0jgf0pwktLPtx6cTZiH7tyeGRRHs0zX0rbrahXPnXlUnbeXyaBBuIaw==", + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/open/-/open-11.0.0.tgz", + "integrity": "sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw==", "dev": true, "license": "MIT", "dependencies": { - "default-browser": "^5.2.1", + "default-browser": "^5.4.0", "define-lazy-prop": "^3.0.0", + "is-in-ssh": "^1.0.0", "is-inside-container": "^1.0.0", - "is-wsl": "^3.1.0" + "powershell-utils": "^0.1.0", + "wsl-utils": "^0.3.0" }, "engines": { - "node": ">=18" + "node": ">=20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -38499,6 +38431,76 @@ "node": ">= 0.8.0" } }, + "node_modules/ora": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.3.0.tgz", + "integrity": "sha512-zAKMgGXUim0Jyd6CXK9lraBnD3H5yPGBPPOkC23a2BG6hsm4Zu6OQSjQuEtV0BHDf4aKHcUFvJiGRrFuW3MG8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "bl": "^4.0.3", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "log-symbols": "^4.0.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/ora/node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/os-homedir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", @@ -38535,9 +38537,9 @@ } }, "node_modules/oxlint": { - "version": "1.53.0", - "resolved": "https://registry.npmjs.org/oxlint/-/oxlint-1.53.0.tgz", - "integrity": "sha512-TLW0PzGbpO1JxUnuy1pIqVPjQUGh4fNfxu5XJbdFIRFVaJ0UFzTjjk/hSFTMRxN6lZub53xL/IwJNEkrh7VtDg==", + "version": "1.61.0", + "resolved": "https://registry.npmjs.org/oxlint/-/oxlint-1.61.0.tgz", + "integrity": "sha512-ZC0ALuhDZ6ivOFG+sy0D0pEDN49EvsId98zVlmYdkcXHsEM14m/qTNUEsUpiFiCVbpIxYtVBmmLE87nsbUHohQ==", "dev": true, "license": "MIT", "bin": { @@ -38550,28 +38552,28 @@ "url": "https://github.com/sponsors/Boshen" }, "optionalDependencies": { - "@oxlint/binding-android-arm-eabi": "1.53.0", - "@oxlint/binding-android-arm64": "1.53.0", - "@oxlint/binding-darwin-arm64": "1.53.0", - "@oxlint/binding-darwin-x64": "1.53.0", - "@oxlint/binding-freebsd-x64": "1.53.0", - "@oxlint/binding-linux-arm-gnueabihf": "1.53.0", - "@oxlint/binding-linux-arm-musleabihf": "1.53.0", - "@oxlint/binding-linux-arm64-gnu": "1.53.0", - "@oxlint/binding-linux-arm64-musl": "1.53.0", - "@oxlint/binding-linux-ppc64-gnu": "1.53.0", - "@oxlint/binding-linux-riscv64-gnu": "1.53.0", - "@oxlint/binding-linux-riscv64-musl": "1.53.0", - "@oxlint/binding-linux-s390x-gnu": "1.53.0", - "@oxlint/binding-linux-x64-gnu": "1.53.0", - "@oxlint/binding-linux-x64-musl": "1.53.0", - "@oxlint/binding-openharmony-arm64": "1.53.0", - "@oxlint/binding-win32-arm64-msvc": "1.53.0", - "@oxlint/binding-win32-ia32-msvc": "1.53.0", - "@oxlint/binding-win32-x64-msvc": "1.53.0" + "@oxlint/binding-android-arm-eabi": "1.61.0", + "@oxlint/binding-android-arm64": "1.61.0", + "@oxlint/binding-darwin-arm64": "1.61.0", + "@oxlint/binding-darwin-x64": "1.61.0", + "@oxlint/binding-freebsd-x64": "1.61.0", + "@oxlint/binding-linux-arm-gnueabihf": "1.61.0", + "@oxlint/binding-linux-arm-musleabihf": "1.61.0", + "@oxlint/binding-linux-arm64-gnu": "1.61.0", + "@oxlint/binding-linux-arm64-musl": "1.61.0", + "@oxlint/binding-linux-ppc64-gnu": "1.61.0", + "@oxlint/binding-linux-riscv64-gnu": "1.61.0", + "@oxlint/binding-linux-riscv64-musl": "1.61.0", + "@oxlint/binding-linux-s390x-gnu": "1.61.0", + "@oxlint/binding-linux-x64-gnu": "1.61.0", + "@oxlint/binding-linux-x64-musl": "1.61.0", + "@oxlint/binding-openharmony-arm64": "1.61.0", + "@oxlint/binding-win32-arm64-msvc": "1.61.0", + "@oxlint/binding-win32-ia32-msvc": "1.61.0", + "@oxlint/binding-win32-x64-msvc": "1.61.0" }, "peerDependencies": { - "oxlint-tsgolint": ">=0.15.0" + "oxlint-tsgolint": ">=0.18.0" }, "peerDependenciesMeta": { "oxlint-tsgolint": { @@ -38764,6 +38766,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, "license": "BlueOak-1.0.0" }, "node_modules/package-json/node_modules/ky": { @@ -38779,35 +38782,156 @@ } }, "node_modules/pacote": { - "version": "18.0.6", - "resolved": "https://registry.npmjs.org/pacote/-/pacote-18.0.6.tgz", - "integrity": "sha512-+eK3G27SMwsB8kLIuj4h1FUhHtwiEUo21Tw8wNjmvdlpOEr613edv+8FUsTj/4F/VN5ywGE19X18N7CC2EJk6A==", + "version": "21.0.1", + "resolved": "https://registry.npmjs.org/pacote/-/pacote-21.0.1.tgz", + "integrity": "sha512-LHGIUQUrcDIJUej53KJz1BPvUuHrItrR2yrnN0Kl9657cJ0ZT6QJHk9wWPBnQZhYT5KLyZWrk9jaYc2aKDu4yw==", "dev": true, "license": "ISC", "dependencies": { - "@npmcli/git": "^5.0.0", - "@npmcli/installed-package-contents": "^2.0.1", - "@npmcli/package-json": "^5.1.0", - "@npmcli/promise-spawn": "^7.0.0", - "@npmcli/run-script": "^8.0.0", - "cacache": "^18.0.0", + "@npmcli/git": "^6.0.0", + "@npmcli/installed-package-contents": "^3.0.0", + "@npmcli/package-json": "^7.0.0", + "@npmcli/promise-spawn": "^8.0.0", + "@npmcli/run-script": "^10.0.0", + "cacache": "^20.0.0", "fs-minipass": "^3.0.0", "minipass": "^7.0.2", - "npm-package-arg": "^11.0.0", - "npm-packlist": "^8.0.0", - "npm-pick-manifest": "^9.0.0", - "npm-registry-fetch": "^17.0.0", - "proc-log": "^4.0.0", + "npm-package-arg": "^13.0.0", + "npm-packlist": "^10.0.1", + "npm-pick-manifest": "^10.0.0", + "npm-registry-fetch": "^19.0.0", + "proc-log": "^5.0.0", "promise-retry": "^2.0.1", - "sigstore": "^2.2.0", - "ssri": "^10.0.0", - "tar": "^6.1.11" + "sigstore": "^4.0.0", + "ssri": "^12.0.0", + "tar": "^7.4.3" }, "bin": { "pacote": "bin/index.js" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/pacote/node_modules/@npmcli/git": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-6.0.3.tgz", + "integrity": "sha512-GUYESQlxZRAdhs3UhbB6pVRNUELQOHXwK9ruDkwmCv2aZ5y0SApQzUJCg02p3A7Ue2J5hxvlk1YI53c00NmRyQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/promise-spawn": "^8.0.0", + "ini": "^5.0.0", + "lru-cache": "^10.0.1", + "npm-pick-manifest": "^10.0.0", + "proc-log": "^5.0.0", + "promise-retry": "^2.0.1", + "semver": "^7.3.5", + "which": "^5.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/pacote/node_modules/@npmcli/promise-spawn": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-8.0.3.tgz", + "integrity": "sha512-Yb00SWaL4F8w+K8YGhQ55+xE4RUNdMHV43WZGsiTM92gS+lC0mGsn7I4hLug7pbao035S6bj3Y3w0cUNGLfmkg==", + "dev": true, + "license": "ISC", + "dependencies": { + "which": "^5.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/pacote/node_modules/hosted-git-info": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-8.1.0.tgz", + "integrity": "sha512-Rw/B2DNQaPBICNXEm8balFz9a6WpZrkCGpcWFpy7nCj+NyhSdqXipmfvtmWt9xGfp0wZnBxB+iVpLmQMYt47Tw==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^10.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/pacote/node_modules/ini": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-5.0.0.tgz", + "integrity": "sha512-+N0ngpO3e7cRUWOJAS7qw0IZIVc6XPrW4MlFBdD066F2L4k1L6ker3hLqSq7iXxU5tgS4WGkIUElWn5vogAEnw==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/pacote/node_modules/isexe": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.5.tgz", + "integrity": "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/pacote/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/pacote/node_modules/npm-pick-manifest": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-10.0.0.tgz", + "integrity": "sha512-r4fFa4FqYY8xaM7fHecQ9Z2nE9hgNfJR+EmoKv0+chvzWkBcORX3r0FpTByP+CbOVJDladMXnPQGVN8PBLGuTQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "npm-install-checks": "^7.1.0", + "npm-normalize-package-bin": "^4.0.0", + "npm-package-arg": "^12.0.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/pacote/node_modules/npm-pick-manifest/node_modules/npm-package-arg": { + "version": "12.0.2", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-12.0.2.tgz", + "integrity": "sha512-f1NpFjNI9O4VbKMOlA5QoBq/vSQPORHcTZ2feJpFkTHJ9eQkdlmZEKSjcAhxTGInC7RlEyScT9ui67NaOsjFWA==", + "dev": true, + "license": "ISC", + "dependencies": { + "hosted-git-info": "^8.0.0", + "proc-log": "^5.0.0", + "semver": "^7.3.5", + "validate-npm-package-name": "^6.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/pacote/node_modules/which": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", + "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/pad-component": { @@ -38845,18 +38969,28 @@ } }, "node_modules/parse-conflict-json": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/parse-conflict-json/-/parse-conflict-json-3.0.1.tgz", - "integrity": "sha512-01TvEktc68vwbJOtWZluyWeVGWjP+bZwXtPDMQVbBKzbJ/vZBif0L69KH1+cHv1SZ6e0FKLvjyHe8mqsIqYOmw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-conflict-json/-/parse-conflict-json-4.0.0.tgz", + "integrity": "sha512-37CN2VtcuvKgHUs8+0b1uJeEsbGn61GRHz469C94P5xiOoqpDYJYwjg4RY9Vmz39WyZAVkR5++nbJwLMIgOCnQ==", "dev": true, "license": "ISC", "dependencies": { - "json-parse-even-better-errors": "^3.0.0", + "json-parse-even-better-errors": "^4.0.0", "just-diff": "^6.0.0", "just-diff-apply": "^5.2.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/parse-conflict-json/node_modules/json-parse-even-better-errors": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-4.0.0.tgz", + "integrity": "sha512-lR4MXjGNgkJc7tkQ97kb2nuEMnNCyU//XYVH0MKTGcXEiSudQ5MKGKen3C5QubYy0vmq+JGitUg92uuywGEwIA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/parse-entities": { @@ -39037,6 +39171,21 @@ "node": ">=8" } }, + "node_modules/path-expression-matcher": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.5.0.tgz", + "integrity": "sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", @@ -39065,6 +39214,7 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, "license": "BlueOak-1.0.0", "dependencies": { "lru-cache": "^10.2.0", @@ -39081,12 +39231,13 @@ "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, "license": "ISC" }, "node_modules/path-to-regexp": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", - "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz", + "integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==", "dev": true, "license": "MIT" }, @@ -39122,20 +39273,6 @@ "pbf": "bin/pbf" } }, - "node_modules/peek-readable": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-5.3.1.tgz", - "integrity": "sha512-GVlENSDW6KHaXcd9zkZltB7tCLosKB/4Hg0fqBJkAoBgYG2Tn1xtMgXtSUuMU9AK/gCm/tTdT8mgAeF4YNeeqw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.16" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, "node_modules/peek-stream": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/peek-stream/-/peek-stream-1.1.3.tgz", @@ -39169,9 +39306,9 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "license": "MIT", "engines": { "node": ">=8.6" @@ -39288,13 +39425,13 @@ } }, "node_modules/playwright": { - "version": "1.58.2", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.58.2.tgz", - "integrity": "sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A==", + "version": "1.59.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.59.1.tgz", + "integrity": "sha512-C8oWjPR3F81yljW9o5OxcWzfh6avkVwDD2VYdwIGqTkl+OGFISgypqzfu7dOe4QNLL2aqcWBmI3PMtLIK233lw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "playwright-core": "1.58.2" + "playwright-core": "1.59.1" }, "bin": { "playwright": "cli.js" @@ -39307,9 +39444,9 @@ } }, "node_modules/playwright-core": { - "version": "1.58.2", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.58.2.tgz", - "integrity": "sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg==", + "version": "1.59.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.59.1.tgz", + "integrity": "sha512-HBV/RJg81z5BiiZ9yPzIiClYV/QMsDCKUyogwH9p3MCP6IYjUFu/MActgYAvK0oWyV9NlwM3GLBjADyWgydVyg==", "dev": true, "license": "Apache-2.0", "bin": { @@ -39997,22 +40134,25 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "node_modules/postcss/node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/potpack": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/potpack/-/potpack-2.0.0.tgz", - "integrity": "sha512-Q+/tYsFU9r7xoOJ+y/ZTtdVQwTWfzjbiXBDMM/JKUux3+QPP02iUuIoeBQ+Ot6oEDlC+/PGjB/5A3K7KKb7hcw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/potpack/-/potpack-2.1.0.tgz", + "integrity": "sha512-pcaShQc1Shq0y+E7GqJqvZj8DTthWV1KeHGdi0Z6IAin2Oi3JnLCOfwnCo84qc+HAp52wT9nK9H7FAJp5a44GQ==", "license": "ISC" }, + "node_modules/powershell-utils": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/powershell-utils/-/powershell-utils-0.1.0.tgz", + "integrity": "sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/preact": { "version": "10.28.3", "resolved": "https://registry.npmjs.org/preact/-/preact-10.28.3.tgz", @@ -40035,9 +40175,9 @@ } }, "node_modules/prettier": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz", - "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.3.tgz", + "integrity": "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==", "dev": true, "license": "MIT", "bin": { @@ -40163,13 +40303,13 @@ } }, "node_modules/proc-log": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-4.2.0.tgz", - "integrity": "sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-5.0.0.tgz", + "integrity": "sha512-Azwzvl90HaF0aCz1JrDdXQykFakSSNPaPoiZ9fm5qJIMHioDZEi7OAdRwSm6rSoPtY3Qutnm3L7ogmg3dc+wbQ==", "dev": true, "license": "ISC", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/process": { @@ -40215,13 +40355,13 @@ } }, "node_modules/proggy": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/proggy/-/proggy-2.0.0.tgz", - "integrity": "sha512-69agxLtnI8xBs9gUGqEnK26UfiexpHy+KUpBQWabiytQjnn5wFY8rklAi7GRfABIuPNnQ/ik48+LGLkYYJcy4A==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/proggy/-/proggy-3.0.0.tgz", + "integrity": "sha512-QE8RApCM3IaRRxVzxrjbgNMpQEX6Wu0p0KBeoSiSEw5/bsGwZHsshF4LCxH2jp/r6BU+bqA3LrMDEYNfJnpD8Q==", "dev": true, "license": "ISC", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/proj4": { @@ -40255,13 +40395,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/promise-inflight": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", - "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", - "dev": true, - "license": "ISC" - }, "node_modules/promise-retry": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", @@ -40301,16 +40434,16 @@ } }, "node_modules/promzard": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/promzard/-/promzard-1.0.2.tgz", - "integrity": "sha512-2FPputGL+mP3jJ3UZg/Dl9YOkovB7DX0oOr+ck5QbZ5MtORtds8k/BZdn+02peDLI8/YWbmzx34k5fA+fHvCVQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/promzard/-/promzard-2.0.0.tgz", + "integrity": "sha512-Ncd0vyS2eXGOjchIRg6PVCYKetJYrW1BSbbIo+bKdig61TB6nH2RQNF2uP+qMpsI73L/jURLWojcw8JNIKZ3gg==", "dev": true, "license": "ISC", "dependencies": { - "read": "^3.0.1" + "read": "^4.0.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/prop-types": { @@ -40347,9 +40480,9 @@ "license": "ISC" }, "node_modules/protocol-buffers-schema": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/protocol-buffers-schema/-/protocol-buffers-schema-3.6.0.tgz", - "integrity": "sha512-TdDRD+/QNdrCGCE7v8340QyuXd4kIWIgapsE2+n/SaGiSSbomYl4TjHlvIoCWRpE7wFt02EpB35VVA2ImcBVqw==", + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/protocol-buffers-schema/-/protocol-buffers-schema-3.6.1.tgz", + "integrity": "sha512-VG2K63Igkiv9p76tk1lilczEK1cT+kCjKtkdhw1dQZV3k3IXJbd3o6Ho8b9zJZaHSnT2hKe4I+ObmX9w6m5SmQ==", "license": "MIT" }, "node_modules/protocols": { @@ -40536,9 +40669,9 @@ } }, "node_modules/quickselect": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-2.0.0.tgz", - "integrity": "sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-3.0.0.tgz", + "integrity": "sha512-XdjUArbK4Bm5fLLvlm5KpTFOiOThgfWWI4axAZDWg4E/0mKdZyI9tNEfds27qCi1ze/vwTR16kvmmGhRra3c2g==", "license": "ISC" }, "node_modules/quote-stream": { @@ -40565,16 +40698,6 @@ "performance-now": "^2.1.0" } }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, "node_modules/range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -40654,12 +40777,6 @@ "quickselect": "^3.0.0" } }, - "node_modules/rbush/node_modules/quickselect": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-3.0.0.tgz", - "integrity": "sha512-XdjUArbK4Bm5fLLvlm5KpTFOiOThgfWWI4axAZDWg4E/0mKdZyI9tNEfds27qCi1ze/vwTR16kvmmGhRra3c2g==", - "license": "ISC" - }, "node_modules/rc": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", @@ -41363,9 +41480,9 @@ } }, "node_modules/react-arborist": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/react-arborist/-/react-arborist-3.4.3.tgz", - "integrity": "sha512-yFnq1nIQhT2uJY4TZVz2tgAiBb9lxSyvF4vC3S8POCK8xLzjGIxVv3/4dmYquQJ7AHxaZZArRGHiHKsEewKdTQ==", + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/react-arborist/-/react-arborist-3.5.0.tgz", + "integrity": "sha512-FdXOICSt7P2h+Pxin1ULN02b4qrXJznNcshgwwWVtuYMLWSJcD245PQ4HOSj/Lr2T1uEegmnEm5Lbns2hUUsqg==", "license": "MIT", "dependencies": { "react-dnd": "^14.0.3", @@ -41470,36 +41587,18 @@ } }, "node_modules/react-checkbox-tree": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/react-checkbox-tree/-/react-checkbox-tree-1.8.0.tgz", - "integrity": "sha512-ufC4aorihOvjLpvY1beab2hjVLGZbDTFRzw62foG0+th+KX7e/sdmWu/nD1ZS/U5Yr0rWGwedGH5GOtR0IkUXw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/react-checkbox-tree/-/react-checkbox-tree-2.0.1.tgz", + "integrity": "sha512-ZUh9strXP3a+RpXEGPSq5qWC0HSo3pjjGQEwNWYdmo1OfSNq0L61boy4ANIN2O+ybo/n80hadQYNAeMgwdQqRQ==", "license": "MIT", "dependencies": { "classnames": "^2.2.5", - "lodash": "^4.17.10", - "nanoid": "^3.0.0", + "fast-equals": "^6.0.0", + "lodash.memoize": "^4.1.2", "prop-types": "^15.5.8" }, "peerDependencies": { - "react": "^15.3.0 || ^16.0.0 || ^17.0.0 || ^18.0.0" - } - }, - "node_modules/react-checkbox-tree/node_modules/nanoid": { - "version": "3.3.8", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", - "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "node_modules/react-diff-viewer-continued": { @@ -41654,15 +41753,6 @@ "react": ">=16.4.1" } }, - "node_modules/react-icons": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.4.0.tgz", - "integrity": "sha512-7eltJxgVt7X64oHh6wSWNwwbKTCtMfK35hcjvJS0yxEAhPM8oUKdS3+kqaW1vicIltw+kR2unHaa12S9pPALoQ==", - "license": "MIT", - "peerDependencies": { - "react": "*" - } - }, "node_modules/react-intersection-observer": { "version": "10.0.3", "resolved": "https://registry.npmjs.org/react-intersection-observer/-/react-intersection-observer-10.0.3.tgz", @@ -41721,96 +41811,95 @@ } }, "node_modules/react-map-gl": { - "version": "6.1.21", - "resolved": "https://registry.npmjs.org/react-map-gl/-/react-map-gl-6.1.21.tgz", - "integrity": "sha512-7ENXxAeYaI4dhol5bir3iK6TeR9teA3MF0WH6VIhmkMRdYY3lgA4t1GzDh2BwpSO340Ngw+5mC0nTsc6gd1O4w==", + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/react-map-gl/-/react-map-gl-8.1.1.tgz", + "integrity": "sha512-aSqFAFoxvY7wxbGI93Dz0E41171mkAb3GcNbnkFIotmu88OFw495os6mIDZSi7irYNT/PZEIOEHUxhun4ToGuQ==", "license": "MIT", "dependencies": { - "@babel/runtime": "^7.0.0", - "@types/geojson": "^7946.0.7", - "@types/mapbox-gl": "^2.0.3", - "mapbox-gl": "^2.3.0", - "mjolnir.js": "^2.5.0", - "prop-types": "^15.7.2", - "resize-observer-polyfill": "^1.5.1", - "viewport-mercator-project": "^7.0.4" - }, - "engines": { - "node": ">= 4", - "npm": ">= 3" + "@vis.gl/react-mapbox": "8.1.1", + "@vis.gl/react-maplibre": "8.1.1" }, "peerDependencies": { - "react": ">=16.3.0" + "mapbox-gl": ">=1.13.0", + "maplibre-gl": ">=1.13.0", + "react": ">=16.3.0", + "react-dom": ">=16.3.0" + }, + "peerDependenciesMeta": { + "mapbox-gl": { + "optional": true + }, + "maplibre-gl": { + "optional": true + } } }, - "node_modules/react-map-gl/node_modules/@mapbox/mapbox-gl-supported": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@mapbox/mapbox-gl-supported/-/mapbox-gl-supported-2.0.1.tgz", - "integrity": "sha512-HP6XvfNIzfoMVfyGjBckjiAOQK9WfX0ywdLubuPMPv+Vqf5fj0uCbgBQYpiqcWZT6cbyyRnTSXDheT1ugvF6UQ==", - "license": "BSD-3-Clause" - }, - "node_modules/react-map-gl/node_modules/geojson-vt": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/geojson-vt/-/geojson-vt-3.2.1.tgz", - "integrity": "sha512-EvGQQi/zPrDA6zr6BnJD/YhwAkBP8nnJ9emh3EnHQKVMfg/MRVtPbMYdgVy/IaEmn4UfagD2a6fafPDL5hbtwg==", - "license": "ISC" - }, - "node_modules/react-map-gl/node_modules/mapbox-gl": { - "version": "2.15.0", - "resolved": "https://registry.npmjs.org/mapbox-gl/-/mapbox-gl-2.15.0.tgz", - "integrity": "sha512-fjv+aYrd5TIHiL7wRa+W7KjtUqKWziJMZUkK5hm8TvJ3OLeNPx4NmW/DgfYhd/jHej8wWL+QJBDbdMMAKvNC0A==", - "license": "SEE LICENSE IN LICENSE.txt", + "node_modules/react-map-gl/node_modules/@maplibre/maplibre-gl-style-spec": { + "version": "19.3.3", + "resolved": "https://registry.npmjs.org/@maplibre/maplibre-gl-style-spec/-/maplibre-gl-style-spec-19.3.3.tgz", + "integrity": "sha512-cOZZOVhDSulgK0meTsTkmNXb1ahVvmTmWmfx9gRBwc6hq98wS9JP35ESIoNq3xqEan+UN+gn8187Z6E4NKhLsw==", + "license": "ISC", "dependencies": { - "@mapbox/geojson-rewind": "^0.5.2", - "@mapbox/jsonlint-lines-primitives": "^2.0.2", - "@mapbox/mapbox-gl-supported": "^2.0.1", - "@mapbox/point-geometry": "^0.1.0", - "@mapbox/tiny-sdf": "^2.0.6", + "@mapbox/jsonlint-lines-primitives": "~2.0.2", "@mapbox/unitbezier": "^0.0.1", - "@mapbox/vector-tile": "^1.3.1", - "@mapbox/whoots-js": "^3.1.0", - "csscolorparser": "~1.0.3", - "earcut": "^2.2.4", - "geojson-vt": "^3.2.1", - "gl-matrix": "^3.4.3", - "grid-index": "^1.1.0", - "kdbush": "^4.0.1", - "murmurhash-js": "^1.0.0", - "pbf": "^3.2.1", - "potpack": "^2.0.0", - "quickselect": "^2.0.0", + "json-stringify-pretty-compact": "^3.0.0", + "minimist": "^1.2.8", "rw": "^1.3.3", - "supercluster": "^8.0.0", - "tinyqueue": "^2.0.3", - "vt-pbf": "^3.1.3" + "sort-object": "^3.0.3" + }, + "bin": { + "gl-style-format": "dist/gl-style-format.mjs", + "gl-style-migrate": "dist/gl-style-migrate.mjs", + "gl-style-validate": "dist/gl-style-validate.mjs" } }, - "node_modules/react-map-gl/node_modules/mjolnir.js": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/mjolnir.js/-/mjolnir.js-2.7.3.tgz", - "integrity": "sha512-Z5z/+FzZqOSO3juSVKV3zcm4R2eAlWwlKMcqHmyFEJAaLILNcDKnIbnb4/kbcGyIuhtdWrzu8WOIR7uM6I34aw==", + "node_modules/react-map-gl/node_modules/@vis.gl/react-mapbox": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/@vis.gl/react-mapbox/-/react-mapbox-8.1.1.tgz", + "integrity": "sha512-KMDTjtWESXxHS4uqWxjsvgQUHvuL3Z6SdKe68o7Nxma2qUfuyH3x4TCkIqGn3FQTrFvZLWvTnSAbGvtm+Kd13A==", + "license": "MIT", + "peerDependencies": { + "mapbox-gl": ">=3.5.0", + "react": ">=16.3.0", + "react-dom": ">=16.3.0" + }, + "peerDependenciesMeta": { + "mapbox-gl": { + "optional": true + } + } + }, + "node_modules/react-map-gl/node_modules/@vis.gl/react-maplibre": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/@vis.gl/react-maplibre/-/react-maplibre-8.1.1.tgz", + "integrity": "sha512-iUOfzJAhFAJwEZp1644tQb7LOTFgi5/GzdaztkhzNgFVuoF2Ez7guvwZjQAKB9CN2TlHTgNuYH8UW85kO7cVhw==", "license": "MIT", "dependencies": { - "@types/hammerjs": "^2.0.41", - "hammerjs": "^2.0.8" + "@maplibre/maplibre-gl-style-spec": "^19.2.1" }, - "engines": { - "node": ">= 4", - "npm": ">= 3" + "peerDependencies": { + "maplibre-gl": ">=4.0.0", + "react": ">=16.3.0", + "react-dom": ">=16.3.0" + }, + "peerDependenciesMeta": { + "maplibre-gl": { + "optional": true + } } }, + "node_modules/react-map-gl/node_modules/json-stringify-pretty-compact": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/json-stringify-pretty-compact/-/json-stringify-pretty-compact-3.0.0.tgz", + "integrity": "sha512-Rc2suX5meI0S3bfdZuA7JMFBGkJ875ApfVyq2WHELjBiiG22My/l7/8zPpH/CfFVQHuVLd8NLR0nv6vi0BYYKA==", + "license": "MIT" + }, "node_modules/react-map-gl/node_modules/rw": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==", "license": "BSD-3-Clause" }, - "node_modules/react-map-gl/node_modules/tinyqueue": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/tinyqueue/-/tinyqueue-2.0.3.tgz", - "integrity": "sha512-ppJZNDuKGgxzkHihX8v9v9G5f+18gzaTfrukGrq6ueg0lmH4nqVnA2IPG0AEH3jKEk2GRJCUhDoqpoiw3PHLBA==", - "license": "ISC" - }, "node_modules/react-markdown": { "version": "8.0.7", "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-8.0.7.tgz", @@ -42148,16 +42237,16 @@ } }, "node_modules/read": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/read/-/read-3.0.1.tgz", - "integrity": "sha512-SLBrDU/Srs/9EoWhU5GdbAoxG1GzpQHo/6qiGItaoLJ1thmYpcNIM1qISEUvyHBzfGlWIyd6p2DNi1oV1VmAuw==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/read/-/read-4.1.0.tgz", + "integrity": "sha512-uRfX6K+f+R8OOrYScaM3ixPY4erg69f8DN6pgTvMcA9iRc8iDhwrA4m3Yu8YYKsXJgVvum+m8PkRboZwwuLzYA==", "dev": true, "license": "ISC", "dependencies": { - "mute-stream": "^1.0.0" + "mute-stream": "^2.0.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/read-cmd-shim": { @@ -42170,85 +42259,6 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/read-package-json-fast": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/read-package-json-fast/-/read-package-json-fast-3.0.2.tgz", - "integrity": "sha512-0J+Msgym3vrLOUB3hzQCuZHII0xkNGCtz/HJH9xZshwv9DbDwkw1KaE3gx/e2J5rpEY5rtOy6cyhKOPrkP7FZw==", - "dev": true, - "license": "ISC", - "dependencies": { - "json-parse-even-better-errors": "^3.0.0", - "npm-normalize-package-bin": "^3.0.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/read-package-up": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/read-package-up/-/read-package-up-11.0.0.tgz", - "integrity": "sha512-MbgfoNPANMdb4oRBNg5eqLbB2t2r+o5Ua1pNt8BqGp4I0FJZhuVSOj3PaBPni4azWuSzEdNn2evevzVmEk1ohQ==", - "license": "MIT", - "dependencies": { - "find-up-simple": "^1.0.0", - "read-pkg": "^9.0.0", - "type-fest": "^4.6.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/read-package-up/node_modules/parse-json": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-8.1.0.tgz", - "integrity": "sha512-rum1bPifK5SSar35Z6EKZuYPJx85pkNaFrxBK3mwdfSJ1/WKbYrjoW/zTPSjRRamfmVX1ACBIdFAO0VRErW/EA==", - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.22.13", - "index-to-position": "^0.1.2", - "type-fest": "^4.7.1" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/read-package-up/node_modules/read-pkg": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-9.0.1.tgz", - "integrity": "sha512-9viLL4/n1BJUCT1NXVTdS1jtm80yDEgR5T4yCelII49Mbj0v1rZdKqj7zCiYdbB0CuCgdrvHcNogAKTFPBocFA==", - "license": "MIT", - "dependencies": { - "@types/normalize-package-data": "^2.4.3", - "normalize-package-data": "^6.0.0", - "parse-json": "^8.0.0", - "type-fest": "^4.6.0", - "unicorn-magic": "^0.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/read-package-up/node_modules/type-fest": { - "version": "4.33.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.33.0.tgz", - "integrity": "sha512-s6zVrxuyKbbAsSAD5ZPTB77q4YIdRctkTbJ2/Dqlinwz+8ooH2gd+YA7VA6Pa93KML9GockVvoxjZ2vHP+mu8g==", - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/read-pkg": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", @@ -42444,6 +42454,16 @@ "node": ">=4" } }, + "node_modules/read/node_modules/mute-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-2.0.0.tgz", + "integrity": "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, "node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", @@ -42458,23 +42478,6 @@ "node": ">= 6" } }, - "node_modules/readable-web-to-node-stream": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.2.tgz", - "integrity": "sha512-ePeK6cc1EcKLEhJFt/AebMCLL+GgSKhuygrZ/GLaKZYEecIgIECf4UaUuaByiGtzckwR4ain9VzUh95T1exYGw==", - "dev": true, - "license": "MIT", - "dependencies": { - "readable-stream": "^3.6.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -43675,19 +43678,6 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, - "node_modules/renderkid/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/replace-ext": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-2.0.0.tgz", @@ -43912,81 +43902,6 @@ "node": ">= 0.8.15" } }, - "node_modules/rimraf": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-4.4.1.tgz", - "integrity": "sha512-Gk8NlF062+T9CqNGn6h4tls3k6T1+/nXdOcSZVikNVtlRdYpA7wRJJMoXmuvOnLW844rPjdQ7JgXCYM6PPC/og==", - "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^9.2.0" - }, - "bin": { - "rimraf": "dist/cjs/src/bin.js" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rimraf/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/rimraf/node_modules/glob": { - "version": "9.3.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-9.3.5.tgz", - "integrity": "sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "minimatch": "^8.0.2", - "minipass": "^4.2.4", - "path-scurry": "^1.6.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rimraf/node_modules/minimatch": { - "version": "8.0.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-8.0.6.tgz", - "integrity": "sha512-JVNTX5Qc03lB0PuFDuUcVTbi8u5kKchLXDYEnLJrOosZW8cqamFiyItG/7cn0QEt7XmeFHSLJRYg4KujJKuqlw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rimraf/node_modules/minipass": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.8.tgz", - "integrity": "sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=8" - } - }, "node_modules/rison": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/rison/-/rison-0.1.1.tgz", @@ -44318,13 +44233,13 @@ } }, "node_modules/serialize-javascript": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", - "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-7.0.5.tgz", + "integrity": "sha512-F4LcB0UqUl1zErq+1nYEEzSHJnIwb3AF2XWB94b+afhrekOUijwooAYqFyRbjYkm2PAKBabx6oYv/xDxNi8IBw==", "dev": true, "license": "BSD-3-Clause", - "dependencies": { - "randombytes": "^2.1.0" + "engines": { + "node": ">=20.0.0" } }, "node_modules/serialize-query-params": { @@ -44488,6 +44403,21 @@ "node": ">= 0.4" } }, + "node_modules/set-value": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", + "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", + "license": "MIT", + "dependencies": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/setimmediate": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", @@ -44664,21 +44594,21 @@ "license": "ISC" }, "node_modules/sigstore": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/sigstore/-/sigstore-2.3.1.tgz", - "integrity": "sha512-8G+/XDU8wNsJOQS5ysDVO0Etg9/2uA5gR9l4ZwijjlwxBcrU6RPfwi2+jJmbP+Ap1Hlp/nVAaEO4Fj22/SL2gQ==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/sigstore/-/sigstore-4.1.0.tgz", + "integrity": "sha512-/fUgUhYghuLzVT/gaJoeVehLCgZiUxPCPMcyVNY0lIf/cTCz58K/WTI7PefDarXxp9nUKpEwg1yyz3eSBMTtgA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@sigstore/bundle": "^2.3.2", - "@sigstore/core": "^1.0.0", - "@sigstore/protobuf-specs": "^0.3.2", - "@sigstore/sign": "^2.3.2", - "@sigstore/tuf": "^2.3.4", - "@sigstore/verify": "^1.2.1" + "@sigstore/bundle": "^4.0.0", + "@sigstore/core": "^3.1.0", + "@sigstore/protobuf-specs": "^0.5.0", + "@sigstore/sign": "^4.1.0", + "@sigstore/tuf": "^4.0.1", + "@sigstore/verify": "^3.1.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/simple-git": { @@ -44831,13 +44761,13 @@ } }, "node_modules/socks": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz", - "integrity": "sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==", + "version": "2.8.7", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz", + "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==", "dev": true, "license": "MIT", "dependencies": { - "ip-address": "^9.0.5", + "ip-address": "^10.0.1", "smart-buffer": "^4.2.0" }, "engines": { @@ -44861,26 +44791,75 @@ } }, "node_modules/socks-proxy-agent/node_modules/agent-base": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", - "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", "dev": true, "license": "MIT", "engines": { "node": ">= 14" } }, + "node_modules/sort-asc": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/sort-asc/-/sort-asc-0.2.0.tgz", + "integrity": "sha512-umMGhjPeHAI6YjABoSTrFp2zaBtXBej1a0yKkuMUyjjqu6FJsTF+JYwCswWDg+zJfk/5npWUUbd33HH/WLzpaA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sort-desc": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/sort-desc/-/sort-desc-0.2.0.tgz", + "integrity": "sha512-NqZqyvL4VPW+RAxxXnB8gvE1kyikh8+pR+T+CXLksVRN9eiQqkQlPwqWYU0mF9Jm7UnctShlxLyAt1CaBOTL1w==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/sort-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-2.0.0.tgz", - "integrity": "sha512-/dPCrG1s3ePpWm6yBbxZq5Be1dXGLyLn9Z791chDC3NFrpkVbWGzkBwPN1knaciexFXgRJ7hzdnwZ4stHSDmjg==", - "dev": true, + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-6.0.0.tgz", + "integrity": "sha512-ueSlHJMwpIw42CJ4B11Uxzh/S0p0AlOyiNktlv2KOu5e1JpUE6DlC4AAUjXqesHdBRv/g0wC9Q4vwq0NP2pA9w==", "license": "MIT", "dependencies": { - "is-plain-obj": "^1.0.0" + "is-plain-obj": "^4.1.0" }, "engines": { - "node": ">=4" + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/sort-keys/node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/sort-object": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/sort-object/-/sort-object-3.0.3.tgz", + "integrity": "sha512-nK7WOY8jik6zaG9CRwZTaD5O7ETWDLZYMM12pqY8htll+7dYeqGfEUPcUBHOpSJg2vJOrvFIY2Dl5cX2ih1hAQ==", + "license": "MIT", + "dependencies": { + "bytewise": "^1.1.0", + "get-value": "^2.0.2", + "is-extendable": "^0.1.1", + "sort-asc": "^0.2.0", + "sort-desc": "^0.2.0", + "union-value": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" } }, "node_modules/sort-object-keys": { @@ -44955,9 +44934,9 @@ } }, "node_modules/source-map-js": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.1.tgz", - "integrity": "sha512-4+TN2b3tqOCd/kaGRJ/sTYA0tR0mdXx26ipdolxcwtJVqEnqNYvlCAt1q3ypy4QMlYus+Zh34RNtYLoq2oQ4IA==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "dev": true, "license": "BSD-3-Clause", "engines": { @@ -45158,9 +45137,9 @@ } }, "node_modules/speed-measure-webpack-plugin": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/speed-measure-webpack-plugin/-/speed-measure-webpack-plugin-1.5.0.tgz", - "integrity": "sha512-Re0wX5CtM6gW7bZA64ONOfEPEhwbiSF/vz6e2GvadjuaPrQcHTQdRGsD8+BE7iUOysXH8tIenkPCQBEcspXsNg==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/speed-measure-webpack-plugin/-/speed-measure-webpack-plugin-1.6.0.tgz", + "integrity": "sha512-hz09hUQeP74zHZOLtSJwpVsuLy8EWxuyGiMVZ2tuvIQcMlL5mGrmbe8RKvrVV5geRkxms9JusQuIBt0UnB0XFw==", "dev": true, "license": "MIT", "dependencies": { @@ -45221,6 +45200,43 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "license": "MIT", + "dependencies": { + "extend-shallow": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/split-string/node_modules/extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", + "license": "MIT", + "dependencies": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/split-string/node_modules/is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "license": "MIT", + "dependencies": { + "is-plain-object": "^2.0.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/split.js": { "version": "1.6.5", "resolved": "https://registry.npmjs.org/split.js/-/split.js-1.6.5.tgz", @@ -45279,16 +45295,16 @@ "peer": true }, "node_modules/ssri": { - "version": "10.0.6", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-10.0.6.tgz", - "integrity": "sha512-MGrFH9Z4NP9Iyhqn16sDtBpRRNJ0Y2hNa6D65h736fVSaPCHr4DM4sWUNvVaSuC+0OBGhwsrydQwmgfg5LncqQ==", + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-12.0.0.tgz", + "integrity": "sha512-S7iGNosepx9RadX82oimUkvr0Ct7IjJbEbs4mJcTxst8um95J3sDYU1RBEOvdu6oL1Wek2ODI5i4MAw+dZ6cAQ==", "dev": true, "license": "ISC", "dependencies": { "minipass": "^7.0.3" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/stable-hash-x": { @@ -45591,13 +45607,13 @@ } }, "node_modules/storybook": { - "version": "8.6.17", - "resolved": "https://registry.npmjs.org/storybook/-/storybook-8.6.17.tgz", - "integrity": "sha512-krR/l680A6qVnkGiK9p8jY0ucX3+kFCs2f4zw+S3w2Cdq8EiM/tFebPcX2V4S3z2UsO0v0dwAJOJNpzbFPdmVg==", + "version": "8.6.18", + "resolved": "https://registry.npmjs.org/storybook/-/storybook-8.6.18.tgz", + "integrity": "sha512-p8seiSI6FiVY6P3V0pG+5v7c8pDMehMAFRWEhG5XqIBSQszzOjDnW2rNvm3odoLKfo3V3P6Cs6Hv9ILzymULyQ==", "dev": true, "license": "MIT", "dependencies": { - "@storybook/core": "8.6.17" + "@storybook/core": "8.6.18" }, "bin": { "getstorybook": "bin/index.cjs", @@ -45695,6 +45711,7 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -45709,38 +45726,15 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, "license": "MIT" }, - "node_modules/string-width-cjs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/string-width/node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "license": "MIT" }, - "node_modules/string-width/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/string.prototype.trim": { "version": "1.2.10", "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", @@ -45798,12 +45792,12 @@ } }, "node_modules/strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "license": "MIT", "dependencies": { - "ansi-regex": "^5.0.0" + "ansi-regex": "^5.0.1" }, "engines": { "node": ">=8" @@ -45814,6 +45808,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -45924,17 +45919,16 @@ "license": "MIT" }, "node_modules/strtok3": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-7.1.1.tgz", - "integrity": "sha512-mKX8HA/cdBqMKUr0MMZAFssCkIGoZeSCMXgnt79yKxNFguMLVFgRe6wB+fsL0NmoHDbeyZXczy7vEPSoo3rkzg==", + "version": "10.3.5", + "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-10.3.5.tgz", + "integrity": "sha512-ki4hZQfh5rX0QDLLkOCj+h+CVNkqmp/CMf8v8kZpkNVK6jGQooMytqzLZYUVYIZcFZ6yDB70EfD8POcFXiF5oA==", "dev": true, "license": "MIT", "dependencies": { - "@tokenizer/token": "^0.3.0", - "peek-readable": "^5.1.3" + "@tokenizer/token": "^0.3.0" }, "engines": { - "node": ">=16" + "node": ">=18" }, "funding": { "type": "github", @@ -46127,7 +46121,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/tagged-tag/-/tagged-tag-1.0.0.tgz", "integrity": "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==", - "dev": true, "license": "MIT", "engines": { "node": ">=20" @@ -46151,21 +46144,20 @@ } }, "node_modules/tar": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", - "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "version": "7.5.11", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.11.tgz", + "integrity": "sha512-ChjMH33/KetonMTAtpYdgUFr0tbz69Fp2v7zWxQfYZX4g5ZN2nOBXm1R2xyA+lMIKrLKIoKAwFj93jE/avX9cQ==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "minipass": "^5.0.0", - "minizlib": "^2.1.1", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.1.0", + "yallist": "^5.0.0" }, "engines": { - "node": ">=10" + "node": ">=18" } }, "node_modules/tar-stream": { @@ -46185,48 +46177,15 @@ "node": ">=6" } }, - "node_modules/tar/node_modules/fs-minipass": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", - "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", - "dev": true, - "license": "ISC", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/tar/node_modules/fs-minipass/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/tar/node_modules/minipass": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", - "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=8" - } - }, "node_modules/tar/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", "dev": true, - "license": "ISC" + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } }, "node_modules/teex": { "version": "1.0.1", @@ -46238,19 +46197,19 @@ } }, "node_modules/temp-dir": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-1.0.0.tgz", - "integrity": "sha512-xZFXEGbG7SNC3itwBzI3RYjq/cEhBkx2hJuKGIUOcEULmkQExXiHat2z/qkISYsuR+IKumhEfKKbV5qXmhICFQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-3.0.0.tgz", + "integrity": "sha512-nHc6S/bwIilKHNRgK/3jlhDoIHcp45YgyiwcAk46Tr0LfEqGBVpmiAyuiuxeVE44m3mXnEeVhaipLOEWmH+Njw==", "dev": true, "license": "MIT", "engines": { - "node": ">=4" + "node": ">=14.16" } }, "node_modules/tempy": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/tempy/-/tempy-3.1.0.tgz", - "integrity": "sha512-7jDLIdD2Zp0bDe5r3D2qtkd1QOCacylBuL7oa4udvN6v2pqr4+LcCr67C8DR1zkpaZ8XosF5m1yQSabKAW6f2g==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/tempy/-/tempy-3.2.0.tgz", + "integrity": "sha512-d79HhZya5Djd7am0q+W4RTsSU+D/aJzM+4Y4AGJGuGlgM2L6sx5ZvOYTmZjqPhrDrV6xJTtRSm1JCLj6V6LHLQ==", "dev": true, "license": "MIT", "dependencies": { @@ -46279,16 +46238,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/tempy/node_modules/temp-dir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-3.0.0.tgz", - "integrity": "sha512-nHc6S/bwIilKHNRgK/3jlhDoIHcp45YgyiwcAk46Tr0LfEqGBVpmiAyuiuxeVE44m3mXnEeVhaipLOEWmH+Njw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.16" - } - }, "node_modules/terser": { "version": "5.37.0", "resolved": "https://registry.npmjs.org/terser/-/terser-5.37.0.tgz", @@ -46309,9 +46258,9 @@ } }, "node_modules/terser-webpack-plugin": { - "version": "5.3.17", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.17.tgz", - "integrity": "sha512-YR7PtUp6GMU91BgSJmlaX/rS2lGDbAF7D+Wtq7hRO+MiljNmodYvqslzCFiYVAgW+Qoaaia/QUIP4lGXufjdZw==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.4.0.tgz", + "integrity": "sha512-Bn5vxm48flOIfkdl5CaD2+1CiUVbonWQ3KQPyP7/EuIl9Gbzq/gQFOzaMFUEgVjB1396tcK0SG8XcNJ/2kDH8g==", "dev": true, "license": "MIT", "dependencies": { @@ -46593,13 +46542,6 @@ "integrity": "sha512-pkJC8uIP/gxDHxNQUBUbjHyl6oZfT+ofn7tbaHW+CFIUjI+Q2MBbHcx1JSBQfhDaTcO9bNg328q0i7Vk5PismQ==", "license": "MIT" }, - "node_modules/timezone-mock": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/timezone-mock/-/timezone-mock-1.4.0.tgz", - "integrity": "sha512-LMnGHctfNWTfGyNghWMGjaIzBQqtnHzOUzi9cL0nDS4dx5yuCwbD7dn9qZbU4jT1HMqQW1khEOloXUTTuxiSaQ==", - "dev": true, - "license": "MIT" - }, "node_modules/tiny-invariant": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", @@ -46622,7 +46564,6 @@ "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", - "dev": true, "license": "MIT", "dependencies": { "fdir": "^6.5.0", @@ -46639,7 +46580,6 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, "license": "MIT", "engines": { "node": ">=12.0.0" @@ -46654,10 +46594,9 @@ } }, "node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "license": "MIT", "engines": { "node": ">=12" @@ -46760,12 +46699,13 @@ } }, "node_modules/token-types": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/token-types/-/token-types-5.0.1.tgz", - "integrity": "sha512-Y2fmSnZjQdDb9W4w4r1tswlMHylzWIeOKpx0aZH9BgGtACHhrk3OkT52AzwcuqTRBZtvvnTjDBh8eynMulu8Vg==", + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/token-types/-/token-types-6.1.2.tgz", + "integrity": "sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww==", "dev": true, "license": "MIT", "dependencies": { + "@borewit/text-codec": "^0.2.1", "@tokenizer/token": "^0.3.0", "ieee754": "^1.2.1" }, @@ -46960,19 +46900,19 @@ } }, "node_modules/ts-jest": { - "version": "29.4.6", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.6.tgz", - "integrity": "sha512-fSpWtOO/1AjSNQguk43hb/JCo16oJDnMJf3CdEGNkqsEX3t0KX96xvyX1D7PfLCpVoKu4MfVrqUkFyblYoY4lA==", + "version": "29.4.9", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.9.tgz", + "integrity": "sha512-LTb9496gYPMCqjeDLdPrKuXtncudeV1yRZnF4Wo5l3SFi0RYEnYRNgMrFIdg+FHvfzjCyQk1cLncWVqiSX+EvQ==", "dev": true, "license": "MIT", "dependencies": { "bs-logger": "^0.2.6", "fast-json-stable-stringify": "^2.1.0", - "handlebars": "^4.7.8", + "handlebars": "^4.7.9", "json5": "^2.2.3", "lodash.memoize": "^4.1.2", "make-error": "^1.3.6", - "semver": "^7.7.3", + "semver": "^7.7.4", "type-fest": "^4.41.0", "yargs-parser": "^21.1.1" }, @@ -46989,7 +46929,7 @@ "babel-jest": "^29.0.0 || ^30.0.0", "jest": "^29.0.0 || ^30.0.0", "jest-util": "^29.0.0 || ^30.0.0", - "typescript": ">=4.3 <6" + "typescript": ">=4.3 <7" }, "peerDependenciesMeta": { "@babel/core": { @@ -47080,18 +47020,6 @@ "node": ">=0.4.0" } }, - "node_modules/ts-node/node_modules/acorn-walk": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", - "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", - "license": "MIT", - "dependencies": { - "acorn": "^8.11.0" - }, - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/ts-node/node_modules/diff": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz", @@ -47658,18 +47586,18 @@ "license": "0BSD" }, "node_modules/tuf-js": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tuf-js/-/tuf-js-2.2.1.tgz", - "integrity": "sha512-GwIJau9XaA8nLVbUXsN3IlFi7WmQ48gBUrl3FTkkL/XLu/POhBzfmX9hd33FNMX1qAsfl6ozO1iMmW9NC8YniA==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/tuf-js/-/tuf-js-4.1.0.tgz", + "integrity": "sha512-50QV99kCKH5P/Vs4E2Gzp7BopNV+KzTXqWeaxrfu5IQJBOULRsTIS9seSsOVT8ZnGXzCyx55nYWAi4qJzpZKEQ==", "dev": true, "license": "MIT", "dependencies": { - "@tufjs/models": "2.0.1", - "debug": "^4.3.4", - "make-fetch-happen": "^13.0.1" + "@tufjs/models": "4.1.0", + "debug": "^4.4.3", + "make-fetch-happen": "^15.0.1" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/tunnel-agent": { @@ -47870,9 +47798,9 @@ } }, "node_modules/typescript-json-schema": { - "version": "0.65.1", - "resolved": "https://registry.npmjs.org/typescript-json-schema/-/typescript-json-schema-0.65.1.tgz", - "integrity": "sha512-tuGH7ff2jPaUYi6as3lHyHcKpSmXIqN7/mu50x3HlYn0EHzLpmt3nplZ7EuhUkO0eqDRc9GqWNkfjgBPIS9kxg==", + "version": "0.67.1", + "resolved": "https://registry.npmjs.org/typescript-json-schema/-/typescript-json-schema-0.67.1.tgz", + "integrity": "sha512-vKTZB/RoYTIBdVP7E7vrgHMCssBuhja91wQy498QIVhvfRimaOgjc98uwAXmZ7mbLUytJmOSbF11wPz+ByQeXg==", "license": "BSD-3-Clause", "dependencies": { "@types/json-schema": "^7.0.9", @@ -47882,6 +47810,7 @@ "safe-stable-stringify": "^2.2.0", "ts-node": "^10.9.1", "typescript": "~5.5.0", + "vm2": "^3.10.0", "yargs": "^17.1.1" }, "bin": { @@ -47916,6 +47845,21 @@ "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", "license": "MIT" }, + "node_modules/typewise": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typewise/-/typewise-1.0.3.tgz", + "integrity": "sha512-aXofE06xGhaQSPzt8hlTY+/YWQhm9P0jYUp1f2XtmW/3Bk0qzXcyFWAtPoo2uTGQj1ZwbDuSyuxicq+aDo8lCQ==", + "license": "MIT", + "dependencies": { + "typewise-core": "^1.2.0" + } + }, + "node_modules/typewise-core": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/typewise-core/-/typewise-core-1.2.0.tgz", + "integrity": "sha512-2SCC/WLzj2SbUwzFOzqMCkz5amXLlxtJqDKTICqg30x+2DZxcfZN2MvQZmGfXWKNWaKK9pBPsvkcwv8bF/gxKg==", + "license": "MIT" + }, "node_modules/uglify-js": { "version": "3.19.3", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", @@ -47929,6 +47873,19 @@ "node": ">=0.8.0" } }, + "node_modules/uint8array-extras": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.5.0.tgz", + "integrity": "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/ultimate-pagination": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/ultimate-pagination/-/ultimate-pagination-1.0.0.tgz", @@ -47967,9 +47924,9 @@ "license": "MIT" }, "node_modules/undici": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.24.1.tgz", - "integrity": "sha512-5xoBibbmnjlcR3jdqtY2Lnx7WbrD/tHlT01TmvqZUFVc9Q1w4+j5hbnapTqbcXITMH1ovjq/W7BkqBilHiVAaA==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.24.6.tgz", + "integrity": "sha512-Xi4agocCbRzt0yYMZGMA6ApD7gvtUFaxm4ZmeacWI4cZxaF6C+8I8QfofC20NAePiB/IcvZmzkJ7XPa471AEtA==", "dev": true, "license": "MIT", "engines": { @@ -47977,9 +47934,9 @@ } }, "node_modules/undici-types": { - "version": "7.18.2", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", - "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "version": "7.19.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.19.2.tgz", + "integrity": "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==", "license": "MIT" }, "node_modules/unicode-canonical-property-names-ecmascript": { @@ -48031,6 +47988,8 @@ "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">=18" }, @@ -48081,30 +48040,19 @@ "node": ">= 0.8.0" } }, - "node_modules/unique-filename": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-3.0.0.tgz", - "integrity": "sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g==", - "dev": true, - "license": "ISC", + "node_modules/union-value": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", + "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", + "license": "MIT", "dependencies": { - "unique-slug": "^4.0.0" + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^2.0.1" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/unique-slug": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-4.0.0.tgz", - "integrity": "sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "imurmurhash": "^0.1.4" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">=0.10.0" } }, "node_modules/unique-string": { @@ -48252,12 +48200,6 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/universal-user-agent": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.3.tgz", - "integrity": "sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A==", - "license": "ISC" - }, "node_modules/universalify": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", @@ -48292,9 +48234,9 @@ } }, "node_modules/unplugin/node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "dev": true, "license": "MIT", "bin": { @@ -48574,9 +48516,9 @@ } }, "node_modules/uuid": { - "version": "13.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-13.0.0.tgz", - "integrity": "sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==", + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-14.0.0.tgz", + "integrity": "sha512-Qo+uWgilfSmAhXCMav1uYFynlQO7fMFiMVZsQqZRMIXp0O7rR7qjkj+cPvBHLgBqi960QCoo/PH2/6ZtVqKvrg==", "funding": [ "https://github.com/sponsors/broofa", "https://github.com/sponsors/ctavan" @@ -48664,13 +48606,13 @@ } }, "node_modules/validate-npm-package-name": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.1.tgz", - "integrity": "sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-6.0.2.tgz", + "integrity": "sha512-IUoow1YUtvoBBC06dXs8bR8B9vuA3aJfmQNKMoaPG/OFsPmoQvw8xh+6Ye25Gx9DQhoEom3Pcu9MKHerm/NpUQ==", "dev": true, "license": "ISC", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/validate.io-array": { @@ -48854,31 +48796,12 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/viewport-mercator-project": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/viewport-mercator-project/-/viewport-mercator-project-7.0.4.tgz", - "integrity": "sha512-0jzpL6pIMocCKWg1C3mqi/N4UPgZC3FzwghEm1H+XsUo8hNZAyJc3QR7YqC816ibOR8aWT5pCsV+gCu8/BMJgg==", - "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", - "license": "MIT", - "dependencies": { - "@math.gl/web-mercator": "^3.5.5" - } - }, - "node_modules/viewport-mercator-project/node_modules/@math.gl/web-mercator": { - "version": "3.6.3", - "resolved": "https://registry.npmjs.org/@math.gl/web-mercator/-/web-mercator-3.6.3.tgz", - "integrity": "sha512-UVrkSOs02YLehKaehrxhAejYMurehIHPfFQvPFZmdJHglHOU4V2cCUApTVEwOksvCp161ypEqVp+9H6mGhTTcw==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.12.0", - "gl-matrix": "^3.4.0" - } - }, "node_modules/vinyl": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-3.0.0.tgz", "integrity": "sha512-rC2VRfAVVCGEgjnxHUnpIVh3AGuk62rP3tqVrn+yab0YH7UULisC085+NYH+mnqf3Wx4SpSi1RQMwudL89N03g==", "license": "MIT", + "peer": true, "dependencies": { "clone": "^2.1.2", "clone-stats": "^1.0.0", @@ -48922,15 +48845,32 @@ "dev": true, "license": "MIT" }, - "node_modules/vt-pbf": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/vt-pbf/-/vt-pbf-3.1.3.tgz", - "integrity": "sha512-2LzDFzt0mZKZ9IpVF2r69G9bXaP2Q2sArJCmcCgvfTdCCZzSyz4aCLoQyUilu37Ll56tCblIZrXFIjNUpGIlmA==", + "node_modules/vm2": { + "version": "3.10.5", + "resolved": "https://registry.npmjs.org/vm2/-/vm2-3.10.5.tgz", + "integrity": "sha512-3P/2QDccVFBcujfCOeP8vVNuGfuBJHEuvGR8eMmI10p/iwLL2UwF5PDaNaoOS2pRGQEDmJRyeEcc8kmm2Z59RA==", "license": "MIT", "dependencies": { - "@mapbox/point-geometry": "0.1.0", - "@mapbox/vector-tile": "^1.3.1", - "pbf": "^3.2.1" + "acorn": "^8.15.0", + "acorn-walk": "^8.3.4" + }, + "bin": { + "vm2": "bin/vm2" + }, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/vm2/node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" } }, "node_modules/w3c-xmlserializer": { @@ -48947,15 +48887,15 @@ } }, "node_modules/wait-on": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-9.0.4.tgz", - "integrity": "sha512-k8qrgfwrPVJXTeFY8tl6BxVHiclK11u72DVKhpybHfUL/K6KM4bdyK9EhIVYGytB5MJe/3lq4Tf0hrjM+pvJZQ==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-9.0.5.tgz", + "integrity": "sha512-qgnbHDfDTRIp73ANEJNRW/7kn8CrDUcvZz18xotJQku/P4saTGkbIzvnMZebPmVvVNUiRq1qWAPyqCH+W4H8KA==", "dev": true, "license": "MIT", "dependencies": { - "axios": "^1.13.5", - "joi": "^18.0.2", - "lodash": "^4.17.23", + "axios": "^1.15.0", + "joi": "^18.1.2", + "lodash": "^4.18.1", "minimist": "^1.2.8", "rxjs": "^7.8.2" }, @@ -49070,11 +49010,14 @@ } }, "node_modules/walk-up-path": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/walk-up-path/-/walk-up-path-3.0.1.tgz", - "integrity": "sha512-9YlCL/ynK3CTlrSRrDxZvUauLzAswPCrsaCgilqFevUYpeEW0/3ScEjaa3kbW/T0ghhkEr7mv+fpjqn1Y1YuTA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/walk-up-path/-/walk-up-path-4.0.0.tgz", + "integrity": "sha512-3hu+tD8YzSLGuFYtPRb48vdhKMi0KQV5sn+uWr8+7dMEq/2G/dtLrdDinkLjqq5TIbIBjYJ4Ax/n3YiaW7QM8A==", "dev": true, - "license": "ISC" + "license": "ISC", + "engines": { + "node": "20 || >=22" + } }, "node_modules/walker": { "version": "1.0.8", @@ -49155,9 +49098,9 @@ } }, "node_modules/webpack": { - "version": "5.105.4", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.105.4.tgz", - "integrity": "sha512-jTywjboN9aHxFlToqb0K0Zs9SbBoW4zRUlGzI2tYNxVYcEi/IPpn+Xi4ye5jTLvX2YeLuic/IvxNot+Q1jMoOw==", + "version": "5.106.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.106.0.tgz", + "integrity": "sha512-Pkx5joZ9RrdgO5LBkyX1L2ZAJeK/Taz3vqZ9CbcP0wS5LEMx5QkKsEwLl29QJfihZ+DKRBFldzy1O30pJ1MDpA==", "dev": true, "license": "MIT", "dependencies": { @@ -49204,19 +49147,18 @@ } }, "node_modules/webpack-bundle-analyzer": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-5.2.0.tgz", - "integrity": "sha512-Etrauj1wYO/xjiz/Vfd6bW1lG9fEhrJpNmu10tv0X9kv+gyY3qiE09uYepqg1Xd0PxOvllRXwWYWjtQYoO/glQ==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-5.3.0.tgz", + "integrity": "sha512-PEhAoqiJ+47d0uLMx/+zo5XOvaU+Vk6N2ZLht7H3n09QLy/fhyvqGNwjdRUHJDgMN8crBR2ZwVHkIswT3Xuawg==", "dev": true, "license": "MIT", "dependencies": { - "@discoveryjs/json-ext": "0.5.7", + "@discoveryjs/json-ext": "^0.6.3", "acorn": "^8.0.4", "acorn-walk": "^8.0.0", - "commander": "^7.2.0", - "debounce": "^1.2.1", - "escape-string-regexp": "^4.0.0", - "html-escaper": "^2.0.2", + "commander": "^14.0.2", + "escape-string-regexp": "^5.0.0", + "html-escaper": "^3.0.3", "opener": "^1.5.2", "picocolors": "^1.0.0", "sirv": "^3.0.2", @@ -49242,29 +49184,36 @@ "node": ">=0.4.0" } }, - "node_modules/webpack-bundle-analyzer/node_modules/acorn-walk": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", - "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "node_modules/webpack-bundle-analyzer/node_modules/commander": { + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", + "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", "dev": true, "license": "MIT", - "dependencies": { - "acorn": "^8.11.0" - }, "engines": { - "node": ">=0.4.0" + "node": ">=20" } }, - "node_modules/webpack-bundle-analyzer/node_modules/commander": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "node_modules/webpack-bundle-analyzer/node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", "dev": true, "license": "MIT", "engines": { - "node": ">= 10" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/webpack-bundle-analyzer/node_modules/html-escaper": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-3.0.3.tgz", + "integrity": "sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==", + "dev": true, + "license": "MIT" + }, "node_modules/webpack-bundle-analyzer/node_modules/ws": { "version": "8.19.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz", @@ -49330,16 +49279,6 @@ } } }, - "node_modules/webpack-cli/node_modules/@discoveryjs/json-ext": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.6.3.tgz", - "integrity": "sha512-4B4OijXeVNOPZlYA2oEwWOTkzyltLao+xbotHQeqN++Rv27Y6s818+n2Qkp8q+Fxhn0t/5lA5X1Mxktud8eayQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.17.0" - } - }, "node_modules/webpack-cli/node_modules/commander": { "version": "12.1.0", "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", @@ -49869,9 +49808,9 @@ } }, "node_modules/whatwg-url": { - "version": "16.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.0.tgz", - "integrity": "sha512-9CcxtEKsf53UFwkSUZjG+9vydAsFO4lFHBpJUtjBcoJOCJpKnSJNwCw813zrYJHpCJ7sgfbtOe0V5Ku7Pa1XMQ==", + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.1.tgz", + "integrity": "sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==", "dev": true, "license": "MIT", "dependencies": { @@ -50090,6 +50029,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", @@ -50136,81 +50076,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/write-json-file": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/write-json-file/-/write-json-file-3.2.0.tgz", - "integrity": "sha512-3xZqT7Byc2uORAatYiP3DHUUAVEkNOswEWNs9H5KXiicRTvzYzYqKjYc4G7p+8pltvAw641lVByKVtMpf+4sYQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "detect-indent": "^5.0.0", - "graceful-fs": "^4.1.15", - "make-dir": "^2.1.0", - "pify": "^4.0.1", - "sort-keys": "^2.0.0", - "write-file-atomic": "^2.4.2" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/write-json-file/node_modules/detect-indent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-5.0.0.tgz", - "integrity": "sha512-rlpvsxUtM0PQvy9iZe640/IWwWYyBsTApREbA1pHOpmOUIl9MkP/U4z7vTtg4Oaojvqhxt7sdufnT0EzGaR31g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/write-json-file/node_modules/pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/write-json-file/node_modules/write-file-atomic": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.3.tgz", - "integrity": "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "graceful-fs": "^4.1.11", - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.2" - } - }, - "node_modules/write-pkg": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/write-pkg/-/write-pkg-4.0.0.tgz", - "integrity": "sha512-v2UQ+50TNf2rNHJ8NyWttfm/EJUBWMJcx6ZTYZr6Qp52uuegWw/lBkCtCbnYZEmPRNL61m+u67dAmGxo+HTULA==", - "dev": true, - "license": "MIT", - "dependencies": { - "sort-keys": "^2.0.0", - "type-fest": "^0.4.1", - "write-json-file": "^3.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/write-pkg/node_modules/type-fest": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.4.1.tgz", - "integrity": "sha512-IwzA/LSfD2vC1/YDYMv/zHP4rDF1usCwllsDpbolT3D4fUepIO7f9K70jjmUewU/LmGUKJcwcVtDCpnKk4BPMw==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=6" - } - }, "node_modules/ws": { "version": "8.17.1", "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", @@ -50233,6 +50098,39 @@ } } }, + "node_modules/wsl-utils": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.3.1.tgz", + "integrity": "sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-wsl": "^3.1.0", + "powershell-utils": "^0.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/wsl-utils/node_modules/is-wsl": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz", + "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/xlsx": { "version": "0.20.3", "resolved": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz", @@ -50355,9 +50253,9 @@ "license": "ISC" }, "node_modules/yaml": { - "version": "2.8.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", - "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz", + "integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==", "dev": true, "license": "ISC", "bin": { @@ -50409,216 +50307,6 @@ "fd-slicer": "~1.1.0" } }, - "node_modules/yeoman-generator": { - "version": "7.5.1", - "resolved": "https://registry.npmjs.org/yeoman-generator/-/yeoman-generator-7.5.1.tgz", - "integrity": "sha512-MYncRvzSTd71BMwiUMAVhfX00sDD8DZDrmPzRxQkWuWQ0V1Qt4Rd0gS/Nee2QDTWvRjvCa+KBfiAVrtOySq+JA==", - "license": "BSD-2-Clause", - "dependencies": { - "@types/lodash-es": "^4.17.9", - "@yeoman/namespace": "^1.0.0", - "chalk": "^5.3.0", - "debug": "^4.1.1", - "execa": "^8.0.1", - "github-username": "^9.0.0", - "json-schema": "^0.4.0", - "latest-version": "^9.0.0", - "lodash-es": "^4.17.21", - "mem-fs-editor": "^11.0.1", - "minimist": "^1.2.8", - "read-package-up": "^11.0.0", - "semver": "^7.5.4", - "simple-git": "^3.20.0", - "sort-keys": "^5.0.0", - "text-table": "^0.2.0" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - }, - "peerDependencies": { - "@types/node": ">=18.18.5", - "@yeoman/types": "^1.1.1", - "mem-fs": "^4.0.0" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/yeoman-generator/node_modules/chalk": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", - "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/yeoman-generator/node_modules/execa": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", - "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", - "license": "MIT", - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^8.0.1", - "human-signals": "^5.0.0", - "is-stream": "^3.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^5.1.0", - "onetime": "^6.0.0", - "signal-exit": "^4.1.0", - "strip-final-newline": "^3.0.0" - }, - "engines": { - "node": ">=16.17" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/yeoman-generator/node_modules/get-stream": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", - "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", - "license": "MIT", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/yeoman-generator/node_modules/human-signals": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", - "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", - "license": "Apache-2.0", - "engines": { - "node": ">=16.17.0" - } - }, - "node_modules/yeoman-generator/node_modules/is-plain-obj": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", - "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/yeoman-generator/node_modules/is-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", - "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/yeoman-generator/node_modules/mimic-fn": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", - "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/yeoman-generator/node_modules/npm-run-path": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", - "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", - "license": "MIT", - "dependencies": { - "path-key": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/yeoman-generator/node_modules/onetime": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", - "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", - "license": "MIT", - "dependencies": { - "mimic-fn": "^4.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/yeoman-generator/node_modules/path-key": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", - "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/yeoman-generator/node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/yeoman-generator/node_modules/sort-keys": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-5.1.0.tgz", - "integrity": "sha512-aSbHV0DaBcr7u0PVHXzM6NbZNAtrr9sF6+Qfs9UUVG7Ll3jQ6hHi8F/xqIIcn2rvIVbr0v/2zyjSdwSV47AgLQ==", - "license": "MIT", - "dependencies": { - "is-plain-obj": "^4.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/yeoman-generator/node_modules/strip-final-newline": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", - "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", @@ -50646,7 +50334,6 @@ "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.1.tgz", "integrity": "sha512-GQHQqAopRhwU8Kt1DDM8NjibDXHC8eoh1erhGAJPEyveY9qqVeXvVikNKrDz69sHowPMorbPUrH/mx8c50eiBQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=18" }, @@ -50655,11 +50342,10 @@ } }, "node_modules/yoctocolors-cjs": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.2.tgz", - "integrity": "sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.3.tgz", + "integrity": "sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==", "license": "MIT", - "peer": true, "engines": { "node": ">=18" }, @@ -50891,13 +50577,13 @@ "license": "Apache-2.0", "dependencies": { "chalk": "^5.6.2", - "lodash-es": "^4.17.23", - "yeoman-generator": "^7.5.1", + "lodash-es": "^4.18.1", + "yeoman-generator": "^8.1.2", "yosay": "^3.0.0" }, "devDependencies": { "cross-env": "^10.1.0", - "fs-extra": "^11.3.3", + "fs-extra": "^11.3.4", "jest": "^30.3.0", "yeoman-test": "^11.3.1" }, @@ -50906,11 +50592,22 @@ "npm": ">= 4.0.0" } }, + "packages/generator-superset/node_modules/@sindresorhus/merge-streams": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", + "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "packages/generator-superset/node_modules/array-differ": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-4.0.0.tgz", "integrity": "sha512-Q6VPTLMsmXZ47ENG3V+wQyZS1ZxXMxFyYzA+Z/GMrJ6yIutAIEf9wTyroTzmGjNfox9/h3GdGBCVh43GVFx4Uw==", - "dev": true, "license": "MIT", "engines": { "node": "^12.20.0 || ^14.13.1 || >=16.0.0" @@ -50923,7 +50620,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/array-union/-/array-union-3.0.1.tgz", "integrity": "sha512-1OvF9IbWwaeiM9VhzYXVQacMibxpXOMYVNIvMtKRyX9SImBXpKcFr8XvFDeEslCyuH/t6KRt7HEO94AlP8Iatw==", - "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -50936,17 +50632,15 @@ "version": "4.0.4", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "dev": true, "license": "MIT", "engines": { "node": "18 || 20 || >=22" } }, "packages/generator-superset/node_modules/brace-expansion": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", - "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", - "dev": true, + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", "license": "MIT", "dependencies": { "balanced-match": "^4.0.2" @@ -50971,7 +50665,6 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/ejs/-/ejs-4.0.1.tgz", "integrity": "sha512-krvQtxc0btwSm/nvnt1UpnaFDFVJpJ0fdckmALpCgShsr/iGYHTnJiUliZTgmzq/UxTX33TtOQVKaNigMQp/6Q==", - "dev": true, "license": "Apache-2.0", "dependencies": { "jake": "^10.9.1" @@ -50983,6 +50676,59 @@ "node": ">=0.12.18" } }, + "packages/generator-superset/node_modules/execa": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-9.6.1.tgz", + "integrity": "sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==", + "license": "MIT", + "dependencies": { + "@sindresorhus/merge-streams": "^4.0.0", + "cross-spawn": "^7.0.6", + "figures": "^6.1.0", + "get-stream": "^9.0.0", + "human-signals": "^8.0.1", + "is-plain-obj": "^4.1.0", + "is-stream": "^4.0.1", + "npm-run-path": "^6.0.0", + "pretty-ms": "^9.2.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^4.0.0", + "yoctocolors": "^2.1.1" + }, + "engines": { + "node": "^18.19.0 || >=20.5.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "packages/generator-superset/node_modules/figures": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz", + "integrity": "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==", + "license": "MIT", + "dependencies": { + "is-unicode-supported": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "packages/generator-superset/node_modules/find-up-simple": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/find-up-simple/-/find-up-simple-1.0.1.tgz", + "integrity": "sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "packages/generator-superset/node_modules/fs-extra": { "version": "11.3.2", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.2.tgz", @@ -50998,11 +50744,83 @@ "node": ">=14.14" } }, + "packages/generator-superset/node_modules/get-stream": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", + "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", + "license": "MIT", + "dependencies": { + "@sec-ant/readable-stream": "^0.4.1", + "is-stream": "^4.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "packages/generator-superset/node_modules/hosted-git-info": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-9.0.2.tgz", + "integrity": "sha512-M422h7o/BR3rmCQ8UHi7cyyMqKltdP9Uo+J2fXK+RSAY+wTcKOIRyhTuKv4qn+DJf3g+PL890AzId5KZpX+CBg==", + "license": "ISC", + "dependencies": { + "lru-cache": "^11.1.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "packages/generator-superset/node_modules/human-signals": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.1.tgz", + "integrity": "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "packages/generator-superset/node_modules/index-to-position": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/index-to-position/-/index-to-position-1.2.0.tgz", + "integrity": "sha512-Yg7+ztRkqslMAS2iFaU+Oa4KTSidr63OsFGlOrJoW981kIYO3CGCS3wA95P1mUi/IVSJkn0D479KTJpVpvFNuw==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "packages/generator-superset/node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "packages/generator-superset/node_modules/is-stream": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", + "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "packages/generator-superset/node_modules/isbinaryfile": { "version": "5.0.7", "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-5.0.7.tgz", "integrity": "sha512-gnWD14Jh3FzS3CPhF0AxNOJ8CxqeblPTADzI38r0wt8ZyQl5edpy75myt08EG2oKvpyiqSqsx+Wkz9vtkbTqYQ==", - "dev": true, "license": "MIT", "engines": { "node": ">= 18.0.0" @@ -51011,11 +50829,19 @@ "url": "https://github.com/sponsors/gjtorikian/" } }, + "packages/generator-superset/node_modules/lru-cache": { + "version": "11.2.7", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", + "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==", + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, "packages/generator-superset/node_modules/mem-fs-editor": { "version": "12.0.2", "resolved": "https://registry.npmjs.org/mem-fs-editor/-/mem-fs-editor-12.0.2.tgz", "integrity": "sha512-H31IlLheZAEViQpInX2Wv9YdH98hLMOeqiaSmuENIDoXriQBd7KUmqXOgby0Xgy/9MoZvvDly2k9Zm77EdngAA==", - "dev": true, "license": "MIT", "dependencies": { "@types/ejs": "^3.1.5", @@ -51053,7 +50879,6 @@ "version": "10.2.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", - "dev": true, "license": "BlueOak-1.0.0", "dependencies": { "brace-expansion": "^5.0.2" @@ -51069,7 +50894,6 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/multimatch/-/multimatch-8.0.0.tgz", "integrity": "sha512-0D10M2/MnEyvoog7tmozlpSqL3HEU1evxUFa3v1dsKYmBDFSP1dLSX4CH2rNjpQ+4Fps8GKmUkCwiKryaKqd9A==", - "dev": true, "license": "MIT", "dependencies": { "array-differ": "^4.0.0", @@ -51083,11 +50907,129 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "packages/generator-superset/node_modules/normalize-package-data": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-8.0.0.tgz", + "integrity": "sha512-RWk+PI433eESQ7ounYxIp67CYuVsS1uYSonX3kA6ps/3LWfjVQa/ptEg6Y3T6uAMq1mWpX9PQ+qx+QaHpsc7gQ==", + "license": "BSD-2-Clause", + "dependencies": { + "hosted-git-info": "^9.0.0", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "packages/generator-superset/node_modules/npm-run-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz", + "integrity": "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==", + "license": "MIT", + "dependencies": { + "path-key": "^4.0.0", + "unicorn-magic": "^0.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "packages/generator-superset/node_modules/parse-json": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-8.3.0.tgz", + "integrity": "sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.26.2", + "index-to-position": "^1.1.0", + "type-fest": "^4.39.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "packages/generator-superset/node_modules/parse-json/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "packages/generator-superset/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "packages/generator-superset/node_modules/read-package-up": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/read-package-up/-/read-package-up-12.0.0.tgz", + "integrity": "sha512-Q5hMVBYur/eQNWDdbF4/Wqqr9Bjvtrw2kjGxxBbKLbx8bVCL8gcArjTy8zDUuLGQicftpMuU0riQNcAsbtOVsw==", + "license": "MIT", + "dependencies": { + "find-up-simple": "^1.0.1", + "read-pkg": "^10.0.0", + "type-fest": "^5.2.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "packages/generator-superset/node_modules/read-pkg": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-10.1.0.tgz", + "integrity": "sha512-I8g2lArQiP78ll51UeMZojewtYgIRCKCWqZEgOO8c/uefTI+XDXvCSXu3+YNUaTNvZzobrL5+SqHjBrByRRTdg==", + "license": "MIT", + "dependencies": { + "@types/normalize-package-data": "^2.4.4", + "normalize-package-data": "^8.0.0", + "parse-json": "^8.3.0", + "type-fest": "^5.4.4", + "unicorn-magic": "^0.4.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "packages/generator-superset/node_modules/read-pkg/node_modules/unicorn-magic": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.4.0.tgz", + "integrity": "sha512-wH590V9VNgYH9g3lH9wWjTrUoKsjLF6sGLjhR4sH1LWpLmCOH0Zf7PukhDA8BiS7KHe4oPNkcTHqYkj7SOGUOw==", + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "packages/generator-superset/node_modules/signal-exit": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, "license": "ISC", "engines": { "node": ">=14" @@ -51096,11 +51038,22 @@ "url": "https://github.com/sponsors/isaacs" } }, + "packages/generator-superset/node_modules/strip-final-newline": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz", + "integrity": "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "packages/generator-superset/node_modules/type-fest": { "version": "5.4.4", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.4.4.tgz", "integrity": "sha512-JnTrzGu+zPV3aXIUhnyWJj4z/wigMsdYajGLIYakqyOW1nPllzXEJee0QQbHj+CTIQtXGlAjuK0UY+2xTyjVAw==", - "dev": true, "license": "(MIT OR CC0-1.0)", "dependencies": { "tagged-tag": "^1.0.0" @@ -51112,11 +51065,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "packages/generator-superset/node_modules/unicorn-magic": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", + "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "packages/generator-superset/node_modules/vinyl": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-3.0.1.tgz", "integrity": "sha512-0QwqXteBNXgnLCdWdvPQBX6FXRHtIH3VhJPTd5Lwn28tJXc34YqSCWUmkOvtJHBmB3gGoPtrOKk3Ts8/kEZ9aA==", - "dev": true, "license": "MIT", "dependencies": { "clone": "^2.1.2", @@ -51128,6 +51092,43 @@ "node": ">=10.13.0" } }, + "packages/generator-superset/node_modules/yeoman-generator": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/yeoman-generator/-/yeoman-generator-8.1.2.tgz", + "integrity": "sha512-5e9jK2EPm6/c/zaYnuKnCLbRWfD9XwJYup1VzdJMgj010cgDn3YJWDfOIczabLfSoRSXRFxTDHfx1MEdIJEcGQ==", + "license": "BSD-2-Clause", + "dependencies": { + "@types/debug": "^4.1.12", + "@types/lodash-es": "^4.17.12", + "@yeoman/namespace": "^1.0.1", + "chalk": "^5.6.2", + "debug": "^4.4.3", + "execa": "^9.6.1", + "latest-version": "^9.0.0", + "lodash-es": "^4.17.23", + "mem-fs-editor": "^12.0.2", + "minimist": "^1.2.8", + "read-package-up": "^12.0.0", + "semver": "^7.7.4", + "simple-git": "^3.32.3", + "sort-keys": "^6.0.0", + "text-table": "^0.2.0", + "type-fest": "^5.4.4" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + }, + "peerDependencies": { + "@types/node": ">=20.19.35", + "@yeoman/types": "^1.1.1", + "mem-fs": "^4.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, "packages/generator-superset/node_modules/yeoman-test": { "version": "11.3.1", "resolved": "https://registry.npmjs.org/yeoman-test/-/yeoman-test-11.3.1.tgz", @@ -51167,12 +51168,12 @@ }, "packages/superset-core": { "name": "@apache-superset/core", - "version": "0.1.0-rc1", - "license": "ISC", + "version": "0.1.0-rc2", + "license": "Apache-2.0", "devDependencies": { "@babel/cli": "^7.28.6", "@babel/core": "^7.29.0", - "@babel/preset-env": "^7.29.0", + "@babel/preset-env": "^7.29.2", "@babel/preset-react": "^7.28.5", "@babel/preset-typescript": "^7.28.5", "@emotion/styled": "^11.14.1", @@ -51196,7 +51197,7 @@ "@fontsource/inter": "^5.2.6", "antd": "^5.26.0", "jed": "^1.1.1", - "lodash": "^4.17.21", + "lodash": "^4.18.1", "nanoid": "^5.0.9", "react": "^17.0.2", "react-dom": "^17.0.2", @@ -51211,10 +51212,11 @@ "dependencies": { "@apache-superset/core": "*", "@types/react": "*", - "lodash": "^4.17.23" + "lodash": "^4.18.1", + "tinycolor2": "*" }, "peerDependencies": { - "@ant-design/icons": "^5.2.6", + "@ant-design/icons": "^5.6.1", "@emotion/react": "^11.4.1", "@superset-ui/core": "*", "@testing-library/dom": "^8.20.1", @@ -51235,29 +51237,29 @@ "version": "0.20.4", "license": "Apache-2.0", "dependencies": { - "@ant-design/icons": "^5.2.6", + "@ant-design/icons": "^6.1.1", "@apache-superset/core": "*", - "@babel/runtime": "^7.28.6", + "@babel/runtime": "^7.29.2", "@types/json-bigint": "^1.0.4", "@visx/responsive": "^3.12.0", "ace-builds": "^1.43.6", - "ag-grid-community": "35.0.1", - "ag-grid-react": "35.0.1", + "ag-grid-community": "35.2.1", + "ag-grid-react": "35.2.1", "brace": "^0.11.1", "classnames": "^2.5.1", - "core-js": "^3.48.0", + "core-js": "^3.49.0", "csstype": "^3.2.3", "d3-format": "^3.1.2", "d3-interpolate": "^3.0.1", "d3-scale": "^4.0.2", "d3-time": "^3.1.0", "d3-time-format": "^4.1.0", - "dayjs": "^1.11.19", - "dompurify": "^3.3.3", + "dayjs": "^1.11.20", + "dompurify": "^3.4.0", "fetch-retry": "^6.0.0", - "handlebars": "^4.7.8", + "handlebars": "^4.7.9", "jed": "^1.1.1", - "lodash": "^4.17.23", + "lodash": "^4.18.1", "math-expression-evaluator": "^2.0.7", "pretty-ms": "^9.3.0", "re-resizable": "^6.11.2", @@ -51267,7 +51269,7 @@ "react-js-cron": "^5.2.0", "react-markdown": "^8.0.7", "react-resize-detector": "^7.1.2", - "react-syntax-highlighter": "^16.1.1", + "react-syntax-highlighter": "^16.1.0", "react-ultimate-pagination": "^1.3.2", "regenerator-runtime": "^0.14.1", "rehype-raw": "^7.0.0", @@ -51285,9 +51287,9 @@ "@types/d3-scale": "^2.1.1", "@types/d3-time": "^3.0.4", "@types/d3-time-format": "^4.0.3", - "@types/jquery": "^3.5.33", + "@types/jquery": "^4.0.0", "@types/lodash": "^4.17.24", - "@types/node": "^25.3.3", + "@types/node": "^25.6.0", "@types/prop-types": "^15.7.15", "@types/react-syntax-highlighter": "^15.5.13", "@types/react-table": "^7.7.20", @@ -51296,7 +51298,7 @@ "fetch-mock": "^12.6.0", "jest-mock-console": "^2.0.0", "resize-observer-polyfill": "1.5.1", - "timezone-mock": "1.4.0" + "timezone-mock": "^1.4.2" }, "peerDependencies": { "@emotion/cache": "^11.4.0", @@ -51319,6 +51321,57 @@ "tinycolor2": "*" } }, + "packages/superset-ui-core/node_modules/@ant-design/colors": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@ant-design/colors/-/colors-8.0.1.tgz", + "integrity": "sha512-foPVl0+SWIslGUtD/xBr1p9U4AKzPhNYEseXYRRo5QSzGACYZrQbe11AYJbYfAWnWSpGBx6JjBmSeugUsD9vqQ==", + "license": "MIT", + "dependencies": { + "@ant-design/fast-color": "^3.0.0" + } + }, + "packages/superset-ui-core/node_modules/@ant-design/fast-color": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@ant-design/fast-color/-/fast-color-3.0.1.tgz", + "integrity": "sha512-esKJegpW4nckh0o6kV3Tkb7NPIZYbPnnFxmQDUmL08ukXZAvV85TZBr70eGuke/CIArLaP6aw8lt9KILjnWuOw==", + "license": "MIT", + "engines": { + "node": ">=8.x" + } + }, + "packages/superset-ui-core/node_modules/@ant-design/icons": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-6.1.1.tgz", + "integrity": "sha512-AMT4N2y++TZETNHiM77fs4a0uPVCJGuL5MTonk13Pvv7UN7sID1cNEZOc1qNqx6zLKAOilTEFAdAoAFKa0U//Q==", + "license": "MIT", + "dependencies": { + "@ant-design/colors": "^8.0.0", + "@ant-design/icons-svg": "^4.4.0", + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "packages/superset-ui-core/node_modules/@ant-design/icons/node_modules/@rc-component/util": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@rc-component/util/-/util-1.10.0.tgz", + "integrity": "sha512-aY9GLBuiUdpyfIUpAWSYer4Tu3mVaZCo5A0q9NtXcazT3MRiI3/WNHCR+DUn5VAtR6iRRf0ynCqQUcHli5UdYw==", + "license": "MIT", + "dependencies": { + "is-mobile": "^5.0.0", + "react-is": "^18.2.0" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, "packages/superset-ui-core/node_modules/@types/mdast": { "version": "3.0.15", "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.15.tgz", @@ -51336,9 +51389,9 @@ "license": "MIT" }, "packages/superset-ui-core/node_modules/core-js": { - "version": "3.48.0", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.48.0.tgz", - "integrity": "sha512-zpEHTy1fjTMZCKLHUZoVeylt9XrzaIN2rbPXEt0k+q7JE5CkCZdo6bNq55bn24a69CH7ErAVLKijxJja4fw+UQ==", + "version": "3.49.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.49.0.tgz", + "integrity": "sha512-es1U2+YTtzpwkxVLwAFdSpaIMyQaq0PBgm3YD1W3Qpsn1NAmO3KSgZfu+oGSWVu6NvLHoHCV/aYcsE5wiB7ALg==", "hasInstallScript": true, "license": "MIT", "funding": { @@ -52200,6 +52253,13 @@ "url": "https://opencollective.com/unified" } }, + "packages/superset-ui-core/node_modules/timezone-mock": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/timezone-mock/-/timezone-mock-1.4.2.tgz", + "integrity": "sha512-RgPQkSrARNHBnchuFpjrYf97uWgEaseCXK2asSeGcgUzRUO3cBR9Zu0uO1z2Eyi/TQsQtHIvQLi0br30COHWUA==", + "dev": true, + "license": "MIT" + }, "packages/superset-ui-core/node_modules/unist-util-is": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-5.2.1.tgz", @@ -52291,7 +52351,7 @@ "dependencies": { "d3": "^3.5.17", "prop-types": "^15.8.1", - "react": "^19.2.1" + "react": "^19.2.5" }, "peerDependencies": { "@apache-superset/core": "*", @@ -52300,9 +52360,9 @@ } }, "plugins/legacy-plugin-chart-chord/node_modules/react": { - "version": "19.2.3", - "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz", - "integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==", + "version": "19.2.5", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.5.tgz", + "integrity": "sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA==", "license": "MIT", "engines": { "node": ">=0.10.0" @@ -52373,6 +52433,7 @@ "plugins/legacy-plugin-chart-map-box": { "name": "@superset-ui/legacy-plugin-chart-map-box", "version": "0.20.3", + "extraneous": true, "license": "Apache-2.0", "dependencies": { "@math.gl/web-mercator": "^4.1.0", @@ -52488,15 +52549,16 @@ "plugins/legacy-preset-chart-deckgl": { "name": "@superset-ui/legacy-preset-chart-deckgl", "version": "0.20.4", + "extraneous": true, "license": "Apache-2.0", "dependencies": { - "@deck.gl/aggregation-layers": "~9.2.5", + "@deck.gl/aggregation-layers": "~9.2.11", "@deck.gl/core": "~9.2.5", - "@deck.gl/extensions": "~9.2.5", + "@deck.gl/extensions": "~9.2.9", "@deck.gl/geo-layers": "~9.2.5", "@deck.gl/layers": "~9.2.5", "@deck.gl/mesh-layers": "~9.2.5", - "@deck.gl/react": "~9.2.5", + "@deck.gl/react": "~9.2.11", "@luma.gl/constants": "~9.2.5", "@luma.gl/core": "~9.2.5", "@luma.gl/engine": "~9.2.6", @@ -52511,8 +52573,8 @@ "d3-array": "^3.2.4", "d3-color": "^3.1.0", "d3-scale": "^4.0.2", - "handlebars": "^4.7.8", - "lodash": "^4.17.23", + "handlebars": "^4.7.9", + "lodash": "^4.18.1", "mousetrap": "^1.6.5", "ngeohash": "^0.6.3", "prop-types": "^15.8.1", @@ -52537,67 +52599,6 @@ "react-map-gl": "^6.1.19" } }, - "plugins/legacy-preset-chart-deckgl/node_modules/@deck.gl/mesh-layers": { - "version": "9.2.5", - "resolved": "https://registry.npmjs.org/@deck.gl/mesh-layers/-/mesh-layers-9.2.5.tgz", - "integrity": "sha512-OJ7Nx6xhp7+bQ4S4asUUp7PdGwcfmQpQhe5SHDGy1UBZ0yZE+ojOo9uvVfXvGBnqq4Zpg9avV+WRN1/BffBsOw==", - "license": "MIT", - "dependencies": { - "@loaders.gl/gltf": "^4.2.0", - "@loaders.gl/schema": "^4.2.0", - "@luma.gl/gltf": "^9.2.4", - "@luma.gl/shadertools": "^9.2.4" - }, - "peerDependencies": { - "@deck.gl/core": "~9.2.0", - "@luma.gl/core": "~9.2.4", - "@luma.gl/engine": "~9.2.4", - "@luma.gl/gltf": "~9.2.4", - "@luma.gl/shadertools": "~9.2.4" - } - }, - "plugins/legacy-preset-chart-deckgl/node_modules/@luma.gl/gltf": { - "version": "9.2.4", - "resolved": "https://registry.npmjs.org/@luma.gl/gltf/-/gltf-9.2.4.tgz", - "integrity": "sha512-OhT9Aq8/MC9r/ii8hAW7sPlIbol1ZG74jsZXsFsoGzw9gfuxT5LX71rQ/oKnsDz7dGf2cM+bM3qe8C7QiIaUvw==", - "license": "MIT", - "dependencies": { - "@loaders.gl/core": "^4.2.0", - "@loaders.gl/gltf": "^4.2.0", - "@loaders.gl/textures": "^4.2.0", - "@math.gl/core": "^4.1.0" - }, - "peerDependencies": { - "@luma.gl/constants": "~9.2.0", - "@luma.gl/core": "~9.2.0", - "@luma.gl/engine": "~9.2.0", - "@luma.gl/shadertools": "~9.2.0" - } - }, - "plugins/legacy-preset-chart-deckgl/node_modules/@mapbox/tiny-sdf": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/@mapbox/tiny-sdf/-/tiny-sdf-2.0.7.tgz", - "integrity": "sha512-25gQLQMcpivjOSA40g3gO6qgiFPDpWRoMfd+G/GoppPIeP6JDaMMkMrEJnMZhKyyS6iKwVt5YKu02vCUyJM3Ug==", - "license": "BSD-2-Clause" - }, - "plugins/legacy-preset-chart-deckgl/node_modules/d3-array": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", - "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", - "license": "ISC", - "dependencies": { - "internmap": "1 - 2" - }, - "engines": { - "node": ">=12" - } - }, - "plugins/legacy-preset-chart-deckgl/node_modules/internmap": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz", - "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==", - "license": "ISC" - }, "plugins/legacy-preset-chart-nvd3": { "name": "@superset-ui/legacy-preset-chart-nvd3", "version": "0.20.3", @@ -52605,9 +52606,9 @@ "dependencies": { "d3": "^3.5.17", "d3-tip": "^0.9.1", - "dompurify": "^3.3.3", + "dompurify": "^3.4.0", "fast-safe-stringify": "^2.1.1", - "lodash": "^4.17.23", + "lodash": "^4.18.1", "nvd3-fork": "^2.0.5", "prop-types": "^15.8.1", "urijs": "^1.19.11" @@ -52629,14 +52630,14 @@ "@types/react-table": "^7.7.20", "classnames": "^2.5.1", "d3-array": "^3.2.4", - "lodash": "^4.17.23", + "lodash": "^4.18.1", "memoize-one": "^5.2.1", "react-table": "^7.8.0", "regenerator-runtime": "^0.14.1", "xss": "^1.0.15" }, "peerDependencies": { - "@ant-design/icons": "^5.2.6", + "@ant-design/icons": "^5.6.1", "@apache-superset/core": "*", "@superset-ui/chart-controls": "*", "@superset-ui/core": "*", @@ -52646,7 +52647,6 @@ "@testing-library/react-hooks": "*", "@testing-library/user-event": "*", "@types/react": "*", - "match-sorter": "^6.3.3", "react": "^17.0.2", "react-dom": "^17.0.2" } @@ -52670,10 +52670,10 @@ "dependencies": { "@types/geojson": "^7946.0.16", "geojson": "^0.5.0", - "lodash": "^4.17.23" + "lodash": "^4.18.1" }, "peerDependencies": { - "@ant-design/icons": "^5.2.6", + "@ant-design/icons": "^5.6.1", "@apache-superset/core": "*", "@reduxjs/toolkit": "*", "@superset-ui/chart-controls": "*", @@ -52699,7 +52699,7 @@ "@types/react-redux": "^7.1.34", "acorn": "^8.16.0", "d3-array": "^3.2.4", - "lodash": "^4.17.23", + "lodash": "^4.18.1", "zod": "^4.3.6" }, "peerDependencies": { @@ -52757,7 +52757,7 @@ "ace-builds": "^1.4.14", "dayjs": "^1.11.19", "handlebars": "^4.7.8", - "lodash": "^4.17.11", + "lodash": "^4.18.1", "react": "^17.0.2", "react-ace": "^10.1.0", "react-dom": "^17.0.2" @@ -52792,15 +52792,135 @@ "jest": "^30.3.0" }, "peerDependencies": { - "@ant-design/icons": "^5.2.6", + "@ant-design/icons": "^5.6.1", "@apache-superset/core": "*", "@superset-ui/chart-controls": "*", "@superset-ui/core": "*", - "lodash": "^4.17.11", + "lodash": "^4.18.1", "prop-types": "*", "react": "^17.0.2", - "react-dom": "^17.0.2", - "react-icons": "5.4.0" + "react-dom": "^17.0.2" + } + }, + "plugins/plugin-chart-point-cluster-map": { + "name": "@superset-ui/plugin-chart-point-cluster-map", + "version": "1.0.0", + "license": "Apache-2.0", + "dependencies": { + "@math.gl/web-mercator": "^4.1.0", + "mapbox-gl": "^3.22.0", + "maplibre-gl": "^5.23.0", + "react-map-gl": "^8.1.0", + "supercluster": "^8.0.1" + }, + "peerDependencies": { + "@apache-superset/core": "*", + "@superset-ui/chart-controls": "*", + "@superset-ui/core": "*", + "react": "^17.0.2 || ^19.0.0", + "react-dom": "^17.0.2 || ^19.0.0" + } + }, + "plugins/plugin-chart-point-cluster-map/node_modules/@mapbox/point-geometry": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@mapbox/point-geometry/-/point-geometry-1.1.0.tgz", + "integrity": "sha512-YGcBz1cg4ATXDCM/71L9xveh4dynfGmcLDqufR+nQQy3fKwsAZsWd/x4621/6uJaeB9mwOHE6hPeDgXz9uViUQ==", + "license": "ISC" + }, + "plugins/plugin-chart-point-cluster-map/node_modules/@mapbox/vector-tile": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@mapbox/vector-tile/-/vector-tile-2.0.4.tgz", + "integrity": "sha512-AkOLcbgGTdXScosBWwmmD7cDlvOjkg/DetGva26pIRiZPdeJYjYKarIlb4uxVzi6bwHO6EWH82eZ5Nuv4T5DUg==", + "license": "BSD-3-Clause", + "dependencies": { + "@mapbox/point-geometry": "~1.1.0", + "@types/geojson": "^7946.0.16", + "pbf": "^4.0.1" + } + }, + "plugins/plugin-chart-point-cluster-map/node_modules/@maplibre/geojson-vt": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@maplibre/geojson-vt/-/geojson-vt-6.1.0.tgz", + "integrity": "sha512-2eIY4gZxeKIVOZVNkAMb+5NgXhgsMQpOveTQAvnp53LYqHGJZDidk7Ew0Tged9PThidpbS+NFTh0g4zivhPDzQ==", + "license": "ISC", + "dependencies": { + "kdbush": "^4.0.2" + } + }, + "plugins/plugin-chart-point-cluster-map/node_modules/earcut": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/earcut/-/earcut-3.0.2.tgz", + "integrity": "sha512-X7hshQbLyMJ/3RPhyObLARM2sNxxmRALLKx1+NVFFnQ9gKzmCrxm9+uLIAdBcvc8FNLpctqlQ2V6AE92Ol9UDQ==", + "license": "ISC" + }, + "plugins/plugin-chart-point-cluster-map/node_modules/maplibre-gl": { + "version": "5.23.0", + "resolved": "https://registry.npmjs.org/maplibre-gl/-/maplibre-gl-5.23.0.tgz", + "integrity": "sha512-aou8YBNFS8uVtDWFWt0W/6oorfl18wt+oIA8fnXk1kivjkbtXi9gGrQvflTpwrR3hG13aWdIdbYWeN0NFMV7ag==", + "license": "BSD-3-Clause", + "dependencies": { + "@mapbox/jsonlint-lines-primitives": "^2.0.2", + "@mapbox/point-geometry": "^1.1.0", + "@mapbox/tiny-sdf": "^2.0.7", + "@mapbox/unitbezier": "^0.0.1", + "@mapbox/vector-tile": "^2.0.4", + "@mapbox/whoots-js": "^3.1.0", + "@maplibre/geojson-vt": "^6.1.0", + "@maplibre/maplibre-gl-style-spec": "^24.8.1", + "@maplibre/mlt": "^1.1.8", + "@maplibre/vt-pbf": "^4.3.0", + "@types/geojson": "^7946.0.16", + "earcut": "^3.0.2", + "gl-matrix": "^3.4.4", + "kdbush": "^4.0.2", + "murmurhash-js": "^1.0.0", + "pbf": "^4.0.1", + "potpack": "^2.1.0", + "quickselect": "^3.0.0", + "tinyqueue": "^3.0.0" + }, + "engines": { + "node": ">=16.14.0", + "npm": ">=8.1.0" + }, + "funding": { + "url": "https://github.com/maplibre/maplibre-gl-js?sponsor=1" + } + }, + "plugins/plugin-chart-point-cluster-map/node_modules/pbf": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pbf/-/pbf-4.0.1.tgz", + "integrity": "sha512-SuLdBvS42z33m8ejRbInMapQe8n0D3vN/Xd5fmWM3tufNgRQFBpaW2YVJxQZV4iPNqb0vEFvssMEo5w9c6BTIA==", + "license": "BSD-3-Clause", + "dependencies": { + "resolve-protobuf-schema": "^2.1.0" + }, + "bin": { + "pbf": "bin/pbf" + } + }, + "plugins/plugin-chart-point-cluster-map/node_modules/react-map-gl": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/react-map-gl/-/react-map-gl-8.1.0.tgz", + "integrity": "sha512-vDx/QXR3Tb+8/ap/z6gdMjJQ8ZEyaZf6+uMSPz7jhWF5VZeIsKsGfPvwHVPPwGF43Ryn+YR4bd09uEFNR5OPdg==", + "license": "MIT", + "dependencies": { + "@vis.gl/react-mapbox": "8.1.0", + "@vis.gl/react-maplibre": "8.1.0" + }, + "peerDependencies": { + "mapbox-gl": ">=1.13.0", + "maplibre-gl": ">=1.13.0", + "react": ">=16.3.0", + "react-dom": ">=16.3.0" + }, + "peerDependenciesMeta": { + "mapbox-gl": { + "optional": true + }, + "maplibre-gl": { + "optional": true + } } }, "plugins/plugin-chart-table": { @@ -52812,15 +52932,14 @@ "@types/react-table": "^7.7.20", "classnames": "^2.5.1", "d3-array": "^3.2.4", - "lodash": "^4.17.23", + "lodash": "^4.18.1", "memoize-one": "^5.2.1", - "react-icons": "5.4.0", "react-table": "^7.8.0", "regenerator-runtime": "^0.14.1", "xss": "^1.0.15" }, "peerDependencies": { - "@ant-design/icons": "^5.2.6", + "@ant-design/icons": "^5.6.1", "@apache-superset/core": "*", "@superset-ui/chart-controls": "*", "@superset-ui/core": "*", @@ -52830,7 +52949,7 @@ "@testing-library/react-hooks": "*", "@testing-library/user-event": "*", "@types/react": "*", - "match-sorter": "^6.3.3", + "match-sorter": "^8.2.0", "react": "^17.0.2", "react-dom": "^17.0.2" } @@ -52853,7 +52972,7 @@ "license": "Apache-2.0", "dependencies": { "@types/d3-scale": "^4.0.9", - "d3-cloud": "^1.2.9", + "d3-cloud": "^1.2.8", "d3-scale": "^4.0.2" }, "devDependencies": { @@ -52886,6 +53005,202 @@ "d3-dispatch": "^1.0.3" } }, + "plugins/preset-chart-deckgl": { + "name": "@superset-ui/preset-chart-deckgl", + "version": "1.0.0", + "license": "Apache-2.0", + "dependencies": { + "@deck.gl/aggregation-layers": "~9.2.11", + "@deck.gl/core": "~9.2.5", + "@deck.gl/extensions": "~9.2.9", + "@deck.gl/geo-layers": "~9.2.5", + "@deck.gl/layers": "~9.2.5", + "@deck.gl/mapbox": "^9.3.1", + "@deck.gl/mesh-layers": "~9.2.5", + "@luma.gl/constants": "~9.2.5", + "@luma.gl/core": "~9.2.5", + "@luma.gl/engine": "~9.2.6", + "@luma.gl/shadertools": "~9.2.6", + "@luma.gl/webgl": "~9.2.6", + "@mapbox/geojson-extent": "^1.0.1", + "@math.gl/web-mercator": "^4.1.0", + "@types/d3-array": "^3.2.2", + "@types/geojson": "^7946.0.16", + "bootstrap-slider": "^11.0.2", + "d3-array": "^3.2.4", + "d3-color": "^3.1.0", + "d3-scale": "^4.0.2", + "handlebars": "^4.7.9", + "lodash": "^4.18.1", + "maplibre-gl": "^5.23.0", + "mousetrap": "^1.6.5", + "ngeohash": "^0.6.3", + "prop-types": "^15.8.1", + "react-map-gl": "^8.1.1", + "underscore": "^1.13.7", + "urijs": "^1.19.11", + "xss": "^1.0.15" + }, + "devDependencies": { + "@types/mapbox__geojson-extent": "^1.0.3", + "@types/ngeohash": "^0.6.8", + "@types/underscore": "^1.13.0", + "@types/urijs": "^1.19.26" + }, + "peerDependencies": { + "@apache-superset/core": "*", + "@superset-ui/chart-controls": "*", + "@superset-ui/core": "*", + "dayjs": "^1.11.19", + "mapbox-gl": ">=1.0.0", + "react": "^17.0.2 || ^19.0.0", + "react-dom": "^17.0.2 || ^19.0.0" + }, + "peerDependenciesMeta": { + "mapbox-gl": { + "optional": true + } + } + }, + "plugins/preset-chart-deckgl/node_modules/@deck.gl/aggregation-layers": { + "version": "9.2.11", + "resolved": "https://registry.npmjs.org/@deck.gl/aggregation-layers/-/aggregation-layers-9.2.11.tgz", + "integrity": "sha512-MRFbBHtMcDkOthxXnMPm6nF08DjFDACaIQsJSyHkdWtLUTSLHsWnOTn/8QbB4ka86WyNyfJy3dibLu/m3ei2ow==", + "license": "MIT", + "dependencies": { + "@luma.gl/constants": "~9.2.6", + "@luma.gl/shadertools": "~9.2.6", + "@math.gl/core": "^4.1.0", + "@math.gl/web-mercator": "^4.1.0", + "d3-hexbin": "^0.2.1" + }, + "peerDependencies": { + "@deck.gl/core": "~9.2.0", + "@deck.gl/layers": "~9.2.0", + "@luma.gl/core": "~9.2.6", + "@luma.gl/engine": "~9.2.6" + } + }, + "plugins/preset-chart-deckgl/node_modules/@deck.gl/extensions": { + "version": "9.2.11", + "resolved": "https://registry.npmjs.org/@deck.gl/extensions/-/extensions-9.2.11.tgz", + "integrity": "sha512-zlpM4Bg1ifBziW1Juiii9NY5gyW2rEhyVTWnhagH/bpTCZ2E73OhnToYt1ouqmoxL6lMtIjhRXz6LPb7tJbHHQ==", + "license": "MIT", + "dependencies": { + "@luma.gl/constants": "~9.2.6", + "@luma.gl/shadertools": "~9.2.6", + "@math.gl/core": "^4.1.0" + }, + "peerDependencies": { + "@deck.gl/core": "~9.2.0", + "@luma.gl/core": "~9.2.6", + "@luma.gl/engine": "~9.2.6" + } + }, + "plugins/preset-chart-deckgl/node_modules/@deck.gl/mapbox": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/@deck.gl/mapbox/-/mapbox-9.3.1.tgz", + "integrity": "sha512-4SgpWMeZiqiZEiz9yPdr89cVRL8HFcvXLxXUA0ExhMreUdNuK/j2OIQHPhw6vp1xCFbJEEqRelQ0pJYkhGDkYw==", + "license": "MIT", + "dependencies": { + "@math.gl/web-mercator": "^4.1.0" + }, + "peerDependencies": { + "@deck.gl/core": "~9.3.0", + "@luma.gl/core": "~9.3.2", + "@math.gl/web-mercator": "^4.1.0" + } + }, + "plugins/preset-chart-deckgl/node_modules/@mapbox/point-geometry": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@mapbox/point-geometry/-/point-geometry-1.1.0.tgz", + "integrity": "sha512-YGcBz1cg4ATXDCM/71L9xveh4dynfGmcLDqufR+nQQy3fKwsAZsWd/x4621/6uJaeB9mwOHE6hPeDgXz9uViUQ==", + "license": "ISC" + }, + "plugins/preset-chart-deckgl/node_modules/@mapbox/vector-tile": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@mapbox/vector-tile/-/vector-tile-2.0.4.tgz", + "integrity": "sha512-AkOLcbgGTdXScosBWwmmD7cDlvOjkg/DetGva26pIRiZPdeJYjYKarIlb4uxVzi6bwHO6EWH82eZ5Nuv4T5DUg==", + "license": "BSD-3-Clause", + "dependencies": { + "@mapbox/point-geometry": "~1.1.0", + "@types/geojson": "^7946.0.16", + "pbf": "^4.0.1" + } + }, + "plugins/preset-chart-deckgl/node_modules/@maplibre/geojson-vt": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@maplibre/geojson-vt/-/geojson-vt-6.1.0.tgz", + "integrity": "sha512-2eIY4gZxeKIVOZVNkAMb+5NgXhgsMQpOveTQAvnp53LYqHGJZDidk7Ew0Tged9PThidpbS+NFTh0g4zivhPDzQ==", + "license": "ISC", + "dependencies": { + "kdbush": "^4.0.2" + } + }, + "plugins/preset-chart-deckgl/node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "plugins/preset-chart-deckgl/node_modules/earcut": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/earcut/-/earcut-3.0.2.tgz", + "integrity": "sha512-X7hshQbLyMJ/3RPhyObLARM2sNxxmRALLKx1+NVFFnQ9gKzmCrxm9+uLIAdBcvc8FNLpctqlQ2V6AE92Ol9UDQ==", + "license": "ISC" + }, + "plugins/preset-chart-deckgl/node_modules/maplibre-gl": { + "version": "5.23.0", + "resolved": "https://registry.npmjs.org/maplibre-gl/-/maplibre-gl-5.23.0.tgz", + "integrity": "sha512-aou8YBNFS8uVtDWFWt0W/6oorfl18wt+oIA8fnXk1kivjkbtXi9gGrQvflTpwrR3hG13aWdIdbYWeN0NFMV7ag==", + "license": "BSD-3-Clause", + "dependencies": { + "@mapbox/jsonlint-lines-primitives": "^2.0.2", + "@mapbox/point-geometry": "^1.1.0", + "@mapbox/tiny-sdf": "^2.0.7", + "@mapbox/unitbezier": "^0.0.1", + "@mapbox/vector-tile": "^2.0.4", + "@mapbox/whoots-js": "^3.1.0", + "@maplibre/geojson-vt": "^6.1.0", + "@maplibre/maplibre-gl-style-spec": "^24.8.1", + "@maplibre/mlt": "^1.1.8", + "@maplibre/vt-pbf": "^4.3.0", + "@types/geojson": "^7946.0.16", + "earcut": "^3.0.2", + "gl-matrix": "^3.4.4", + "kdbush": "^4.0.2", + "murmurhash-js": "^1.0.0", + "pbf": "^4.0.1", + "potpack": "^2.1.0", + "quickselect": "^3.0.0", + "tinyqueue": "^3.0.0" + }, + "engines": { + "node": ">=16.14.0", + "npm": ">=8.1.0" + }, + "funding": { + "url": "https://github.com/maplibre/maplibre-gl-js?sponsor=1" + } + }, + "plugins/preset-chart-deckgl/node_modules/pbf": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pbf/-/pbf-4.0.1.tgz", + "integrity": "sha512-SuLdBvS42z33m8ejRbInMapQe8n0D3vN/Xd5fmWM3tufNgRQFBpaW2YVJxQZV4iPNqb0vEFvssMEo5w9c6BTIA==", + "license": "BSD-3-Clause", + "dependencies": { + "resolve-protobuf-schema": "^2.1.0" + }, + "bin": { + "pbf": "bin/pbf" + } + }, "tools/eslint-i18n-strings": { "version": "1.0.0", "extraneous": true, diff --git a/superset-frontend/package.json b/superset-frontend/package.json index b1a82dd245e..e13d08ca31c 100644 --- a/superset-frontend/package.json +++ b/superset-frontend/package.json @@ -128,17 +128,17 @@ "@superset-ui/legacy-plugin-chart-chord": "file:./plugins/legacy-plugin-chart-chord", "@superset-ui/legacy-plugin-chart-country-map": "file:./plugins/legacy-plugin-chart-country-map", "@superset-ui/legacy-plugin-chart-horizon": "file:./plugins/legacy-plugin-chart-horizon", - "@superset-ui/legacy-plugin-chart-map-box": "file:./plugins/legacy-plugin-chart-map-box", "@superset-ui/legacy-plugin-chart-paired-t-test": "file:./plugins/legacy-plugin-chart-paired-t-test", "@superset-ui/legacy-plugin-chart-parallel-coordinates": "file:./plugins/legacy-plugin-chart-parallel-coordinates", "@superset-ui/legacy-plugin-chart-partition": "file:./plugins/legacy-plugin-chart-partition", "@superset-ui/legacy-plugin-chart-rose": "file:./plugins/legacy-plugin-chart-rose", "@superset-ui/legacy-plugin-chart-world-map": "file:./plugins/legacy-plugin-chart-world-map", - "@superset-ui/legacy-preset-chart-deckgl": "file:./plugins/legacy-preset-chart-deckgl", + "@superset-ui/preset-chart-deckgl": "file:./plugins/preset-chart-deckgl", "@superset-ui/legacy-preset-chart-nvd3": "file:./plugins/legacy-preset-chart-nvd3", "@superset-ui/plugin-chart-ag-grid-table": "file:./plugins/plugin-chart-ag-grid-table", "@superset-ui/plugin-chart-cartodiagram": "file:./plugins/plugin-chart-cartodiagram", "@superset-ui/plugin-chart-echarts": "file:./plugins/plugin-chart-echarts", + "@superset-ui/plugin-chart-point-cluster-map": "file:./plugins/plugin-chart-point-cluster-map", "@superset-ui/plugin-chart-handlebars": "file:./plugins/plugin-chart-handlebars", "@superset-ui/plugin-chart-pivot-table": "file:./plugins/plugin-chart-pivot-table", "@superset-ui/plugin-chart-table": "file:./plugins/plugin-chart-table", @@ -154,49 +154,49 @@ "@visx/scale": "^3.5.0", "@visx/tooltip": "^3.0.0", "@visx/xychart": "^3.5.1", - "ag-grid-community": "35.0.1", - "ag-grid-react": "35.0.1", + "ag-grid-community": "35.2.1", + "ag-grid-react": "35.2.1", "antd": "^5.26.0", "chrono-node": "^2.9.0", "classnames": "^2.2.5", "content-disposition": "^1.0.1", "d3-color": "^3.1.0", "d3-scale": "^4.0.2", - "dayjs": "^1.11.19", + "dayjs": "^1.11.20", "dom-to-image-more": "^3.7.2", "dom-to-pdf": "^0.3.2", "echarts": "^5.6.0", "fast-glob": "^3.3.2", - "fs-extra": "^11.3.3", - "fuse.js": "^7.1.0", - "geolib": "^3.3.4", - "geostyler": "^18.3.1", + "fs-extra": "^11.3.4", + "fuse.js": "^7.3.0", + "geolib": "^3.3.14", + "geostyler": "^18.5.0", "geostyler-data": "^1.1.0", - "geostyler-openlayers-parser": "^5.4.0", + "geostyler-openlayers-parser": "^5.7.0", "geostyler-style": "11.0.2", "geostyler-wfs-parser": "^3.0.1", - "google-auth-library": "^10.6.1", + "google-auth-library": "^10.6.2", "immer": "^11.1.4", "interweave": "^13.1.1", "jquery": "^4.0.0", "js-levenshtein": "^1.1.6", "json-bigint": "^1.0.0", "json-stringify-pretty-compact": "^2.0.0", - "lodash": "^4.17.23", - "mapbox-gl": "^3.19.0", - "markdown-to-jsx": "^9.7.6", - "match-sorter": "^6.3.4", + "lodash": "^4.18.1", + "mapbox-gl": "^3.22.0", + "markdown-to-jsx": "^9.7.16", + "match-sorter": "^8.2.0", "memoize-one": "^5.2.1", "mousetrap": "^1.6.5", "mustache": "^4.2.0", - "nanoid": "^5.1.6", + "nanoid": "^5.1.9", "ol": "^10.8.0", "pretty-ms": "^9.3.0", "query-string": "9.3.1", "re-resizable": "^6.11.2", "react": "^17.0.2", - "react-arborist": "^3.4.3", - "react-checkbox-tree": "^1.8.0", + "react-arborist": "^3.5.0", + "react-checkbox-tree": "^2.0.1", "react-diff-viewer-continued": "^4.2.0", "react-dnd": "^11.1.3", "react-dnd-html5-backend": "^11.1.3", @@ -230,7 +230,7 @@ "use-event-callback": "^0.1.0", "use-immer": "^0.11.0", "use-query-params": "^2.2.2", - "uuid": "^13.0.0", + "uuid": "^14.0.0", "xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz", "yargs": "^17.7.2" }, @@ -244,34 +244,34 @@ "@babel/plugin-transform-export-namespace-from": "^7.27.1", "@babel/plugin-transform-modules-commonjs": "^7.28.6", "@babel/plugin-transform-runtime": "^7.29.0", - "@babel/preset-env": "^7.29.0", + "@babel/preset-env": "^7.29.2", "@babel/preset-react": "^7.28.5", "@babel/preset-typescript": "^7.28.5", "@babel/register": "^7.23.7", - "@babel/runtime": "^7.28.6", - "@babel/runtime-corejs3": "^7.29.0", + "@babel/runtime": "^7.29.2", + "@babel/runtime-corejs3": "^7.29.2", "@babel/types": "^7.28.6", "@cypress/react": "^8.0.2", "@emotion/babel-plugin": "^11.13.5", "@emotion/jest": "^11.14.2", "@istanbuljs/nyc-config-typescript": "^1.0.1", "@mihkeleidast/storybook-addon-source": "^1.0.1", - "@playwright/test": "^1.58.2", + "@playwright/test": "^1.59.1", "@pmmmwh/react-refresh-webpack-plugin": "^0.6.2", - "@storybook/addon-actions": "^8.6.17", - "@storybook/addon-controls": "^8.6.17", - "@storybook/addon-essentials": "^8.6.17", - "@storybook/addon-links": "^8.6.17", - "@storybook/addon-mdx-gfm": "^8.6.17", - "@storybook/components": "^8.6.17", - "@storybook/preview-api": "^8.6.17", - "@storybook/react": "^8.6.17", - "@storybook/react-webpack5": "^8.6.17", - "@storybook/test": "^8.6.15", + "@storybook/addon-actions": "^8.6.18", + "@storybook/addon-controls": "^8.6.18", + "@storybook/addon-essentials": "^8.6.18", + "@storybook/addon-links": "^8.6.18", + "@storybook/addon-mdx-gfm": "^8.6.18", + "@storybook/components": "^8.6.18", + "@storybook/preview-api": "^8.6.18", + "@storybook/react": "^8.6.18", + "@storybook/react-webpack5": "^8.6.18", + "@storybook/test": "^8.6.18", "@storybook/test-runner": "^0.17.0", "@svgr/webpack": "^8.1.0", - "@swc/core": "^1.15.18", - "@swc/plugin-emotion": "^14.6.0", + "@swc/core": "^1.15.30", + "@swc/plugin-emotion": "^14.8.0", "@swc/plugin-transform-imports": "^12.5.0", "@testing-library/dom": "^8.20.1", "@testing-library/jest-dom": "^6.9.1", @@ -281,10 +281,11 @@ "@types/content-disposition": "^0.5.9", "@types/dom-to-image": "^2.6.7", "@types/jest": "^30.0.0", + "@types/jquery": "^4.0.0", "@types/js-levenshtein": "^1.1.3", "@types/json-bigint": "^1.0.4", "@types/mousetrap": "^1.6.15", - "@types/node": "^25.3.3", + "@types/node": "^25.6.0", "@types/react": "^17.0.83", "@types/react-dom": "^17.0.26", "@types/react-loadable": "^5.5.11", @@ -301,14 +302,14 @@ "@typescript-eslint/eslint-plugin": "^7.18.0", "@typescript-eslint/parser": "^7.18.0", "babel-jest": "^30.0.2", - "babel-loader": "^10.0.0", + "babel-loader": "^10.1.1", "babel-plugin-dynamic-import-node": "^2.3.3", "babel-plugin-jsx-remove-data-test-id": "^3.0.0", "babel-plugin-lodash": "^3.3.4", - "baseline-browser-mapping": "^2.10.7", + "baseline-browser-mapping": "^2.10.16", "cheerio": "1.2.0", "concurrently": "^9.2.1", - "copy-webpack-plugin": "^13.0.1", + "copy-webpack-plugin": "^14.0.0", "cross-env": "^10.1.0", "css-loader": "^7.1.4", "css-minimizer-webpack-plugin": "^8.0.0", @@ -317,17 +318,17 @@ "eslint-import-resolver-alias": "^1.1.2", "eslint-import-resolver-typescript": "^4.4.4", "eslint-plugin-cypress": "^3.6.0", - "eslint-plugin-file-progress": "^1.5.0", "eslint-plugin-i18n-strings": "file:eslint-rules/eslint-plugin-i18n-strings", "eslint-plugin-icons": "file:eslint-rules/eslint-plugin-icons", "eslint-plugin-import": "^2.32.0", "eslint-plugin-jest-dom": "^5.5.0", "eslint-plugin-lodash": "^7.4.0", + "eslint-plugin-no-only-tests": "^3.3.0", "eslint-plugin-prettier": "^5.5.5", "eslint-plugin-react-prefer-function-component": "^5.0.0", - "eslint-plugin-react-you-might-not-need-an-effect": "^0.9.2", + "eslint-plugin-react-you-might-not-need-an-effect": "^0.9.3", "eslint-plugin-storybook": "^0.8.0", - "eslint-plugin-testing-library": "^7.16.0", + "eslint-plugin-testing-library": "^7.16.2", "eslint-plugin-theme-colors": "file:eslint-rules/eslint-plugin-theme-colors", "fetch-mock": "^12.6.0", "fork-ts-checker-webpack-plugin": "^9.1.0", @@ -337,17 +338,17 @@ "imports-loader": "^5.0.0", "jest": "^30.3.0", "jest-environment-jsdom": "^29.7.0", - "jest-html-reporter": "^4.3.0", + "jest-html-reporter": "^4.4.0", "jest-websocket-mock": "^2.5.0", "js-yaml-loader": "^1.2.2", - "jsdom": "^28.1.0", - "lerna": "^8.2.3", + "jsdom": "^29.0.2", + "lerna": "^9.0.4", "lightningcss": "^1.32.0", - "mini-css-extract-plugin": "^2.10.1", - "open-cli": "^8.0.0", - "oxlint": "^1.53.0", + "mini-css-extract-plugin": "^2.10.2", + "open-cli": "^9.0.0", + "oxlint": "^1.61.0", "po2json": "^0.4.5", - "prettier": "3.8.1", + "prettier": "3.8.3", "prettier-plugin-packagejson": "^3.0.2", "process": "^0.11.10", "react-refresh": "^0.18.0", @@ -355,21 +356,21 @@ "redux-mock-store": "^1.5.4", "source-map": "^0.7.6", "source-map-support": "^0.5.21", - "speed-measure-webpack-plugin": "^1.5.0", - "storybook": "8.6.17", + "speed-measure-webpack-plugin": "^1.6.0", + "storybook": "8.6.18", "style-loader": "^4.0.0", "swc-loader": "^0.2.7", - "terser-webpack-plugin": "^5.3.17", + "terser-webpack-plugin": "^5.4.0", "thread-loader": "^4.0.4", - "ts-jest": "^29.4.6", + "ts-jest": "^29.4.9", "tscw-config": "^1.1.2", "tsx": "^4.21.0", "typescript": "5.4.5", "unzipper": "^0.12.3", "vm-browserify": "^1.1.2", - "wait-on": "^9.0.4", - "webpack": "^5.105.4", - "webpack-bundle-analyzer": "^5.2.0", + "wait-on": "^9.0.5", + "webpack": "^5.106.0", + "webpack-bundle-analyzer": "^5.3.0", "webpack-cli": "^6.0.1", "webpack-dev-server": "^5.2.3", "webpack-manifest-plugin": "^5.0.1", diff --git a/superset-frontend/packages/generator-superset/package.json b/superset-frontend/packages/generator-superset/package.json index 78b199b90a5..662e437107f 100644 --- a/superset-frontend/packages/generator-superset/package.json +++ b/superset-frontend/packages/generator-superset/package.json @@ -29,13 +29,13 @@ }, "dependencies": { "chalk": "^5.6.2", - "lodash-es": "^4.17.23", - "yeoman-generator": "^7.5.1", + "lodash-es": "^4.18.1", + "yeoman-generator": "^8.1.2", "yosay": "^3.0.0" }, "devDependencies": { "cross-env": "^10.1.0", - "fs-extra": "^11.3.3", + "fs-extra": "^11.3.4", "jest": "^30.3.0", "yeoman-test": "^11.3.1" }, diff --git a/superset-frontend/packages/superset-core/package.json b/superset-frontend/packages/superset-core/package.json index d95b5bea6ec..1ccf3616b81 100644 --- a/superset-frontend/packages/superset-core/package.json +++ b/superset-frontend/packages/superset-core/package.json @@ -1,6 +1,6 @@ { "name": "@apache-superset/core", - "version": "0.1.0-rc1", + "version": "0.1.0-rc2", "description": "This package contains UI elements, APIs, and utility functions used by Superset.", "sideEffects": false, "main": "lib/index.js", @@ -70,12 +70,12 @@ "files": [ "lib" ], - "author": "", - "license": "ISC", + "author": "Apache Software Foundation", + "license": "Apache-2.0", "devDependencies": { "@babel/cli": "^7.28.6", "@babel/core": "^7.29.0", - "@babel/preset-env": "^7.29.0", + "@babel/preset-env": "^7.29.2", "@babel/preset-react": "^7.28.5", "@babel/preset-typescript": "^7.28.5", "typescript": "^5.0.0", @@ -102,7 +102,7 @@ "react-dom": "^17.0.2", "react-loadable": "^5.5.0", "tinycolor2": "*", - "lodash": "^4.17.21", + "lodash": "^4.18.1", "antd": "^5.26.0", "jed": "^1.1.1" }, diff --git a/superset-frontend/packages/superset-core/src/theme/Theme.test.tsx b/superset-frontend/packages/superset-core/src/theme/Theme.test.tsx index 873cf94ec5f..01d18e7483e 100644 --- a/superset-frontend/packages/superset-core/src/theme/Theme.test.tsx +++ b/superset-frontend/packages/superset-core/src/theme/Theme.test.tsx @@ -839,3 +839,73 @@ test('Theme includes both echartsOptionsOverrides and echartsOptionsOverridesByC }, }); }); + +test('colorLink derives from colorPrimary when merging with base theme', () => { + const baseTheme: AnyThemeConfig = { + token: { + colorPrimary: '#2893B3', + colorLink: '#2893B3', + colorInfo: '#66bcfe', + }, + }; + + const userTheme: AnyThemeConfig = { + token: { + colorPrimary: '#f759ab', + }, + }; + + const theme = Theme.fromConfig(userTheme, baseTheme); + + expect(theme.theme.colorPrimary).toBe('#f759ab'); + expect(theme.theme.colorLink).toBe('#f759ab'); + expect(theme.theme.colorInfo).toBe('#66bcfe'); +}); + +test('colorLink is not overridden when user explicitly sets it', () => { + const baseTheme: AnyThemeConfig = { + token: { + colorPrimary: '#2893B3', + colorLink: '#2893B3', + }, + }; + + const userTheme: AnyThemeConfig = { + token: { + colorPrimary: '#f759ab', + colorLink: '#ff0000', + }, + }; + + const theme = Theme.fromConfig(userTheme, baseTheme); + + expect(theme.theme.colorPrimary).toBe('#f759ab'); + expect(theme.theme.colorLink).toBe('#ff0000'); +}); + +test('colorLink derives from colorPrimary in setConfig when not explicitly set', () => { + const theme = Theme.fromConfig(); + + theme.setConfig({ + token: { + colorPrimary: '#f759ab', + }, + }); + + expect(theme.theme.colorPrimary).toBe('#f759ab'); + expect(theme.theme.colorLink).toBe('#f759ab'); +}); + +test('colorLink is preserved in setConfig when explicitly set', () => { + const theme = Theme.fromConfig(); + + theme.setConfig({ + token: { + colorPrimary: '#f759ab', + colorLink: '#ff0000', + }, + }); + + expect(theme.theme.colorPrimary).toBe('#f759ab'); + expect(theme.theme.colorLink).toBe('#ff0000'); +}); diff --git a/superset-frontend/packages/superset-core/src/theme/Theme.tsx b/superset-frontend/packages/superset-core/src/theme/Theme.tsx index 7a890982eaa..138a0ace9b6 100644 --- a/superset-frontend/packages/superset-core/src/theme/Theme.tsx +++ b/superset-frontend/packages/superset-core/src/theme/Theme.tsx @@ -66,6 +66,17 @@ export class Theme { mergedConfig = mergeWith({}, baseTheme, config, (objValue, srcValue) => Array.isArray(srcValue) ? srcValue : undefined, ); + + // In Ant Design v5, colorLink derives from colorInfo, not colorPrimary. + // Currently we expectlinks to follow the brand/primary color. When the user + // overrides colorPrimary without explicitly setting colorLink, update the + // merged colorLink so links match the new primary palette. + if (config.token?.colorPrimary && !config.token?.colorLink) { + const mToken = mergedConfig?.token; + if (mToken) { + mToken.colorLink = mToken.colorPrimary; + } + } } else if (baseTheme && !config) { mergedConfig = baseTheme; } @@ -98,6 +109,10 @@ export class Theme { setConfig(config: AnyThemeConfig): void { const antdConfig = normalizeThemeConfig(config); + if (antdConfig.token?.colorPrimary && !antdConfig.token?.colorLink) { + antdConfig.token.colorLink = antdConfig.token.colorPrimary; + } + // First phase: Let Ant Design compute the tokens const tokens = Theme.getFilteredAntdTheme(antdConfig); diff --git a/superset-frontend/packages/superset-core/src/theme/types.ts b/superset-frontend/packages/superset-core/src/theme/types.ts index 87bd226f874..fccbfd41d36 100644 --- a/superset-frontend/packages/superset-core/src/theme/types.ts +++ b/superset-frontend/packages/superset-core/src/theme/types.ts @@ -110,6 +110,26 @@ export interface ColorVariants { } export interface SupersetSpecificTokens { + // Label variant tokens — Published/Draft (dashboard status) + labelPublishedColor?: string; + labelPublishedBg?: string; + labelPublishedBorderColor?: string; + labelPublishedIconColor?: string; + labelDraftColor?: string; + labelDraftBg?: string; + labelDraftBorderColor?: string; + labelDraftIconColor?: string; + + // Label variant tokens — Dataset type (Physical/Virtual) + labelDatasetPhysicalColor?: string; + labelDatasetPhysicalBg?: string; + labelDatasetPhysicalBorderColor?: string; + labelDatasetPhysicalIconColor?: string; + labelDatasetVirtualColor?: string; + labelDatasetVirtualBg?: string; + labelDatasetVirtualBorderColor?: string; + labelDatasetVirtualIconColor?: string; + // Font-related fontSizeXS: string; fontSizeXXL: string; @@ -168,6 +188,55 @@ export interface SupersetSpecificTokens { * Defaults to colorPrimaryBgHover if not specified. */ colorEditorSelection?: string; + + // Secondary button tokens (Superset-specific) + // Ant Design's filled variant has no component tokens, so we provide our own. + // These fallback to colorPrimary* derived tokens when not set. + /** + * Text color for secondary buttons. + * Fallback: colorPrimary + */ + buttonSecondaryColor?: string; + /** + * Background color for secondary buttons. + * Fallback: colorPrimaryBg + */ + buttonSecondaryBg?: string; + /** + * Border color for secondary buttons. + * Fallback: transparent + */ + buttonSecondaryBorderColor?: string; + /** + * Text color for secondary buttons on hover. + * Fallback: colorPrimary + */ + buttonSecondaryHoverColor?: string; + /** + * Background color for secondary buttons on hover. + * Fallback: colorPrimaryBgHover + */ + buttonSecondaryHoverBg?: string; + /** + * Border color for secondary buttons on hover. + * Fallback: transparent + */ + buttonSecondaryHoverBorderColor?: string; + /** + * Text color for secondary buttons when active/pressed. + * Fallback: colorPrimary + */ + buttonSecondaryActiveColor?: string; + /** + * Background color for secondary buttons when active/pressed. + * Fallback: colorPrimaryBorder + */ + buttonSecondaryActiveBg?: string; + /** + * Border color for secondary buttons when active/pressed. + * Fallback: transparent + */ + buttonSecondaryActiveBorderColor?: string; } /** diff --git a/superset-frontend/packages/superset-ui-chart-controls/package.json b/superset-frontend/packages/superset-ui-chart-controls/package.json index a9eb07107a1..6fe28fc9dbb 100644 --- a/superset-frontend/packages/superset-ui-chart-controls/package.json +++ b/superset-frontend/packages/superset-ui-chart-controls/package.json @@ -26,10 +26,11 @@ "dependencies": { "@apache-superset/core": "*", "@types/react": "*", - "lodash": "^4.17.23" + "lodash": "^4.18.1", + "tinycolor2": "*" }, "peerDependencies": { - "@ant-design/icons": "^5.2.6", + "@ant-design/icons": "^5.6.1", "@emotion/react": "^11.4.1", "@superset-ui/core": "*", "@testing-library/dom": "^8.20.1", diff --git a/superset-frontend/packages/superset-ui-chart-controls/src/components/ColumnOption.tsx b/superset-frontend/packages/superset-ui-chart-controls/src/components/ColumnOption.tsx index ca3c9edb164..17f02eecbd5 100644 --- a/superset-frontend/packages/superset-ui-chart-controls/src/components/ColumnOption.tsx +++ b/superset-frontend/packages/superset-ui-chart-controls/src/components/ColumnOption.tsx @@ -59,7 +59,7 @@ export function ColumnOption({ const type = hasExpression ? 'expression' : type_generic; const [tooltipText, setTooltipText] = useState(column.column_name); const [columnTypeTooltipText, setcolumnTypeTooltipText] = useState( - column.type, + getColumnTypeTooltipNode(column), ); useLayoutEffect(() => { diff --git a/superset-frontend/packages/superset-ui-chart-controls/src/components/ControlSubSectionHeader.tsx b/superset-frontend/packages/superset-ui-chart-controls/src/components/ControlSubSectionHeader.tsx index 79be75e55dd..0022dfde3a4 100644 --- a/superset-frontend/packages/superset-ui-chart-controls/src/components/ControlSubSectionHeader.tsx +++ b/superset-frontend/packages/superset-ui-chart-controls/src/components/ControlSubSectionHeader.tsx @@ -21,7 +21,11 @@ import { styled, css } from '@apache-superset/core/theme'; export const ControlSubSectionHeader = styled.div` ${({ theme }) => css` font-weight: ${theme.fontWeightStrong}; + margin-top: ${theme.sizeUnit * 3}px; margin-bottom: ${theme.sizeUnit}px; font-size: ${theme.fontSizeSM}px; + text-transform: uppercase; + letter-spacing: 0.05em; + color: ${theme.colorTextSecondary}; `} `; diff --git a/superset-frontend/packages/superset-ui-chart-controls/src/components/labelUtils.tsx b/superset-frontend/packages/superset-ui-chart-controls/src/components/labelUtils.tsx index d4a1aec7fe7..ffee313a4df 100644 --- a/superset-frontend/packages/superset-ui-chart-controls/src/components/labelUtils.tsx +++ b/superset-frontend/packages/superset-ui-chart-controls/src/components/labelUtils.tsx @@ -20,6 +20,7 @@ import { ReactNode, RefObject } from 'react'; import { t } from '@apache-superset/core/translation'; import { css, styled } from '@apache-superset/core/theme'; +import { GenericDataType } from '@apache-superset/core/common'; import { ColumnMeta, Metric } from '@superset-ui/chart-controls'; const TooltipSectionWrapper = styled.div` @@ -64,11 +65,29 @@ export const getColumnLabelText = (column: ColumnMeta): string => column.verbose_name || column.column_name; export const getColumnTypeTooltipNode = (column: ColumnMeta): ReactNode => { - if (!column.type) { + const rawType = typeof column.type === 'string' ? column.type.trim() : ''; + + let typeLabel: ReactNode | null = null; + + if (rawType && rawType.toLowerCase() !== 'column') { + typeLabel = rawType; + } else if (typeof column.type_generic === 'number') { + if (column.type_generic === GenericDataType.String) { + typeLabel = t('string'); + } else if (column.type_generic === GenericDataType.Numeric) { + typeLabel = t('numeric'); + } else if (column.type_generic === GenericDataType.Temporal) { + typeLabel = t('timestamp'); + } else if (column.type_generic === GenericDataType.Boolean) { + typeLabel = t('boolean'); + } + } + + if (!typeLabel) { return null; } - return ; + return ; }; export const getColumnTooltipNode = ( diff --git a/superset-frontend/packages/superset-ui-chart-controls/src/sections/chartTitle.tsx b/superset-frontend/packages/superset-ui-chart-controls/src/sections/chartTitle.tsx index cc98584223d..3720f7437c4 100644 --- a/superset-frontend/packages/superset-ui-chart-controls/src/sections/chartTitle.tsx +++ b/superset-frontend/packages/superset-ui-chart-controls/src/sections/chartTitle.tsx @@ -23,7 +23,7 @@ import { ControlPanelSectionConfig } from '../types'; import { formatSelectOptions } from '../utils'; export const TITLE_MARGIN_OPTIONS: number[] = [ - 0, 15, 30, 50, 75, 100, 125, 150, 200, + 0, 15, 30, 40, 50, 75, 100, 125, 150, 200, ]; export const TITLE_POSITION_OPTIONS: [string, string][] = [ ['Left', t('Left')], @@ -56,7 +56,7 @@ export const titleControls: ControlPanelSectionConfig = { clearable: true, label: t('X Axis Title Margin'), renderTrigger: true, - default: TITLE_MARGIN_OPTIONS[0], + default: TITLE_MARGIN_OPTIONS[3], choices: formatSelectOptions(TITLE_MARGIN_OPTIONS), }, }, @@ -82,7 +82,7 @@ export const titleControls: ControlPanelSectionConfig = { clearable: true, label: t('Y Axis Title Margin'), renderTrigger: true, - default: TITLE_MARGIN_OPTIONS[0], + default: TITLE_MARGIN_OPTIONS[4], choices: formatSelectOptions(TITLE_MARGIN_OPTIONS), }, }, diff --git a/superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/matrixifyControls.test.ts b/superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/matrixifyControls.test.ts new file mode 100644 index 00000000000..2b007fda917 --- /dev/null +++ b/superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/matrixifyControls.test.ts @@ -0,0 +1,139 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { isMatrixifyVisible } from './matrixifyControls'; + +/** + * Helper to build a controls object matching the shape used by + * control panel visibility callbacks. + */ +function makeControls( + overrides: Record = {}, +): Record { + const defaults: Record = { + matrixify_enable: false, + matrixify_mode_rows: 'disabled', + matrixify_mode_columns: 'disabled', + matrixify_dimension_selection_mode_rows: 'members', + matrixify_dimension_selection_mode_columns: 'members', + }; + const merged = { ...defaults, ...overrides }; + return Object.fromEntries( + Object.entries(merged).map(([k, v]) => [k, { value: v }]), + ); +} + +// ── matrixify_enable guard ────────────────────────────────────────── + +test('returns false when matrixify_enable is false, even with active axis modes', () => { + const controls = makeControls({ + matrixify_enable: false, + matrixify_mode_rows: 'metrics', + matrixify_mode_columns: 'dimensions', + }); + expect(isMatrixifyVisible(controls, 'rows')).toBe(false); + expect(isMatrixifyVisible(controls, 'columns')).toBe(false); +}); + +test('returns false when matrixify_enable is undefined (old form_data without the field)', () => { + const controls = makeControls({ + matrixify_mode_rows: 'metrics', + }); + delete (controls as any).matrixify_enable; + expect(isMatrixifyVisible(controls, 'rows')).toBe(false); +}); + +test('returns false when controls object is undefined', () => { + expect(isMatrixifyVisible(undefined, 'rows')).toBe(false); +}); + +// ── axis mode checks ──────────────────────────────────────────────── + +test('returns false when axis mode is disabled', () => { + const controls = makeControls({ + matrixify_enable: true, + matrixify_mode_rows: 'disabled', + }); + expect(isMatrixifyVisible(controls, 'rows')).toBe(false); +}); + +test('returns true when matrixify_enable is true and axis mode is metrics', () => { + const controls = makeControls({ + matrixify_enable: true, + matrixify_mode_rows: 'metrics', + }); + expect(isMatrixifyVisible(controls, 'rows')).toBe(true); +}); + +test('returns true when matrixify_enable is true and axis mode is dimensions', () => { + const controls = makeControls({ + matrixify_enable: true, + matrixify_mode_columns: 'dimensions', + }); + expect(isMatrixifyVisible(controls, 'columns')).toBe(true); +}); + +// ── mode filter ───────────────────────────────────────────────────── + +test('returns false when mode filter does not match axis value', () => { + const controls = makeControls({ + matrixify_enable: true, + matrixify_mode_rows: 'metrics', + }); + expect(isMatrixifyVisible(controls, 'rows', 'dimensions')).toBe(false); +}); + +test('returns true when mode filter matches axis value', () => { + const controls = makeControls({ + matrixify_enable: true, + matrixify_mode_rows: 'dimensions', + }); + expect(isMatrixifyVisible(controls, 'rows', 'dimensions')).toBe(true); +}); + +// ── selectionMode filter ──────────────────────────────────────────── + +test('returns true when selectionMode matches', () => { + const controls = makeControls({ + matrixify_enable: true, + matrixify_mode_rows: 'dimensions', + matrixify_dimension_selection_mode_rows: 'topn', + }); + expect(isMatrixifyVisible(controls, 'rows', 'dimensions', 'topn')).toBe(true); +}); + +test('returns false when selectionMode does not match', () => { + const controls = makeControls({ + matrixify_enable: true, + matrixify_mode_rows: 'dimensions', + matrixify_dimension_selection_mode_rows: 'members', + }); + expect(isMatrixifyVisible(controls, 'rows', 'dimensions', 'topn')).toBe( + false, + ); +}); + +test('ignores selectionMode filter when mode is metrics', () => { + const controls = makeControls({ + matrixify_enable: true, + matrixify_mode_columns: 'metrics', + }); + // selectionMode only applies to dimensions mode, should be ignored + expect(isMatrixifyVisible(controls, 'columns', 'metrics', 'topn')).toBe(true); +}); diff --git a/superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/matrixifyControls.tsx b/superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/matrixifyControls.tsx index 48dcaa3dafc..3e1b331d167 100644 --- a/superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/matrixifyControls.tsx +++ b/superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/matrixifyControls.tsx @@ -36,6 +36,8 @@ const isMatrixifyVisible = ( mode?: 'metrics' | 'dimensions', selectionMode?: 'members' | 'topn' | 'all', ) => { + if (controls?.matrixify_enable?.value !== true) return false; + const modeControl = `matrixify_mode_${axis}`; const selectionModeControl = `matrixify_dimension_selection_mode_${axis}`; @@ -372,4 +374,4 @@ matrixifyControls.matrixify_show_column_headers = { visibility: ({ controls }) => isMatrixifyVisible(controls, 'columns'), }; -export { matrixifyControls }; +export { matrixifyControls, isMatrixifyVisible }; diff --git a/superset-frontend/packages/superset-ui-chart-controls/src/types.ts b/superset-frontend/packages/superset-ui-chart-controls/src/types.ts index 1cc9743f4cf..6344222d594 100644 --- a/superset-frontend/packages/superset-ui-chart-controls/src/types.ts +++ b/superset-frontend/packages/superset-ui-chart-controls/src/types.ts @@ -507,6 +507,11 @@ export type ColorFormatters = { ) => string | undefined; }[]; +export type ResolvedColorFormatterResult = { + backgroundColor?: string; + color?: string; +}; + export default {}; export function isColumnMeta(column: AnyDict): column is ColumnMeta { diff --git a/superset-frontend/packages/superset-ui-chart-controls/src/utils/getColorFormatters.ts b/superset-frontend/packages/superset-ui-chart-controls/src/utils/getColorFormatters.ts index 5869472de9b..dbff332cdc0 100644 --- a/superset-frontend/packages/superset-ui-chart-controls/src/utils/getColorFormatters.ts +++ b/superset-frontend/packages/superset-ui-chart-controls/src/utils/getColorFormatters.ts @@ -20,11 +20,13 @@ import memoizeOne from 'memoize-one'; import { isString, isBoolean } from 'lodash'; import { isBlank } from '@apache-superset/core/utils'; import { addAlpha, DataRecord } from '@superset-ui/core'; +import tinycolor from 'tinycolor2'; import { ColorFormatters, Comparator, ConditionalFormattingConfig, MultipleValueComparators, + ResolvedColorFormatterResult, } from '../types'; export const round = (num: number, precision = 0) => @@ -33,6 +35,11 @@ export const round = (num: number, precision = 0) => const MIN_OPACITY_BOUNDED = 0.05; const MIN_OPACITY_UNBOUNDED = 0; const MAX_OPACITY = 1; +const READABLE_TEXT_COLORS = [ + { r: 0, g: 0, b: 0 }, + { r: 255, g: 255, b: 255 }, +]; + export const getOpacity = ( value: number | string | boolean | null, cutoffPoint: number | string, @@ -325,3 +332,59 @@ export const getColorFormatters = memoizeOne( [], ) ?? [], ); + +export const getReadableTextColor = ( + backgroundColor: string | undefined, + surfaceColor: string, +): string | undefined => { + if (!backgroundColor) { + return undefined; + } + + const background = tinycolor(backgroundColor); + const surface = tinycolor(surfaceColor); + + if (!background.isValid() || !surface.isValid()) { + return undefined; + } + + const { r: bgR, g: bgG, b: bgB, a: bgAlpha } = background.toRgb(); + const { r: surfaceR, g: surfaceG, b: surfaceB } = surface.toRgb(); + const alpha = bgAlpha; + + const compositeColor = tinycolor({ + r: bgR * alpha + surfaceR * (1 - alpha), + g: bgG * alpha + surfaceG * (1 - alpha), + b: bgB * alpha + surfaceB * (1 - alpha), + }); + + return tinycolor + .mostReadable(compositeColor, READABLE_TEXT_COLORS, { + includeFallbackColors: true, + level: 'AA', + size: 'small', + }) + .toRgbString(); +}; + +export const getNormalizedTextColor = ( + color: string | undefined, +): string | undefined => { + if (!color) { + return undefined; + } + + const parsedColor = tinycolor(color); + if (!parsedColor.isValid()) { + return color; + } + + return parsedColor.setAlpha(1).toRgbString(); +}; + +export const getTextColorForBackground = ( + result: ResolvedColorFormatterResult, + surfaceColor: string, +): string | undefined => + getNormalizedTextColor(result.color) ?? + getReadableTextColor(result.backgroundColor, surfaceColor); diff --git a/superset-frontend/packages/superset-ui-chart-controls/test/components/labelUtils.test.tsx b/superset-frontend/packages/superset-ui-chart-controls/test/components/labelUtils.test.tsx index 64e75887abd..ae050c820ed 100644 --- a/superset-frontend/packages/superset-ui-chart-controls/test/components/labelUtils.test.tsx +++ b/superset-frontend/packages/superset-ui-chart-controls/test/components/labelUtils.test.tsx @@ -24,6 +24,7 @@ import { getMetricTooltipNode, getColumnTypeTooltipNode, } from '../../src/components/labelUtils'; +import { GenericDataType } from '@apache-superset/core/common'; test("should get column name when column doesn't have verbose_name", () => { expect( @@ -89,6 +90,24 @@ test('should get column datatype rendered as tooltip when column has a type', () expect(screen.getByText('text')).toBeVisible(); }); +test('should fall back to generic data type label when type is "column"', () => { + render( + <> + {getColumnTypeTooltipNode({ + id: 123, + column_name: 'column name', + verbose_name: '', + description: '', + type: 'column', + type_generic: GenericDataType.String, + })} + , + ); + + expect(screen.getByText('Column type')).toBeVisible(); + expect(screen.getByText('string')).toBeVisible(); +}); + test('should get column name, verbose name and description when it has a verbose name', () => { const ref = { current: { scrollWidth: 100, clientWidth: 100 } }; render( diff --git a/superset-frontend/packages/superset-ui-chart-controls/test/utils/getColorFormatters.test.ts b/superset-frontend/packages/superset-ui-chart-controls/test/utils/getColorFormatters.test.ts index 1ed3f031d70..821a864a546 100644 --- a/superset-frontend/packages/superset-ui-chart-controls/test/utils/getColorFormatters.test.ts +++ b/superset-frontend/packages/superset-ui-chart-controls/test/utils/getColorFormatters.test.ts @@ -24,6 +24,11 @@ import { getColorFormatters, getColorFunction, } from '../../src'; +import { + getReadableTextColor, + getNormalizedTextColor, + getTextColorForBackground, +} from '../../src/utils/getColorFormatters'; configure(); const mockData = [ @@ -107,6 +112,64 @@ test('getColorFunction LESS_THAN', () => { expect(colorFunction(50)).toEqual('#FF0000FF'); }); +test('getReadableTextColor returns white for dark backgrounds', () => { + expect(getReadableTextColor('#111111', '#ffffff')).toBe('rgb(255, 255, 255)'); +}); + +test('getReadableTextColor returns black for light backgrounds', () => { + expect(getReadableTextColor('#f5f5f5', '#ffffff')).toBe('rgb(0, 0, 0)'); +}); + +test('getReadableTextColor blends alpha over the provided surface', () => { + expect(getReadableTextColor('rgba(0, 0, 0, 0.6)', '#ffffff')).toBe( + 'rgb(255, 255, 255)', + ); + expect(getReadableTextColor('rgba(255, 255, 255, 0.6)', '#000000')).toBe( + 'rgb(0, 0, 0)', + ); +}); + +test('getReadableTextColor returns undefined for invalid colors', () => { + expect(getReadableTextColor('not-a-color', '#ffffff')).toBeUndefined(); + expect(getReadableTextColor('#111111', 'not-a-color')).toBeUndefined(); +}); + +test('getTextColorForBackground prefers explicit text color', () => { + expect( + getTextColorForBackground( + { backgroundColor: '#111111', color: '#ace1c4ff' }, + '#ffffff', + ), + ).toBe('rgb(172, 225, 196)'); +}); + +test('getNormalizedTextColor removes alpha from explicit text colors', () => { + expect(getNormalizedTextColor('#ace1c40d')).toBe('rgb(172, 225, 196)'); + expect(getNormalizedTextColor('rgba(172, 225, 196, 0.2)')).toBe( + 'rgb(172, 225, 196)', + ); +}); + +test('getNormalizedTextColor preserves invalid explicit text colors', () => { + expect(getNormalizedTextColor('not-a-color')).toBe('not-a-color'); +}); + +test('getTextColorForBackground normalizes explicit text color alpha', () => { + expect( + getTextColorForBackground( + { backgroundColor: '#111111', color: '#ace1c40d' }, + '#ffffff', + ), + ).toBe('rgb(172, 225, 196)'); +}); + +test('getTextColorForBackground falls back to adaptive contrast', () => { + expect( + getTextColorForBackground({ backgroundColor: '#111111' }, '#ffffff'), + ).toBe('rgb(255, 255, 255)'); + expect(getTextColorForBackground({}, '#ffffff')).toBeUndefined(); +}); + test('getColorFunction GREATER_OR_EQUAL', () => { const colorFunction = getColorFunction( { diff --git a/superset-frontend/packages/superset-ui-core/package.json b/superset-frontend/packages/superset-ui-core/package.json index 3d4855ca3d0..217d9f088f8 100644 --- a/superset-frontend/packages/superset-ui-core/package.json +++ b/superset-frontend/packages/superset-ui-core/package.json @@ -24,39 +24,40 @@ "lib" ], "dependencies": { + "@ant-design/icons": "^6.1.1", "@apache-superset/core": "*", - "@ant-design/icons": "^5.2.6", - "@babel/runtime": "^7.28.6", + "@babel/runtime": "^7.29.2", "@types/json-bigint": "^1.0.4", + "@visx/responsive": "^3.12.0", "ace-builds": "^1.43.6", - "ag-grid-community": "35.0.1", - "ag-grid-react": "35.0.1", + "ag-grid-community": "35.2.1", + "ag-grid-react": "35.2.1", "brace": "^0.11.1", "classnames": "^2.5.1", + "core-js": "^3.49.0", "csstype": "^3.2.3", - "core-js": "^3.48.0", "d3-format": "^3.1.2", - "dayjs": "^1.11.19", "d3-interpolate": "^3.0.1", "d3-scale": "^4.0.2", "d3-time": "^3.1.0", "d3-time-format": "^4.1.0", - "dompurify": "^3.3.3", + "dayjs": "^1.11.20", + "dompurify": "^3.4.0", "fetch-retry": "^6.0.0", - "handlebars": "^4.7.8", + "handlebars": "^4.7.9", "jed": "^1.1.1", - "lodash": "^4.17.23", + "lodash": "^4.18.1", "math-expression-evaluator": "^2.0.7", "pretty-ms": "^9.3.0", "re-resizable": "^6.11.2", "react-ace": "^14.0.1", - "react-js-cron": "^5.2.0", "react-draggable": "^4.5.0", + "react-error-boundary": "6.0.0", + "react-js-cron": "^5.2.0", + "react-markdown": "^8.0.7", "react-resize-detector": "^7.1.2", "react-syntax-highlighter": "^16.1.1", "react-ultimate-pagination": "^1.3.2", - "react-error-boundary": "6.0.0", - "react-markdown": "^8.0.7", "regenerator-runtime": "^0.14.1", "rehype-raw": "^7.0.0", "rehype-sanitize": "^6.0.0", @@ -64,7 +65,6 @@ "reselect": "^5.1.1", "rison": "^0.1.1", "seedrandom": "^3.0.5", - "@visx/responsive": "^3.12.0", "xss": "^1.0.15" }, "devDependencies": { @@ -74,21 +74,20 @@ "@types/d3-scale": "^2.1.1", "@types/d3-time": "^3.0.4", "@types/d3-time-format": "^4.0.3", - "@types/react-table": "^7.7.20", - "@types/react-syntax-highlighter": "^15.5.13", - "@types/jquery": "^3.5.33", + "@types/jquery": "^4.0.0", "@types/lodash": "^4.17.24", - "@types/node": "^25.3.3", + "@types/node": "^25.6.0", "@types/prop-types": "^15.7.15", + "@types/react-syntax-highlighter": "^15.5.13", + "@types/react-table": "^7.7.20", "@types/rison": "0.1.0", "@types/seedrandom": "^3.0.8", "fetch-mock": "^12.6.0", "jest-mock-console": "^2.0.0", "resize-observer-polyfill": "1.5.1", - "timezone-mock": "1.4.0" + "timezone-mock": "^1.4.2" }, "peerDependencies": { - "antd": "^5.26.0", "@emotion/cache": "^11.4.0", "@emotion/react": "^11.4.1", "@emotion/styled": "^11.14.1", @@ -101,6 +100,7 @@ "@types/react-loadable": "*", "@types/react-window": "^1.8.8", "@types/tinycolor2": "*", + "antd": "^5.26.0", "nanoid": "^5.0.9", "react": "^17.0.2", "react-dom": "^17.0.2", diff --git a/superset-frontend/packages/superset-ui-core/src/chart-composition/ChartFrame.tsx b/superset-frontend/packages/superset-ui-core/src/chart-composition/ChartFrame.tsx index 88cf52ec72f..91836814e9f 100644 --- a/superset-frontend/packages/superset-ui-core/src/chart-composition/ChartFrame.tsx +++ b/superset-frontend/packages/superset-ui-core/src/chart-composition/ChartFrame.tsx @@ -17,7 +17,7 @@ * under the License. */ -import { PureComponent, ReactNode } from 'react'; +import { memo, ReactNode } from 'react'; import { isDefined } from '../utils'; @@ -29,7 +29,7 @@ type Props = { contentWidth?: number; contentHeight?: number; height: number; - renderContent: ({ + renderContent?: ({ height, width, }: { @@ -39,36 +39,35 @@ type Props = { width: number; }; -export default class ChartFrame extends PureComponent { - static defaultProps = { - renderContent() {}, - }; +function ChartFrame({ + contentWidth, + contentHeight, + width, + height, + renderContent = () => null, +}: Props) { + const overflowX = checkNumber(contentWidth) && contentWidth > width; + const overflowY = checkNumber(contentHeight) && contentHeight > height; - render() { - const { contentWidth, contentHeight, width, height, renderContent } = - this.props; - - const overflowX = checkNumber(contentWidth) && contentWidth > width; - const overflowY = checkNumber(contentHeight) && contentHeight > height; - - if (overflowX || overflowY) { - return ( -
- {renderContent({ - height: Math.max(contentHeight ?? 0, height), - width: Math.max(contentWidth ?? 0, width), - })} -
- ); - } - - return renderContent({ height, width }); + if (overflowX || overflowY) { + return ( +
+ {renderContent({ + height: Math.max(contentHeight ?? 0, height), + width: Math.max(contentWidth ?? 0, width), + })} +
+ ); } + + return <>{renderContent({ height, width })}; } + +export default memo(ChartFrame); diff --git a/superset-frontend/packages/superset-ui-core/src/chart-composition/legend/WithLegend.tsx b/superset-frontend/packages/superset-ui-core/src/chart-composition/legend/WithLegend.tsx index a2cac92ee33..48e7309cdab 100644 --- a/superset-frontend/packages/superset-ui-core/src/chart-composition/legend/WithLegend.tsx +++ b/superset-frontend/packages/superset-ui-core/src/chart-composition/legend/WithLegend.tsx @@ -17,26 +17,19 @@ * under the License. */ -import { CSSProperties, ReactNode, PureComponent } from 'react'; +import { CSSProperties, ReactNode, memo, useMemo } from 'react'; import { ParentSize } from '@visx/responsive'; -const defaultProps = { - className: '', - height: 'auto' as number | string, - position: 'top', - width: 'auto' as number | string, -}; - type Props = { - className: string; + className?: string; debounceTime?: number; - width: number | string; - height: number | string; + width?: number | string; + height?: number | string; legendJustifyContent?: 'center' | 'flex-start' | 'flex-end'; - position: 'top' | 'left' | 'bottom' | 'right'; + position?: 'top' | 'left' | 'bottom' | 'right'; renderChart: (dim: { width: number; height: number }) => ReactNode; renderLegend?: (params: { direction: string }) => ReactNode; -} & Readonly; +}; const LEGEND_STYLE_BASE: CSSProperties = { display: 'flex', @@ -52,95 +45,101 @@ const CHART_STYLE_BASE: CSSProperties = { position: 'relative', }; -class WithLegend extends PureComponent { - static defaultProps = defaultProps; - - getContainerDirection(): CSSProperties['flexDirection'] { - const { position } = this.props; - - if (position === 'left') { - return 'row'; - } - if (position === 'right') { - return 'row-reverse'; - } - if (position === 'bottom') { - return 'column-reverse'; - } - - return 'column'; +function getContainerDirection( + position: Props['position'], +): CSSProperties['flexDirection'] { + if (position === 'left') { + return 'row'; + } + if (position === 'right') { + return 'row-reverse'; + } + if (position === 'bottom') { + return 'column-reverse'; } - getLegendJustifyContent() { - const { legendJustifyContent, position } = this.props; - if (legendJustifyContent) { - return legendJustifyContent; - } - - if (position === 'left' || position === 'right') { - return 'flex-start'; - } - - return 'flex-end'; - } - - render() { - const { - className, - debounceTime, - width, - height, - position, - renderChart, - renderLegend, - } = this.props; - - const isHorizontal = position === 'left' || position === 'right'; - - const style: CSSProperties = { - display: 'flex', - flexDirection: this.getContainerDirection(), - height, - width, - }; - - const chartStyle: CSSProperties = { ...CHART_STYLE_BASE }; - if (isHorizontal) { - chartStyle.width = 0; - } else { - chartStyle.height = 0; - } - - const legendDirection = isHorizontal ? 'column' : 'row'; - const legendStyle: CSSProperties = { - ...LEGEND_STYLE_BASE, - flexDirection: legendDirection, - justifyContent: this.getLegendJustifyContent(), - }; - - return ( -
- {renderLegend && ( -
- {renderLegend({ - // Pass flexDirection for @vx/legend to arrange legend items - direction: legendDirection, - })} -
- )} -
- - {(parent: { width: number; height: number }) => - parent.width > 0 && parent.height > 0 - ? // Only render when necessary - renderChart(parent) - : null - } - -
-
- ); - } + return 'column'; } -export default WithLegend; +function getLegendJustifyContent( + legendJustifyContent: Props['legendJustifyContent'], + position: Props['position'], +) { + if (legendJustifyContent) { + return legendJustifyContent; + } + + if (position === 'left' || position === 'right') { + return 'flex-start'; + } + + return 'flex-end'; +} + +function WithLegend({ + className = '', + debounceTime, + width = 'auto', + height = 'auto', + legendJustifyContent, + position = 'top', + renderChart, + renderLegend, +}: Props) { + const isHorizontal = position === 'left' || position === 'right'; + + const style: CSSProperties = useMemo( + () => ({ + display: 'flex', + flexDirection: getContainerDirection(position), + height, + width, + }), + [position, height, width], + ); + + const chartStyle: CSSProperties = useMemo(() => { + const baseStyle = { ...CHART_STYLE_BASE }; + if (isHorizontal) { + baseStyle.width = 0; + } else { + baseStyle.height = 0; + } + return baseStyle; + }, [isHorizontal]); + + const legendDirection = isHorizontal ? 'column' : 'row'; + const legendStyle: CSSProperties = useMemo( + () => ({ + ...LEGEND_STYLE_BASE, + flexDirection: legendDirection, + justifyContent: getLegendJustifyContent(legendJustifyContent, position), + }), + [legendDirection, legendJustifyContent, position], + ); + + return ( +
+ {renderLegend && ( +
+ {renderLegend({ + // Pass flexDirection for @vx/legend to arrange legend items + direction: legendDirection, + })} +
+ )} +
+ + {(parent: { width: number; height: number }) => + parent.width > 0 && parent.height > 0 + ? // Only render when necessary + renderChart(parent) + : null + } + +
+
+ ); +} + +export default memo(WithLegend); diff --git a/superset-frontend/packages/superset-ui-core/src/chart-composition/tooltip/TooltipFrame.tsx b/superset-frontend/packages/superset-ui-core/src/chart-composition/tooltip/TooltipFrame.tsx index d470a229403..19008654316 100644 --- a/superset-frontend/packages/superset-ui-core/src/chart-composition/tooltip/TooltipFrame.tsx +++ b/superset-frontend/packages/superset-ui-core/src/chart-composition/tooltip/TooltipFrame.tsx @@ -17,31 +17,21 @@ * under the License. */ -import { PureComponent, ReactNode } from 'react'; - -const defaultProps = { - className: '', -}; +import { memo, ReactNode } from 'react'; type Props = { className?: string; children: ReactNode; -} & Readonly; +}; const CONTAINER_STYLE = { padding: 8 }; -class TooltipFrame extends PureComponent { - static defaultProps = defaultProps; - - render() { - const { className, children } = this.props; - - return ( -
- {children} -
- ); - } +function TooltipFrame({ className = '', children }: Props) { + return ( +
+ {children} +
+ ); } -export default TooltipFrame; +export default memo(TooltipFrame); diff --git a/superset-frontend/packages/superset-ui-core/src/chart/components/Matrixify/MatrixifyGridGenerator.test.ts b/superset-frontend/packages/superset-ui-core/src/chart/components/Matrixify/MatrixifyGridGenerator.test.ts index 8f660e1a776..047105ed56b 100644 --- a/superset-frontend/packages/superset-ui-core/src/chart/components/Matrixify/MatrixifyGridGenerator.test.ts +++ b/superset-frontend/packages/superset-ui-core/src/chart/components/Matrixify/MatrixifyGridGenerator.test.ts @@ -39,6 +39,7 @@ const createSqlMetric = (label: string, sql: string): AdhocMetric => ({ const baseFormData: TestFormData = { viz_type: 'table', datasource: '1__table', + matrixify_enable: true, matrixify_mode_rows: 'metrics', matrixify_mode_columns: 'metrics', matrixify_rows: [createAdhocMetric('Revenue'), createAdhocMetric('Profit')], @@ -77,6 +78,7 @@ test('should generate grid for dimensions mode', () => { const dimensionFormData: TestFormData = { viz_type: 'table', datasource: '1__table', + matrixify_enable: true, matrixify_mode_rows: 'dimensions', matrixify_mode_columns: 'dimensions', matrixify_dimension_rows: { @@ -117,6 +119,7 @@ test('should generate grid for mixed mode (metrics rows, dimensions columns)', ( const mixedFormData: TestFormData = { viz_type: 'table', datasource: '1__table', + matrixify_enable: true, matrixify_mode_rows: 'metrics', matrixify_mode_columns: 'dimensions', matrixify_rows: [createAdhocMetric('Total Sales')], @@ -139,6 +142,7 @@ test('should handle empty configuration', () => { const emptyFormData: TestFormData = { viz_type: 'table', datasource: '1__table', + matrixify_enable: true, matrixify_mode_rows: 'metrics', matrixify_mode_columns: 'metrics', matrixify_rows: [], @@ -157,6 +161,7 @@ test('should handle single row and column', () => { const singleCellFormData: TestFormData = { viz_type: 'table', datasource: '1__table', + matrixify_enable: true, matrixify_mode_rows: 'metrics', matrixify_mode_columns: 'metrics', matrixify_rows: [createAdhocMetric('Count')], @@ -177,6 +182,7 @@ test('should handle string metrics', () => { const stringMetricFormData: TestFormData = { viz_type: 'table', datasource: '1__table', + matrixify_enable: true, matrixify_mode_rows: 'metrics', matrixify_mode_columns: 'metrics', matrixify_rows: ['count', 'sum'], @@ -190,10 +196,30 @@ test('should handle string metrics', () => { expect(grid!.colHeaders).toEqual(['avg', 'max']); }); +test('should skip missing column metrics when generating cell form data', () => { + const missingColumnMetricFormData: TestFormData = { + viz_type: 'table', + datasource: '1__table', + matrixify_enable: true, + matrixify_mode_rows: 'metrics', + matrixify_mode_columns: 'metrics', + matrixify_rows: [createAdhocMetric('Revenue')], + matrixify_columns: [null], + }; + + const grid = generateMatrixifyGrid(missingColumnMetricFormData); + + expect(grid).not.toBeNull(); + expect(grid!.cells[0][0]!.formData.metrics).toEqual([ + createAdhocMetric('Revenue'), + ]); +}); + test('should not escape HTML entities in cell titles', () => { const formDataWithSpecialChars: TestFormData = { viz_type: 'table', datasource: '1__table', + matrixify_enable: true, matrixify_mode_rows: 'metrics', matrixify_mode_columns: 'metrics', matrixify_rows: [createAdhocMetric('Sales & Revenue')], @@ -309,6 +335,7 @@ test('should generate single-column grid when only rows are configured', () => { const rowsOnlyFormData: TestFormData = { viz_type: 'table', datasource: '1__table', + matrixify_enable: true, matrixify_mode_rows: 'metrics', matrixify_rows: [createAdhocMetric('Revenue'), createAdhocMetric('Profit')], // No column config @@ -326,6 +353,7 @@ test('should generate single-row grid when only columns are configured', () => { const colsOnlyFormData: TestFormData = { viz_type: 'table', datasource: '1__table', + matrixify_enable: true, matrixify_mode_columns: 'metrics', matrixify_columns: [ createSqlMetric('Q1', 'SUM(q1)'), @@ -359,6 +387,7 @@ test('should return empty string header for null metric in array (line 76)', () const formData: TestFormData = { viz_type: 'table', datasource: '1__table', + matrixify_enable: true, matrixify_mode_rows: 'metrics', matrixify_mode_columns: 'metrics', matrixify_rows: [null], @@ -373,6 +402,7 @@ test('should return empty string header for empty-string dimension value (line 8 const formData: TestFormData = { viz_type: 'table', datasource: '1__table', + matrixify_enable: true, matrixify_mode_rows: 'dimensions', matrixify_mode_columns: 'dimensions', matrixify_dimension_rows: { dimension: 'country', values: [''] }, @@ -387,6 +417,7 @@ test('should skip dimension filter when value is undefined (lines 151, 165)', () const formData: TestFormData = { viz_type: 'table', datasource: '1__table', + matrixify_enable: true, matrixify_mode_rows: 'dimensions', matrixify_mode_columns: 'dimensions', matrixify_dimension_rows: { @@ -418,6 +449,7 @@ test('should handle metrics without labels', () => { const metricsWithoutLabels: TestFormData = { viz_type: 'table', datasource: '1__table', + matrixify_enable: true, matrixify_mode_rows: 'metrics', matrixify_mode_columns: 'metrics', matrixify_rows: [ diff --git a/superset-frontend/packages/superset-ui-core/src/chart/types/VizType.ts b/superset-frontend/packages/superset-ui-core/src/chart/types/VizType.ts index 0052f160927..cadb4c187ca 100644 --- a/superset-frontend/packages/superset-ui-core/src/chart/types/VizType.ts +++ b/superset-frontend/packages/superset-ui-core/src/chart/types/VizType.ts @@ -41,6 +41,7 @@ export enum VizType { LegacyBubble = 'bubble', Line = 'echarts_timeseries_line', MapBox = 'mapbox', + PointClusterMap = 'point_cluster_map', MixedTimeseries = 'mixed_timeseries', PairedTTest = 'paired_ttest', ParallelCoordinates = 'para', diff --git a/superset-frontend/packages/superset-ui-core/src/chart/types/matrixify.test.ts b/superset-frontend/packages/superset-ui-core/src/chart/types/matrixify.test.ts index b0eaf837fcd..81c36a0cca2 100644 --- a/superset-frontend/packages/superset-ui-core/src/chart/types/matrixify.test.ts +++ b/superset-frontend/packages/superset-ui-core/src/chart/types/matrixify.test.ts @@ -137,9 +137,31 @@ test('getMatrixifyConfig should return null when no matrixify configuration exis expect(getMatrixifyConfig(formData)).toBeNull(); }); +test('getMatrixifyConfig should return null when matrixify_enable is false', () => { + const formData = { + viz_type: 'table', + matrixify_enable: false, + matrixify_mode_rows: 'metrics', + matrixify_mode_columns: 'metrics', + matrixify_rows: [createMetric('Revenue')], + matrixify_columns: [createMetric('Q1')], + } as MatrixifyFormData; + expect(getMatrixifyConfig(formData)).toBeNull(); +}); + +test('getMatrixifyConfig should return null when no axes are enabled', () => { + const formData = { + viz_type: 'table', + matrixify_enable: true, + } as MatrixifyFormData; + + expect(getMatrixifyConfig(formData)).toBeNull(); +}); + test('getMatrixifyConfig should return valid config for metrics mode', () => { const formData = { viz_type: 'table', + matrixify_enable: true, matrixify_mode_rows: 'metrics', matrixify_mode_columns: 'metrics', matrixify_rows: [createMetric('Revenue')], @@ -157,6 +179,7 @@ test('getMatrixifyConfig should return valid config for metrics mode', () => { test('getMatrixifyConfig should return valid config for dimensions mode', () => { const formData = { viz_type: 'table', + matrixify_enable: true, matrixify_mode_rows: 'dimensions', matrixify_mode_columns: 'dimensions', matrixify_dimension_rows: { dimension: 'country', values: ['USA'] }, @@ -180,6 +203,7 @@ test('getMatrixifyConfig should return valid config for dimensions mode', () => test('getMatrixifyConfig should handle topn selection mode', () => { const formData = { viz_type: 'table', + matrixify_enable: true, matrixify_mode_rows: 'dimensions', matrixify_mode_columns: 'dimensions', matrixify_dimension_rows: { @@ -197,6 +221,24 @@ test('getMatrixifyConfig should handle topn selection mode', () => { expect(config!.rows.dimension).toEqual(formData.matrixify_dimension_rows); }); +test('getMatrixifyConfig should preserve ascending topn order when explicitly disabled', () => { + const formData = { + viz_type: 'table', + matrixify_enable: true, + matrixify_mode_rows: 'dimensions', + matrixify_mode_columns: 'dimensions', + matrixify_dimension_rows: { dimension: 'country', values: ['USA'] }, + matrixify_dimension_columns: { dimension: 'product', values: ['Widget'] }, + matrixify_topn_order_rows: false, + matrixify_topn_order_columns: false, + } as MatrixifyFormData; + + const config = getMatrixifyConfig(formData); + expect(config).not.toBeNull(); + expect(config!.rows.topnOrder).toBe('asc'); + expect(config!.columns.topnOrder).toBe('asc'); +}); + test('getMatrixifyValidationErrors should return empty array when matrixify is not enabled', () => { const formData = { viz_type: 'table', @@ -207,9 +249,31 @@ test('getMatrixifyValidationErrors should return empty array when matrixify is n expect(getMatrixifyValidationErrors(formData)).toEqual([]); }); +test('getMatrixifyValidationErrors should return empty array when matrixify_enable is false even with stale mode values', () => { + const formData = { + viz_type: 'table', + matrixify_enable: false, + matrixify_mode_rows: 'metrics', + matrixify_mode_columns: 'dimensions', + } as MatrixifyFormData; + + expect(getMatrixifyValidationErrors(formData)).toEqual([]); +}); + +test('getMatrixifyValidationErrors should return empty array when matrixify_enable is undefined with stale defaults', () => { + const formData = { + viz_type: 'bar', + matrixify_mode_rows: 'metrics', + matrixify_rows: [], + } as MatrixifyFormData; + + expect(getMatrixifyValidationErrors(formData)).toEqual([]); +}); + test('getMatrixifyValidationErrors should return empty array when properly configured', () => { const formData = { viz_type: 'table', + matrixify_enable: true, matrixify_mode_rows: 'metrics', matrixify_mode_columns: 'metrics', matrixify_rows: [createMetric('Revenue')], @@ -219,9 +283,19 @@ test('getMatrixifyValidationErrors should return empty array when properly confi expect(getMatrixifyValidationErrors(formData)).toEqual([]); }); +test('getMatrixifyValidationErrors should return empty array when enabled with no active axes', () => { + const formData = { + viz_type: 'table', + matrixify_enable: true, + } as MatrixifyFormData; + + expect(getMatrixifyValidationErrors(formData)).toEqual([]); +}); + test('getMatrixifyValidationErrors should return error when enabled but no configuration exists', () => { const formData = { viz_type: 'table', + matrixify_enable: true, matrixify_mode_rows: 'metrics', } as MatrixifyFormData; @@ -232,6 +306,7 @@ test('getMatrixifyValidationErrors should return error when enabled but no confi test('getMatrixifyValidationErrors should return error when metrics mode has no metrics', () => { const formData = { viz_type: 'table', + matrixify_enable: true, matrixify_mode_rows: 'metrics', matrixify_rows: [], matrixify_columns: [], @@ -276,6 +351,7 @@ test('isMatrixifyEnabled should return false when switch is off even with valid test('getMatrixifyValidationErrors should return dimension error for rows when dimension has no data', () => { const formData = { viz_type: 'table', + matrixify_enable: true, matrixify_mode_rows: 'dimensions', // No matrixify_dimension_rows set matrixify_mode_columns: 'metrics', @@ -289,6 +365,7 @@ test('getMatrixifyValidationErrors should return dimension error for rows when d test('getMatrixifyValidationErrors should return metric error for columns when metrics array is empty', () => { const formData = { viz_type: 'table', + matrixify_enable: true, matrixify_mode_rows: 'metrics', matrixify_rows: [createMetric('Revenue')], matrixify_mode_columns: 'metrics', @@ -302,6 +379,7 @@ test('getMatrixifyValidationErrors should return metric error for columns when m test('getMatrixifyValidationErrors should return dimension error for columns when no dimension data', () => { const formData = { viz_type: 'table', + matrixify_enable: true, matrixify_mode_rows: 'metrics', matrixify_rows: [createMetric('Revenue')], matrixify_mode_columns: 'dimensions', @@ -315,6 +393,7 @@ test('getMatrixifyValidationErrors should return dimension error for columns whe test('getMatrixifyValidationErrors skips row check when matrixify_mode_rows is not set', () => { const formData = { viz_type: 'table', + matrixify_enable: true, // No matrixify_mode_rows — hasRowMode = false matrixify_mode_columns: 'metrics', matrixify_columns: [createMetric('Q1')], @@ -327,6 +406,7 @@ test('getMatrixifyValidationErrors skips row check when matrixify_mode_rows is n test('getMatrixifyValidationErrors evaluates full && expression when dimension is set but values are empty', () => { const formData = { viz_type: 'table', + matrixify_enable: true, matrixify_mode_rows: 'dimensions', matrixify_dimension_rows: { dimension: 'country', values: [] }, matrixify_mode_columns: 'dimensions', diff --git a/superset-frontend/packages/superset-ui-core/src/chart/types/matrixify.ts b/superset-frontend/packages/superset-ui-core/src/chart/types/matrixify.ts index e43dbe31406..b179daf1fbf 100644 --- a/superset-frontend/packages/superset-ui-core/src/chart/types/matrixify.ts +++ b/superset-frontend/packages/superset-ui-core/src/chart/types/matrixify.ts @@ -154,6 +154,10 @@ function isAxisEnabled(mode?: MatrixifyMode): boolean { export function getMatrixifyConfig( formData: MatrixifyFormData & any, ): MatrixifyConfig | null { + if (formData.matrixify_enable !== true) { + return null; + } + const rowEnabled = isAxisEnabled(formData.matrixify_mode_rows); const colEnabled = isAxisEnabled(formData.matrixify_mode_columns); @@ -196,10 +200,7 @@ export function isMatrixifyEnabled(formData: MatrixifyFormData): boolean { return false; } - const config = getMatrixifyConfig(formData); - if (!config) { - return false; - } + const config = getMatrixifyConfig(formData)!; const hasRowData = rowEnabled && @@ -230,6 +231,10 @@ export function isMatrixifyEnabled(formData: MatrixifyFormData): boolean { export function getMatrixifyValidationErrors( formData: MatrixifyFormData, ): string[] { + if (formData.matrixify_enable !== true) { + return []; + } + const errors: string[] = []; const rowEnabled = isAxisEnabled(formData.matrixify_mode_rows); @@ -239,11 +244,7 @@ export function getMatrixifyValidationErrors( return errors; } - const config = getMatrixifyConfig(formData); - if (!config) { - errors.push('Please configure at least one row or column axis'); - return errors; - } + const config = getMatrixifyConfig(formData)!; // Check row configuration (only if explicitly set in form data) const hasRowMode = Boolean(formData.matrixify_mode_rows); diff --git a/superset-frontend/packages/superset-ui-core/src/components/AsyncAceEditor/index.tsx b/superset-frontend/packages/superset-ui-core/src/components/AsyncAceEditor/index.tsx index 56230e130f0..19c7b46c675 100644 --- a/superset-frontend/packages/superset-ui-core/src/components/AsyncAceEditor/index.tsx +++ b/superset-frontend/packages/superset-ui-core/src/components/AsyncAceEditor/index.tsx @@ -283,6 +283,16 @@ export function AsyncAceEditor( color: ${token.colorText} !important; } + /* Fix cursor misalignment by ensuring consistent font-family */ + .ace_editor .ace_content { + font-family: ${editorFontFamily} !important; + } + + /* Ensure the text layer uses the same font-family */ + .ace_editor .ace_text-layer { + font-family: ${editorFontFamily} !important; + } + /* Adjust gutter colors */ .ace_editor .ace_gutter { background-color: ${token.colorBgElevated} !important; @@ -309,6 +319,11 @@ export function AsyncAceEditor( opacity: 0.5; } + /* Style bracket matching to blend with theme */ + .ace_editor .ace_bracket { + border-color: ${token.colorPrimaryBorderHover} !important; + } + /* Adjust cursor color */ .ace_editor .ace_cursor { color: ${token.colorPrimaryText} !important; diff --git a/superset-frontend/packages/superset-ui-core/src/components/AsyncEsmComponent/index.tsx b/superset-frontend/packages/superset-ui-core/src/components/AsyncEsmComponent/index.tsx index 5f8562cd3eb..aeb8f8b7220 100644 --- a/superset-frontend/packages/superset-ui-core/src/components/AsyncEsmComponent/index.tsx +++ b/superset-frontend/packages/superset-ui-core/src/components/AsyncEsmComponent/index.tsx @@ -33,20 +33,22 @@ import type { PlaceholderProps } from './types'; function DefaultPlaceholder({ width, height, - showLoadingForImport = false, + showLoadingForImport = true, placeholderStyle: style, }: PlaceholderProps) { - return ( - // since `width` defaults to 100%, we can display the placeholder once - // height is specified. - (height && ( + if (showLoadingForImport) { + return (
- {showLoadingForImport && } +
- )) || - // `|| null` is for in case of height=0. - null - ); + ); + } + if (height) { + return ( +
+ ); + } + return null; } /** diff --git a/superset-frontend/packages/superset-ui-core/src/components/Button/Button.test.tsx b/superset-frontend/packages/superset-ui-core/src/components/Button/Button.test.tsx index 2f2c16db27b..a01dee81a79 100644 --- a/superset-frontend/packages/superset-ui-core/src/components/Button/Button.test.tsx +++ b/superset-frontend/packages/superset-ui-core/src/components/Button/Button.test.tsx @@ -23,6 +23,11 @@ import { SIZES as buttonSizes, STYLES as buttonStyles, } from './Button.stories'; +import { + getSecondaryButtonStyle, + getSecondaryButtonHoverStyles, +} from './index'; +import type { SupersetTheme } from '@apache-superset/core/theme'; test('works with an onClick handler', () => { const mockAction = jest.fn(); @@ -47,3 +52,165 @@ test('All the sorybook gallery variants mount', () => { expect(getAllByRole('button')).toHaveLength(permutationCount); }); + +test('secondary button renders without errors', () => { + const { getByRole } = render( + , + ); + expect(getByRole('button')).toBeInTheDocument(); +}); + +test('getSecondaryButtonStyle uses fallback tokens when custom tokens not set', () => { + const mockTheme = { + colorPrimary: '#2893B3', + colorPrimaryBg: '#e6f4f7', + colorPrimaryBgHover: '#cce9ef', + colorPrimaryBorder: '#99d3df', + } as SupersetTheme; + + const styles = getSecondaryButtonStyle(mockTheme); + + // Default state uses inline styles (no !important needed) + expect(styles.color).toBe('#2893B3'); + expect(styles.backgroundColor).toBe('#e6f4f7'); + expect(styles.borderColor).toBe('transparent'); +}); + +test('getSecondaryButtonHoverStyles uses fallback tokens when custom tokens not set', () => { + const mockTheme = { + colorPrimary: '#2893B3', + colorPrimaryBg: '#e6f4f7', + colorPrimaryBgHover: '#cce9ef', + colorPrimaryBorder: '#99d3df', + } as SupersetTheme; + + const hoverStyles = getSecondaryButtonHoverStyles(mockTheme); + + // Hover/active states use CSS with !important for specificity + expect(hoverStyles['&:hover'].backgroundColor).toBe('#cce9ef !important'); + expect(hoverStyles['&:active'].backgroundColor).toBe('#99d3df !important'); +}); + +test('getSecondaryButtonStyle uses custom tokens when provided', () => { + const mockTheme = { + colorPrimary: '#2893B3', + colorPrimaryBg: '#e6f4f7', + colorPrimaryBgHover: '#cce9ef', + colorPrimaryBorder: '#99d3df', + // Custom secondary button tokens + buttonSecondaryColor: '#custom-color', + buttonSecondaryBg: '#custom-bg', + buttonSecondaryBorderColor: '#custom-border', + } as SupersetTheme; + + const styles = getSecondaryButtonStyle(mockTheme); + + // Default state uses inline styles (no !important needed) + expect(styles.color).toBe('#custom-color'); + expect(styles.backgroundColor).toBe('#custom-bg'); + expect(styles.borderColor).toBe('#custom-border'); +}); + +test('getSecondaryButtonHoverStyles uses custom tokens when provided', () => { + const mockTheme = { + colorPrimary: '#2893B3', + colorPrimaryBg: '#e6f4f7', + colorPrimaryBgHover: '#cce9ef', + colorPrimaryBorder: '#99d3df', + // Custom secondary button tokens + buttonSecondaryHoverColor: '#custom-hover-color', + buttonSecondaryHoverBg: '#custom-hover-bg', + buttonSecondaryHoverBorderColor: '#custom-hover-border', + buttonSecondaryActiveColor: '#custom-active-color', + buttonSecondaryActiveBg: '#custom-active-bg', + buttonSecondaryActiveBorderColor: '#custom-active-border', + } as SupersetTheme; + + const hoverStyles = getSecondaryButtonHoverStyles(mockTheme); + + // Hover/active states use CSS with !important for specificity + expect(hoverStyles['&:hover'].color).toBe('#custom-hover-color !important'); + expect(hoverStyles['&:hover'].backgroundColor).toBe( + '#custom-hover-bg !important', + ); + expect(hoverStyles['&:hover'].borderColor).toBe( + '#custom-hover-border !important', + ); + expect(hoverStyles['&:active'].color).toBe('#custom-active-color !important'); + expect(hoverStyles['&:active'].backgroundColor).toBe( + '#custom-active-bg !important', + ); + expect(hoverStyles['&:active'].borderColor).toBe( + '#custom-active-border !important', + ); +}); + +test('getSecondaryButtonStyle supports partial token overrides', () => { + const mockTheme = { + colorPrimary: '#2893B3', + colorPrimaryBg: '#e6f4f7', + colorPrimaryBgHover: '#cce9ef', + colorPrimaryBorder: '#99d3df', + // Only override some tokens + buttonSecondaryBg: '#custom-bg', + buttonSecondaryBorderColor: '#custom-border', + } as SupersetTheme; + + const styles = getSecondaryButtonStyle(mockTheme); + + // Should use custom values where provided (no !important for inline styles) + expect(styles.backgroundColor).toBe('#custom-bg'); + expect(styles.borderColor).toBe('#custom-border'); + // Should fallback to Ant Design tokens where not provided + expect(styles.color).toBe('#2893B3'); +}); + +test('getSecondaryButtonHoverStyles supports partial token overrides', () => { + const mockTheme = { + colorPrimary: '#2893B3', + colorPrimaryBg: '#e6f4f7', + colorPrimaryBgHover: '#cce9ef', + colorPrimaryBorder: '#99d3df', + // Only override hover bg + buttonSecondaryHoverBg: '#custom-hover-bg', + } as SupersetTheme; + + const hoverStyles = getSecondaryButtonHoverStyles(mockTheme); + + // Should use custom value where provided + expect(hoverStyles['&:hover'].backgroundColor).toBe( + '#custom-hover-bg !important', + ); + // Should fallback to Ant Design tokens where not provided + expect(hoverStyles['&:hover'].color).toBe('#2893B3 !important'); + expect(hoverStyles['&:active'].backgroundColor).toBe('#99d3df !important'); +}); + +test('getSecondaryButtonStyle falls back when tokens are empty strings', () => { + const mockTheme = { + colorPrimary: '#2893B3', + colorPrimaryBg: '#e6f4f7', + buttonSecondaryColor: '', + buttonSecondaryBg: '', + buttonSecondaryBorderColor: '', + } as SupersetTheme; + + const styles = getSecondaryButtonStyle(mockTheme); + + // Empty strings should trigger fallback to primary tokens + expect(styles.color).toBe('#2893B3'); + expect(styles.backgroundColor).toBe('#e6f4f7'); + expect(styles.borderColor).toBe('transparent'); +}); + +test('secondary button merges consumer style with theme styles', () => { + const { getByRole } = render( + , + ); + const button = getByRole('button'); + + // Consumer styles should be applied + expect(button).toHaveStyle({ marginTop: '10px', padding: '20px' }); +}); diff --git a/superset-frontend/packages/superset-ui-core/src/components/Button/index.tsx b/superset-frontend/packages/superset-ui-core/src/components/Button/index.tsx index 626abfaa404..78f525e5a7c 100644 --- a/superset-frontend/packages/superset-ui-core/src/components/Button/index.tsx +++ b/superset-frontend/packages/superset-ui-core/src/components/Button/index.tsx @@ -21,6 +21,7 @@ import cx from 'classnames'; import { Button as AntdButton } from 'antd'; import { useTheme } from '@apache-superset/core/theme'; import { Tooltip } from '../Tooltip'; +import type { SupersetTheme } from '@apache-superset/core/theme'; import type { ButtonColorType, ButtonProps, @@ -30,6 +31,59 @@ import type { OnClickHandler, } from './types'; +/** + * Secondary Button Theming + * + * Ant Design's "filled" variant (used for secondary buttons) has no component-level + * tokens for customization. To enable full theming of secondary buttons, we use + * Superset-specific tokens (buttonSecondary*) with fallbacks to Ant Design's + * colorPrimary* derived tokens. + * + * Implementation approach (follows PR #38679 pattern for label tokens): + * - Default state: Applied via inline `style` prop (higher specificity than CSS classes) + * - Hover/Active states: Applied via `css` prop with !important (pseudo-selectors + * cannot be applied via inline styles) + * + * Available tokens (all optional, with sensible fallbacks): + * - buttonSecondaryColor: Text color (fallback: colorPrimary) + * - buttonSecondaryBg: Background color (fallback: colorPrimaryBg) + * - buttonSecondaryBorderColor: Border color (fallback: transparent) + * - buttonSecondaryHoverColor: Hover text color (fallback: colorPrimary) + * - buttonSecondaryHoverBg: Hover background (fallback: colorPrimaryBgHover) + * - buttonSecondaryHoverBorderColor: Hover border (fallback: transparent) + * - buttonSecondaryActiveColor: Active/pressed text color (fallback: colorPrimary) + * - buttonSecondaryActiveBg: Active/pressed background (fallback: colorPrimaryBorder) + * - buttonSecondaryActiveBorderColor: Active/pressed border (fallback: transparent) + */ + +/** + * Generates inline styles for secondary buttons (default state). + * Inline styles have higher specificity than CSS classes, so no !important needed. + */ +export const getSecondaryButtonStyle = (theme: SupersetTheme) => ({ + color: theme.buttonSecondaryColor || theme.colorPrimary, + backgroundColor: theme.buttonSecondaryBg || theme.colorPrimaryBg, + borderColor: theme.buttonSecondaryBorderColor || 'transparent', +}); + +/** + * Generates CSS styles for secondary button hover/active states. + * Must use CSS (not inline styles) since pseudo-selectors cannot be applied via style prop. + * Uses !important to override Ant Design's default styles. + */ +export const getSecondaryButtonHoverStyles = (theme: SupersetTheme) => ({ + '&:hover': { + color: `${theme.buttonSecondaryHoverColor || theme.colorPrimary} !important`, + backgroundColor: `${theme.buttonSecondaryHoverBg || theme.colorPrimaryBgHover} !important`, + borderColor: `${theme.buttonSecondaryHoverBorderColor || 'transparent'} !important`, + }, + '&:active': { + color: `${theme.buttonSecondaryActiveColor || theme.colorPrimary} !important`, + backgroundColor: `${theme.buttonSecondaryActiveBg || theme.colorPrimaryBorder} !important`, + borderColor: `${theme.buttonSecondaryActiveBorderColor || 'transparent'} !important`, + }, +}); + const BUTTON_STYLE_MAP: Record< ButtonStyle, { @@ -98,6 +152,12 @@ export function Button(props: ButtonProps) { const effectiveButtonStyle: ButtonStyle = buttonStyle ?? 'primary'; + // Secondary button inline styles (default state) - inline styles override CSS classes + const secondaryStyle = + effectiveButtonStyle === 'secondary' && !disabled + ? getSecondaryButtonStyle(theme) + : undefined; + const button = ( span > :first-of-type': { marginRight: firstChildMargin, }, - ':not(:hover)': effectiveButtonStyle === 'secondary' && - !disabled && { - // NOTE: This is the best we can do contrast wise for the secondary button using antd tokens - // and abusing the semantics. Should be revisited when possible. https://github.com/apache/superset/pull/34253#issuecomment-3104834692 - color: `${theme.colorPrimaryTextHover} !important`, - }, + // Secondary button hover/active states via CSS + ...(effectiveButtonStyle === 'secondary' && + !disabled && + getSecondaryButtonHoverStyles(theme)), }} icon={icon} {...restProps} + style={ + secondaryStyle + ? { ...secondaryStyle, ...restProps.style } + : restProps.style + } > {children} diff --git a/superset-frontend/packages/superset-ui-core/src/components/CodeSyntaxHighlighter/index.test.tsx b/superset-frontend/packages/superset-ui-core/src/components/CodeSyntaxHighlighter/index.test.tsx index 8734447c302..c70da8df1c7 100644 --- a/superset-frontend/packages/superset-ui-core/src/components/CodeSyntaxHighlighter/index.test.tsx +++ b/superset-frontend/packages/superset-ui-core/src/components/CodeSyntaxHighlighter/index.test.tsx @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import { render, screen } from '../../spec'; +import { render, screen, fireEvent } from '../../spec'; import CodeSyntaxHighlighter from './index'; // Simple mock that just returns the content @@ -153,4 +153,44 @@ describe('CodeSyntaxHighlighter', () => { expect(screen.getByText('SELECT * FROM users;')).toBeInTheDocument(); }); + + test('shows copy button by default', () => { + render( + SELECT 1;, + ); + + expect(screen.getByTitle('Copy to clipboard')).toBeInTheDocument(); + }); + + test('hides copy button when showCopyButton is false', () => { + render( + + SELECT 1; + , + ); + + expect(screen.queryByTitle('Copy to clipboard')).not.toBeInTheDocument(); + }); + + test('copy button does not throw when clipboard API is unavailable', () => { + const originalClipboard = navigator.clipboard; + Object.defineProperty(navigator, 'clipboard', { + value: undefined, + configurable: true, + }); + document.execCommand = jest.fn().mockReturnValue(true); + + render( + SELECT 1;, + ); + + expect(() => + fireEvent.click(screen.getByTitle('Copy to clipboard')), + ).not.toThrow(); + + Object.defineProperty(navigator, 'clipboard', { + value: originalClipboard, + configurable: true, + }); + }); }); diff --git a/superset-frontend/packages/superset-ui-core/src/components/CodeSyntaxHighlighter/index.tsx b/superset-frontend/packages/superset-ui-core/src/components/CodeSyntaxHighlighter/index.tsx index 131abad4304..29a035885f4 100644 --- a/superset-frontend/packages/superset-ui-core/src/components/CodeSyntaxHighlighter/index.tsx +++ b/superset-frontend/packages/superset-ui-core/src/components/CodeSyntaxHighlighter/index.tsx @@ -16,11 +16,14 @@ * specific language governing permissions and limitations * under the License. */ -import { useEffect, useState } from 'react'; +import { useCallback, useEffect, useRef, useState } from 'react'; import SyntaxHighlighterBase from 'react-syntax-highlighter/dist/cjs/light'; import github from 'react-syntax-highlighter/dist/cjs/styles/hljs/github'; import tomorrow from 'react-syntax-highlighter/dist/cjs/styles/hljs/tomorrow-night'; -import { isThemeDark, useTheme } from '@apache-superset/core/theme'; +import { css, isThemeDark, useTheme } from '@apache-superset/core/theme'; +import { t } from '@apache-superset/core/translation'; +import copyTextToClipboard from '../../utils/copy'; +import { Icons } from '../Icons'; export type SupportedLanguage = 'sql' | 'htmlbars' | 'markdown' | 'json'; @@ -31,6 +34,7 @@ export interface CodeSyntaxHighlighterProps { showLineNumbers?: boolean; wrapLines?: boolean; style?: any; // Override theme style if needed + showCopyButton?: boolean; } // Track which languages have been registered to avoid duplicate registrations @@ -76,11 +80,14 @@ export const CodeSyntaxHighlighter: React.FC = ({ showLineNumbers = false, wrapLines = true, style: overrideStyle, + showCopyButton = true, }) => { const theme = useTheme(); const [isLanguageReady, setIsLanguageReady] = useState( registeredLanguages.has(language), ); + const [copied, setCopied] = useState(false); + const copyTimeoutRef = useRef | null>(null); useEffect(() => { const loadLanguage = async () => { @@ -93,6 +100,21 @@ export const CodeSyntaxHighlighter: React.FC = ({ loadLanguage(); }, [language]); + useEffect( + () => () => { + if (copyTimeoutRef.current) clearTimeout(copyTimeoutRef.current); + }, + [], + ); + + const handleCopy = useCallback(() => { + copyTextToClipboard(() => Promise.resolve(children)).then(() => { + if (copyTimeoutRef.current) clearTimeout(copyTimeoutRef.current); + setCopied(true); + copyTimeoutRef.current = setTimeout(() => setCopied(false), 1500); + }); + }, [children]); + const isDark = isThemeDark(theme); const themeStyle = overrideStyle || (isDark ? tomorrow : github); @@ -104,32 +126,79 @@ export const CodeSyntaxHighlighter: React.FC = ({ ...customStyle, }; + const copyButton = showCopyButton && ( + + ); + // Show a simple pre-formatted text while language is loading if (!isLanguageReady) { return ( -
-        {children}
-      
+ {copyButton} +
+          {children}
+        
+
); } return ( - - {children} - + {copyButton} + + {children} + + ); }; diff --git a/superset-frontend/packages/superset-ui-core/src/components/DropdownContainer/DropdownContainer.tsx b/superset-frontend/packages/superset-ui-core/src/components/DropdownContainer/DropdownContainer.tsx index 32a464f2ce8..e8deaf8e120 100644 --- a/superset-frontend/packages/superset-ui-core/src/components/DropdownContainer/DropdownContainer.tsx +++ b/superset-frontend/packages/superset-ui-core/src/components/DropdownContainer/DropdownContainer.tsx @@ -352,6 +352,7 @@ export const DropdownContainer = forwardRef( onOpenChange={visible => setPopoverVisible(visible)} placement="bottom" forceRender={forceRender} + fresh // This prop prevents caching and stale data for filter scoping. > + } + > + +
+ + +
+ + ); + } + + return ( + {}} + formSubmitHandler={handleFormSubmit} + requiredFields={['name']} + > + + + + + ); +} diff --git a/superset-frontend/src/features/apiKeys/ApiKeyList.tsx b/superset-frontend/src/features/apiKeys/ApiKeyList.tsx new file mode 100644 index 00000000000..9b12962eb0a --- /dev/null +++ b/superset-frontend/src/features/apiKeys/ApiKeyList.tsx @@ -0,0 +1,236 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { useEffect, useRef, useState } from 'react'; +import { SupersetClient } from '@superset-ui/core'; +import { t } from '@apache-superset/core/translation'; +import { css, useTheme } from '@apache-superset/core/theme'; +import { + Button, + Table, + Modal, + Tag, + Tooltip, +} from '@superset-ui/core/components'; +import { useToasts } from 'src/components/MessageToasts/withToasts'; +import { ApiKeyCreateModal } from './ApiKeyCreateModal'; + +export interface ApiKey { + uuid: string; + name: string; + key_prefix: string; + active: boolean; + created_on: string; + expires_on: string | null; + revoked_on: string | null; + last_used_on: string | null; + scopes: string | null; +} + +export function ApiKeyList() { + const theme = useTheme(); + const { addDangerToast, addSuccessToast } = useToasts(); + const [apiKeys, setApiKeys] = useState([]); + const [loading, setLoading] = useState(false); + const [showCreateModal, setShowCreateModal] = useState(false); + const fetchCounterRef = useRef(0); + + async function fetchApiKeys() { + fetchCounterRef.current += 1; + const thisRequest = fetchCounterRef.current; + setLoading(true); + try { + const response = await SupersetClient.get({ + endpoint: '/api/v1/security/api_keys/', + }); + // Only apply results if this is still the most recent request + if (thisRequest === fetchCounterRef.current) { + setApiKeys(response.json.result || []); + } + } catch (error) { + if (thisRequest === fetchCounterRef.current) { + addDangerToast(t('Failed to fetch API keys')); + } + } finally { + if (thisRequest === fetchCounterRef.current) { + setLoading(false); + } + } + } + + useEffect(() => { + fetchApiKeys(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + function handleRevokeKey(keyUuid: string) { + Modal.confirm({ + title: t('Revoke API Key'), + content: t( + 'Are you sure you want to revoke this API key? This action cannot be undone.', + ), + okText: t('Revoke'), + okType: 'danger', + cancelText: t('Cancel'), + onOk: async () => { + try { + await SupersetClient.delete({ + endpoint: `/api/v1/security/api_keys/${keyUuid}`, + }); + addSuccessToast(t('API key revoked successfully')); + fetchApiKeys(); + } catch (error) { + addDangerToast(t('Failed to revoke API key')); + } + }, + }); + } + + const formatDate = (dateString: string | null) => { + if (!dateString) return '-'; + return new Date(dateString).toLocaleDateString(undefined, { + year: 'numeric', + month: 'short', + day: 'numeric', + }); + }; + + const getStatusBadge = (key: ApiKey) => { + if (key.revoked_on) { + return {t('Revoked')}; + } + if (key.expires_on && new Date(key.expires_on) < new Date()) { + return {t('Expired')}; + } + if (!key.active) { + return {t('Inactive')}; + } + return {t('Active')}; + }; + + const columns = [ + { + title: t('Name'), + dataIndex: 'name', + key: 'name', + }, + { + title: t('Key Prefix'), + dataIndex: 'key_prefix', + key: 'key_prefix', + render: (prefix: string) => ( + + {prefix}... + + ), + }, + { + title: t('Created'), + dataIndex: 'created_on', + key: 'created_on', + render: formatDate, + }, + { + title: t('Last Used'), + dataIndex: 'last_used_on', + key: 'last_used_on', + render: formatDate, + }, + { + title: t('Status'), + key: 'status', + render: (_: unknown, record: ApiKey) => getStatusBadge(record), + }, + { + title: t('Actions'), + key: 'actions', + render: (_: unknown, record: ApiKey) => ( + <> + {!record.revoked_on && record.active && ( + + + + )} + + ), + }, + ]; + + return ( +
+
+
+

+ {t('API keys allow scoped programmatic access to Superset.')} +

+

+ {t('Keys are shown only once at creation. Store them securely.')} +

+
+ +
+ + {showCreateModal && ( + { + setShowCreateModal(false); + }} + onSuccess={() => { + fetchApiKeys(); + }} + /> + )} + + ); +} diff --git a/superset-frontend/src/features/cssTemplates/CssTemplateModal.tsx b/superset-frontend/src/features/cssTemplates/CssTemplateModal.tsx index 4d2ada88e9d..e298bc58458 100644 --- a/superset-frontend/src/features/cssTemplates/CssTemplateModal.tsx +++ b/superset-frontend/src/features/cssTemplates/CssTemplateModal.tsx @@ -268,6 +268,7 @@ const CssTemplateModal: FunctionComponent = ({ onChange={onCssChange} value={currentCssTemplate?.css ?? ''} language="css" + height="250px" width="100%" /> diff --git a/superset-frontend/src/features/datasets/AddDataset/LeftPanel/LeftPanel.test.tsx b/superset-frontend/src/features/datasets/AddDataset/LeftPanel/LeftPanel.test.tsx index 847ef197435..789b5783359 100644 --- a/superset-frontend/src/features/datasets/AddDataset/LeftPanel/LeftPanel.test.tsx +++ b/superset-frontend/src/features/datasets/AddDataset/LeftPanel/LeftPanel.test.tsx @@ -180,7 +180,7 @@ test('should render schema selector, database selector container, and selects', name: 'Select database or type to search databases', }); const schemaSelect = screen.getByRole('combobox', { - name: 'Select schema or type to search schemas', + name: 'Select schema', }); expect(databaseSelect).toBeInTheDocument(); expect(schemaSelect).toBeInTheDocument(); @@ -211,7 +211,7 @@ test('renders list of options when user clicks on schema', async () => { // Schema select will be automatically populated if there is only one schema const schemaSelect = screen.getByRole('combobox', { - name: /select schema or type to search schemas/i, + name: /select schema/i, }); await waitFor(() => { expect(schemaSelect).toBeEnabled(); @@ -231,7 +231,7 @@ test('searches for a table name', async () => { userEvent.click(await screen.findByText('test-postgres')); const schemaSelect = screen.getByRole('combobox', { - name: /select schema or type to search schemas/i, + name: /select schema/i, }); const tableSelect = screen.getByRole('combobox', { name: /select table or type to search tables/i, @@ -287,7 +287,7 @@ test('renders a warning icon when a table name has a preexisting dataset', async userEvent.click(await screen.findByText('test-postgres')); const schemaSelect = screen.getByRole('combobox', { - name: /select schema or type to search schemas/i, + name: /select schema/i, }); const tableSelect = screen.getByRole('combobox', { name: /select table or type to search tables/i, diff --git a/superset-frontend/src/features/home/Menu.test.tsx b/superset-frontend/src/features/home/Menu.test.tsx index f2583cb20b3..6cbacebcb40 100644 --- a/superset-frontend/src/features/home/Menu.test.tsx +++ b/superset-frontend/src/features/home/Menu.test.tsx @@ -30,6 +30,14 @@ jest.mock('@apache-superset/core/theme', () => ({ useTheme: jest.fn(), })); +jest.mock('antd', () => ({ + ...jest.requireActual('antd'), + Grid: { + ...jest.requireActual('antd').Grid, + useBreakpoint: () => ({ md: true }), + }, +})); + const dropdownItems = [ { label: 'Data', diff --git a/superset-frontend/src/features/home/Menu.tsx b/superset-frontend/src/features/home/Menu.tsx index b7eacb65cf4..9300195bda9 100644 --- a/superset-frontend/src/features/home/Menu.tsx +++ b/superset-frontend/src/features/home/Menu.tsx @@ -18,6 +18,7 @@ */ import { useState, useEffect } from 'react'; import { styled, css, useTheme } from '@apache-superset/core/theme'; +import { t } from '@apache-superset/core/translation'; import { ensureStaticPrefix } from 'src/utils/assetUrl'; import { ensureAppRoot } from 'src/utils/pathUtils'; import { getUrlParam } from 'src/utils/urlUtils'; @@ -176,6 +177,7 @@ const StyledCol = styled(Col)` ${({ theme }) => css` display: flex; gap: ${theme.sizeUnit * 4}px; + flex-wrap: wrap; `} `; @@ -279,8 +281,10 @@ export function Menu({ return { key: label, label, - icon: , - popupOffset: NAVBAR_MENU_POPUP_OFFSET, + ...(screens.md && { + icon: , + popupOffset: NAVBAR_MENU_POPUP_OFFSET, + }), children: childItems, }; }; @@ -331,7 +335,7 @@ export function Menu({ return <>{link}; }; return ( - + )} = props => { return ( - + {props.name &&
{props.name}
} theme.sizeUnit * 6}px; + padding-top: 0; } `; @@ -93,6 +94,15 @@ function QueryPreviewModal({ currentQueryId: query.id, fetchData, }); + const theme = useTheme(); + const codeBlockStyle = { + border: 1, + borderColor: theme.colorBorder, + borderStyle: 'solid', + marginTop: theme.sizeUnit * 4, + fontSize: theme.fontSize * 0.75, + height: theme.sizeUnit * 100, + }; const [currentTab, setCurrentTab] = useState<'user' | 'executed'>('user'); @@ -157,6 +167,7 @@ function QueryPreviewModal({ addDangerToast={addDangerToast} addSuccessToast={addSuccessToast} language="sql" + customStyle={codeBlockStyle} > {(currentTab === 'user' ? sql : executed_sql) || ''} diff --git a/superset-frontend/src/features/queries/SavedQueryPreviewModal.tsx b/superset-frontend/src/features/queries/SavedQueryPreviewModal.tsx index 3e6e45871b8..6f0989e1a61 100644 --- a/superset-frontend/src/features/queries/SavedQueryPreviewModal.tsx +++ b/superset-frontend/src/features/queries/SavedQueryPreviewModal.tsx @@ -18,7 +18,7 @@ */ import { FunctionComponent } from 'react'; import { t } from '@apache-superset/core/translation'; -import { styled } from '@apache-superset/core/theme'; +import { useTheme, styled } from '@apache-superset/core/theme'; import { Button, Modal } from '@superset-ui/core/components'; import SyntaxHighlighterCopy from 'src/features/queries/SyntaxHighlighterCopy'; import withToasts, { @@ -41,6 +41,7 @@ const QueryLabel = styled.div` const StyledModal = styled(Modal)` .ant-modal-body { padding: 24px; + padding-top: 0; } `; @@ -77,6 +78,15 @@ const SavedQueryPreviewModal: FunctionComponent< currentQueryId: savedQuery.id, fetchData, }); + const theme = useTheme(); + const codeBlockStyle = { + border: 1, + borderColor: theme.colorBorder, + borderStyle: 'solid', + marginTop: theme.sizeUnit * 4, + fontSize: theme.fontSize * 0.75, + height: theme.sizeUnit * 100, + }; return (
@@ -123,6 +133,7 @@ const SavedQueryPreviewModal: FunctionComponent< language="sql" addDangerToast={addDangerToast} addSuccessToast={addSuccessToast} + customStyle={codeBlockStyle} > {savedQuery.sql || ''} diff --git a/superset-frontend/src/features/queries/SyntaxHighlighterCopy.tsx b/superset-frontend/src/features/queries/SyntaxHighlighterCopy.tsx index 1daecd6d594..f247c51f61f 100644 --- a/superset-frontend/src/features/queries/SyntaxHighlighterCopy.tsx +++ b/superset-frontend/src/features/queries/SyntaxHighlighterCopy.tsx @@ -124,7 +124,11 @@ export default function SyntaxHighlighterCopy({ onClick={handleCopyClick} onKeyDown={handleKeyDown} /> - + {children} diff --git a/superset-frontend/src/features/reports/ReportModal/ReportModal.test.tsx b/superset-frontend/src/features/reports/ReportModal/ReportModal.test.tsx index c07381817b7..5fad71c7ad6 100644 --- a/superset-frontend/src/features/reports/ReportModal/ReportModal.test.tsx +++ b/superset-frontend/src/features/reports/ReportModal/ReportModal.test.tsx @@ -22,9 +22,10 @@ import { screen, userEvent, waitFor, + createStore, } from 'spec/helpers/testing-library'; +import reducerIndex from 'spec/helpers/reducerIndex'; import { FeatureFlag, VizType, isFeatureEnabled } from '@superset-ui/core'; -import * as actions from 'src/features/reports/ReportModal/actions'; import ReportModal from '.'; const REPORT_ENDPOINT = 'glob:*/api/v1/report*'; @@ -56,115 +57,260 @@ jest.mock('@superset-ui/core', () => ({ })); const mockedIsFeatureEnabled = isFeatureEnabled as jest.Mock; -// eslint-disable-next-line no-restricted-globals -- TODO: Migrate from describe blocks -describe('Email Report Modal', () => { - beforeEach(() => { - mockedIsFeatureEnabled.mockImplementation( - featureFlag => featureFlag === FeatureFlag.AlertReports, - ); - render(, { useRedux: true }); - }); - test('inputs respond correctly', () => { - // ----- Report name textbox - // Initial value - const reportNameTextbox = screen.getByTestId('report-name-test'); - expect(reportNameTextbox).toHaveDisplayValue('Weekly Report'); - // Type in the textbox and assert that it worked - userEvent.clear(reportNameTextbox); - userEvent.type(reportNameTextbox, 'Report name text test'); - expect(reportNameTextbox).toHaveDisplayValue('Report name text test'); - - // ----- Report description textbox - // Initial value - const reportDescriptionTextbox = screen.getByTestId( - 'report-description-test', - ); - expect(reportDescriptionTextbox).toHaveDisplayValue(''); - // Type in the textbox and assert that it worked - userEvent.type(reportDescriptionTextbox, 'Report description text test'); - expect(reportDescriptionTextbox).toHaveDisplayValue( - 'Report description text test', - ); - - // ----- Crontab - const crontabInputs = screen.getAllByRole('combobox'); - expect(crontabInputs).toHaveLength(5); - }); - - test('does not allow user to create a report without a name', () => { - // Grab name textbox and add button - const reportNameTextbox = screen.getByTestId('report-name-test'); - const addButton = screen.getByRole('button', { name: /add/i }); - - // Add button should be enabled while name textbox has text - expect(reportNameTextbox).toHaveDisplayValue('Weekly Report'); - expect(addButton).toBeEnabled(); - - // Clear the text from the name textbox - userEvent.clear(reportNameTextbox); - - // Add button should now be disabled, blocking user from creation - expect(reportNameTextbox).toHaveDisplayValue(''); - expect(addButton).toBeDisabled(); - }); - - // eslint-disable-next-line no-restricted-globals -- TODO: Migrate from describe blocks - describe('Email Report Modal', () => { - let dispatch: any; - - beforeEach(async () => { - dispatch = jest.fn(); - }); - - test('creates a new email report', async () => { - // ---------- Render/value setup ---------- - const reportValues = { - id: 1, - result: { - active: true, - creation_method: 'dashboards', - crontab: '0 12 * * 1', - dashboard: 1, - name: 'Weekly Report', - owners: [1], - recipients: [ - { - recipient_config_json: { - target: 'test@test.com', - }, - type: 'Email', - }, - ], - type: 'Report', - }, - }; - // This is needed to structure the reportValues to match the fetchMock return - const stringyReportValues = `{"id":1,"result":{"active":true,"creation_method":"dashboards","crontab":"0 12 * * 1","dashboard":${1},"name":"Weekly Report","owners":[${1}],"recipients":[{"recipient_config_json":{"target":"test@test.com"},"type":"Email"}],"type":"Report"}}`; - // Watch for report POST - fetchMock.post(REPORT_ENDPOINT, reportValues); - - // Click "Add" button to create a new email report - const addButton = screen.getByRole('button', { name: /add/i }); - await waitFor(() => userEvent.click(addButton)); - - // Mock addReport from Redux - const makeRequest = () => { - const request = actions.addReport(reportValues); - return request(dispatch); - }; - - await makeRequest(); - - // 🐞 ----- There are 2 POST calls at this point ----- 🐞 - - // addReport's mocked POST return should match the mocked values - expect(fetchMock.callHistory.lastCall()?.options?.body).toEqual( - stringyReportValues, - ); - expect(dispatch).toHaveBeenCalledTimes(2); - const reportCalls = fetchMock.callHistory.calls(REPORT_ENDPOINT); - expect(reportCalls).toHaveLength(2); - }); - }); +beforeEach(() => { + mockedIsFeatureEnabled.mockImplementation( + featureFlag => featureFlag === FeatureFlag.AlertReports, + ); +}); + +test('inputs respond correctly', () => { + render(, { useRedux: true }); + // ----- Report name textbox + const reportNameTextbox = screen.getByTestId('report-name-test'); + expect(reportNameTextbox).toHaveDisplayValue('Weekly Report'); + userEvent.clear(reportNameTextbox); + userEvent.type(reportNameTextbox, 'Report name text test'); + expect(reportNameTextbox).toHaveDisplayValue('Report name text test'); + + // ----- Report description textbox + const reportDescriptionTextbox = screen.getByTestId( + 'report-description-test', + ); + expect(reportDescriptionTextbox).toHaveDisplayValue(''); + userEvent.type(reportDescriptionTextbox, 'Report description text test'); + expect(reportDescriptionTextbox).toHaveDisplayValue( + 'Report description text test', + ); + + // ----- Crontab + const crontabInputs = screen.getAllByRole('combobox'); + expect(crontabInputs).toHaveLength(5); +}); + +test('does not allow user to create a report without a name', () => { + render(, { useRedux: true }); + const reportNameTextbox = screen.getByTestId('report-name-test'); + const addButton = screen.getByRole('button', { name: /add/i }); + + expect(reportNameTextbox).toHaveDisplayValue('Weekly Report'); + expect(addButton).toBeEnabled(); + + userEvent.clear(reportNameTextbox); + + expect(reportNameTextbox).toHaveDisplayValue(''); + expect(addButton).toBeDisabled(); +}); + +test('creates a new email report via modal Add button', async () => { + fetchMock.post( + REPORT_ENDPOINT, + { id: 1, result: {} }, + { name: 'post-report' }, + ); + + render(, { useRedux: true }); + + const addButton = screen.getByRole('button', { name: /add/i }); + await waitFor(() => userEvent.click(addButton)); + + // Verify exactly one POST from the modal submit path + await waitFor(() => { + const postCalls = fetchMock.callHistory.calls('post-report'); + expect(postCalls).toHaveLength(1); + }); + + const postCalls = fetchMock.callHistory.calls('post-report'); + const body = JSON.parse(postCalls[0].options.body as string); + expect(body.name).toBe('Weekly Report'); + expect(body.type).toBe('Report'); + expect(body.creation_method).toBe('dashboards'); + expect(body.crontab).toBeDefined(); + expect(body.recipients).toBeDefined(); + expect(body.recipients[0].type).toBe('Email'); + + fetchMock.removeRoute('post-report'); +}); + +test('text-based chart hides screenshot width and shows message content', () => { + // Table is text-based: should show message content but hide custom width + const textChartProps = { + ...defaultProps, + dashboardId: undefined, + chart: { id: 1, sliceFormData: { viz_type: VizType.Table } }, + chartName: 'My Table Chart', + creationMethod: 'charts' as const, + }; + render(, { useRedux: true }); + + // Message content section should be visible + expect(screen.getByText('Message content')).toBeInTheDocument(); + expect(screen.getByText(/Text embedded in email/i)).toBeInTheDocument(); + + // Screenshot width should NOT be visible for text-based chart + expect(screen.queryByText('Screenshot width')).not.toBeInTheDocument(); +}); + +test('non-text chart shows screenshot width and message content', () => { + const lineChartProps = { + ...defaultProps, + dashboardId: undefined, + chart: { id: 1, sliceFormData: { viz_type: VizType.Line } }, + chartName: 'My Line Chart', + creationMethod: 'charts' as const, + }; + render(, { useRedux: true }); + + // Both message content and screenshot width should be visible + expect(screen.getByText('Message content')).toBeInTheDocument(); + expect(screen.getByText('Screenshot width')).toBeInTheDocument(); +}); + +test('dashboard report hides message content section', () => { + const dashboardProps = { + ...defaultProps, + chart: undefined, + dashboardName: 'My Dashboard', + }; + render(, { useRedux: true }); + + // Message content (radio group) should NOT be visible for dashboard + expect(screen.queryByText('Message content')).not.toBeInTheDocument(); + // Screenshot width SHOULD be visible + expect(screen.getByText('Screenshot width')).toBeInTheDocument(); +}); + +test('renders edit mode when report exists in store', () => { + const existingReport = { + id: 42, + name: 'Existing Dashboard Report', + description: 'An existing report', + crontab: '0 9 * * 1', + creation_method: 'dashboards', + report_format: 'PNG', + timezone: 'America/New_York', + active: true, + type: 'Report', + dashboard: 1, + owners: [1], + recipients: [ + { + recipient_config_json: { target: 'test@test.com' }, + type: 'Email', + }, + ], + }; + const store = createStore( + { + reports: { + dashboards: { 1: existingReport }, + }, + }, + reducerIndex, + ); + + render(, { useRedux: true, store }); + + // Edit mode title + expect(screen.getByText('Edit email report')).toBeInTheDocument(); + // Report name populated from store + expect(screen.getByTestId('report-name-test')).toHaveDisplayValue( + 'Existing Dashboard Report', + ); + // Save button instead of Add + expect(screen.getByRole('button', { name: /save/i })).toBeInTheDocument(); +}); + +test('edit mode dispatches editReport via PUT on save', async () => { + const existingReport = { + id: 42, + name: 'Existing Report', + description: '', + crontab: '0 12 * * 1', + creation_method: 'dashboards', + report_format: 'PNG', + timezone: 'America/New_York', + active: true, + type: 'Report', + dashboard: 1, + owners: [1], + recipients: [ + { + recipient_config_json: { target: 'test@test.com' }, + type: 'Email', + }, + ], + }; + const store = createStore( + { + reports: { + dashboards: { 1: existingReport }, + }, + }, + reducerIndex, + ); + + fetchMock.put( + 'glob:*/api/v1/report/42', + { id: 42, result: {} }, + { + name: 'put-report-42', + }, + ); + + render(, { useRedux: true, store }); + + expect(screen.getByText('Edit email report')).toBeInTheDocument(); + const saveButton = screen.getByRole('button', { name: /save/i }); + await waitFor(() => userEvent.click(saveButton)); + + await waitFor(() => { + const calls = fetchMock.callHistory.calls('put-report-42'); + expect(calls.length).toBeGreaterThan(0); + }); + + const calls = fetchMock.callHistory.calls('put-report-42'); + const body = JSON.parse(calls[calls.length - 1].options.body as string); + + // Pin critical payload fields to catch regressions + expect(body.type).toBe('Report'); + expect(body.name).toBe('Existing Report'); + expect(body.crontab).toBe('0 12 * * 1'); + expect(body.report_format).toBe('PNG'); + expect(body.dashboard).toBe(1); + expect(body.recipients).toBeDefined(); + expect(body.recipients[0].type).toBe('Email'); + + fetchMock.removeRoute('put-report-42'); +}); + +test('submit failure dispatches danger toast and keeps modal open', async () => { + fetchMock.post(REPORT_ENDPOINT, 500, { name: 'post-fail' }); + const onHide = jest.fn(); + + const store = createStore({}, reducerIndex); + render(, { + useRedux: true, + store, + }); + + const addButton = screen.getByRole('button', { name: /add/i }); + await waitFor(() => userEvent.click(addButton)); + + // The addReport action catches 500 errors, dispatches a danger toast, and re-throws + await waitFor(() => { + const toasts = (store.getState() as any).messageToasts; + expect(toasts.length).toBeGreaterThan(0); + expect( + toasts.some((t: { text: string }) => + t.text.includes('Failed to create report'), + ), + ).toBe(true); + }); + + // Modal stays open — onHide should NOT have been called + expect(onHide).not.toHaveBeenCalled(); + expect(screen.getByText('Schedule a new email report')).toBeInTheDocument(); + + fetchMock.removeRoute('post-fail'); }); diff --git a/superset-frontend/src/features/reports/ReportModal/actions.test.ts b/superset-frontend/src/features/reports/ReportModal/actions.test.ts new file mode 100644 index 00000000000..4c3d2f87888 --- /dev/null +++ b/superset-frontend/src/features/reports/ReportModal/actions.test.ts @@ -0,0 +1,178 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import fetchMock from 'fetch-mock'; +import { + addReport, + editReport, + deleteActiveReport, + fetchUISpecificReport, + ADD_REPORT, + EDIT_REPORT, + DELETE_REPORT, + SET_REPORT, +} from './actions'; + +const REPORT_ENDPOINT = 'glob:*/api/v1/report/*'; +const REPORT_POST_ENDPOINT = 'glob:*/api/v1/report/'; + +afterEach(() => { + fetchMock.clearHistory().removeRoutes(); +}); + +test('addReport dispatches ADD_REPORT and success toast on success', async () => { + const jsonResponse = { id: 1, result: { name: 'New Report' } }; + fetchMock.post(REPORT_POST_ENDPOINT, jsonResponse); + const dispatch = jest.fn(); + + await addReport({ name: 'New Report' })(dispatch); + + const types = dispatch.mock.calls.map(([action]: any) => action.type); + expect(types).toContain(ADD_REPORT); + expect(types).toContain('ADD_TOAST'); + const toastAction = dispatch.mock.calls.find( + ([a]: any) => a.type === 'ADD_TOAST', + )?.[0]; + expect(toastAction.payload.toastType).toBe('SUCCESS_TOAST'); +}); + +test('addReport dispatches danger toast on failure and rejects', async () => { + fetchMock.post(REPORT_POST_ENDPOINT, 500); + const dispatch = jest.fn(); + + await expect( + addReport({ name: 'Bad Report' })(dispatch), + ).rejects.toBeDefined(); + + const types = dispatch.mock.calls.map(([action]: any) => action.type); + expect(types).not.toContain(ADD_REPORT); + expect(types).toContain('ADD_TOAST'); + const toastAction = dispatch.mock.calls.find( + ([a]: any) => a.type === 'ADD_TOAST', + )?.[0]; + expect(toastAction.payload.toastType).toBe('DANGER_TOAST'); +}); + +test('editReport dispatches EDIT_REPORT and success toast on success', async () => { + const jsonResponse = { id: 5, result: { name: 'Updated Report' } }; + fetchMock.put(REPORT_ENDPOINT, jsonResponse); + const dispatch = jest.fn(); + + await editReport(5, { name: 'Updated Report' })(dispatch); + + const types = dispatch.mock.calls.map(([action]: any) => action.type); + expect(types).toContain(EDIT_REPORT); + expect(types).toContain('ADD_TOAST'); + const toastAction = dispatch.mock.calls.find( + ([a]: any) => a.type === 'ADD_TOAST', + )?.[0]; + expect(toastAction.payload.toastType).toBe('SUCCESS_TOAST'); +}); + +test('editReport dispatches danger toast on failure and rejects', async () => { + fetchMock.put(REPORT_ENDPOINT, 500); + const dispatch = jest.fn(); + + await expect( + editReport(5, { name: 'Bad Update' })(dispatch), + ).rejects.toBeDefined(); + + const types = dispatch.mock.calls.map(([action]: any) => action.type); + expect(types).not.toContain(EDIT_REPORT); + const toastAction = dispatch.mock.calls.find( + ([a]: any) => a.type === 'ADD_TOAST', + )?.[0]; + expect(toastAction.payload.toastType).toBe('DANGER_TOAST'); +}); + +test('deleteActiveReport dispatches DELETE_REPORT and success toast on success', async () => { + fetchMock.delete(REPORT_ENDPOINT, {}); + const dispatch = jest.fn(); + const report = { + id: 10, + name: 'To Delete', + creation_method: 'dashboards', + dashboard: 1, + }; + + await deleteActiveReport(report)(dispatch); + + const types = dispatch.mock.calls.map(([action]: any) => action.type); + expect(types).toContain(DELETE_REPORT); + expect(types).toContain('ADD_TOAST'); + const toastAction = dispatch.mock.calls.find( + ([a]: any) => a.type === 'ADD_TOAST', + )?.[0]; + expect(toastAction.payload.toastType).toBe('SUCCESS_TOAST'); +}); + +test('deleteActiveReport dispatches danger toast on failure', async () => { + fetchMock.delete(REPORT_ENDPOINT, 500); + const dispatch = jest.fn(); + const report = { id: 10, name: 'To Delete' }; + + await deleteActiveReport(report)(dispatch); + + const types = dispatch.mock.calls.map(([action]: any) => action.type); + expect(types).not.toContain(DELETE_REPORT); + const toastAction = dispatch.mock.calls.find( + ([a]: any) => a.type === 'ADD_TOAST', + )?.[0]; + expect(toastAction.payload.toastType).toBe('DANGER_TOAST'); +}); + +test('fetchUISpecificReport dispatches SET_REPORT on success', async () => { + const jsonResponse = { result: [{ id: 1, name: 'Dashboard Report' }] }; + fetchMock.get('glob:*/api/v1/report/?q=*', jsonResponse); + const dispatch = jest.fn(); + + await fetchUISpecificReport({ + userId: 1, + filterField: 'dashboard_id', + creationMethod: 'dashboards', + resourceId: 42, + })(dispatch); + + const setAction = dispatch.mock.calls.find( + ([a]: any) => a.type === SET_REPORT, + )?.[0]; + expect(setAction).toBeDefined(); + expect(setAction.resourceId).toBe(42); + expect(setAction.creationMethod).toBe('dashboards'); + expect(setAction.filterField).toBe('dashboard_id'); +}); + +test('fetchUISpecificReport dispatches danger toast on failure', async () => { + fetchMock.get('glob:*/api/v1/report/?q=*', 500); + const dispatch = jest.fn(); + + await fetchUISpecificReport({ + userId: 1, + filterField: 'chart_id', + creationMethod: 'charts', + resourceId: 10, + })(dispatch); + + const types = dispatch.mock.calls.map(([action]: any) => action.type); + expect(types).not.toContain(SET_REPORT); + expect(types).toContain('ADD_TOAST'); + const toastAction = dispatch.mock.calls.find( + ([a]: any) => a.type === 'ADD_TOAST', + )?.[0]; + expect(toastAction.payload.toastType).toBe('DANGER_TOAST'); +}); diff --git a/superset-frontend/src/features/reports/ReportModal/actions.ts b/superset-frontend/src/features/reports/ReportModal/actions.ts index db28f9360ec..6ae2c3e6ee3 100644 --- a/superset-frontend/src/features/reports/ReportModal/actions.ts +++ b/superset-frontend/src/features/reports/ReportModal/actions.ts @@ -169,8 +169,9 @@ export const addReport = dispatch({ type: ADD_REPORT, json } as AddReportAction); dispatch(addSuccessToast(t('The report has been created'))); }) - .catch(() => { + .catch(err => { dispatch(addDangerToast(t('Failed to create report'))); + throw err; }); export const EDIT_REPORT = 'EDIT_REPORT' as const; @@ -191,8 +192,9 @@ export const editReport = dispatch({ type: EDIT_REPORT, json } as EditReportAction); dispatch(addSuccessToast(t('Report updated'))); }) - .catch(() => { + .catch(err => { dispatch(addDangerToast(t('Failed to update report'))); + throw err; }); export function toggleActive(report: ReportObject, isActive: boolean) { diff --git a/superset-frontend/src/features/reports/ReportModal/reducer.test.ts b/superset-frontend/src/features/reports/ReportModal/reducer.test.ts new file mode 100644 index 00000000000..51f39302c89 --- /dev/null +++ b/superset-frontend/src/features/reports/ReportModal/reducer.test.ts @@ -0,0 +1,214 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import reportsReducer, { ReportsState } from './reducer'; +import { + SET_REPORT, + ADD_REPORT, + EDIT_REPORT, + DELETE_REPORT, + SetReportAction, + AddReportAction, + EditReportAction, + DeleteReportAction, +} from './actions'; +import { ReportObject } from 'src/features/reports/types'; + +const makeReport = (overrides: Partial = {}): ReportObject => ({ + active: true, + crontab: '0 12 * * 1', + name: 'Test Report', + owners: [1], + recipients: [ + { + recipient_config_json: { target: 'a@b.com', ccTarget: '', bccTarget: '' }, + type: 'Email', + }, + ], + report_format: 'PNG', + timezone: 'UTC', + type: 'Report', + validator_config_json: null, + validator_type: '', + working_timeout: 3600, + log_retention: 90, + creation_method: 'dashboards', + force_screenshot: false, + ...overrides, +}); + +test('SET_REPORT stores report keyed by resourceId under creationMethod', () => { + const report = makeReport({ id: 5, dashboard: 10 }); + const action: SetReportAction = { + type: SET_REPORT, + report: { result: [report] }, + resourceId: 10, + creationMethod: 'dashboards', + filterField: 'dashboard_id', + }; + + const result = reportsReducer({}, action); + + expect(result.dashboards?.[10]).toEqual(report); +}); + +test('SET_REPORT removes entry when API returns empty result', () => { + const initial: ReportsState = { + dashboards: { 10: makeReport({ id: 5, dashboard: 10 }) }, + }; + const action: SetReportAction = { + type: SET_REPORT, + report: { result: [] }, + resourceId: 10, + creationMethod: 'dashboards', + filterField: 'dashboard_id', + }; + + const result = reportsReducer(initial, action); + + expect(result.dashboards?.[10]).toBeUndefined(); +}); + +test('SET_REPORT uses chart property when filterField is chart_id', () => { + const report = makeReport({ + id: 7, + chart: 42, + creation_method: 'charts', + }); + const action: SetReportAction = { + type: SET_REPORT, + report: { result: [report] }, + resourceId: 42, + creationMethod: 'charts', + filterField: 'chart_id', + }; + + const result = reportsReducer({}, action); + + expect(result.charts?.[42]).toEqual(report); +}); + +test('ADD_REPORT keys dashboard report by dashboard id', () => { + const action: AddReportAction = { + type: ADD_REPORT, + json: { + id: 1, + result: { dashboard: 10, creation_method: 'dashboards' }, + }, + }; + + const result = reportsReducer({}, action); + + expect(result.dashboards?.[10]).toMatchObject({ id: 1, dashboard: 10 }); +}); + +test('ADD_REPORT keys alerts_reports report by report id', () => { + const action: AddReportAction = { + type: ADD_REPORT, + json: { + id: 99, + result: { creation_method: 'alerts_reports' }, + }, + }; + + const result = reportsReducer({}, action); + + expect(result.alerts_reports?.[99]).toMatchObject({ id: 99 }); +}); + +test('ADD_REPORT returns unchanged state when key is undefined', () => { + const initial: ReportsState = { dashboards: {} }; + const action: AddReportAction = { + type: ADD_REPORT, + json: { + id: 1, + result: { creation_method: 'dashboards' }, + // no dashboard or chart field → key is undefined + }, + }; + + const result = reportsReducer(initial, action); + + expect(result).toBe(initial); +}); + +test('EDIT_REPORT replaces existing report at same key', () => { + const initial: ReportsState = { + dashboards: { + 10: makeReport({ id: 1, dashboard: 10, name: 'Old Name' }), + }, + }; + const action: EditReportAction = { + type: EDIT_REPORT, + json: { + id: 1, + result: { + dashboard: 10, + creation_method: 'dashboards', + name: 'New Name', + }, + }, + }; + + const result = reportsReducer(initial, action); + + expect(result.dashboards?.[10]?.name).toBe('New Name'); +}); + +test('DELETE_REPORT removes report from state', () => { + const report = makeReport({ + id: 5, + dashboard: 10, + creation_method: 'dashboards', + }); + const initial: ReportsState = { dashboards: { 10: report } }; + const action: DeleteReportAction = { + type: DELETE_REPORT, + report: { + id: 5, + dashboard: 10, + creation_method: 'dashboards', + }, + }; + + const result = reportsReducer(initial, action); + + expect(result.dashboards?.[10]).toBeUndefined(); +}); + +test('DELETE_REPORT for alerts_reports keys by report id', () => { + const report = makeReport({ id: 99, creation_method: 'alerts_reports' }); + const initial: ReportsState = { alerts_reports: { 99: report } }; + const action: DeleteReportAction = { + type: DELETE_REPORT, + report: { id: 99, creation_method: 'alerts_reports' }, + }; + + const result = reportsReducer(initial, action); + + expect(result.alerts_reports?.[99]).toBeUndefined(); +}); + +test('unknown action type returns state unchanged', () => { + const initial: ReportsState = { dashboards: { 1: makeReport({ id: 1 }) } }; + const action = { type: 'UNKNOWN_ACTION' } as any; + + const result = reportsReducer(initial, action); + + expect(result).toBe(initial); +}); diff --git a/superset-frontend/src/features/tags/BulkTagModal.tsx b/superset-frontend/src/features/tags/BulkTagModal.tsx index 76ce8730cf9..46adac21021 100644 --- a/superset-frontend/src/features/tags/BulkTagModal.tsx +++ b/superset-frontend/src/features/tags/BulkTagModal.tsx @@ -125,7 +125,7 @@ const BulkTagModal: FC = ({
{t('You are adding tags to %s %ss', selected.length, resourceName)}
- {t('tags')} + {t('Tags')} = ({ onHide={onHide} // @ts-expect-error onChange={tags => setTags(tags)} + getPopupContainer={() => document.body} placeholder={t('Select Tags')} mode="multiple" /> diff --git a/superset-frontend/src/features/tags/TagModal.tsx b/superset-frontend/src/features/tags/TagModal.tsx index 53b059a4425..20736d4610c 100644 --- a/superset-frontend/src/features/tags/TagModal.tsx +++ b/superset-frontend/src/features/tags/TagModal.tsx @@ -332,6 +332,7 @@ const TagModal: FC = ({ onChange={value => handleOptionChange(TaggableResources.Dashboard, value) } + getPopupContainer={() => document.body} header={{t('Dashboards')}} allowClear /> @@ -344,6 +345,7 @@ const TagModal: FC = ({ value={chartsToTag} options={loadCharts} onChange={value => handleOptionChange(TaggableResources.Chart, value)} + getPopupContainer={() => document.body} header={{t('Charts')}} allowClear /> @@ -358,6 +360,7 @@ const TagModal: FC = ({ onChange={value => handleOptionChange(TaggableResources.SavedQuery, value) } + getPopupContainer={() => document.body} header={{t('Saved queries')}} allowClear /> diff --git a/superset-frontend/src/features/themes/ThemeModal.test.tsx b/superset-frontend/src/features/themes/ThemeModal.test.tsx index 4994863b074..2b4c29efc4f 100644 --- a/superset-frontend/src/features/themes/ThemeModal.test.tsx +++ b/superset-frontend/src/features/themes/ThemeModal.test.tsx @@ -712,6 +712,138 @@ test('applies theme locally when clicking Apply button', async () => { expect(mockThemeContext.setTemporaryTheme).toHaveBeenCalled(); }); +test('shows Format button when modal is in edit mode', () => { + render( + , + { useRedux: true, useRouter: true }, + ); + + expect(screen.getByRole('button', { name: /format/i })).toBeInTheDocument(); +}); + +test('does not show Format button for read-only system themes', async () => { + render( + , + { useRedux: true, useRouter: true }, + ); + + await screen.findByText('System Theme - Read Only'); + + expect( + screen.queryByRole('button', { name: /format/i }), + ).not.toBeInTheDocument(); +}); + +test('disables Format button when JSON is invalid', async () => { + render( + , + { useRedux: true, useRouter: true }, + ); + + const jsonEditor = screen.getByTestId('json-editor'); + userEvent.clear(jsonEditor); + userEvent.type(jsonEditor, '{invalid json'); + + await waitFor(() => { + expect(screen.getByRole('button', { name: /format/i })).toBeDisabled(); + }); +}); + +test('enables Format button when JSON is valid', async () => { + render( + , + { useRedux: true, useRouter: true }, + ); + + await addValidJsonData(); + + await waitFor(() => { + expect(screen.getByRole('button', { name: /format/i })).toBeEnabled(); + }); +}); + +test('Format button pretty-prints minified JSON', async () => { + render( + , + { useRedux: true, useRouter: true }, + ); + + const minifiedJson = '{"token":{"colorPrimary":"#1890ff"}}'; + const jsonEditor = screen.getByTestId('json-editor'); + userEvent.clear(jsonEditor); + userEvent.type(jsonEditor, minifiedJson); + + const formatButton = screen.getByRole('button', { name: /format/i }); + userEvent.click(formatButton); + + const expectedFormatted = JSON.stringify( + { token: { colorPrimary: '#1890ff' } }, + null, + 2, + ); + await waitFor(() => { + expect(jsonEditor).toHaveValue(expectedFormatted); + }); +}); + +test('Format button is disabled when JSON editor is empty', async () => { + render( + , + { useRedux: true, useRouter: true }, + ); + + // The editor initializes with `{}` — clear it to reach the empty state + const jsonEditor = screen.getByTestId('json-editor'); + userEvent.clear(jsonEditor); + + await waitFor(() => { + expect(screen.getByRole('button', { name: /format/i })).toBeDisabled(); + }); +}); + test('disables Apply button when JSON configuration is invalid', async () => { fetchMock.clearHistory().removeRoutes(); fetchMock.get('glob:*/api/v1/theme/*', { diff --git a/superset-frontend/src/features/themes/ThemeModal.tsx b/superset-frontend/src/features/themes/ThemeModal.tsx index 59d843d1d90..db7692443bb 100644 --- a/superset-frontend/src/features/themes/ThemeModal.tsx +++ b/superset-frontend/src/features/themes/ThemeModal.tsx @@ -71,6 +71,15 @@ const toEditorAnnotations = ( message: ann.text, })); +const formatJsonData = (jsonData?: string): string | undefined => { + if (!jsonData) return jsonData; + try { + return JSON.stringify(JSON.parse(jsonData), null, 2); + } catch { + return jsonData; + } +}; + interface ThemeModalProps { addDangerToast: (msg: string) => void; addSuccessToast?: (msg: string) => void; @@ -316,6 +325,15 @@ const ThemeModal: FunctionComponent = ({ [currentTheme], ); + const onFormat = useCallback(() => { + if (currentTheme?.json_data) { + const formatted = formatJsonData(currentTheme.json_data); + if (formatted !== currentTheme.json_data) { + onJsonDataChange(formatted || ''); + } + } + }, [currentTheme?.json_data, onJsonDataChange]); + const validate = () => { if (isReadOnly || !currentTheme) { setDisableSave(true); @@ -357,8 +375,12 @@ const ThemeModal: FunctionComponent = ({ useEffect(() => { if (resource) { - setCurrentTheme(resource); - setInitialTheme(resource); + const formatted = { + ...resource, + json_data: formatJsonData(resource.json_data), + }; + setCurrentTheme(formatted); + setInitialTheme(formatted); } }, [resource]); @@ -522,27 +544,47 @@ const ThemeModal: FunctionComponent = ({ annotations={toEditorAnnotations(validation.annotations)} /> - {canDevelopThemes && ( -
- - - -
- )} + + + )} + {canDevelopThemes && ( + + + + )} + +
diff --git a/superset-frontend/src/filters/components/Select/SelectFilterPlugin.tsx b/superset-frontend/src/filters/components/Select/SelectFilterPlugin.tsx index c282b893c35..1ebe8fad437 100644 --- a/superset-frontend/src/filters/components/Select/SelectFilterPlugin.tsx +++ b/superset-frontend/src/filters/components/Select/SelectFilterPlugin.tsx @@ -590,6 +590,7 @@ export default function PluginFilterSelect(props: PluginFilterSelectProps) {
{{#each data}}{{/each}}
{{this.name}}
" + ) + config = HandlebarsChartConfig( + chart_type="handlebars", + handlebars_template=template, + query_mode="raw", + columns=[ColumnRef(name="product"), ColumnRef(name="price")], + ) + assert config.query_mode == "raw" + assert config.columns is not None + assert len(config.columns) == 2 + + def test_aggregate_mode_requires_metrics(self) -> None: + with pytest.raises(ValueError, match="requires 'metrics'"): + HandlebarsChartConfig( + chart_type="handlebars", + handlebars_template="

test

", + query_mode="aggregate", + ) + + def test_raw_mode_requires_columns(self) -> None: + with pytest.raises(ValueError, match="requires 'columns'"): + HandlebarsChartConfig( + chart_type="handlebars", + handlebars_template="

test

", + query_mode="raw", + ) + + def test_template_min_length(self) -> None: + with pytest.raises(ValueError, match="at least 1 character"): + HandlebarsChartConfig( + chart_type="handlebars", + handlebars_template="", + metrics=[ColumnRef(name="sales", aggregate="SUM")], + ) + + def test_raw_mode_rejects_metrics(self) -> None: + with pytest.raises(ValueError, match="does not use 'metrics'"): + HandlebarsChartConfig( + chart_type="handlebars", + handlebars_template="

test

", + query_mode="raw", + columns=[ColumnRef(name="product")], + metrics=[ColumnRef(name="sales", aggregate="SUM")], + ) + + def test_raw_mode_rejects_groupby(self) -> None: + with pytest.raises(ValueError, match="does not use 'groupby'"): + HandlebarsChartConfig( + chart_type="handlebars", + handlebars_template="

test

", + query_mode="raw", + columns=[ColumnRef(name="product")], + groupby=[ColumnRef(name="region")], + ) + + def test_aggregate_mode_requires_aggregate_function(self) -> None: + with pytest.raises(ValueError, match="Missing aggregate for"): + HandlebarsChartConfig( + chart_type="handlebars", + handlebars_template="

test

", + query_mode="aggregate", + metrics=[ColumnRef(name="sales")], + ) + + def test_extra_fields_forbidden(self) -> None: + with pytest.raises(ValueError, match="Unknown field 'unknown_field'"): + HandlebarsChartConfig( + chart_type="handlebars", + handlebars_template="

test

", + metrics=[ColumnRef(name="sales", aggregate="SUM")], + unknown_field="bad", + ) + + def test_full_aggregate_config(self) -> None: + template = ( + "
{{#each data}}" + "{{this.region}}: {{this.total}}" + "{{/each}}
" + ) + config = HandlebarsChartConfig( + chart_type="handlebars", + handlebars_template=template, + query_mode="aggregate", + groupby=[ColumnRef(name="region")], + metrics=[ColumnRef(name="sales", aggregate="SUM", label="total")], + filters=[FilterConfig(column="status", op="=", value="active")], + row_limit=500, + order_desc=False, + style_template="div { color: blue; }", + ) + assert config.row_limit == 500 + assert config.order_desc is False + assert config.style_template == "div { color: blue; }" + assert config.filters is not None + assert len(config.filters) == 1 + assert config.groupby is not None + assert len(config.groupby) == 1 + + def test_row_limit_too_low(self) -> None: + with pytest.raises(ValueError, match="greater than or equal to 1"): + HandlebarsChartConfig( + chart_type="handlebars", + handlebars_template="

test

", + metrics=[ColumnRef(name="x", aggregate="COUNT")], + row_limit=0, + ) + + def test_row_limit_too_high(self) -> None: + with pytest.raises(ValueError, match="less than or equal to 50000"): + HandlebarsChartConfig( + chart_type="handlebars", + handlebars_template="

test

", + metrics=[ColumnRef(name="x", aggregate="COUNT")], + row_limit=50001, + ) + + +class TestMapHandlebarsConfig: + """Test map_handlebars_config function.""" + + def test_aggregate_mode_basic(self) -> None: + config = HandlebarsChartConfig( + chart_type="handlebars", + handlebars_template="

{{data}}

", + metrics=[ColumnRef(name="sales", aggregate="SUM")], + ) + result = map_handlebars_config(config) + + assert result["viz_type"] == "handlebars" + assert result["handlebars_template"] == "

{{data}}

" + assert result["query_mode"] == "aggregate" + assert result["row_limit"] == 1000 + assert result["order_desc"] is True + assert len(result["metrics"]) == 1 + assert result["metrics"][0]["aggregate"] == "SUM" + + def test_aggregate_mode_with_groupby(self) -> None: + config = HandlebarsChartConfig( + chart_type="handlebars", + handlebars_template="

{{data}}

", + groupby=[ColumnRef(name="region")], + metrics=[ColumnRef(name="sales", aggregate="SUM")], + ) + result = map_handlebars_config(config) + + assert result["groupby"] == ["region"] + + def test_raw_mode(self) -> None: + config = HandlebarsChartConfig( + chart_type="handlebars", + handlebars_template="rows
", + query_mode="raw", + columns=[ColumnRef(name="product"), ColumnRef(name="price")], + ) + result = map_handlebars_config(config) + + assert result["query_mode"] == "raw" + assert result["all_columns"] == ["product", "price"] + assert "metrics" not in result + assert "groupby" not in result + + def test_with_filters(self) -> None: + config = HandlebarsChartConfig( + chart_type="handlebars", + handlebars_template="

test

", + metrics=[ColumnRef(name="count", aggregate="COUNT")], + filters=[FilterConfig(column="status", op="=", value="active")], + ) + result = map_handlebars_config(config) + + assert "adhoc_filters" in result + assert len(result["adhoc_filters"]) == 1 + assert result["adhoc_filters"][0]["subject"] == "status" + assert result["adhoc_filters"][0]["operator"] == "==" + assert result["adhoc_filters"][0]["comparator"] == "active" + + def test_with_style_template(self) -> None: + config = HandlebarsChartConfig( + chart_type="handlebars", + handlebars_template="

test

", + metrics=[ColumnRef(name="x", aggregate="COUNT")], + style_template="p { font-size: 24px; }", + ) + result = map_handlebars_config(config) + + assert result["styleTemplate"] == "p { font-size: 24px; }" + + def test_without_style_template(self) -> None: + config = HandlebarsChartConfig( + chart_type="handlebars", + handlebars_template="

test

", + metrics=[ColumnRef(name="x", aggregate="COUNT")], + ) + result = map_handlebars_config(config) + + assert "styleTemplate" not in result + + def test_custom_row_limit_and_order(self) -> None: + config = HandlebarsChartConfig( + chart_type="handlebars", + handlebars_template="

test

", + metrics=[ColumnRef(name="x", aggregate="COUNT")], + row_limit=50, + order_desc=False, + ) + result = map_handlebars_config(config) + + assert result["row_limit"] == 50 + assert result["order_desc"] is False + + +class TestMapConfigToFormDataHandlebars: + """Test map_config_to_form_data dispatches to handlebars correctly.""" + + def test_dispatches_handlebars_config(self) -> None: + config = HandlebarsChartConfig( + chart_type="handlebars", + handlebars_template="

test

", + metrics=[ColumnRef(name="sales", aggregate="SUM")], + ) + result = map_config_to_form_data(config) + assert result["viz_type"] == "handlebars" + + +class TestGenerateChartNameHandlebars: + """Test generate_chart_name for handlebars configs.""" + + def test_raw_mode_with_columns(self) -> None: + config = HandlebarsChartConfig( + chart_type="handlebars", + handlebars_template="

test

", + query_mode="raw", + columns=[ColumnRef(name="product"), ColumnRef(name="price")], + ) + name = generate_chart_name(config) + assert "Handlebars" in name + assert "product" in name + assert "price" in name + + def test_aggregate_mode_with_metrics(self) -> None: + config = HandlebarsChartConfig( + chart_type="handlebars", + handlebars_template="

test

", + metrics=[ColumnRef(name="sales", aggregate="SUM")], + ) + name = generate_chart_name(config) + assert "Handlebars" in name + assert "sales" in name + + def test_aggregate_mode_no_groupby(self) -> None: + config = HandlebarsChartConfig( + chart_type="handlebars", + handlebars_template="

test

", + metrics=[ColumnRef(name="count", aggregate="COUNT")], + ) + name = generate_chart_name(config) + assert "Handlebars" in name + assert "count" in name + + def test_truncates_to_three_columns(self) -> None: + config = HandlebarsChartConfig( + chart_type="handlebars", + handlebars_template="

test

", + query_mode="raw", + columns=[ + ColumnRef(name="alpha"), + ColumnRef(name="bravo"), + ColumnRef(name="charlie"), + ColumnRef(name="delta"), + ], + ) + name = generate_chart_name(config) + assert "alpha" in name + assert "bravo" in name + assert "charlie" in name + assert "delta" not in name + + +class TestResolveVizType: + """Test _resolve_viz_type helper.""" + + def test_xy_line(self) -> None: + config = MagicMock(chart_type="xy", kind="line") + assert _resolve_viz_type(config) == "echarts_timeseries_line" + + def test_xy_bar(self) -> None: + config = MagicMock(chart_type="xy", kind="bar") + assert _resolve_viz_type(config) == "echarts_timeseries_bar" + + def test_xy_area(self) -> None: + config = MagicMock(chart_type="xy", kind="area") + assert _resolve_viz_type(config) == "echarts_area" + + def test_xy_scatter(self) -> None: + config = MagicMock(chart_type="xy", kind="scatter") + assert _resolve_viz_type(config) == "echarts_timeseries_scatter" + + def test_table(self) -> None: + config = MagicMock(chart_type="table", viz_type="table") + assert _resolve_viz_type(config) == "table" + + def test_ag_grid_table(self) -> None: + config = MagicMock(chart_type="table", viz_type="ag-grid-table") + assert _resolve_viz_type(config) == "ag-grid-table" + + def test_handlebars(self) -> None: + config = MagicMock(chart_type="handlebars") + assert _resolve_viz_type(config) == "handlebars" + + def test_unknown(self) -> None: + config = MagicMock(chart_type="unknown_type") + assert _resolve_viz_type(config) == "unknown" + + +class TestAnalyzeChartCapabilitiesHandlebars: + """Test analyze_chart_capabilities for handlebars config.""" + + def test_handlebars_capabilities(self) -> None: + config = HandlebarsChartConfig( + chart_type="handlebars", + handlebars_template="

test

", + metrics=[ColumnRef(name="sales", aggregate="SUM")], + ) + caps = analyze_chart_capabilities(None, config) + + assert caps.supports_interaction is False + assert caps.supports_drill_down is False + assert caps.supports_export is True + assert "url" in caps.optimal_formats + + +class TestAnalyzeChartSemanticsHandlebars: + """Test analyze_chart_semantics for handlebars config.""" + + def test_handlebars_semantics(self) -> None: + config = HandlebarsChartConfig( + chart_type="handlebars", + handlebars_template="

test

", + metrics=[ColumnRef(name="sales", aggregate="SUM")], + ) + semantics = analyze_chart_semantics(None, config) + + assert ( + "Handlebars" in semantics.primary_insight + or "template" in semantics.primary_insight + ) + assert semantics.data_story is not None + + +class TestSchemaValidatorHandlebars: + """Test SchemaValidator pre-validation for handlebars chart type.""" + + def test_handlebars_accepted_as_valid_chart_type(self) -> None: + data = { + "dataset_id": 1, + "config": { + "chart_type": "handlebars", + "handlebars_template": "

{{data}}

", + "metrics": [{"name": "sales", "aggregate": "SUM"}], + }, + } + is_valid, request, error = SchemaValidator.validate_request(data) + assert is_valid is True + assert request is not None + assert error is None + + def test_missing_handlebars_template(self) -> None: + data = { + "dataset_id": 1, + "config": { + "chart_type": "handlebars", + "metrics": [{"name": "sales", "aggregate": "SUM"}], + }, + } + is_valid, _, error = SchemaValidator.validate_request(data) + assert is_valid is False + assert error is not None + assert error.error_code == "MISSING_HANDLEBARS_TEMPLATE" + + def test_empty_handlebars_template(self) -> None: + data = { + "dataset_id": 1, + "config": { + "chart_type": "handlebars", + "handlebars_template": " ", + "metrics": [{"name": "sales", "aggregate": "SUM"}], + }, + } + is_valid, _, error = SchemaValidator.validate_request(data) + assert is_valid is False + assert error is not None + assert error.error_code == "INVALID_HANDLEBARS_TEMPLATE" + + def test_non_string_handlebars_template(self) -> None: + data = { + "dataset_id": 1, + "config": { + "chart_type": "handlebars", + "handlebars_template": 123, + "metrics": [{"name": "sales", "aggregate": "SUM"}], + }, + } + is_valid, _, error = SchemaValidator.validate_request(data) + assert is_valid is False + assert error is not None + assert error.error_code == "INVALID_HANDLEBARS_TEMPLATE" + + def test_invalid_query_mode_rejected(self) -> None: + data = { + "dataset_id": 1, + "config": { + "chart_type": "handlebars", + "handlebars_template": "

test

", + "query_mode": "invalid", + }, + } + is_valid, _, error = SchemaValidator.validate_request(data) + assert is_valid is False + assert error is not None + assert error.error_code == "INVALID_QUERY_MODE" + + def test_raw_mode_missing_columns(self) -> None: + data = { + "dataset_id": 1, + "config": { + "chart_type": "handlebars", + "handlebars_template": "

test

", + "query_mode": "raw", + }, + } + is_valid, _, error = SchemaValidator.validate_request(data) + assert is_valid is False + assert error is not None + assert error.error_code == "MISSING_RAW_COLUMNS" + + def test_aggregate_mode_missing_metrics(self) -> None: + data = { + "dataset_id": 1, + "config": { + "chart_type": "handlebars", + "handlebars_template": "

test

", + "query_mode": "aggregate", + }, + } + is_valid, _, error = SchemaValidator.validate_request(data) + assert is_valid is False + assert error is not None + assert error.error_code == "MISSING_AGGREGATE_METRICS" + + def test_default_aggregate_mode_missing_metrics(self) -> None: + data = { + "dataset_id": 1, + "config": { + "chart_type": "handlebars", + "handlebars_template": "

test

", + }, + } + is_valid, _, error = SchemaValidator.validate_request(data) + assert is_valid is False + assert error is not None + assert error.error_code == "MISSING_AGGREGATE_METRICS" + + def test_handlebars_raw_mode_valid(self) -> None: + data = { + "dataset_id": 1, + "config": { + "chart_type": "handlebars", + "handlebars_template": "

{{#each data}}{{this.name}}{{/each}}

", + "query_mode": "raw", + "columns": [{"name": "product"}, {"name": "price"}], + }, + } + is_valid, request, error = SchemaValidator.validate_request(data) + assert is_valid is True + assert request is not None + assert error is None + + def test_non_string_chart_type_rejected(self) -> None: + data = { + "dataset_id": 1, + "config": {"chart_type": ["handlebars"]}, + } + is_valid, _, error = SchemaValidator.validate_request(data) + assert is_valid is False + assert error is not None + assert error.error_code == "INVALID_CHART_TYPE" + + def test_handlebars_in_error_messages(self) -> None: + """Verify 'handlebars' appears in missing chart_type suggestions.""" + data = { + "dataset_id": 1, + "config": {}, + } + is_valid, _, error = SchemaValidator.validate_request(data) + assert is_valid is False + assert error is not None + suggestions_text = " ".join(error.suggestions or []) + assert "handlebars" in suggestions_text + + def test_invalid_chart_type_mentions_handlebars(self) -> None: + """Invalid chart_type error should mention handlebars as option.""" + data = { + "dataset_id": 1, + "config": {"chart_type": "invalid"}, + } + is_valid, _, error = SchemaValidator.validate_request(data) + assert is_valid is False + assert error is not None + assert "handlebars" in (error.details or "") diff --git a/tests/unit_tests/mcp_service/chart/test_new_chart_types.py b/tests/unit_tests/mcp_service/chart/test_new_chart_types.py index e2e37ba99e6..9469f63b39a 100644 --- a/tests/unit_tests/mcp_service/chart/test_new_chart_types.py +++ b/tests/unit_tests/mcp_service/chart/test_new_chart_types.py @@ -104,7 +104,7 @@ class TestPieChartConfigSchema: assert len(config.filters) == 1 def test_pie_config_rejects_extra_fields(self) -> None: - with pytest.raises(ValidationError): + with pytest.raises(ValidationError, match="Unknown field"): PieChartConfig( chart_type="pie", dimension=ColumnRef(name="product"), @@ -324,7 +324,7 @@ class TestPivotTableChartConfigSchema: ) def test_pivot_table_rejects_extra_fields(self) -> None: - with pytest.raises(ValidationError): + with pytest.raises(ValidationError, match="Unknown field"): PivotTableChartConfig( chart_type="pivot_table", rows=[ColumnRef(name="product")], @@ -459,10 +459,10 @@ class TestMixedTimeseriesChartConfigSchema: time_grain="P1M", y=[ColumnRef(name="revenue", aggregate="SUM")], primary_kind="area", - group_by=ColumnRef(name="region"), + group_by=[ColumnRef(name="region")], y_secondary=[ColumnRef(name="orders", aggregate="COUNT")], secondary_kind="scatter", - group_by_secondary=ColumnRef(name="channel"), + group_by_secondary=[ColumnRef(name="channel")], show_legend=False, x_axis=AxisConfig(title="Date"), y_axis=AxisConfig(title="Revenue", format="$,.2f"), @@ -473,9 +473,9 @@ class TestMixedTimeseriesChartConfigSchema: assert config.secondary_kind == "scatter" assert config.time_grain == "P1M" assert config.group_by is not None - assert config.group_by.name == "region" + assert config.group_by[0].name == "region" assert config.group_by_secondary is not None - assert config.group_by_secondary.name == "channel" + assert config.group_by_secondary[0].name == "channel" def test_mixed_timeseries_missing_y(self) -> None: with pytest.raises(ValidationError): @@ -503,7 +503,7 @@ class TestMixedTimeseriesChartConfigSchema: ) def test_mixed_timeseries_rejects_extra_fields(self) -> None: - with pytest.raises(ValidationError): + with pytest.raises(ValidationError, match="Unknown field"): MixedTimeseriesChartConfig( chart_type="mixed_timeseries", x=ColumnRef(name="date"), @@ -512,6 +512,25 @@ class TestMixedTimeseriesChartConfigSchema: unknown_field="bad", ) + def test_mixed_timeseries_default_row_limit(self) -> None: + config = MixedTimeseriesChartConfig( + chart_type="mixed_timeseries", + x=ColumnRef(name="date"), + y=[ColumnRef(name="revenue", aggregate="SUM")], + y_secondary=[ColumnRef(name="orders", aggregate="COUNT")], + ) + assert config.row_limit == 10000 + + def test_mixed_timeseries_custom_row_limit(self) -> None: + config = MixedTimeseriesChartConfig( + chart_type="mixed_timeseries", + x=ColumnRef(name="date"), + y=[ColumnRef(name="revenue", aggregate="SUM")], + y_secondary=[ColumnRef(name="orders", aggregate="COUNT")], + row_limit=500, + ) + assert config.row_limit == 500 + # ============================================================ # Mixed Timeseries Form Data Mapping Tests @@ -587,9 +606,9 @@ class TestMapMixedTimeseriesConfig: chart_type="mixed_timeseries", x=ColumnRef(name="date"), y=[ColumnRef(name="revenue", aggregate="SUM")], - group_by=ColumnRef(name="region"), + group_by=[ColumnRef(name="region")], y_secondary=[ColumnRef(name="orders", aggregate="COUNT")], - group_by_secondary=ColumnRef(name="channel"), + group_by_secondary=[ColumnRef(name="channel")], ) result = map_mixed_timeseries_config(config, dataset_id=1) @@ -604,9 +623,9 @@ class TestMapMixedTimeseriesConfig: chart_type="mixed_timeseries", x=ColumnRef(name="date"), y=[ColumnRef(name="revenue", aggregate="SUM")], - group_by=ColumnRef(name="date"), # same as x + group_by=[ColumnRef(name="date")], # same as x y_secondary=[ColumnRef(name="orders", aggregate="COUNT")], - group_by_secondary=ColumnRef(name="date"), # same as x + group_by_secondary=[ColumnRef(name="date")], # same as x ) result = map_mixed_timeseries_config(config, dataset_id=1) @@ -636,6 +655,35 @@ class TestMapMixedTimeseriesConfig: assert result["y_axis_format_secondary"] == ",d" assert result["logAxisSecondary"] is True + @patch("superset.mcp_service.chart.chart_utils.is_column_truly_temporal") + def test_mixed_form_data_row_limit(self, mock_is_temporal) -> None: + mock_is_temporal.return_value = True + + config = MixedTimeseriesChartConfig( + chart_type="mixed_timeseries", + x=ColumnRef(name="date"), + y=[ColumnRef(name="revenue", aggregate="SUM")], + y_secondary=[ColumnRef(name="orders", aggregate="COUNT")], + row_limit=300, + ) + result = map_mixed_timeseries_config(config, dataset_id=1) + + assert result["row_limit"] == 300 + + @patch("superset.mcp_service.chart.chart_utils.is_column_truly_temporal") + def test_mixed_form_data_default_row_limit(self, mock_is_temporal) -> None: + mock_is_temporal.return_value = True + + config = MixedTimeseriesChartConfig( + chart_type="mixed_timeseries", + x=ColumnRef(name="date"), + y=[ColumnRef(name="revenue", aggregate="SUM")], + y_secondary=[ColumnRef(name="orders", aggregate="COUNT")], + ) + result = map_mixed_timeseries_config(config, dataset_id=1) + + assert result["row_limit"] == 10000 + @patch("superset.mcp_service.chart.chart_utils.is_column_truly_temporal") def test_mixed_form_data_with_filters(self, mock_is_temporal) -> None: mock_is_temporal.return_value = True diff --git a/tests/unit_tests/mcp_service/chart/tool/test_generate_chart.py b/tests/unit_tests/mcp_service/chart/tool/test_generate_chart.py index f4d834c5306..2a7f1854dfb 100644 --- a/tests/unit_tests/mcp_service/chart/tool/test_generate_chart.py +++ b/tests/unit_tests/mcp_service/chart/tool/test_generate_chart.py @@ -19,9 +19,10 @@ Unit tests for MCP generate_chart tool """ -from unittest.mock import MagicMock, patch +from unittest.mock import MagicMock, Mock, patch import pytest +from sqlalchemy.orm.exc import DetachedInstanceError from superset.mcp_service.chart.schemas import ( AxisConfig, @@ -56,10 +57,11 @@ class TestGenerateChart: ) table_request = GenerateChartRequest(dataset_id="1", config=table_config) assert table_request.dataset_id == "1" - assert table_request.config.chart_type == "table" - assert len(table_request.config.columns) == 2 - assert table_request.config.columns[0].name == "region" - assert table_request.config.columns[1].aggregate == "SUM" + # config is now Dict[str, Any] in the schema; validate via dict access + assert table_request.config["chart_type"] == "table" + assert len(table_request.config["columns"]) == 2 + assert table_request.config["columns"][0]["name"] == "region" + assert table_request.config["columns"][1]["aggregate"] == "SUM" # XY chart request xy_config = XYChartConfig( @@ -73,12 +75,12 @@ class TestGenerateChart: legend=LegendConfig(show=True, position="top"), ) xy_request = GenerateChartRequest(dataset_id="2", config=xy_config) - assert xy_request.config.chart_type == "xy" - assert xy_request.config.x.name == "date" - assert xy_request.config.y[0].aggregate == "SUM" - assert xy_request.config.kind == "line" - assert xy_request.config.x_axis.title == "Date" - assert xy_request.config.legend.show is True + assert xy_request.config["chart_type"] == "xy" + assert xy_request.config["x"]["name"] == "date" + assert xy_request.config["y"][0]["aggregate"] == "SUM" + assert xy_request.config["kind"] == "line" + assert xy_request.config["x_axis"]["title"] == "Date" + assert xy_request.config["legend"]["show"] is True @pytest.mark.asyncio async def test_generate_chart_validation_error_handling(self): @@ -351,3 +353,142 @@ class TestCompileChart: assert result.success is False assert "invalid metric" in (result.error or "") + + +def _make_mock_chart(chart_id: int = 42) -> Mock: + """Create a mock chart with all attributes needed by serialize_chart_object.""" + chart = Mock() + chart.id = chart_id + chart.slice_name = "Test Chart" + chart.viz_type = "echarts_timeseries_bar" + chart.datasource_name = "test_table" + chart.datasource_type = "table" + chart.description = None + chart.certified_by = None + chart.certification_details = None + chart.cache_timeout = None + chart.changed_by = None + chart.changed_by_name = "admin" + chart.changed_on = None + chart.changed_on_humanized = "1 day ago" + chart.created_by = None + chart.created_by_name = "admin" + chart.created_on = None + chart.created_on_humanized = "2 days ago" + chart.uuid = "test-uuid-42" + chart.tags = [] + chart.owners = [] + return chart + + +class TestChartSerializationEagerLoading: + """Tests for eager loading fix in generate_chart serialization path.""" + + def test_serialize_chart_object_succeeds_with_loaded_relationships(self): + """serialize_chart_object works when tags/owners are already loaded.""" + from superset.mcp_service.chart.schemas import serialize_chart_object + + chart = _make_mock_chart() + result = serialize_chart_object(chart) + + assert result is not None + assert result.id == 42 + assert result.slice_name == "Test Chart" + assert result.tags == [] + assert result.owners == [] + + def test_serialize_chart_object_with_certification_fields(self): + """serialize_chart_object correctly serializes non-None certification values.""" + from superset.mcp_service.chart.schemas import serialize_chart_object + + chart = _make_mock_chart() + chart.certified_by = "Data Team" + chart.certification_details = "Verified Q1 2026 metrics" + + result = serialize_chart_object(chart) + + assert result is not None + assert result.certified_by == "Data Team" + assert result.certification_details == "Verified Q1 2026 metrics" + + def test_serialize_chart_object_fails_on_detached_instance(self): + """serialize_chart_object raises when accessing lazy attrs on detached + instance — this is the bug scenario that the eager-loading fix prevents.""" + from superset.mcp_service.chart.schemas import serialize_chart_object + + chart = _make_mock_chart() + # Simulate detached instance: accessing .tags raises DetachedInstanceError + type(chart).tags = property( + lambda self: (_ for _ in ()).throw( + DetachedInstanceError("Instance is not bound to a Session") + ) + ) + + with pytest.raises(DetachedInstanceError): + serialize_chart_object(chart) + + def test_generate_chart_refetches_via_dao(self): + """The serialization path re-fetches the chart via + ChartDAO.find_by_id() with query_options for owners and tags.""" + refetched_chart = _make_mock_chart() + refetched_chart.tags = [Mock(id=1, name="tag1", type="custom")] + refetched_chart.tags[0].description = "" + + mock_dao = MagicMock() + mock_dao.find_by_id.return_value = refetched_chart + + chart = ( + mock_dao.find_by_id(42, query_options=[Mock(), Mock()]) + or _make_mock_chart() + ) + + assert chart is refetched_chart + mock_dao.find_by_id.assert_called() + + def test_generate_chart_falls_back_to_original_on_dao_none(self): + """Falls back to original chart if ChartDAO.find_by_id() + returns None.""" + original_chart = _make_mock_chart() + + mock_dao = MagicMock() + mock_dao.find_by_id.return_value = None + + chart = mock_dao.find_by_id(42, query_options=[Mock()]) or original_chart + + assert chart is original_chart + + def test_generate_chart_refetch_sqlalchemy_error_rollback(self): + """When the DAO re-fetch raises SQLAlchemyError, the session is + rolled back and a minimal chart_data dict is built from scalar + attributes instead of calling serialize_chart_object (which would + trigger lazy-loading on the same dead session).""" + from sqlalchemy.exc import SQLAlchemyError + + original_chart = _make_mock_chart() + mock_dao = MagicMock() + mock_dao.find_by_id.side_effect = SQLAlchemyError("session error") + mock_session = MagicMock() + explore_url = "http://example.com/explore/?slice_id=42" + + chart_data = None + try: + mock_dao.find_by_id(42, query_options=[Mock()]) + except SQLAlchemyError: + mock_session.rollback() + chart_data = { + "id": original_chart.id, + "slice_name": original_chart.slice_name, + "viz_type": original_chart.viz_type, + "url": explore_url, + "uuid": str(original_chart.uuid) if original_chart.uuid else None, + } + + mock_session.rollback.assert_called() + # Minimal chart_data should contain scalar fields only + assert chart_data is not None + assert chart_data["id"] == original_chart.id + assert chart_data["slice_name"] == original_chart.slice_name + assert chart_data["url"] == explore_url + # No tags/owners keys — those would require relationship access + assert "tags" not in chart_data + assert "owners" not in chart_data diff --git a/tests/unit_tests/mcp_service/chart/tool/test_get_chart_sql.py b/tests/unit_tests/mcp_service/chart/tool/test_get_chart_sql.py new file mode 100644 index 00000000000..00fa65991c5 --- /dev/null +++ b/tests/unit_tests/mcp_service/chart/tool/test_get_chart_sql.py @@ -0,0 +1,592 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +""" +Unit tests for get_chart_sql MCP tool +""" + +import importlib +from unittest.mock import Mock, patch + +import pytest + +from superset.mcp_service.chart.schemas import ( + ChartError, + ChartSql, + GetChartSqlRequest, +) +from superset.mcp_service.chart.tool.get_chart_sql import ( + _build_query_context_from_form_data, + _extract_sql_from_result, + _find_chart_by_identifier, + _resolve_effective_form_data, + _resolve_groupby, + _resolve_metrics, + _resolve_metrics_and_groupby, +) + +_get_chart_sql_mod = importlib.import_module( + "superset.mcp_service.chart.tool.get_chart_sql" +) + + +class TestGetChartSqlRequestSchema: + """Tests for GetChartSqlRequest schema validation.""" + + def test_valid_request_with_identifier(self): + """Test creating request with a numeric identifier.""" + request = GetChartSqlRequest(identifier=123) + assert request.identifier == 123 + assert request.form_data_key is None + + def test_valid_request_with_uuid_identifier(self): + """Test creating request with a UUID string identifier.""" + request = GetChartSqlRequest(identifier="abc-def-123") + assert request.identifier == "abc-def-123" + assert request.form_data_key is None + + def test_valid_request_with_form_data_key_only(self): + """Test creating request with form_data_key only (unsaved chart).""" + request = GetChartSqlRequest(form_data_key="some-key") + assert request.identifier is None + assert request.form_data_key == "some-key" + + def test_valid_request_with_both(self): + """Test creating request with both identifier and form_data_key.""" + request = GetChartSqlRequest(identifier=42, form_data_key="some-key") + assert request.identifier == 42 + assert request.form_data_key == "some-key" + + def test_invalid_request_neither_provided(self): + """Test request fails when neither identifier nor key given.""" + with pytest.raises(ValueError, match="At least one of"): + GetChartSqlRequest() + + +class TestExtractSqlFromResult: + """Tests for the _extract_sql_from_result helper.""" + + def test_successful_sql_extraction(self): + """Test extracting SQL from a normal result.""" + result = { + "queries": [ + { + "query": "SELECT * FROM my_table WHERE x > 1", + "language": "sql", + } + ] + } + output = _extract_sql_from_result( + result, + chart_id=10, + chart_name="Sales Chart", + datasource_name="my_table", + ) + assert isinstance(output, ChartSql) + assert output.sql == "SELECT * FROM my_table WHERE x > 1" + assert output.language == "sql" + assert output.chart_id == 10 + assert output.chart_name == "Sales Chart" + assert output.datasource_name == "my_table" + assert output.error is None + + def test_empty_queries_returns_error(self): + """Test that empty query results return a ChartError.""" + result = {"queries": []} + output = _extract_sql_from_result( + result, chart_id=1, chart_name="Test", datasource_name="ds" + ) + assert isinstance(output, ChartError) + assert output.error_type == "EmptyQuery" + + def test_missing_queries_key_returns_error(self): + """Test that missing 'queries' key returns a ChartError.""" + result = {} + output = _extract_sql_from_result( + result, chart_id=1, chart_name="Test", datasource_name="ds" + ) + assert isinstance(output, ChartError) + assert output.error_type == "EmptyQuery" + + def test_no_sql_with_error_returns_chart_error(self): + """Test that empty sql with an error message returns ChartError.""" + result = { + "queries": [ + { + "query": "", + "language": "sql", + "error": "Unknown column 'foo'", + } + ] + } + output = _extract_sql_from_result( + result, chart_id=5, chart_name="Bad Chart", datasource_name="ds" + ) + assert isinstance(output, ChartError) + assert output.error_type == "QueryGenerationFailed" + assert "Unknown column" in output.error + + def test_sql_with_error_returns_chart_sql(self): + """Test that partial SQL with a non-fatal error returns ChartSql with error.""" + result = { + "queries": [ + { + "query": "SELECT col1 FROM tbl", + "language": "sql", + "error": "Warning: column col2 not found", + } + ] + } + output = _extract_sql_from_result( + result, chart_id=7, chart_name="Partial", datasource_name="tbl" + ) + assert isinstance(output, ChartSql) + assert output.sql == "SELECT col1 FROM tbl" + assert output.error is not None + + def test_null_chart_metadata(self): + """Test extraction when chart metadata is None.""" + result = {"queries": [{"query": "SELECT 1", "language": "sql"}]} + output = _extract_sql_from_result( + result, chart_id=None, chart_name=None, datasource_name=None + ) + assert isinstance(output, ChartSql) + assert output.chart_id is None + assert output.chart_name is None + assert output.datasource_name is None + + +class TestFindChartByIdentifier: + """Tests for the _find_chart_by_identifier helper.""" + + @patch("superset.daos.chart.ChartDAO.find_by_id") + def test_find_by_numeric_id(self, mock_find): + """Test finding chart by numeric ID.""" + mock_chart = Mock() + mock_find.return_value = mock_chart + + result = _find_chart_by_identifier(42) + + mock_find.assert_called_once_with(42) + assert result == mock_chart + + @patch("superset.daos.chart.ChartDAO.find_by_id") + def test_find_by_digit_string(self, mock_find): + """Test finding chart by digit string (treated as numeric ID).""" + mock_chart = Mock() + mock_find.return_value = mock_chart + + result = _find_chart_by_identifier("123") + + mock_find.assert_called_once_with(123) + assert result == mock_chart + + @patch("superset.daos.chart.ChartDAO.find_by_id") + def test_find_by_uuid_string(self, mock_find): + """Test finding chart by UUID string.""" + mock_chart = Mock() + mock_find.return_value = mock_chart + + result = _find_chart_by_identifier("abc-123-def") + + mock_find.assert_called_once_with("abc-123-def", id_column="uuid") + assert result == mock_chart + + @patch("superset.daos.chart.ChartDAO.find_by_id") + def test_not_found_returns_none(self, mock_find): + """Test that None is returned when chart is not found.""" + mock_find.return_value = None + + result = _find_chart_by_identifier(999) + + assert result is None + + +class TestResolveEffectiveFormData: + """Tests for the _resolve_effective_form_data helper.""" + + def test_returns_saved_params_when_no_form_data_key(self): + """Test that saved chart params are returned when no form_data_key.""" + mock_chart = Mock() + mock_chart.params = '{"metrics": ["count"], "viz_type": "table"}' + + form_data, using_unsaved = _resolve_effective_form_data( + mock_chart, form_data_key=None + ) + + assert form_data == {"metrics": ["count"], "viz_type": "table"} + assert using_unsaved is False + + def test_returns_empty_dict_when_no_params(self): + """Test that empty dict is returned when chart has no params.""" + mock_chart = Mock() + mock_chart.params = None + + form_data, using_unsaved = _resolve_effective_form_data( + mock_chart, form_data_key=None + ) + + assert form_data == {} + assert using_unsaved is False + + def test_returns_empty_dict_for_invalid_json_params(self): + """Test that invalid JSON in params returns empty dict.""" + mock_chart = Mock() + mock_chart.params = "not-valid-json" + + form_data, using_unsaved = _resolve_effective_form_data( + mock_chart, form_data_key=None + ) + + assert form_data == {} + assert using_unsaved is False + + @patch.object(_get_chart_sql_mod, "_get_cached_form_data") + def test_returns_cached_form_data_when_key_provided(self, mock_get_cached): + """Test that cached form_data is used when form_data_key is provided.""" + mock_get_cached.return_value = '{"metrics": ["sum_sales"], "unsaved": true}' + mock_chart = Mock() + mock_chart.params = '{"metrics": ["count"]}' + + form_data, using_unsaved = _resolve_effective_form_data( + mock_chart, form_data_key="test-key-123" + ) + + assert form_data == {"metrics": ["sum_sales"], "unsaved": True} + assert using_unsaved is True + + @patch.object(_get_chart_sql_mod, "_get_cached_form_data") + def test_falls_back_to_saved_when_cache_miss(self, mock_get_cached): + """Test fallback to saved params when cache returns nothing.""" + mock_get_cached.return_value = None + mock_chart = Mock() + mock_chart.params = '{"metrics": ["count"]}' + + form_data, using_unsaved = _resolve_effective_form_data( + mock_chart, form_data_key="expired-key" + ) + + assert form_data == {"metrics": ["count"]} + assert using_unsaved is False + + +class TestResolveMetricsAndGroupby: + """Tests for metric/groupby resolution helpers.""" + + def test_bubble_chart_extracts_x_y_size(self): + """Bubble charts store measures in x, y, size fields.""" + form_data = { + "viz_type": "bubble", + "x": "col_x", + "y": "col_y", + "size": "col_size", + } + metrics = _resolve_metrics(form_data, "bubble") + assert metrics == ["col_x", "col_y", "col_size"] + + def test_bubble_chart_via_resolve_metrics_and_groupby(self): + """Integration: _resolve_metrics_and_groupby handles bubble type.""" + form_data = { + "viz_type": "bubble", + "x": "metric_a", + "y": "metric_b", + "size": "metric_c", + "groupby": ["dim1"], + } + metrics, groupby = _resolve_metrics_and_groupby(form_data, chart=None) + assert metrics == ["metric_a", "metric_b", "metric_c"] + assert groupby == ["dim1"] + + def test_bubble_chart_missing_fields(self): + """Bubble charts with missing x/y/size fields produce partial metrics.""" + form_data = {"viz_type": "bubble", "x": "only_x"} + metrics = _resolve_metrics(form_data, "bubble") + assert metrics == ["only_x"] + + def test_string_groupby_normalised_to_list(self): + """A scalar string groupby must become a single-item list, not chars.""" + groupby = _resolve_groupby({"groupby": "country"}) + assert groupby == ["country"] + + def test_list_groupby_unchanged(self): + """A list groupby should pass through normally.""" + groupby = _resolve_groupby({"groupby": ["col_a", "col_b"]}) + assert groupby == ["col_a", "col_b"] + + def test_empty_groupby_falls_back_to_entity_series(self): + """Missing groupby falls back to entity/series/columns.""" + form_data = {"entity": "country", "series": "year"} + groupby = _resolve_groupby(form_data) + assert groupby == ["country", "year"] + + def test_empty_groupby_falls_back_to_columns(self): + """Missing groupby falls back to columns list.""" + form_data = {"columns": ["a", "b"]} + groupby = _resolve_groupby(form_data) + assert groupby == ["a", "b"] + + +class TestBuildQueryContextFromFormData: + """Regression tests for fallback query context construction. + + Verifies that temporal fields, adhoc_filters, and legacy filters from + form_data are passed through to QueryContextFactory (not dropped). + """ + + @patch("superset.common.query_context_factory.QueryContextFactory") + def test_temporal_fields_passed_to_factory(self, mock_factory_cls): + """time_range, granularity_sqla, adhoc_filters from form_data are + forwarded via form_data= to the factory, not dropped.""" + mock_factory = Mock() + mock_factory.create.return_value = Mock() + mock_factory_cls.return_value = mock_factory + + form_data = { + "datasource_id": 1, + "datasource_type": "table", + "metrics": ["count"], + "groupby": ["country"], + "time_range": "Last 7 days", + "granularity_sqla": "created_at", + "adhoc_filters": [ + { + "clause": "WHERE", + "expressionType": "SIMPLE", + "subject": "status", + "operator": "==", + "comparator": "active", + } + ], + "where": "region = 'US'", + "having": "count > 10", + "filters": [{"col": "city", "op": "==", "val": "NYC"}], + } + + with patch( + "superset.common.chart_data.ChartDataResultType" + ) as mock_result_type: + mock_result_type.QUERY = "QUERY" + _build_query_context_from_form_data(form_data, chart=None) + + # Verify factory.create was called with form_data containing all fields + call_kwargs = mock_factory.create.call_args[1] + assert call_kwargs["form_data"] is form_data + assert call_kwargs["form_data"]["time_range"] == "Last 7 days" + assert call_kwargs["form_data"]["granularity_sqla"] == "created_at" + assert call_kwargs["form_data"]["adhoc_filters"] is not None + assert call_kwargs["form_data"]["where"] == "region = 'US'" + assert call_kwargs["form_data"]["having"] == "count > 10" + assert call_kwargs["form_data"]["filters"] == [ + {"col": "city", "op": "==", "val": "NYC"} + ] + + @patch("superset.common.query_context_factory.QueryContextFactory") + def test_metrics_and_groupby_in_queries(self, mock_factory_cls): + """Resolved metrics and groupby are passed in queries parameter.""" + mock_factory = Mock() + mock_factory.create.return_value = Mock() + mock_factory_cls.return_value = mock_factory + + form_data = { + "datasource_id": 1, + "datasource_type": "table", + "metrics": ["sum_revenue"], + "groupby": ["product"], + } + + with patch( + "superset.common.chart_data.ChartDataResultType" + ) as mock_result_type: + mock_result_type.QUERY = "QUERY" + _build_query_context_from_form_data(form_data, chart=None) + + call_kwargs = mock_factory.create.call_args[1] + queries = call_kwargs["queries"] + assert len(queries) == 1 + assert queries[0]["metrics"] == ["sum_revenue"] + assert queries[0]["columns"] == ["product"] + + +class TestGetChartSqlTool: + """Integration-style tests for the get_chart_sql MCP tool via Client.""" + + @pytest.fixture(autouse=True) + def mock_auth(self): + """Mock authentication for all tests.""" + with patch("superset.mcp_service.auth.get_user_from_request") as mock_get_user: + mock_user = Mock() + mock_user.id = 1 + mock_user.username = "admin" + mock_get_user.return_value = mock_user + yield mock_get_user + + @pytest.fixture + def mcp_server(self): + from superset.mcp_service.app import mcp + + return mcp + + @patch.object(_get_chart_sql_mod, "validate_chart_dataset") + @patch.object(_get_chart_sql_mod, "_find_chart_by_identifier") + @pytest.mark.asyncio + async def test_chart_not_found(self, mock_find, mock_validate, mcp_server): + """Test that a not-found chart returns an error.""" + from fastmcp import Client + + mock_find.return_value = None + + async with Client(mcp_server) as client: + result = await client.call_tool( + "get_chart_sql", {"request": {"identifier": 999}} + ) + + data = result.structured_content.get("result", result.structured_content) + assert data["error_type"] == "NotFound" + assert "999" in data["error"] + + @patch.object(_get_chart_sql_mod, "_sql_from_form_data") + @patch.object(_get_chart_sql_mod, "_sql_from_saved_query_context") + @patch.object(_get_chart_sql_mod, "_resolve_effective_form_data") + @patch.object(_get_chart_sql_mod, "validate_chart_dataset") + @patch.object(_get_chart_sql_mod, "_find_chart_by_identifier") + @pytest.mark.asyncio + async def test_success_via_saved_query_context( + self, + mock_find, + mock_validate, + mock_resolve, + mock_saved_qc, + mock_form_data_sql, + mcp_server, + ): + """Test successful SQL retrieval via saved query_context.""" + from fastmcp import Client + + from superset.mcp_service.chart.chart_utils import ( + DatasetValidationResult, + ) + + mock_chart = Mock() + mock_chart.id = 10 + mock_chart.slice_name = "Sales Chart" + mock_chart.viz_type = "table" + mock_find.return_value = mock_chart + + mock_validate.return_value = DatasetValidationResult( + is_valid=True, dataset_id=1, dataset_name="ds", warnings=[] + ) + mock_resolve.return_value = ({"metrics": ["count"]}, False) + mock_saved_qc.return_value = ChartSql( + chart_id=10, + chart_name="Sales Chart", + sql="SELECT COUNT(*) FROM sales", + language="sql", + datasource_name="sales", + ) + + async with Client(mcp_server) as client: + result = await client.call_tool( + "get_chart_sql", {"request": {"identifier": 10}} + ) + + data = result.structured_content.get("result", result.structured_content) + assert "SELECT COUNT(*) FROM sales" in data["sql"] + assert data["chart_id"] == 10 + + @patch.object(_get_chart_sql_mod, "_sql_from_form_data") + @patch.object(_get_chart_sql_mod, "_sql_from_saved_query_context") + @patch.object(_get_chart_sql_mod, "_resolve_effective_form_data") + @patch.object(_get_chart_sql_mod, "validate_chart_dataset") + @patch.object(_get_chart_sql_mod, "_find_chart_by_identifier") + @pytest.mark.asyncio + async def test_fallback_to_form_data_when_saved_qc_fails( + self, + mock_find, + mock_validate, + mock_resolve, + mock_saved_qc, + mock_form_data_sql, + mcp_server, + ): + """Test fallback to form_data path when saved query_context returns None.""" + from fastmcp import Client + + from superset.mcp_service.chart.chart_utils import ( + DatasetValidationResult, + ) + + mock_chart = Mock() + mock_chart.id = 20 + mock_chart.slice_name = "Revenue" + mock_chart.viz_type = "bar" + mock_find.return_value = mock_chart + + mock_validate.return_value = DatasetValidationResult( + is_valid=True, dataset_id=2, dataset_name="revenue", warnings=[] + ) + mock_resolve.return_value = ({"metrics": ["sum_revenue"]}, False) + mock_saved_qc.return_value = None + mock_form_data_sql.return_value = ChartSql( + chart_id=20, + chart_name="Revenue", + sql="SELECT SUM(revenue) FROM orders", + language="sql", + datasource_name="orders", + ) + + async with Client(mcp_server) as client: + result = await client.call_tool( + "get_chart_sql", {"request": {"identifier": 20}} + ) + + data = result.structured_content.get("result", result.structured_content) + assert "SELECT SUM(revenue) FROM orders" in data["sql"] + mock_form_data_sql.assert_called_once() + + @patch.object(_get_chart_sql_mod, "validate_chart_dataset") + @patch.object(_get_chart_sql_mod, "_find_chart_by_identifier") + @pytest.mark.asyncio + async def test_dataset_not_accessible(self, mock_find, mock_validate, mcp_server): + """Test that inaccessible dataset returns error.""" + from fastmcp import Client + + from superset.mcp_service.chart.chart_utils import ( + DatasetValidationResult, + ) + + mock_chart = Mock() + mock_chart.id = 30 + mock_chart.slice_name = "Restricted" + mock_chart.viz_type = "table" + mock_find.return_value = mock_chart + + mock_validate.return_value = DatasetValidationResult( + is_valid=False, + dataset_id=3, + dataset_name="secret_ds", + warnings=[], + error="Access denied to dataset secret_ds", + ) + + async with Client(mcp_server) as client: + result = await client.call_tool( + "get_chart_sql", {"request": {"identifier": 30}} + ) + + data = result.structured_content.get("result", result.structured_content) + assert data["error_type"] == "DatasetNotAccessible" + assert "Access denied" in data["error"] diff --git a/tests/unit_tests/mcp_service/chart/tool/test_get_chart_type_schema.py b/tests/unit_tests/mcp_service/chart/tool/test_get_chart_type_schema.py new file mode 100644 index 00000000000..f7f2d166a5a --- /dev/null +++ b/tests/unit_tests/mcp_service/chart/tool/test_get_chart_type_schema.py @@ -0,0 +1,86 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +"""Tests for get_chart_type_schema tool logic.""" + +import pytest + +from superset.mcp_service.chart.tool.get_chart_type_schema import ( + _CHART_EXAMPLES, + _get_chart_type_schema_impl as _call_schema, + VALID_CHART_TYPES, +) + + +class TestGetChartTypeSchema: + @pytest.mark.parametrize("chart_type", VALID_CHART_TYPES) + def test_valid_chart_type_returns_schema(self, chart_type: str) -> None: + result = _call_schema(chart_type) + assert "schema" in result + assert result["chart_type"] == chart_type + assert isinstance(result["schema"], dict) + assert "properties" in result["schema"] + assert "examples" in result + + def test_xy_schema_has_expected_fields(self) -> None: + result = _call_schema("xy") + props = result["schema"]["properties"] + assert "x" in props + assert "y" in props + assert "kind" in props + + def test_table_schema_has_columns(self) -> None: + result = _call_schema("table") + props = result["schema"]["properties"] + assert "columns" in props + + def test_pie_schema_has_dimension_metric(self) -> None: + result = _call_schema("pie") + props = result["schema"]["properties"] + assert "dimension" in props + assert "metric" in props + + def test_big_number_schema_has_metric(self) -> None: + result = _call_schema("big_number") + props = result["schema"]["properties"] + assert "metric" in props + + def test_include_examples_false_omits_examples(self) -> None: + result = _call_schema("xy", include_examples=False) + assert "schema" in result + assert "examples" not in result + + def test_invalid_chart_type_returns_error(self) -> None: + result = _call_schema("nonexistent") + assert "error" in result + assert "valid_chart_types" in result + assert result["valid_chart_types"] == VALID_CHART_TYPES + + def test_examples_match_chart_type(self) -> None: + result = _call_schema("pie") + for example in result["examples"]: + assert example["chart_type"] == "pie" + + def test_valid_chart_types_constant(self) -> None: + assert len(VALID_CHART_TYPES) == 7 + assert "xy" in VALID_CHART_TYPES + assert "table" in VALID_CHART_TYPES + + def test_all_chart_types_have_examples(self) -> None: + for chart_type in VALID_CHART_TYPES: + assert chart_type in _CHART_EXAMPLES + assert len(_CHART_EXAMPLES[chart_type]) >= 1 diff --git a/tests/unit_tests/mcp_service/chart/tool/test_list_charts.py b/tests/unit_tests/mcp_service/chart/tool/test_list_charts.py index c7fa505ac93..9d4d053b8c0 100644 --- a/tests/unit_tests/mcp_service/chart/tool/test_list_charts.py +++ b/tests/unit_tests/mcp_service/chart/tool/test_list_charts.py @@ -28,6 +28,7 @@ from superset.mcp_service.chart.schemas import ( ChartFilter, ListChartsRequest, ) +from superset.mcp_service.constants import MAX_PAGE_SIZE @pytest.fixture @@ -45,6 +46,8 @@ def mock_chart(): chart.datasource_name = "test_dataset" chart.datasource_type = "table" chart.description = "Test chart" + chart.certified_by = None + chart.certification_details = None chart.url = "/chart/1" chart.changed_by_name = "admin" chart.changed_on = None @@ -133,6 +136,19 @@ class TestListChartsRequestSchema: with pytest.raises(ValueError, match="Input should be greater than 0"): ListChartsRequest(page_size=0) + def test_page_size_exceeds_max(self): + """Test that page_size over MAX_PAGE_SIZE raises validation error.""" + with pytest.raises( + ValueError, + match=f"Input should be less than or equal to {MAX_PAGE_SIZE}", + ): + ListChartsRequest(page_size=MAX_PAGE_SIZE + 1) + + def test_page_size_at_max(self): + """Test that page_size at MAX_PAGE_SIZE is accepted.""" + request = ListChartsRequest(page_size=MAX_PAGE_SIZE) + assert request.page_size == MAX_PAGE_SIZE + def test_filter_validation(self): """Test that filter validation works correctly.""" # Valid filter @@ -183,20 +199,21 @@ class TestChartDefaultColumnFiltering: """Test that minimal default columns are properly defined.""" from superset.mcp_service.common.schema_discovery import CHART_DEFAULT_COLUMNS - # Required minimal columns must be present - required_columns = { + assert set(CHART_DEFAULT_COLUMNS) == { "id", "slice_name", "viz_type", + "description", + "certified_by", + "certification_details", "url", + "changed_on", "changed_on_humanized", } - assert required_columns.issubset(set(CHART_DEFAULT_COLUMNS)) # Heavy columns should NOT be in defaults assert "form_data" not in CHART_DEFAULT_COLUMNS assert "query_context" not in CHART_DEFAULT_COLUMNS - assert "description" not in CHART_DEFAULT_COLUMNS assert "datasource_name" not in CHART_DEFAULT_COLUMNS assert "uuid" not in CHART_DEFAULT_COLUMNS diff --git a/tests/unit_tests/mcp_service/chart/tool/test_update_chart.py b/tests/unit_tests/mcp_service/chart/tool/test_update_chart.py index f21c4ee11e6..d6a49c42674 100644 --- a/tests/unit_tests/mcp_service/chart/tool/test_update_chart.py +++ b/tests/unit_tests/mcp_service/chart/tool/test_update_chart.py @@ -19,17 +19,36 @@ Unit tests for update_chart MCP tool """ -import pytest +import importlib +from unittest.mock import Mock, patch +import pytest +from fastmcp import Client + +from superset.mcp_service.app import mcp +from superset.mcp_service.chart.chart_helpers import find_chart_by_identifier +from superset.mcp_service.chart.chart_utils import DatasetValidationResult from superset.mcp_service.chart.schemas import ( AxisConfig, ColumnRef, FilterConfig, + GenerateChartResponse, LegendConfig, TableChartConfig, UpdateChartRequest, XYChartConfig, ) +from superset.mcp_service.chart.tool.update_chart import ( + _build_preview_form_data, + _build_update_payload, +) + +# The __init__.py re-exports the update_chart *function*, so a plain +# `from ... import update_chart` gives the function, not the module. +# Use importlib to get the module for patch.object(). +update_chart_module = importlib.import_module( + "superset.mcp_service.chart.tool.update_chart" +) class TestUpdateChart: @@ -50,10 +69,11 @@ class TestUpdateChart: ) table_request = UpdateChartRequest(identifier=123, config=table_config) assert table_request.identifier == 123 - assert table_request.config.chart_type == "table" - assert len(table_request.config.columns) == 2 - assert table_request.config.columns[0].name == "region" - assert table_request.config.columns[1].aggregate == "SUM" + # config is now Dict[str, Any] in the schema; validate via dict access + assert table_request.config["chart_type"] == "table" + assert len(table_request.config["columns"]) == 2 + assert table_request.config["columns"][0]["name"] == "region" + assert table_request.config["columns"][1]["aggregate"] == "SUM" # XY chart update with UUID xy_config = XYChartConfig( @@ -70,10 +90,10 @@ class TestUpdateChart: identifier="a1b2c3d4-e5f6-7890-abcd-ef1234567890", config=xy_config ) assert xy_request.identifier == "a1b2c3d4-e5f6-7890-abcd-ef1234567890" - assert xy_request.config.chart_type == "xy" - assert xy_request.config.x.name == "date" - assert xy_request.config.y[0].aggregate == "SUM" - assert xy_request.config.kind == "line" + assert xy_request.config["chart_type"] == "xy" + assert xy_request.config["x"]["name"] == "date" + assert xy_request.config["y"][0]["aggregate"] == "SUM" + assert xy_request.config["kind"] == "line" @pytest.mark.asyncio async def test_update_chart_with_chart_name(self): @@ -94,33 +114,28 @@ class TestUpdateChart: assert request2.chart_name == "Updated Sales Report" @pytest.mark.asyncio - async def test_update_chart_preview_generation(self): - """Test preview generation options in update request.""" + async def test_update_chart_preview_formats(self): + """Test preview_formats options in update request.""" config = TableChartConfig( chart_type="table", columns=[ColumnRef(name="col1")], ) - # Default preview generation + # Default preview formats request1 = UpdateChartRequest(identifier=123, config=config) - assert request1.generate_preview is True assert request1.preview_formats == ["url"] # Custom preview formats request2 = UpdateChartRequest( identifier=123, config=config, - generate_preview=True, preview_formats=["url", "ascii", "table"], ) - assert request2.generate_preview is True assert set(request2.preview_formats) == {"url", "ascii", "table"} - # Disable preview generation - request3 = UpdateChartRequest( - identifier=123, config=config, generate_preview=False - ) - assert request3.generate_preview is False + # Empty preview formats (no extra previews after save) + request3 = UpdateChartRequest(identifier=123, config=config, preview_formats=[]) + assert request3.preview_formats == [] @pytest.mark.asyncio async def test_update_chart_identifier_types(self): @@ -160,7 +175,7 @@ class TestUpdateChart: kind=chart_type, ) request = UpdateChartRequest(identifier=1, config=config) - assert request.config.kind == chart_type + assert request.config["kind"] == chart_type # Test multiple Y columns multi_y_config = XYChartConfig( @@ -174,8 +189,8 @@ class TestUpdateChart: kind="line", ) request = UpdateChartRequest(identifier=1, config=multi_y_config) - assert len(request.config.y) == 3 - assert request.config.y[1].aggregate == "AVG" + assert len(request.config["y"]) == 3 + assert request.config["y"][1]["aggregate"] == "AVG" # Test filter operators operators = ["=", "!=", ">", ">=", "<", "<="] @@ -186,7 +201,7 @@ class TestUpdateChart: filters=filters, ) request = UpdateChartRequest(identifier=1, config=table_config) - assert len(request.config.filters) == 6 + assert len(request.config["filters"]) == 6 @pytest.mark.asyncio async def test_update_chart_response_structure(self): @@ -242,12 +257,12 @@ class TestUpdateChart: ), ) request = UpdateChartRequest(identifier=1, config=config) - assert request.config.x_axis.title == "Date" - assert request.config.x_axis.format == "smart_date" - assert request.config.x_axis.scale == "linear" - assert request.config.y_axis.title == "Sales Amount" - assert request.config.y_axis.format == "$,.2f" - assert request.config.y_axis.scale == "log" + assert request.config["x_axis"]["title"] == "Date" + assert request.config["x_axis"]["format"] == "smart_date" + assert request.config["x_axis"]["scale"] == "linear" + assert request.config["y_axis"]["title"] == "Sales Amount" + assert request.config["y_axis"]["format"] == "$,.2f" + assert request.config["y_axis"]["scale"] == "log" @pytest.mark.asyncio async def test_update_chart_legend_configurations(self): @@ -261,8 +276,8 @@ class TestUpdateChart: legend=LegendConfig(show=True, position=pos), ) request = UpdateChartRequest(identifier=1, config=config) - assert request.config.legend.position == pos - assert request.config.legend.show is True + assert request.config["legend"]["position"] == pos + assert request.config["legend"]["show"] is True # Hidden legend config = XYChartConfig( @@ -272,7 +287,7 @@ class TestUpdateChart: legend=LegendConfig(show=False), ) request = UpdateChartRequest(identifier=1, config=config) - assert request.config.legend.show is False + assert request.config["legend"]["show"] is False @pytest.mark.asyncio async def test_update_chart_aggregation_functions(self): @@ -284,33 +299,48 @@ class TestUpdateChart: columns=[ColumnRef(name="value", aggregate=agg)], ) request = UpdateChartRequest(identifier=1, config=config) - assert request.config.columns[0].aggregate == agg + assert request.config["columns"][0]["aggregate"] == agg @pytest.mark.asyncio async def test_update_chart_error_responses(self): - """Test expected error response structures.""" + """Test expected error response structures use ChartGenerationError.""" # Chart not found error - error_response = { - "chart": None, - "error": "No chart found with identifier: 999", - "success": False, - "schema_version": "2.0", - "api_version": "v1", - } - assert error_response["success"] is False - assert error_response["chart"] is None - assert "chart found" in error_response["error"].lower() + error_response = GenerateChartResponse.model_validate( + { + "chart": None, + "error": { + "error_type": "NotFound", + "message": "No chart found with identifier: 999", + "details": "No chart found with identifier: 999", + }, + "success": False, + "schema_version": "2.0", + "api_version": "v1", + } + ) + assert error_response.success is False + assert error_response.chart is None + assert error_response.error is not None + assert error_response.error.error_type == "NotFound" + assert "chart found" in error_response.error.message.lower() # General update error - update_error = { - "chart": None, - "error": "Chart update failed: Permission denied", - "success": False, - "schema_version": "2.0", - "api_version": "v1", - } - assert update_error["success"] is False - assert "failed" in update_error["error"].lower() + update_error = GenerateChartResponse.model_validate( + { + "chart": None, + "error": { + "error_type": "ValueError", + "message": "Chart update failed: Permission denied", + "details": "Permission denied", + }, + "success": False, + "schema_version": "2.0", + "api_version": "v1", + } + ) + assert update_error.success is False + assert update_error.error is not None + assert "failed" in update_error.error.message.lower() @pytest.mark.asyncio async def test_chart_name_sanitization(self): @@ -353,10 +383,10 @@ class TestUpdateChart: ) request = UpdateChartRequest(identifier=1, config=config) - assert len(request.config.filters) == 3 - assert request.config.filters[0].column == "region" - assert request.config.filters[1].op == ">=" - assert request.config.filters[2].value == "2024-01-01" + assert len(request.config["filters"]) == 3 + assert request.config["filters"][0]["column"] == "region" + assert request.config["filters"][1]["op"] == ">=" + assert request.config["filters"][2]["value"] == "2024-01-01" @pytest.mark.asyncio async def test_update_chart_cache_control(self): @@ -383,3 +413,727 @@ class TestUpdateChart: assert request2.use_cache is False assert request2.force_refresh is True assert request2.cache_timeout == 300 + + +@pytest.fixture +def mcp_server(): + return mcp + + +@pytest.fixture(autouse=True) +def mock_auth(): + """Mock authentication for all tests.""" + with patch("superset.mcp_service.auth.get_user_from_request") as mock_get_user: + mock_user = Mock() + mock_user.id = 1 + mock_user.username = "admin" + mock_get_user.return_value = mock_user + yield mock_get_user + + +class TestUpdateChartDatasetAccess: + """Tests for dataset access validation in update_chart.""" + + @patch( + "superset.mcp_service.auth.check_chart_data_access", + new_callable=Mock, + ) + @patch("superset.daos.chart.ChartDAO.find_by_id", new_callable=Mock) + @patch("superset.db.session") + @pytest.mark.asyncio + async def test_update_chart_dataset_access_denied( + self, mock_db_session, mock_find_by_id, mock_validate, mcp_server + ): + """Test that update_chart returns error when dataset is inaccessible.""" + mock_chart = Mock() + mock_chart.id = 1 + mock_chart.datasource_id = 10 + mock_find_by_id.return_value = mock_chart + + mock_validate.return_value = DatasetValidationResult( + is_valid=False, + dataset_id=10, + dataset_name="restricted_dataset", + warnings=[], + error="Access denied to dataset 'restricted_dataset' (ID: 10). " + "You do not have permission to view this dataset.", + ) + + request = { + "identifier": 1, + "config": { + "chart_type": "table", + "columns": [{"name": "col1"}], + }, + } + + async with Client(mcp_server) as client: + result = await client.call_tool("update_chart", {"request": request}) + + assert result.structured_content["success"] is False + assert result.structured_content["chart"] is None + error = result.structured_content["error"] + assert error["error_type"] == "DatasetNotAccessible" + assert "Access denied" in error["message"] + + @patch( + "superset.mcp_service.auth.check_chart_data_access", + new_callable=Mock, + ) + @patch("superset.daos.chart.ChartDAO.find_by_id", new_callable=Mock) + @patch("superset.db.session") + @pytest.mark.asyncio + async def test_update_chart_dataset_not_found( + self, mock_db_session, mock_find_by_id, mock_validate, mcp_server + ): + """Test that update_chart returns error when dataset is deleted.""" + mock_chart = Mock() + mock_chart.id = 1 + mock_chart.datasource_id = 99 + mock_find_by_id.return_value = mock_chart + + mock_validate.return_value = DatasetValidationResult( + is_valid=False, + dataset_id=99, + dataset_name=None, + warnings=[], + error="Dataset (ID: 99) has been deleted or does not exist.", + ) + + request = { + "identifier": 1, + "config": { + "chart_type": "table", + "columns": [{"name": "col1"}], + }, + } + + async with Client(mcp_server) as client: + result = await client.call_tool("update_chart", {"request": request}) + + assert result.structured_content["success"] is False + assert result.structured_content["chart"] is None + error = result.structured_content["error"] + assert error["error_type"] == "DatasetNotAccessible" + assert "deleted" in error["message"] + + +class TestFindChart: + """Tests for find_chart_by_identifier helper (moved to chart_helpers).""" + + @patch("superset.daos.chart.ChartDAO.find_by_id") + def test_find_chart_by_numeric_id(self, mock_find): + """Numeric int identifier calls find_by_id with int.""" + mock_chart = Mock() + mock_find.return_value = mock_chart + + result = find_chart_by_identifier(42) + + mock_find.assert_called_once_with(42) + assert result is mock_chart + + @patch("superset.daos.chart.ChartDAO.find_by_id") + def test_find_chart_by_numeric_string(self, mock_find): + """String-digit identifier is converted to int.""" + mock_chart = Mock() + mock_find.return_value = mock_chart + + result = find_chart_by_identifier("123") + + mock_find.assert_called_once_with(123) + assert result is mock_chart + + @patch("superset.daos.chart.ChartDAO.find_by_id") + def test_find_chart_by_uuid(self, mock_find): + """Non-digit string identifier looks up by uuid column.""" + mock_chart = Mock() + mock_find.return_value = mock_chart + + uuid = "a1b2c3d4-e5f6-7890-abcd-ef1234567890" + result = find_chart_by_identifier(uuid) + + mock_find.assert_called_once_with(uuid, id_column="uuid") + assert result is mock_chart + + @patch("superset.daos.chart.ChartDAO.find_by_id") + def test_find_chart_returns_none(self, mock_find): + """Returns None when chart is not found.""" + mock_find.return_value = None + + result = find_chart_by_identifier(999) + + assert result is None + + +class TestBuildUpdatePayload: + """Tests for _build_update_payload helper.""" + + def test_name_only_update(self): + """Name-only update returns a dict with just slice_name.""" + request = UpdateChartRequest( + identifier=1, + chart_name="New Name", + ) + chart = Mock() + + result = _build_update_payload(request, chart) + + assert isinstance(result, dict) + assert result == {"slice_name": "New Name"} + + def test_error_when_no_config_and_no_name(self): + """Returns GenerateChartResponse error when neither config nor chart_name.""" + request = UpdateChartRequest(identifier=1) + chart = Mock() + + result = _build_update_payload(request, chart) + + assert isinstance(result, GenerateChartResponse) + assert result.success is False + assert result.error is not None + assert result.error.error_type == "ValidationError" + assert "config" in result.error.message.lower() + assert "chart_name" in result.error.message.lower() + + def test_config_update_uses_request_chart_name(self): + """When config and chart_name are both provided, uses chart_name.""" + config = TableChartConfig( + chart_type="table", + columns=[ColumnRef(name="col1")], + ) + request = UpdateChartRequest( + identifier=1, + config=config, + chart_name="My Custom Name", + ) + chart = Mock() + chart.datasource_id = None # Avoid dataset lookup + + result = _build_update_payload(request, chart) + + assert isinstance(result, dict) + assert result["slice_name"] == "My Custom Name" + assert "viz_type" in result + assert "params" in result + # query_context must be cleared so get_chart_data uses updated params + assert result["query_context"] is None + + def test_config_update_keeps_existing_name(self): + """When config is provided but no chart_name, keeps existing slice_name.""" + config = TableChartConfig( + chart_type="table", + columns=[ColumnRef(name="col1")], + ) + request = UpdateChartRequest(identifier=1, config=config) + chart = Mock() + chart.datasource_id = None + chart.slice_name = "Existing Name" + + result = _build_update_payload(request, chart) + + assert isinstance(result, dict) + assert result["slice_name"] == "Existing Name" + # query_context must be cleared so get_chart_data uses updated params + assert result["query_context"] is None + + +class TestUpdateChartNameOnly: + """Integration-style tests for name-only update via MCP tool.""" + + @patch( + "superset.mcp_service.auth.check_chart_data_access", + new_callable=Mock, + ) + @patch( + "superset.commands.chart.update.UpdateChartCommand", + new_callable=Mock, + ) + @patch("superset.daos.chart.ChartDAO.find_by_id", new_callable=Mock) + @patch("superset.db.session") + @pytest.mark.asyncio + async def test_name_only_update_success( + self, + mock_db_session, + mock_find_by_id, + mock_update_cmd_cls, + mock_check_access, + mcp_server, + ): + """Successful name-only update (identifier + chart_name, no config).""" + mock_chart = Mock() + mock_chart.id = 1 + mock_chart.datasource_id = 10 + mock_chart.slice_name = "Old Name" + mock_chart.viz_type = "table" + mock_chart.uuid = "abc-123" + mock_find_by_id.return_value = mock_chart + + mock_check_access.return_value = DatasetValidationResult( + is_valid=True, + dataset_id=10, + dataset_name="my_dataset", + warnings=[], + ) + + updated_chart = Mock() + updated_chart.id = 1 + updated_chart.slice_name = "Renamed Chart" + updated_chart.viz_type = "table" + updated_chart.uuid = "abc-123" + mock_update_cmd_cls.return_value.run.return_value = updated_chart + + request = { + "identifier": 1, + "chart_name": "Renamed Chart", + "generate_preview": False, + } + + async with Client(mcp) as client: + result = await client.call_tool("update_chart", {"request": request}) + + assert result.structured_content["success"] is True + assert result.structured_content["chart"]["slice_name"] == "Renamed Chart" + assert result.structured_content["chart"]["is_unsaved_state"] is False + # Rename-only: form_data should be empty (visualization unchanged) + assert result.structured_content["form_data"] == {} + + # Verify UpdateChartCommand was called with name-only payload + mock_update_cmd_cls.assert_called_once_with( + 1, {"slice_name": "Renamed Chart"} + ) + + @patch("superset.daos.chart.ChartDAO.find_by_id", new_callable=Mock) + @patch("superset.db.session") + @pytest.mark.asyncio + async def test_no_config_no_name_returns_error( + self, + mock_db_session, + mock_find_by_id, + mcp_server, + ): + """Error when neither config nor chart_name is provided.""" + mock_chart = Mock() + mock_chart.id = 1 + mock_chart.datasource_id = 10 + mock_find_by_id.return_value = mock_chart + + with patch( + "superset.mcp_service.auth.check_chart_data_access", + new_callable=Mock, + ) as mock_check_access: + mock_check_access.return_value = DatasetValidationResult( + is_valid=True, + dataset_id=10, + dataset_name="my_dataset", + warnings=[], + ) + + request = { + "identifier": 1, + } + + async with Client(mcp) as client: + result = await client.call_tool("update_chart", {"request": request}) + + assert result.structured_content["success"] is False + error = result.structured_content["error"] + assert error["error_type"] == "ValidationError" + assert "config" in error["message"].lower() + assert "chart_name" in error["message"].lower() + + +class TestUpdateChartPreviewFirst: + """Integration-style tests for the preview-first default flow.""" + + @patch.object(update_chart_module, "_create_preview_url", new_callable=Mock) + @patch( + "superset.commands.chart.update.UpdateChartCommand", + new_callable=Mock, + ) + @patch( + "superset.mcp_service.auth.check_chart_data_access", + new_callable=Mock, + ) + @patch("superset.daos.chart.ChartDAO.find_by_id", new_callable=Mock) + @patch("superset.db.session") + @pytest.mark.asyncio + async def test_default_generates_preview_without_saving( + self, + mock_db_session, + mock_find_by_id, + mock_check_access, + mock_update_cmd_cls, + mock_create_preview, + mcp_server, + ): + """Default update flow returns a preview URL and does NOT save.""" + mock_chart = Mock() + mock_chart.id = 1 + mock_chart.datasource_id = 10 + mock_chart.slice_name = "Existing Chart" + mock_chart.viz_type = "table" + mock_chart.uuid = "abc-123" + mock_chart.params = '{"viz_type": "table", "datasource": "10__table"}' + mock_find_by_id.return_value = mock_chart + + mock_check_access.return_value = DatasetValidationResult( + is_valid=True, + dataset_id=10, + dataset_name="my_dataset", + warnings=[], + ) + + preview_url = ( + "http://localhost:8088/explore/?form_data_key=preview_key&slice_id=1" + ) + mock_create_preview.return_value = (preview_url, "preview_key", []) + + request = { + "identifier": 1, + "config": { + "chart_type": "table", + "columns": [{"name": "col1"}], + }, + } + + async with Client(mcp) as client: + result = await client.call_tool("update_chart", {"request": request}) + + assert result.structured_content["success"] is True + assert result.structured_content["chart"]["is_unsaved_state"] is True + assert result.structured_content["chart"]["id"] == 1 + assert result.structured_content["chart"]["form_data_key"] == "preview_key" + assert result.structured_content["explore_url"] == preview_url + assert result.structured_content["form_data_key"] == "preview_key" + + # Ensure the chart was NOT persisted + mock_update_cmd_cls.assert_not_called() + mock_create_preview.assert_called_once() + + @patch.object(update_chart_module, "_create_preview_url", new_callable=Mock) + @patch( + "superset.mcp_service.auth.check_chart_data_access", + new_callable=Mock, + ) + @patch("superset.daos.chart.ChartDAO.find_by_id", new_callable=Mock) + @patch("superset.db.session") + @pytest.mark.asyncio + async def test_preview_missing_config_and_name_returns_error( + self, + mock_db_session, + mock_find_by_id, + mock_check_access, + mock_create_preview, + mcp_server, + ): + """Preview flow also errors when neither config nor chart_name given.""" + mock_chart = Mock() + mock_chart.id = 1 + mock_chart.datasource_id = 10 + mock_chart.params = "{}" + mock_find_by_id.return_value = mock_chart + + mock_check_access.return_value = DatasetValidationResult( + is_valid=True, + dataset_id=10, + dataset_name="my_dataset", + warnings=[], + ) + + async with Client(mcp) as client: + result = await client.call_tool( + "update_chart", {"request": {"identifier": 1}} + ) + + assert result.structured_content["success"] is False + error = result.structured_content["error"] + assert error["error_type"] == "ValidationError" + mock_create_preview.assert_not_called() + + +class TestBuildPreviewFormData: + """Unit tests for _build_preview_form_data helper.""" + + def test_merges_existing_params_with_new_config(self): + """New config values override existing form_data keys.""" + config = TableChartConfig( + chart_type="table", + columns=[ColumnRef(name="region")], + ) + request = UpdateChartRequest(identifier=1, config=config) + chart = Mock() + chart.id = 42 + chart.datasource_id = 7 + chart.slice_name = "Existing" + chart.params = '{"viz_type": "line", "custom_flag": true}' + + result = _build_preview_form_data(request, chart) + + assert isinstance(result, dict) + # Existing keys not touched by the new config are preserved + assert result["custom_flag"] is True + # New config overrides existing keys + assert result["viz_type"] == "table" + # slice_id and datasource are always stamped onto the preview + assert result["slice_id"] == 42 + assert result["datasource"] == "7__table" + assert result["slice_name"] == "Existing" + + def test_name_only_preview_keeps_existing_form_data(self): + """Name-only preview preserves existing form_data and renames.""" + request = UpdateChartRequest(identifier=1, chart_name="Brand New Name") + chart = Mock() + chart.id = 5 + chart.datasource_id = 3 + chart.slice_name = "Old" + chart.params = '{"viz_type": "big_number", "metric": "count"}' + + result = _build_preview_form_data(request, chart) + + assert isinstance(result, dict) + assert result["viz_type"] == "big_number" + assert result["metric"] == "count" + assert result["slice_name"] == "Brand New Name" + assert result["slice_id"] == 5 + + def test_missing_config_and_name_returns_validation_error(self): + """Matches the _build_update_payload validation behavior.""" + request = UpdateChartRequest(identifier=1) + chart = Mock() + chart.id = 1 + chart.datasource_id = 10 + chart.params = "{}" + + result = _build_preview_form_data(request, chart) + + assert isinstance(result, GenerateChartResponse) + assert result.success is False + assert result.error is not None + assert result.error.error_type == "ValidationError" + + def test_handles_invalid_existing_params(self): + """Gracefully recovers when chart.params is not valid JSON.""" + config = TableChartConfig( + chart_type="table", + columns=[ColumnRef(name="col1")], + ) + request = UpdateChartRequest(identifier=1, config=config) + chart = Mock() + chart.id = 9 + chart.datasource_id = 4 + chart.slice_name = "Broken" + chart.params = "not-json" + + result = _build_preview_form_data(request, chart) + + assert isinstance(result, dict) + assert result["slice_id"] == 9 + assert result["slice_name"] == "Broken" + + +class TestUpdateChartSaveWithConfig: + """Save-path integration tests for update_chart with a full config payload.""" + + @patch( + "superset.commands.chart.update.UpdateChartCommand", + new_callable=Mock, + ) + @patch( + "superset.mcp_service.auth.check_chart_data_access", + new_callable=Mock, + ) + @patch("superset.daos.chart.ChartDAO.find_by_id", new_callable=Mock) + @patch("superset.db.session") + @pytest.mark.asyncio + async def test_save_chart_with_config_success( + self, + mock_db_session, + mock_find_by_id, + mock_check_access, + mock_update_cmd_cls, + mcp_server, + ): + """generate_preview=False with config persists and returns saved chart.""" + mock_chart = Mock() + mock_chart.id = 77 + mock_chart.datasource_id = 10 + mock_chart.slice_name = "Pre-save" + mock_chart.viz_type = "table" + mock_chart.uuid = "uuid-77" + mock_chart.params = '{"viz_type": "table"}' + mock_find_by_id.return_value = mock_chart + + mock_check_access.return_value = DatasetValidationResult( + is_valid=True, + dataset_id=10, + dataset_name="my_dataset", + warnings=[], + ) + + updated_chart = Mock() + updated_chart.id = 77 + updated_chart.slice_name = "After-save" + updated_chart.viz_type = "table" + updated_chart.uuid = "uuid-77" + mock_update_cmd_cls.return_value.run.return_value = updated_chart + + request = { + "identifier": 77, + "generate_preview": False, + "config": { + "chart_type": "table", + "columns": [{"name": "col1"}], + }, + } + + async with Client(mcp) as client: + result = await client.call_tool("update_chart", {"request": request}) + + assert result.structured_content["success"] is True + chart = result.structured_content["chart"] + assert chart["is_unsaved_state"] is False + assert chart["id"] == 77 + assert chart["slice_name"] == "After-save" + assert "slice_id=77" in result.structured_content["explore_url"] + mock_update_cmd_cls.assert_called_once() + + # Verify query_context is cleared so get_chart_data uses updated params + payload = mock_update_cmd_cls.call_args[0][1] + assert payload["query_context"] is None + + # Verify form_data is returned in the response + form_data = result.structured_content["form_data"] + assert isinstance(form_data, dict) + assert "viz_type" in form_data + + +class TestUpdateChartErrorPaths: + """Integration tests for error-handling branches in update_chart.""" + + @patch("superset.daos.chart.ChartDAO.find_by_id", new_callable=Mock) + @patch("superset.db.session") + @pytest.mark.asyncio + async def test_chart_not_found_returns_notfound_error( + self, + mock_db_session, + mock_find_by_id, + mcp_server, + ): + """Missing chart returns a structured NotFound error without raising.""" + mock_find_by_id.return_value = None + + async with Client(mcp) as client: + result = await client.call_tool( + "update_chart", {"request": {"identifier": 9999}} + ) + + assert result.structured_content["success"] is False + error = result.structured_content["error"] + assert error["error_type"] == "NotFound" + assert "9999" in error["message"] + + @patch( + "superset.commands.chart.update.UpdateChartCommand", + new_callable=Mock, + ) + @patch( + "superset.mcp_service.auth.check_chart_data_access", + new_callable=Mock, + ) + @patch("superset.daos.chart.ChartDAO.find_by_id", new_callable=Mock) + @patch("superset.db.session") + @pytest.mark.asyncio + async def test_update_command_exception_is_caught( + self, + mock_db_session, + mock_find_by_id, + mock_check_access, + mock_update_cmd_cls, + mcp_server, + ): + """CommandException from UpdateChartCommand.run() is captured and returned.""" + from superset.commands.exceptions import CommandException + + mock_chart = Mock() + mock_chart.id = 5 + mock_chart.datasource_id = 10 + mock_chart.slice_name = "Name" + mock_chart.viz_type = "table" + mock_chart.uuid = "uuid-5" + mock_chart.params = "{}" + mock_find_by_id.return_value = mock_chart + + mock_check_access.return_value = DatasetValidationResult( + is_valid=True, + dataset_id=10, + dataset_name="my_dataset", + warnings=[], + ) + + mock_update_cmd_cls.return_value.run.side_effect = CommandException("boom") + + request = { + "identifier": 5, + "generate_preview": False, + "chart_name": "Retry", + } + + async with Client(mcp) as client: + result = await client.call_tool("update_chart", {"request": request}) + + assert result.structured_content["success"] is False + error = result.structured_content["error"] + assert error["error_type"] == "CommandException" + assert "boom" in error["details"] + + @patch.object(update_chart_module, "_create_preview_url", new_callable=Mock) + @patch( + "superset.mcp_service.auth.check_chart_data_access", + new_callable=Mock, + ) + @patch("superset.daos.chart.ChartDAO.find_by_id", new_callable=Mock) + @patch("superset.db.session") + @pytest.mark.asyncio + async def test_preview_extracts_form_data_key_from_url_fallback( + self, + mock_db_session, + mock_find_by_id, + mock_check_access, + mock_create_preview, + mcp_server, + ): + """If _create_preview_url returns (url, None), form_data_key comes from url.""" + mock_chart = Mock() + mock_chart.id = 8 + mock_chart.datasource_id = 10 + mock_chart.slice_name = "Chart" + mock_chart.viz_type = "table" + mock_chart.uuid = "uuid-8" + mock_chart.params = '{"viz_type": "table"}' + mock_find_by_id.return_value = mock_chart + + mock_check_access.return_value = DatasetValidationResult( + is_valid=True, + dataset_id=10, + dataset_name="my_dataset", + warnings=[], + ) + + preview_url = ( + "http://localhost:8088/explore/?form_data_key=url_embedded_key&slice_id=8" + ) + mock_create_preview.return_value = (preview_url, None, []) + + request = { + "identifier": 8, + "config": { + "chart_type": "table", + "columns": [{"name": "col1"}], + }, + } + + async with Client(mcp) as client: + result = await client.call_tool("update_chart", {"request": request}) + + assert result.structured_content["success"] is True + assert result.structured_content["form_data_key"] == "url_embedded_key" diff --git a/tests/unit_tests/mcp_service/chart/tool/test_update_chart_preview.py b/tests/unit_tests/mcp_service/chart/tool/test_update_chart_preview.py index f51a1e80afe..c80f2797a47 100644 --- a/tests/unit_tests/mcp_service/chart/tool/test_update_chart_preview.py +++ b/tests/unit_tests/mcp_service/chart/tool/test_update_chart_preview.py @@ -53,9 +53,9 @@ class TestUpdateChartPreview: ) assert table_request.form_data_key == "abc123def456" assert table_request.dataset_id == 1 - assert table_request.config.chart_type == "table" - assert len(table_request.config.columns) == 2 - assert table_request.config.columns[0].name == "region" + assert table_request.config["chart_type"] == "table" + assert len(table_request.config["columns"]) == 2 + assert table_request.config["columns"][0]["name"] == "region" # XY chart preview update xy_config = XYChartConfig( @@ -73,9 +73,9 @@ class TestUpdateChartPreview: ) assert xy_request.form_data_key == "xyz789ghi012" assert xy_request.dataset_id == "2" - assert xy_request.config.chart_type == "xy" - assert xy_request.config.x.name == "date" - assert xy_request.config.kind == "line" + assert xy_request.config["chart_type"] == "xy" + assert xy_request.config["x"]["name"] == "date" + assert xy_request.config["kind"] == "line" @pytest.mark.asyncio async def test_update_chart_preview_dataset_id_types(self): @@ -158,7 +158,7 @@ class TestUpdateChartPreview: request = UpdateChartPreviewRequest( form_data_key="abc123", dataset_id=1, config=config ) - assert request.config.kind == chart_type + assert request.config["kind"] == chart_type # Test multiple Y columns multi_y_config = XYChartConfig( @@ -174,8 +174,8 @@ class TestUpdateChartPreview: request = UpdateChartPreviewRequest( form_data_key="abc123", dataset_id=1, config=multi_y_config ) - assert len(request.config.y) == 3 - assert request.config.y[1].aggregate == "AVG" + assert len(request.config["y"]) == 3 + assert request.config["y"][1]["aggregate"] == "AVG" # Test filter operators operators = ["=", "!=", ">", ">=", "<", "<="] @@ -188,7 +188,7 @@ class TestUpdateChartPreview: request = UpdateChartPreviewRequest( form_data_key="abc123", dataset_id=1, config=table_config ) - assert len(request.config.filters) == 6 + assert len(request.config["filters"]) == 6 @pytest.mark.asyncio async def test_update_chart_preview_response_structure(self): @@ -251,10 +251,10 @@ class TestUpdateChartPreview: request = UpdateChartPreviewRequest( form_data_key="abc123", dataset_id=1, config=config ) - assert request.config.x_axis.title == "Date" - assert request.config.x_axis.format == "smart_date" - assert request.config.y_axis.title == "Sales Amount" - assert request.config.y_axis.format == "$,.2f" + assert request.config["x_axis"]["title"] == "Date" + assert request.config["x_axis"]["format"] == "smart_date" + assert request.config["y_axis"]["title"] == "Sales Amount" + assert request.config["y_axis"]["format"] == "$,.2f" @pytest.mark.asyncio async def test_update_chart_preview_legend_configurations(self): @@ -270,8 +270,8 @@ class TestUpdateChartPreview: request = UpdateChartPreviewRequest( form_data_key="abc123", dataset_id=1, config=config ) - assert request.config.legend.position == pos - assert request.config.legend.show is True + assert request.config["legend"]["position"] == pos + assert request.config["legend"]["show"] is True # Hidden legend config = XYChartConfig( @@ -283,7 +283,7 @@ class TestUpdateChartPreview: request = UpdateChartPreviewRequest( form_data_key="abc123", dataset_id=1, config=config ) - assert request.config.legend.show is False + assert request.config["legend"]["show"] is False @pytest.mark.asyncio async def test_update_chart_preview_aggregation_functions(self): @@ -297,7 +297,7 @@ class TestUpdateChartPreview: request = UpdateChartPreviewRequest( form_data_key="abc123", dataset_id=1, config=config ) - assert request.config.columns[0].aggregate == agg + assert request.config["columns"][0]["aggregate"] == agg @pytest.mark.asyncio async def test_update_chart_preview_error_responses(self): @@ -347,10 +347,10 @@ class TestUpdateChartPreview: request = UpdateChartPreviewRequest( form_data_key="abc123", dataset_id=1, config=config ) - assert len(request.config.filters) == 3 - assert request.config.filters[0].column == "region" - assert request.config.filters[1].op == ">=" - assert request.config.filters[2].value == "2024-01-01" + assert len(request.config["filters"]) == 3 + assert request.config["filters"][0]["column"] == "region" + assert request.config["filters"][1]["op"] == ">=" + assert request.config["filters"][2]["value"] == "2024-01-01" @pytest.mark.asyncio async def test_update_chart_preview_form_data_key_handling(self): @@ -447,12 +447,12 @@ class TestUpdateChartPreview: request = UpdateChartPreviewRequest( form_data_key="abc123", dataset_id=1, config=config ) - assert len(request.config.y) == 4 - assert request.config.y[0].name == "revenue" - assert request.config.y[1].name == "cost" - assert request.config.y[2].name == "profit" - assert request.config.y[3].name == "orders" - assert request.config.y[3].aggregate == "COUNT" + assert len(request.config["y"]) == 4 + assert request.config["y"][0]["name"] == "revenue" + assert request.config["y"][1]["name"] == "cost" + assert request.config["y"][2]["name"] == "profit" + assert request.config["y"][3]["name"] == "orders" + assert request.config["y"][3]["aggregate"] == "COUNT" @pytest.mark.asyncio async def test_update_chart_preview_table_sorting(self): @@ -470,5 +470,5 @@ class TestUpdateChartPreview: request = UpdateChartPreviewRequest( form_data_key="abc123", dataset_id=1, config=config ) - assert request.config.sort_by == ["sales", "profit"] - assert len(request.config.columns) == 3 + assert request.config["sort_by"] == ["sales", "profit"] + assert len(request.config["columns"]) == 3 diff --git a/tests/unit_tests/mcp_service/chart/validation/test_column_name_normalization.py b/tests/unit_tests/mcp_service/chart/validation/test_column_name_normalization.py index 77fdf64143f..ab663ebc811 100644 --- a/tests/unit_tests/mcp_service/chart/validation/test_column_name_normalization.py +++ b/tests/unit_tests/mcp_service/chart/validation/test_column_name_normalization.py @@ -163,12 +163,12 @@ class TestNormalizeXYConfig: "x": {"name": "OrderDate"}, "y": [{"name": "Sales", "aggregate": "SUM"}], "kind": "line", - "group_by": {"name": "productline"}, + "group_by": [{"name": "productline"}], } DatasetValidator._normalize_xy_config(config_dict, mock_dataset_context) - assert config_dict["group_by"]["name"] == "ProductLine" + assert config_dict["group_by"][0]["name"] == "ProductLine" class TestNormalizeTableConfig: @@ -397,7 +397,7 @@ class TestNormalizeUppercaseDataset: assert normalized.x.name == "ds" assert normalized.y[0].name == "DISTANCE" assert normalized.group_by is not None - assert normalized.group_by.name == "AIRLINE" + assert normalized.group_by[0].name == "AIRLINE" assert normalized.filters is not None assert normalized.filters[0].column == "AIRLINE" @@ -531,7 +531,7 @@ class TestNormalizeEdgeCases: assert normalized.y[0].name == "Sales" assert normalized.y[1].name == "quantity_ordered" assert normalized.group_by is not None - assert normalized.group_by.name == "ProductLine" + assert normalized.group_by[0].name == "ProductLine" assert normalized.filters is not None assert normalized.filters[0].column == "ProductLine" assert normalized.filters[1].column == "OrderDate" @@ -678,4 +678,54 @@ class TestNormalizeXAxisFilterConsistency: assert normalized.group_by is not None assert normalized.filters is not None - assert normalized.group_by.name == normalized.filters[0].column == "AIRLINE" + assert normalized.group_by[0].name == normalized.filters[0].column == "AIRLINE" + + +class TestValidateSavedMetrics: + """Test that saved_metric refs are validated against dataset metrics.""" + + def test_valid_saved_metric_passes( + self, mock_dataset_context: DatasetContext + ) -> None: + config = XYChartConfig( + chart_type="xy", + x=ColumnRef(name="OrderDate"), + y=[ColumnRef(name="TotalRevenue", saved_metric=True)], + ) + is_valid, error = DatasetValidator.validate_against_dataset( + config, dataset_id=18, dataset_context=mock_dataset_context + ) + assert is_valid + assert error is None + + def test_column_name_as_saved_metric_fails( + self, mock_dataset_context: DatasetContext + ) -> None: + """A regular column marked as saved_metric should be rejected.""" + config = XYChartConfig( + chart_type="xy", + x=ColumnRef(name="OrderDate"), + y=[ColumnRef(name="Sales", saved_metric=True)], + ) + is_valid, error = DatasetValidator.validate_against_dataset( + config, dataset_id=18, dataset_context=mock_dataset_context + ) + assert not is_valid + assert error is not None + assert error.error_code == "INVALID_SAVED_METRIC" + + def test_nonexistent_saved_metric_fails( + self, mock_dataset_context: DatasetContext + ) -> None: + """A nonexistent saved metric should produce a specific error.""" + config = XYChartConfig( + chart_type="xy", + x=ColumnRef(name="OrderDate"), + y=[ColumnRef(name="nonexistent_metric", saved_metric=True)], + ) + is_valid, error = DatasetValidator.validate_against_dataset( + config, dataset_id=18, dataset_context=mock_dataset_context + ) + assert not is_valid + assert error is not None + assert error.error_code == "INVALID_SAVED_METRIC" diff --git a/tests/unit_tests/mcp_service/dashboard/test_dashboard_schemas.py b/tests/unit_tests/mcp_service/dashboard/test_dashboard_schemas.py new file mode 100644 index 00000000000..98fe464a24f --- /dev/null +++ b/tests/unit_tests/mcp_service/dashboard/test_dashboard_schemas.py @@ -0,0 +1,341 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +""" +Unit tests for dashboard schema serialization. + +Tests that serialize_dashboard_object correctly handles slug and other fields. +""" + +from typing import Any +from unittest.mock import MagicMock, patch + +from superset.mcp_service.dashboard.schemas import ( + _extract_cross_filters_enabled, + _extract_native_filters, + serialize_dashboard_object, +) +from superset.utils.json import dumps as json_dumps + + +def _mock_dashboard( + id: int = 1, + title: str = "Test Dashboard", + slug: str | None = None, + owners: list[Any] | None = None, + slices: list[Any] | None = None, + tags: list[Any] | None = None, + roles: list[Any] | None = None, +) -> MagicMock: + """Create a mock Dashboard ORM object.""" + dashboard = MagicMock() + dashboard.id = id + dashboard.dashboard_title = title + dashboard.slug = slug + dashboard.published = True + dashboard.changed_by_name = "admin" + dashboard.changed_on = None + dashboard.changed_on_humanized = "2 hours ago" + dashboard.created_by_name = "admin" + dashboard.created_on = None + dashboard.created_on_humanized = "1 day ago" + dashboard.description = "A test dashboard" + dashboard.css = None + dashboard.certified_by = None + dashboard.certification_details = None + dashboard.json_metadata = None + dashboard.position_json = None + dashboard.is_managed_externally = False + dashboard.external_url = None + dashboard.uuid = None + dashboard.owners = owners or [] + dashboard.slices = slices or [] + dashboard.tags = tags or [] + dashboard.roles = roles or [] + return dashboard + + +class TestSerializeDashboardObject: + """Tests for serialize_dashboard_object slug handling.""" + + @patch("superset.mcp_service.utils.url_utils.get_superset_base_url") + def test_slug_none_returns_empty_string(self, mock_base_url): + """Dashboards with slug=None should return slug="" for consistency + with dashboard_serializer.""" + mock_base_url.return_value = "http://localhost:8088" + + dashboard = _mock_dashboard(id=1, slug=None) + result = serialize_dashboard_object(dashboard) + + assert result.slug == "" + + @patch("superset.mcp_service.utils.url_utils.get_superset_base_url") + def test_slug_empty_string_returns_empty_string(self, mock_base_url): + """Dashboards with slug="" should return slug="".""" + mock_base_url.return_value = "http://localhost:8088" + + dashboard = _mock_dashboard(id=2, slug="") + result = serialize_dashboard_object(dashboard) + + assert result.slug == "" + + @patch("superset.mcp_service.utils.url_utils.get_superset_base_url") + def test_slug_with_value_preserved(self, mock_base_url): + """Dashboards with a real slug should preserve it.""" + mock_base_url.return_value = "http://localhost:8088" + + dashboard = _mock_dashboard(id=3, slug="my-dashboard") + result = serialize_dashboard_object(dashboard) + + assert result.slug == "my-dashboard" + + @patch("superset.mcp_service.utils.url_utils.get_superset_base_url") + def test_url_uses_id_when_no_slug(self, mock_base_url): + """URL should use dashboard id when slug is None.""" + mock_base_url.return_value = "http://localhost:8088" + + dashboard = _mock_dashboard(id=42, slug=None) + result = serialize_dashboard_object(dashboard) + + assert result.url == "http://localhost:8088/superset/dashboard/42/" + + @patch("superset.mcp_service.utils.url_utils.get_superset_base_url") + def test_url_uses_slug_when_available(self, mock_base_url): + """URL should use slug when available.""" + mock_base_url.return_value = "http://localhost:8088" + + dashboard = _mock_dashboard(id=42, slug="my-dashboard") + result = serialize_dashboard_object(dashboard) + + assert result.url == "http://localhost:8088/superset/dashboard/my-dashboard/" + + @patch("superset.mcp_service.utils.url_utils.get_superset_base_url") + def test_no_json_metadata_or_position_json_in_response(self, mock_base_url): + """DashboardInfo should not contain json_metadata or position_json.""" + mock_base_url.return_value = "http://localhost:8088" + + dashboard = _mock_dashboard(id=1) + result = serialize_dashboard_object(dashboard) + + assert not hasattr(result, "json_metadata") + assert not hasattr(result, "position_json") + + @patch("superset.mcp_service.utils.url_utils.get_superset_base_url") + def test_native_filters_extracted_from_json_metadata(self, mock_base_url): + """Native filters should be extracted from json_metadata.""" + mock_base_url.return_value = "http://localhost:8088" + + metadata = { + "native_filter_configuration": [ + { + "id": "NATIVE_FILTER-abc123", + "name": "Region Filter", + "filterType": "filter_select", + "targets": [{"column": {"name": "region"}, "datasetId": 10}], + "controlValues": {"multiSelect": True}, + "defaultDataMask": {"filterState": {"value": ["US"]}}, + "scope": {"rootPath": ["ROOT_ID"]}, + }, + { + "id": "NATIVE_FILTER-def456", + "name": "Date Range", + "filterType": "filter_range", + "targets": [{"column": {"name": "order_date"}, "datasetId": 10}], + }, + ], + "cross_filters_enabled": True, + "color_scheme": "supersetColors", + "shared_label_colors": {"Sales": "#1FA8C9"}, + } + dashboard = _mock_dashboard(id=1) + dashboard.json_metadata = json_dumps(metadata) + + result = serialize_dashboard_object(dashboard) + + assert len(result.native_filters) == 2 + assert result.native_filters[0].id == "NATIVE_FILTER-abc123" + assert result.native_filters[0].name == "Region Filter" + assert result.native_filters[0].filter_type == "filter_select" + assert len(result.native_filters[0].targets) == 1 + assert result.native_filters[1].name == "Date Range" + assert result.cross_filters_enabled is True + + @patch("superset.mcp_service.utils.url_utils.get_superset_base_url") + def test_chart_summaries_are_lightweight(self, mock_base_url): + """Charts in dashboard response should only have core fields.""" + mock_base_url.return_value = "http://localhost:8088" + + chart = MagicMock() + chart.id = 5 + chart.slice_name = "Revenue Chart" + chart.viz_type = "echarts_timeseries_bar" + chart.datasource_name = "sales" + chart.description = "Monthly revenue" + + dashboard = _mock_dashboard(id=1, slices=[chart]) + result = serialize_dashboard_object(dashboard) + + assert len(result.charts) == 1 + assert result.charts[0].id == 5 + assert result.charts[0].slice_name == "Revenue Chart" + assert result.charts[0].viz_type == "echarts_timeseries_bar" + assert result.charts[0].datasource_name == "sales" + assert result.charts[0].url == "http://localhost:8088/explore/?slice_id=5" + # Verify no heavy fields + assert not hasattr(result.charts[0], "form_data") + assert not hasattr(result.charts[0], "tags") + assert not hasattr(result.charts[0], "owners") + + +class TestExtractNativeFilters: + """Tests for _extract_native_filters helper.""" + + def test_none_input(self): + assert _extract_native_filters(None) == [] + + def test_empty_string(self): + assert _extract_native_filters("") == [] + + def test_invalid_json(self): + assert _extract_native_filters("not json") == [] + + def test_no_filter_config(self): + assert _extract_native_filters("{}") == [] + + def test_non_list_filter_config(self): + assert _extract_native_filters('{"native_filter_configuration": "bad"}') == [] + + def test_valid_filters(self): + metadata = json_dumps( + { + "native_filter_configuration": [ + { + "id": "f1", + "name": "Filter 1", + "filterType": "filter_select", + "targets": [{"column": {"name": "col1"}}], + } + ] + } + ) + result = _extract_native_filters(metadata) + assert len(result) == 1 + assert result[0].id == "f1" + assert result[0].name == "Filter 1" + assert result[0].filter_type == "filter_select" + + def test_skips_non_dict_entries(self): + metadata = json_dumps( + {"native_filter_configuration": [{"id": "f1", "name": "ok"}, "bad", 123]} + ) + result = _extract_native_filters(metadata) + assert len(result) == 1 + + def test_non_dict_top_level_json(self): + """json_metadata that parses to a list/number should return empty.""" + assert _extract_native_filters("[]") == [] + assert _extract_native_filters("123") == [] + assert _extract_native_filters('"just a string"') == [] + + +class TestExtractCrossFiltersEnabled: + """Tests for _extract_cross_filters_enabled helper.""" + + def test_none_input(self): + assert _extract_cross_filters_enabled(None) is None + + def test_empty_json(self): + assert _extract_cross_filters_enabled("{}") is None + + def test_true(self): + assert _extract_cross_filters_enabled('{"cross_filters_enabled": true}') is True + + def test_false(self): + assert ( + _extract_cross_filters_enabled('{"cross_filters_enabled": false}') is False + ) + + def test_non_bool_value(self): + assert ( + _extract_cross_filters_enabled('{"cross_filters_enabled": "yes"}') is None + ) + + def test_non_dict_top_level_json(self): + """json_metadata that parses to a list/number should return None.""" + assert _extract_cross_filters_enabled("[]") is None + assert _extract_cross_filters_enabled("123") is None + assert _extract_cross_filters_enabled('"just a string"') is None + + +class TestOmittedFieldsBuilder: + """Tests for the shared OmittedFieldsBuilder utility.""" + + def test_builder_basic(self): + from superset.mcp_service.utils.response_utils import OmittedFieldsBuilder + + result = ( + OmittedFieldsBuilder() + .add_raw_field("big_field", "x" * 2048, "Too large for context.") + .add_extracted_field("meta_field", "y" * 512, "Useful parts above.") + .build() + ) + assert "big_field" in result + assert "~2 KB" in result["big_field"] + assert "Too large" in result["big_field"] + assert "meta_field" in result + assert "extracted" in result["meta_field"] + + def test_builder_none_values(self): + from superset.mcp_service.utils.response_utils import OmittedFieldsBuilder + + result = ( + OmittedFieldsBuilder() + .add_raw_field("empty_field", None, "Was not set.") + .add_extracted_field("also_empty", None, "Nothing to extract.") + .build() + ) + assert "empty" in result["empty_field"] + assert "empty" in result["also_empty"] + + @patch("superset.mcp_service.utils.url_utils.get_superset_base_url") + def test_omitted_fields_in_serialized_dashboard(self, mock_base_url): + """omitted_fields should describe what was stripped and include sizes.""" + mock_base_url.return_value = "http://localhost:8088" + + dashboard = _mock_dashboard(id=1) + dashboard.json_metadata = json_dumps( + {"color_scheme": "preset", "native_filter_configuration": []} + ) + dashboard.position_json = json_dumps({"ROOT_ID": {"children": ["GRID_ID"]}}) + + result = serialize_dashboard_object(dashboard) + + assert "json_metadata" in result.omitted_fields + assert "position_json" in result.omitted_fields + assert "extracted" in result.omitted_fields["json_metadata"] + assert "layout tree" in result.omitted_fields["position_json"].lower() + + @patch("superset.mcp_service.utils.url_utils.get_superset_base_url") + def test_omitted_fields_with_none_values(self, mock_base_url): + """omitted_fields should still be present when raw fields are None.""" + mock_base_url.return_value = "http://localhost:8088" + + dashboard = _mock_dashboard(id=1) + result = serialize_dashboard_object(dashboard) + + assert "json_metadata" in result.omitted_fields + assert "position_json" in result.omitted_fields diff --git a/tests/unit_tests/mcp_service/dashboard/tool/test_add_chart_to_existing_dashboard.py b/tests/unit_tests/mcp_service/dashboard/tool/test_add_chart_to_existing_dashboard.py new file mode 100644 index 00000000000..de8058c92de --- /dev/null +++ b/tests/unit_tests/mcp_service/dashboard/tool/test_add_chart_to_existing_dashboard.py @@ -0,0 +1,297 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +""" +Unit tests for add_chart_to_existing_dashboard MCP tool. + +Follows the same pattern used in test_dashboard_generation.py: +- Tests run through the async MCP Client (not direct function calls) +- Patches applied at source locations (superset.daos.dashboard.*, superset.db.*, etc.) +- auth is mocked via the autouse mock_auth fixture (same as other tool test files) + +Covers: +- Dashboard not found +- Permission denied (user does not own the dashboard) -> permission_denied=True +- Chart not found +- Chart already in dashboard +- Successful add (happy path) +""" + +import logging +from unittest.mock import Mock, patch + +import pytest +from fastmcp import Client + +from superset.mcp_service.app import mcp +from superset.mcp_service.chart.chart_utils import DatasetValidationResult + +logging.basicConfig(level=logging.DEBUG) +logger = logging.getLogger(__name__) + + +# --------------------------------------------------------------------------- +# Fixtures +# --------------------------------------------------------------------------- + + +@pytest.fixture +def mcp_server() -> object: + """Return the FastMCP app instance for use in MCP client tests.""" + return mcp + + +@pytest.fixture(autouse=True) +def mock_auth(): + """Mock authentication for all tests.""" + with patch("superset.mcp_service.auth.get_user_from_request") as mock_get_user: + mock_user = Mock() + mock_user.id = 1 + mock_user.username = "admin" + mock_get_user.return_value = mock_user + yield mock_get_user + + +@pytest.fixture(autouse=True) +def mock_chart_access(): + """Allow chart data access by default so tests focus on dashboard logic.""" + with patch( + "superset.mcp_service.auth.check_chart_data_access", + return_value=DatasetValidationResult( + is_valid=True, + dataset_id=1, + dataset_name="test_dataset", + warnings=[], + error=None, + ), + ): + yield + + +# --------------------------------------------------------------------------- +# Helpers +# --------------------------------------------------------------------------- + + +def _mock_chart(id: int = 10, slice_name: str = "Test Chart") -> Mock: + """Create a minimal mock Slice object with the given ID and name.""" + chart = Mock() + chart.id = id + chart.slice_name = slice_name + chart.uuid = f"chart-uuid-{id}" + chart.tags = [] + chart.owners = [] + chart.viz_type = "table" + chart.datasource_name = None + chart.description = None + return chart + + +def _mock_dashboard( + id: int = 1, + title: str = "Sales Dashboard", + slices: list[Mock] | None = None, +) -> Mock: + """Create a minimal mock Dashboard object with the given ID, title and slices.""" + dashboard = Mock() + dashboard.id = id + dashboard.dashboard_title = title + dashboard.slug = f"test-dashboard-{id}" + dashboard.description = None + dashboard.published = True + dashboard.created_on = None + dashboard.changed_on = None + dashboard.created_by_name = "test_user" + dashboard.changed_by_name = "test_user" + dashboard.uuid = f"dashboard-uuid-{id}" + dashboard.slices = slices or [] + dashboard.owners = [] + dashboard.tags = [] + dashboard.roles = [] + dashboard.position_json = "{}" + dashboard.json_metadata = None + dashboard.css = None + dashboard.certified_by = None + dashboard.certification_details = None + dashboard.is_managed_externally = False + dashboard.external_url = None + return dashboard + + +# --------------------------------------------------------------------------- +# Tests +# --------------------------------------------------------------------------- + + +@patch("superset.daos.dashboard.DashboardDAO.find_by_id") +@pytest.mark.asyncio +async def test_dashboard_not_found(mock_find_by_id: Mock, mcp_server: object) -> None: + """Returns a clear error when the target dashboard does not exist.""" + mock_find_by_id.return_value = None + + async with Client(mcp_server) as client: + result = await client.call_tool( + "add_chart_to_existing_dashboard", + {"request": {"dashboard_id": 999, "chart_id": 10}}, + ) + + assert result.structured_content["dashboard"] is None + assert result.structured_content["dashboard_url"] is None + assert result.structured_content["permission_denied"] is False + assert "not found" in (result.structured_content["error"] or "").lower() + + +@patch("superset.security_manager.raise_for_ownership") +@patch("superset.daos.dashboard.DashboardDAO.find_by_id") +@pytest.mark.asyncio +async def test_permission_denied( + mock_find_by_id: Mock, mock_raise_for_ownership: Mock, mcp_server: object +) -> None: + """Returns permission_denied=True and an actionable error when the user + cannot edit the dashboard. + + This is the core regression test for the bug fix: before the fix the tool + returned a generic error that caused the LLM to silently call + generate_dashboard instead. After the fix it returns permission_denied=True + with a message that explicitly tells the LLM to ask the user first. + """ + from superset.errors import ErrorLevel, SupersetError, SupersetErrorType + from superset.exceptions import SupersetSecurityException + + dashboard = _mock_dashboard(id=1, title="Sales Dashboard") + mock_find_by_id.return_value = dashboard + mock_raise_for_ownership.side_effect = SupersetSecurityException( + SupersetError( + message="Changing this Dashboard is forbidden", + error_type=SupersetErrorType.GENERIC_BACKEND_ERROR, + level=ErrorLevel.ERROR, + ) + ) + + async with Client(mcp_server) as client: + result = await client.call_tool( + "add_chart_to_existing_dashboard", + {"request": {"dashboard_id": 1, "chart_id": 10}}, + ) + + content = result.structured_content + assert content["dashboard"] is None + assert content["permission_denied"] is True, ( + "Expected permission_denied=True so the LLM knows to ask the user " + "before creating a new dashboard — this is the fix for the bug" + ) + assert content["error"] is not None + assert "Sales Dashboard" in content["error"] + assert "permission" in content["error"].lower() + assert "new dashboard" in content["error"].lower() + + +@patch("superset.db.session.get") +@patch("superset.security_manager.raise_for_ownership") +@patch("superset.daos.dashboard.DashboardDAO.find_by_id") +@pytest.mark.asyncio +async def test_chart_not_found( + mock_find_by_id: Mock, + mock_raise_for_ownership: Mock, + mock_session_get: Mock, + mcp_server: object, +) -> None: + """Returns an error when the requested chart does not exist.""" + dashboard = _mock_dashboard() + mock_find_by_id.return_value = dashboard + mock_raise_for_ownership.return_value = None + mock_session_get.return_value = None # chart not found + + async with Client(mcp_server) as client: + result = await client.call_tool( + "add_chart_to_existing_dashboard", + {"request": {"dashboard_id": 1, "chart_id": 99}}, + ) + + content = result.structured_content + assert content["dashboard"] is None + assert content["permission_denied"] is False + assert "99" in (content["error"] or "") + + +@patch("superset.db.session.get") +@patch("superset.security_manager.raise_for_ownership") +@patch("superset.daos.dashboard.DashboardDAO.find_by_id") +@pytest.mark.asyncio +async def test_chart_already_in_dashboard( + mock_find_by_id: Mock, + mock_raise_for_ownership: Mock, + mock_session_get: Mock, + mcp_server: object, +) -> None: + """Returns an error when the chart is already present on the dashboard.""" + chart = _mock_chart(id=10) + dashboard = _mock_dashboard(slices=[chart]) + mock_find_by_id.return_value = dashboard + mock_raise_for_ownership.return_value = None + mock_session_get.return_value = chart + + async with Client(mcp_server) as client: + result = await client.call_tool( + "add_chart_to_existing_dashboard", + {"request": {"dashboard_id": 1, "chart_id": 10}}, + ) + + content = result.structured_content + assert content["dashboard"] is None + assert content["permission_denied"] is False + assert "already" in (content["error"] or "").lower() + + +@patch("superset.commands.dashboard.update.UpdateDashboardCommand") +@patch("superset.db.session.get") +@patch("superset.security_manager.raise_for_ownership") +@patch("superset.daos.dashboard.DashboardDAO.find_by_id") +@pytest.mark.asyncio +async def test_successful_add( + mock_find_by_id: Mock, + mock_raise_for_ownership: Mock, + mock_session_get: Mock, + mock_update_cmd_cls: Mock, + mcp_server: object, +) -> None: + """Happy path: chart added, permission_denied=False, URL and position returned.""" + chart = _mock_chart(id=10) + dashboard = _mock_dashboard(id=1) + updated_dashboard = _mock_dashboard(id=1, slices=[chart]) + + mock_find_by_id.side_effect = [dashboard, updated_dashboard] + mock_raise_for_ownership.return_value = None + mock_session_get.return_value = chart + + mock_update_cmd = Mock() + mock_update_cmd.run.return_value = updated_dashboard + mock_update_cmd_cls.return_value = mock_update_cmd + + async with Client(mcp_server) as client: + result = await client.call_tool( + "add_chart_to_existing_dashboard", + {"request": {"dashboard_id": 1, "chart_id": 10}}, + ) + + content = result.structured_content + assert content["error"] is None + assert content["permission_denied"] is False + assert content["dashboard_url"] is not None + assert "/superset/dashboard/1/" in content["dashboard_url"] + assert content["position"] is not None + assert "chart_key" in content["position"] diff --git a/tests/unit_tests/mcp_service/dashboard/tool/test_dashboard_generation.py b/tests/unit_tests/mcp_service/dashboard/tool/test_dashboard_generation.py index b382f1f4ab2..b5df3e91abc 100644 --- a/tests/unit_tests/mcp_service/dashboard/tool/test_dashboard_generation.py +++ b/tests/unit_tests/mcp_service/dashboard/tool/test_dashboard_generation.py @@ -26,6 +26,7 @@ import pytest from fastmcp import Client from superset.mcp_service.app import mcp +from superset.mcp_service.chart.chart_utils import DatasetValidationResult from superset.mcp_service.dashboard.constants import generate_id from superset.mcp_service.dashboard.tool.add_chart_to_existing_dashboard import ( _add_chart_to_layout, @@ -51,11 +52,28 @@ def mcp_server(): def mock_auth(): """Mock authentication for all tests.""" with patch("superset.mcp_service.auth.get_user_from_request") as mock_get_user: - mock_user = Mock() - mock_user.id = 1 - mock_user.username = "admin" - mock_get_user.return_value = mock_user - yield mock_get_user + with patch("superset.security_manager.raise_for_ownership"): + mock_user = Mock() + mock_user.id = 1 + mock_user.username = "admin" + mock_get_user.return_value = mock_user + yield mock_get_user + + +@pytest.fixture(autouse=True) +def mock_chart_access(): + """Mock chart dataset validation so tests don't hit real security manager.""" + with patch( + "superset.mcp_service.auth.check_chart_data_access", + return_value=DatasetValidationResult( + is_valid=True, + dataset_id=1, + dataset_name="test_dataset", + warnings=[], + error=None, + ), + ): + yield def _mock_chart(id: int = 1, slice_name: str = "Test Chart") -> Mock: @@ -70,6 +88,8 @@ def _mock_chart(id: int = 1, slice_name: str = "Test Chart") -> Mock: chart.datasource_name = None chart.datasource_type = None chart.description = None + chart.certified_by = None + chart.certification_details = None chart.cache_timeout = None chart.changed_by = None chart.changed_by_name = None @@ -96,6 +116,8 @@ def _mock_dashboard(id: int = 1, title: str = "Test Dashboard") -> Mock: dashboard.created_by.username = "test_user" dashboard.changed_by = Mock() dashboard.changed_by.username = "test_user" + dashboard.created_by_name = "test_user" + dashboard.changed_by_name = "test_user" dashboard.uuid = f"dashboard-uuid-{id}" dashboard.slices = [] dashboard.owners = [] @@ -103,28 +125,59 @@ def _mock_dashboard(id: int = 1, title: str = "Test Dashboard") -> Mock: return dashboard +def _setup_generate_dashboard_mocks( + mock_db_session, + mock_find_by_id, + mock_dashboard_cls, + charts, + dashboard, +): + """Set up common mocks for generate_dashboard tests. + + The tool creates dashboards directly via db.session (bypassing + CreateDashboardCommand) and re-queries user/charts in the tool's + own session. This helper wires up the mock chain for that path. + """ + mock_user = Mock() + mock_user.id = 1 + mock_user.username = "admin" + mock_user.first_name = "Admin" + mock_user.last_name = "User" + mock_user.email = "admin@example.com" + mock_user.active = True + + mock_query = Mock() + mock_filter = Mock() + mock_query.filter.return_value = mock_filter + mock_query.filter_by.return_value = mock_filter + mock_filter.order_by.return_value = mock_filter + mock_filter.all.return_value = charts + mock_filter.first.return_value = mock_user + mock_db_session.query.return_value = mock_query + + mock_dashboard_cls.return_value = dashboard + mock_find_by_id.return_value = dashboard + + class TestGenerateDashboard: """Tests for generate_dashboard MCP tool.""" - @patch("superset.commands.dashboard.create.CreateDashboardCommand") + @patch("superset.models.dashboard.Dashboard") + @patch("superset.daos.dashboard.DashboardDAO.find_by_id") @patch("superset.db.session") @pytest.mark.asyncio async def test_generate_dashboard_basic( - self, mock_db_session, mock_create_command, mcp_server + self, mock_db_session, mock_find_by_id, mock_dashboard_cls, mcp_server ): """Test basic dashboard generation with valid charts.""" - mock_query = Mock() - mock_filter = Mock() - mock_query.filter.return_value = mock_filter - mock_filter.order_by.return_value = mock_filter - mock_filter.all.return_value = [ + charts = [ _mock_chart(id=1, slice_name="Sales Chart"), _mock_chart(id=2, slice_name="Revenue Chart"), ] - mock_db_session.query.return_value = mock_query - mock_dashboard = _mock_dashboard(id=10, title="Analytics Dashboard") - mock_create_command.return_value.run.return_value = mock_dashboard + _setup_generate_dashboard_mocks( + mock_db_session, mock_find_by_id, mock_dashboard_cls, charts, mock_dashboard + ) request = { "chart_ids": [1, 2], @@ -169,22 +222,80 @@ class TestGenerateDashboard: assert result.structured_content["dashboard"] is None assert result.structured_content["dashboard_url"] is None - @patch("superset.commands.dashboard.create.CreateDashboardCommand") @patch("superset.db.session") @pytest.mark.asyncio - async def test_generate_dashboard_single_chart( - self, mock_db_session, mock_create_command, mcp_server + async def test_generate_dashboard_inaccessible_charts( + self, mock_db_session, mock_chart_access, mcp_server ): - """Test dashboard generation with a single chart.""" + """Test error when user lacks access to some charts.""" + charts = [ + _mock_chart(id=1, slice_name="Accessible"), + _mock_chart(id=2, slice_name="Restricted"), + _mock_chart(id=3, slice_name="Also Restricted"), + ] + mock_query = Mock() mock_filter = Mock() mock_query.filter.return_value = mock_filter mock_filter.order_by.return_value = mock_filter - mock_filter.all.return_value = [_mock_chart(id=5, slice_name="Single Chart")] + mock_filter.all.return_value = charts mock_db_session.query.return_value = mock_query + # Override the autouse fixture: chart 2 has inaccessible dataset + def mock_validate(chart, check_access=False): + if chart.id == 2: + return DatasetValidationResult( + is_valid=False, + dataset_id=10, + dataset_name="restricted_dataset", + warnings=[], + error=( + "Access denied to dataset 'restricted_dataset' " + "(ID: 10). You do not have permission to view " + "this dataset." + ), + ) + return DatasetValidationResult( + is_valid=True, + dataset_id=chart.id, + dataset_name=f"dataset_{chart.id}", + warnings=[], + error=None, + ) + + with patch( + "superset.mcp_service.auth.check_chart_data_access", + side_effect=mock_validate, + ): + request = { + "chart_ids": [1, 2, 3], + "dashboard_title": "Test Dashboard", + } + + async with Client(mcp_server) as client: + result = await client.call_tool( + "generate_dashboard", {"request": request} + ) + + assert result.structured_content["error"] is not None + assert "not accessible" in result.structured_content["error"] + assert "2" in result.structured_content["error"] + assert result.structured_content["dashboard"] is None + assert result.structured_content["dashboard_url"] is None + + @patch("superset.models.dashboard.Dashboard") + @patch("superset.daos.dashboard.DashboardDAO.find_by_id") + @patch("superset.db.session") + @pytest.mark.asyncio + async def test_generate_dashboard_single_chart( + self, mock_db_session, mock_find_by_id, mock_dashboard_cls, mcp_server + ): + """Test dashboard generation with a single chart.""" + charts = [_mock_chart(id=5, slice_name="Single Chart")] mock_dashboard = _mock_dashboard(id=20, title="Single Chart Dashboard") - mock_create_command.return_value.run.return_value = mock_dashboard + _setup_generate_dashboard_mocks( + mock_db_session, mock_find_by_id, mock_dashboard_cls, charts, mock_dashboard + ) request = { "chart_ids": [5], @@ -197,27 +308,22 @@ class TestGenerateDashboard: assert result.structured_content["error"] is None assert result.structured_content["dashboard"]["chart_count"] == 1 - assert result.structured_content["dashboard"]["published"] is True + assert result.structured_content["dashboard"]["published"] is False - @patch("superset.commands.dashboard.create.CreateDashboardCommand") + @patch("superset.models.dashboard.Dashboard") + @patch("superset.daos.dashboard.DashboardDAO.find_by_id") @patch("superset.db.session") @pytest.mark.asyncio async def test_generate_dashboard_many_charts( - self, mock_db_session, mock_create_command, mcp_server + self, mock_db_session, mock_find_by_id, mock_dashboard_cls, mcp_server ): """Test dashboard generation with many charts (grid layout).""" chart_ids = list(range(1, 7)) - mock_query = Mock() - mock_filter = Mock() - mock_query.filter.return_value = mock_filter - mock_filter.order_by.return_value = mock_filter - mock_filter.all.return_value = [ - _mock_chart(id=i, slice_name=f"Chart {i}") for i in chart_ids - ] - mock_db_session.query.return_value = mock_query - + charts = [_mock_chart(id=i, slice_name=f"Chart {i}") for i in chart_ids] mock_dashboard = _mock_dashboard(id=30, title="Multi Chart Dashboard") - mock_create_command.return_value.run.return_value = mock_dashboard + _setup_generate_dashboard_mocks( + mock_db_session, mock_find_by_id, mock_dashboard_cls, charts, mock_dashboard + ) request = {"chart_ids": chart_ids, "dashboard_title": "Multi Chart Dashboard"} @@ -227,10 +333,14 @@ class TestGenerateDashboard: assert result.structured_content["error"] is None assert result.structured_content["dashboard"]["chart_count"] == 6 - mock_create_command.assert_called_once() - call_args = mock_create_command.call_args[0][0] + # Verify db.session.add and commit were called + # (commit is called multiple times: once by tool + event_logger contexts) + mock_db_session.add.assert_called_once() + assert mock_db_session.commit.call_count >= 1 - position_json = json.loads(call_args["position_json"]) + # Verify layout was set on the dashboard object + created_dashboard = mock_dashboard_cls.return_value + position_json = json.loads(created_dashboard.position_json) assert "ROOT_ID" in position_json assert "GRID_ID" in position_json assert "DASHBOARD_VERSION_KEY" in position_json @@ -270,20 +380,33 @@ class TestGenerateDashboard: assert row_data["type"] == "ROW" assert column_key in row_data["children"] - @patch("superset.commands.dashboard.create.CreateDashboardCommand") + @patch("superset.models.dashboard.Dashboard") @patch("superset.db.session") @pytest.mark.asyncio async def test_generate_dashboard_creation_failure( - self, mock_db_session, mock_create_command, mcp_server + self, mock_db_session, mock_dashboard_cls, mcp_server ): """Test error handling when dashboard creation fails.""" + from sqlalchemy.exc import SQLAlchemyError + mock_query = Mock() mock_filter = Mock() mock_query.filter.return_value = mock_filter + mock_query.filter_by.return_value = mock_filter mock_filter.order_by.return_value = mock_filter mock_filter.all.return_value = [_mock_chart(id=1)] + mock_filter.first.return_value = Mock( + id=1, + username="admin", + first_name="Admin", + last_name="User", + email="admin@example.com", + active=True, + ) mock_db_session.query.return_value = mock_query - mock_create_command.return_value.run.side_effect = Exception("Creation failed") + mock_db_session.commit.side_effect = SQLAlchemyError("Creation failed") + + mock_dashboard_cls.return_value = _mock_dashboard(id=99) request = {"chart_ids": [1], "dashboard_title": "Failed Dashboard"} @@ -293,23 +416,22 @@ class TestGenerateDashboard: assert result.structured_content["error"] is not None assert "Failed to create dashboard" in result.structured_content["error"] assert result.structured_content["dashboard"] is None + # rollback called by tool + event_logger error handling + assert mock_db_session.rollback.call_count >= 1 - @patch("superset.commands.dashboard.create.CreateDashboardCommand") + @patch("superset.models.dashboard.Dashboard") + @patch("superset.daos.dashboard.DashboardDAO.find_by_id") @patch("superset.db.session") @pytest.mark.asyncio async def test_generate_dashboard_minimal_request( - self, mock_db_session, mock_create_command, mcp_server + self, mock_db_session, mock_find_by_id, mock_dashboard_cls, mcp_server ): """Test dashboard generation with minimal required parameters.""" - mock_query = Mock() - mock_filter = Mock() - mock_query.filter.return_value = mock_filter - mock_filter.order_by.return_value = mock_filter - mock_filter.all.return_value = [_mock_chart(id=3)] - mock_db_session.query.return_value = mock_query - + charts = [_mock_chart(id=3)] mock_dashboard = _mock_dashboard(id=40, title="Minimal Dashboard") - mock_create_command.return_value.run.return_value = mock_dashboard + _setup_generate_dashboard_mocks( + mock_db_session, mock_find_by_id, mock_dashboard_cls, charts, mock_dashboard + ) request = { "chart_ids": [3], @@ -325,31 +447,53 @@ class TestGenerateDashboard: == "Minimal Dashboard" ) - call_args = mock_create_command.call_args[0][0] - assert call_args["published"] is True - assert ( - "description" not in call_args or call_args.get("description") is None - ) + # Verify dashboard was created with default published=False + created = mock_dashboard_cls.return_value + assert created.published is False - @patch("superset.commands.dashboard.create.CreateDashboardCommand") + @patch("superset.models.dashboard.Dashboard") + @patch("superset.daos.dashboard.DashboardDAO.find_by_id") + @patch("superset.db.session") + @pytest.mark.asyncio + async def test_generate_dashboard_explicit_published( + self, mock_db_session, mock_find_by_id, mock_dashboard_cls, mcp_server + ): + """Test dashboard generation with published explicitly set to True.""" + charts = [_mock_chart(id=3)] + mock_dashboard = _mock_dashboard(id=41, title="Published Dashboard") + _setup_generate_dashboard_mocks( + mock_db_session, mock_find_by_id, mock_dashboard_cls, charts, mock_dashboard + ) + + request = { + "chart_ids": [3], + "dashboard_title": "Published Dashboard", + "published": True, + } + + async with Client(mcp_server) as client: + result = await client.call_tool("generate_dashboard", {"request": request}) + + assert result.structured_content["error"] is None + created = mock_dashboard_cls.return_value + assert created.published is True + + @patch("superset.models.dashboard.Dashboard") + @patch("superset.daos.dashboard.DashboardDAO.find_by_id") @patch("superset.db.session") @pytest.mark.asyncio async def test_generate_dashboard_auto_title_from_charts( - self, mock_db_session, mock_create_command, mcp_server + self, mock_db_session, mock_find_by_id, mock_dashboard_cls, mcp_server ): """Test that omitting dashboard_title generates a title from chart names.""" - mock_query = Mock() - mock_filter = Mock() - mock_query.filter.return_value = mock_filter - mock_filter.order_by.return_value = mock_filter - mock_filter.all.return_value = [ + charts = [ _mock_chart(id=1, slice_name="Sales Revenue"), _mock_chart(id=2, slice_name="Customer Count"), ] - mock_db_session.query.return_value = mock_query - mock_dashboard = _mock_dashboard(id=50, title="Sales Revenue & Customer Count") - mock_create_command.return_value.run.return_value = mock_dashboard + _setup_generate_dashboard_mocks( + mock_db_session, mock_find_by_id, mock_dashboard_cls, charts, mock_dashboard + ) # No dashboard_title provided request = {"chart_ids": [1, 2]} @@ -359,27 +503,23 @@ class TestGenerateDashboard: assert result.structured_content["error"] is None - call_args = mock_create_command.call_args[0][0] - assert call_args["dashboard_title"] == "Sales Revenue & Customer Count" + # Verify auto-generated title was set on dashboard + created = mock_dashboard_cls.return_value + assert created.dashboard_title == "Sales Revenue & Customer Count" - @patch("superset.commands.dashboard.create.CreateDashboardCommand") + @patch("superset.models.dashboard.Dashboard") + @patch("superset.daos.dashboard.DashboardDAO.find_by_id") @patch("superset.db.session") @pytest.mark.asyncio async def test_generate_dashboard_empty_string_title_preserved( - self, mock_db_session, mock_create_command, mcp_server + self, mock_db_session, mock_find_by_id, mock_dashboard_cls, mcp_server ): """Test that an explicit empty-string title is NOT replaced by auto-gen.""" - mock_query = Mock() - mock_filter = Mock() - mock_query.filter.return_value = mock_filter - mock_filter.order_by.return_value = mock_filter - mock_filter.all.return_value = [ - _mock_chart(id=1, slice_name="Sales Revenue"), - ] - mock_db_session.query.return_value = mock_query - + charts = [_mock_chart(id=1, slice_name="Sales Revenue")] mock_dashboard = _mock_dashboard(id=60, title="") - mock_create_command.return_value.run.return_value = mock_dashboard + _setup_generate_dashboard_mocks( + mock_db_session, mock_find_by_id, mock_dashboard_cls, charts, mock_dashboard + ) # Explicit empty string title request = {"chart_ids": [1], "dashboard_title": ""} @@ -389,8 +529,9 @@ class TestGenerateDashboard: assert result.structured_content["error"] is None - call_args = mock_create_command.call_args[0][0] - assert call_args["dashboard_title"] == "" + # Verify empty string title was preserved (not replaced by auto-gen) + created = mock_dashboard_cls.return_value + assert created.dashboard_title == "" class TestAddChartToExistingDashboard: @@ -439,17 +580,19 @@ class TestAddChartToExistingDashboard: "DASHBOARD_VERSION_KEY": "v2", } ) - mock_find_dashboard.return_value = mock_dashboard - - mock_chart = _mock_chart(id=30, slice_name="New Chart") - mock_db_session.get.return_value = mock_chart - updated_dashboard = _mock_dashboard(id=1, title="Existing Dashboard") updated_dashboard.slices = [ _mock_chart(id=10), _mock_chart(id=20), _mock_chart(id=30), ] + # First call: initial validation returns original dashboard + # Second call: re-fetch after update returns updated dashboard + mock_find_dashboard.side_effect = [mock_dashboard, updated_dashboard] + + mock_chart = _mock_chart(id=30, slice_name="New Chart") + mock_db_session.get.return_value = mock_chart + mock_update_command.return_value.run.return_value = updated_dashboard request = {"dashboard_id": 1, "chart_id": 30} @@ -516,6 +659,43 @@ class TestAddChartToExistingDashboard: assert result.structured_content["error"] is not None assert "Chart with ID 999 not found" in result.structured_content["error"] + @patch("superset.daos.dashboard.DashboardDAO.find_by_id") + @patch("superset.db.session") + @pytest.mark.asyncio + async def test_add_chart_dataset_not_accessible( + self, mock_db_session, mock_find_dashboard, mcp_server + ): + """Test error when chart's dataset is not accessible.""" + mock_find_dashboard.return_value = _mock_dashboard() + mock_chart = _mock_chart(id=7) + mock_db_session.get.return_value = mock_chart + + # Override autouse fixture: chart 7 has inaccessible dataset + with patch( + "superset.mcp_service.auth.check_chart_data_access", + return_value=DatasetValidationResult( + is_valid=False, + dataset_id=10, + dataset_name="restricted_dataset", + warnings=[], + error=( + "Access denied to dataset 'restricted_dataset' " + "(ID: 10). You do not have permission to view " + "this dataset." + ), + ), + ): + request = {"dashboard_id": 1, "chart_id": 7} + + async with Client(mcp_server) as client: + result = await client.call_tool( + "add_chart_to_existing_dashboard", {"request": request} + ) + assert result.structured_content["error"] is not None + assert "not accessible" in result.structured_content["error"] + assert "7" in result.structured_content["error"] + assert result.structured_content["dashboard"] is None + @patch("superset.daos.dashboard.DashboardDAO.find_by_id") @patch("superset.db.session") @pytest.mark.asyncio @@ -659,8 +839,6 @@ class TestAddChartToExistingDashboard: "DASHBOARD_VERSION_KEY": "v2", } ) - mock_find_dashboard.return_value = mock_dashboard - mock_chart = _mock_chart(id=25, slice_name="Tab Chart") mock_db_session.get.return_value = mock_chart @@ -668,6 +846,10 @@ class TestAddChartToExistingDashboard: updated_dashboard.slices = [_mock_chart(id=10), _mock_chart(id=25)] mock_update_command.return_value.run.return_value = updated_dashboard + # side_effect: first call returns initial dashboard (validation), + # second call returns updated dashboard (re-fetch after update) + mock_find_dashboard.side_effect = [mock_dashboard, updated_dashboard] + request = {"dashboard_id": 3, "chart_id": 25} async with Client(mcp_server) as client: @@ -759,8 +941,6 @@ class TestAddChartToExistingDashboard: "DASHBOARD_VERSION_KEY": "v2", } ) - mock_find_dashboard.return_value = mock_dashboard - mock_chart = _mock_chart(id=30, slice_name="Customer Chart") mock_db_session.get.return_value = mock_chart @@ -768,6 +948,10 @@ class TestAddChartToExistingDashboard: updated_dashboard.slices = [_mock_chart(id=10), _mock_chart(id=30)] mock_update_command.return_value.run.return_value = updated_dashboard + # side_effect: first call returns initial dashboard (validation), + # second call returns updated dashboard (re-fetch after update) + mock_find_dashboard.side_effect = [mock_dashboard, updated_dashboard] + request = {"dashboard_id": 3, "chart_id": 30, "target_tab": "Customers"} async with Client(mcp_server) as client: @@ -796,6 +980,118 @@ class TestAddChartToExistingDashboard: assert "TAB-tab2" in chart_parents assert "TAB-tab1" not in chart_parents + @patch("superset.commands.dashboard.update.UpdateDashboardCommand") + @patch("superset.daos.dashboard.DashboardDAO.find_by_id") + @patch("superset.db.session") + @pytest.mark.asyncio + async def test_add_chart_to_tabbed_dashboard_tabs_under_root( + self, mock_db_session, mock_find_dashboard, mock_update_command, mcp_server + ): + """Test adding chart when TABS are under ROOT_ID (real-world layout). + + Real Superset dashboards place TABS directly under ROOT_ID with an + empty GRID_ID, unlike test fixtures that place TABS under GRID_ID. + The tool must NOT inject GRID_ID into ROOT_ID.children alongside + TABS, as the frontend hides non-TABS content when a TABS container + is a ROOT_ID child. + """ + mock_dashboard = _mock_dashboard(id=7, title="COVID Vaccine Dashboard") + mock_dashboard.slices = [_mock_chart(id=10)] + mock_dashboard.position_json = json.dumps( + { + "ROOT_ID": { + "children": ["TABS-wUKya7eQ0Z"], + "id": "ROOT_ID", + "type": "ROOT", + }, + "GRID_ID": { + "children": [], + "id": "GRID_ID", + "parents": ["ROOT_ID"], + "type": "GRID", + }, + "TABS-wUKya7eQ0Z": { + "children": ["TAB-BCIJF4NvgQ", "TAB-kl2Hkh2IR"], + "id": "TABS-wUKya7eQ0Z", + "parents": ["ROOT_ID"], + "type": "TABS", + }, + "TAB-BCIJF4NvgQ": { + "children": ["ROW-existing"], + "id": "TAB-BCIJF4NvgQ", + "meta": {"text": "Vaccine Candidates"}, + "parents": ["ROOT_ID", "TABS-wUKya7eQ0Z"], + "type": "TAB", + }, + "TAB-kl2Hkh2IR": { + "children": [], + "id": "TAB-kl2Hkh2IR", + "meta": {"text": "Doses Administered"}, + "parents": ["ROOT_ID", "TABS-wUKya7eQ0Z"], + "type": "TAB", + }, + "ROW-existing": { + "children": ["CHART-10"], + "id": "ROW-existing", + "meta": {"background": "BACKGROUND_TRANSPARENT"}, + "parents": [ + "ROOT_ID", + "TABS-wUKya7eQ0Z", + "TAB-BCIJF4NvgQ", + ], + "type": "ROW", + }, + "CHART-10": { + "id": "CHART-10", + "type": "CHART", + "parents": [ + "ROOT_ID", + "TABS-wUKya7eQ0Z", + "TAB-BCIJF4NvgQ", + "ROW-existing", + ], + }, + "DASHBOARD_VERSION_KEY": "v2", + } + ) + mock_chart = _mock_chart(id=91, slice_name="Vaccines by Stage") + mock_db_session.get.return_value = mock_chart + + updated_dashboard = _mock_dashboard(id=7, title="COVID Vaccine Dashboard") + updated_dashboard.slices = [_mock_chart(id=10), _mock_chart(id=91)] + mock_update_command.return_value.run.return_value = updated_dashboard + + mock_find_dashboard.side_effect = [mock_dashboard, updated_dashboard] + + request = {"dashboard_id": 7, "chart_id": 91} + + async with Client(mcp_server) as client: + result = await client.call_tool( + "add_chart_to_existing_dashboard", {"request": request} + ) + + assert result.structured_content["error"] is None + + call_args = mock_update_command.call_args[0][1] + layout = json.loads(call_args["position_json"]) + + row_key = result.structured_content["position"]["row_key"] + assert row_key in layout + + # Chart must be inside the first tab, not GRID_ID + assert row_key in layout["TAB-BCIJF4NvgQ"]["children"] + assert row_key not in layout["GRID_ID"]["children"] + + # GRID_ID must NOT be added to ROOT_ID.children alongside TABS + assert "GRID_ID" not in layout["ROOT_ID"]["children"] + assert layout["ROOT_ID"]["children"] == ["TABS-wUKya7eQ0Z"] + + # Parent chain must include the tab hierarchy, not GRID_ID + chart_parents = layout["CHART-91"]["parents"] + assert "TABS-wUKya7eQ0Z" in chart_parents + assert "TAB-BCIJF4NvgQ" in chart_parents + assert "GRID_ID" not in chart_parents + @patch("superset.commands.dashboard.update.UpdateDashboardCommand") @patch("superset.daos.dashboard.DashboardDAO.find_by_id") @patch("superset.db.session") @@ -957,6 +1253,28 @@ class TestLayoutHelpers: _find_tab_insert_target(layout, target_tab="Nonexistent Tab") == "TAB-first" ) + def test_find_tab_insert_target_tabs_under_root(self): + """Test _find_tab_insert_target when TABS are under ROOT_ID (real layout).""" + layout = { + "ROOT_ID": {"children": ["TABS-xxx"], "type": "ROOT"}, + "GRID_ID": {"children": [], "type": "GRID", "parents": ["ROOT_ID"]}, + "TABS-xxx": {"children": ["TAB-a", "TAB-b"], "type": "TABS"}, + "TAB-a": {"children": [], "type": "TAB", "meta": {"text": "Overview"}}, + "TAB-b": {"children": [], "type": "TAB", "meta": {"text": "Details"}}, + } + assert _find_tab_insert_target(layout) == "TAB-a" + + def test_find_tab_insert_target_tabs_under_root_by_name(self): + """Test _find_tab_insert_target matches tab name when TABS under ROOT_ID.""" + layout = { + "ROOT_ID": {"children": ["TABS-xxx"], "type": "ROOT"}, + "GRID_ID": {"children": [], "type": "GRID", "parents": ["ROOT_ID"]}, + "TABS-xxx": {"children": ["TAB-a", "TAB-b"], "type": "TABS"}, + "TAB-a": {"children": [], "type": "TAB", "meta": {"text": "Overview"}}, + "TAB-b": {"children": [], "type": "TAB", "meta": {"text": "Details"}}, + } + assert _find_tab_insert_target(layout, target_tab="Details") == "TAB-b" + def test_find_tab_insert_target_no_grid(self): """Test _find_tab_insert_target with missing GRID_ID.""" assert _find_tab_insert_target({"ROOT_ID": {"type": "ROOT"}}) is None @@ -1014,6 +1332,56 @@ class TestLayoutHelpers: assert "ROW-new" in layout["TAB-first"]["children"] assert "ROW-new" not in layout["GRID_ID"]["children"] + def test_ensure_layout_structure_tabs_under_root_no_grid_added(self): + """Test _ensure_layout_structure does NOT add GRID_ID to ROOT_ID + when TABS already exists as a ROOT_ID child. + + Real Superset tabbed dashboards place TABS under ROOT_ID, not + GRID_ID. Adding GRID_ID as a sibling of TABS confuses the + frontend and makes charts invisible. + """ + layout = { + "ROOT_ID": {"children": ["TABS-xxx"], "type": "ROOT"}, + "GRID_ID": {"children": [], "type": "GRID", "parents": ["ROOT_ID"]}, + "TABS-xxx": { + "children": ["TAB-a", "TAB-b"], + "type": "TABS", + "parents": ["ROOT_ID"], + }, + "TAB-a": { + "children": ["ROW-existing"], + "type": "TAB", + "meta": {"text": "Overview"}, + "parents": ["ROOT_ID", "TABS-xxx"], + }, + "TAB-b": { + "children": [], + "type": "TAB", + "meta": {"text": "Details"}, + "parents": ["ROOT_ID", "TABS-xxx"], + }, + } + _ensure_layout_structure(layout, "ROW-new", "TAB-a") + + # Row added to the correct tab + assert "ROW-new" in layout["TAB-a"]["children"] + # GRID_ID must NOT be injected into ROOT_ID alongside TABS + assert "GRID_ID" not in layout["ROOT_ID"]["children"] + assert layout["ROOT_ID"]["children"] == ["TABS-xxx"] + + def test_ensure_layout_structure_no_tabs_adds_grid_to_root(self): + """Test _ensure_layout_structure still adds GRID_ID to ROOT_ID + when the dashboard has no tabs (non-tabbed dashboard regression check). + """ + layout = { + "ROOT_ID": {"children": [], "type": "ROOT"}, + "GRID_ID": {"children": [], "type": "GRID", "parents": ["ROOT_ID"]}, + } + _ensure_layout_structure(layout, "ROW-new", "GRID_ID") + + assert "GRID_ID" in layout["ROOT_ID"]["children"] + assert "ROW-new" in layout["GRID_ID"]["children"] + class TestGenerateTitleFromCharts: """Tests for _generate_title_from_charts helper.""" @@ -1065,3 +1433,106 @@ class TestGenerateTitleFromCharts: title = _generate_title_from_charts(charts) assert len(title) <= 150 assert title.endswith("\u2026") + + +class TestDashboardSerializationEagerLoading: + """Tests for eager loading fix in dashboard serialization paths.""" + + @patch("superset.models.dashboard.Dashboard") + @patch("superset.daos.dashboard.DashboardDAO.find_by_id") + @patch("superset.db.session") + @pytest.mark.asyncio + async def test_generate_dashboard_refetches_via_dao( + self, mock_db_session, mock_find_by_id, mock_dashboard_cls, mcp_server + ): + """generate_dashboard re-fetches dashboard via DashboardDAO.find_by_id + with eager-loaded slice relationships before serialization.""" + charts = [_mock_chart(id=1, slice_name="Refetched Chart")] + refetched_dashboard = _mock_dashboard(id=10) + refetched_dashboard.slices = charts + + _setup_generate_dashboard_mocks( + mock_db_session, + mock_find_by_id, + mock_dashboard_cls, + charts, + refetched_dashboard, + ) + + request = {"chart_ids": [1]} + + async with Client(mcp_server) as client: + result = await client.call_tool("generate_dashboard", {"request": request}) + + assert result.structured_content["error"] is None + # Verify DashboardDAO.find_by_id was called for re-fetch + mock_find_by_id.assert_called() + + @patch("superset.commands.dashboard.update.UpdateDashboardCommand") + @patch("superset.daos.dashboard.DashboardDAO.find_by_id") + @patch("superset.db.session") + @pytest.mark.asyncio + async def test_add_chart_refetches_dashboard_via_dao( + self, mock_db_session, mock_find_dashboard, mock_update_command, mcp_server + ): + """add_chart_to_existing_dashboard re-fetches dashboard via + DashboardDAO.find_by_id with eager-loaded slice relationships.""" + mock_dashboard = _mock_dashboard(id=1) + mock_dashboard.slices = [] + mock_dashboard.position_json = "{}" + + mock_chart = _mock_chart(id=5, slice_name="New Chart") + mock_db_session.get.return_value = mock_chart + + updated_dashboard = _mock_dashboard(id=1) + updated_dashboard.slices = [mock_chart] + mock_update_command.return_value.run.return_value = updated_dashboard + + # side_effect: first call returns initial dashboard (validation), + # second call returns updated dashboard (re-fetch with eager loading) + mock_find_dashboard.side_effect = [mock_dashboard, updated_dashboard] + + request = {"dashboard_id": 1, "chart_id": 5} + + async with Client(mcp_server) as client: + result = await client.call_tool( + "add_chart_to_existing_dashboard", {"request": request} + ) + + assert result.structured_content["error"] is None + # DashboardDAO.find_by_id called twice: validation + re-fetch + assert mock_find_dashboard.call_count == 2 + + @patch("superset.commands.dashboard.update.UpdateDashboardCommand") + @patch("superset.daos.dashboard.DashboardDAO.find_by_id") + @patch("superset.db.session") + @pytest.mark.asyncio + async def test_add_chart_falls_back_on_refetch_failure( + self, mock_db_session, mock_find_dashboard, mock_update_command, mcp_server + ): + """add_chart_to_existing_dashboard falls back to original dashboard + if DashboardDAO.find_by_id returns None on re-fetch.""" + mock_dashboard = _mock_dashboard(id=1) + mock_dashboard.slices = [] + mock_dashboard.position_json = "{}" + + mock_chart = _mock_chart(id=5, slice_name="New Chart") + mock_db_session.get.return_value = mock_chart + + updated_dashboard = _mock_dashboard(id=1) + updated_dashboard.slices = [mock_chart] + mock_update_command.return_value.run.return_value = updated_dashboard + + # side_effect: first call returns dashboard (validation), + # second call returns None (re-fetch fails, should fall back) + mock_find_dashboard.side_effect = [mock_dashboard, None] + + request = {"dashboard_id": 1, "chart_id": 5} + + async with Client(mcp_server) as client: + result = await client.call_tool( + "add_chart_to_existing_dashboard", {"request": request} + ) + + # Tool should still succeed using fallback dashboard + assert result.structured_content["error"] is None diff --git a/tests/unit_tests/mcp_service/dashboard/tool/test_dashboard_tools.py b/tests/unit_tests/mcp_service/dashboard/tool/test_dashboard_tools.py index fea9a05e3f1..e2af01c4e06 100644 --- a/tests/unit_tests/mcp_service/dashboard/tool/test_dashboard_tools.py +++ b/tests/unit_tests/mcp_service/dashboard/tool/test_dashboard_tools.py @@ -75,7 +75,6 @@ async def test_list_dashboards_basic(mock_list, mcp_server): dashboard.certified_by = None dashboard.certification_details = None dashboard.json_metadata = None - dashboard.position_json = None dashboard.is_managed_externally = False dashboard.external_url = None dashboard.uuid = "test-dashboard-uuid-1" @@ -142,7 +141,6 @@ async def test_list_dashboards_with_filters(mock_list, mcp_server): dashboard.certified_by = None dashboard.certification_details = None dashboard.json_metadata = None - dashboard.position_json = None dashboard.is_managed_externally = False dashboard.external_url = None dashboard.uuid = None @@ -236,7 +234,6 @@ async def test_list_dashboards_with_search(mock_list, mcp_server): dashboard.certified_by = None dashboard.certification_details = None dashboard.json_metadata = None - dashboard.position_json = None dashboard.is_managed_externally = False dashboard.external_url = None dashboard.uuid = None @@ -303,7 +300,6 @@ async def test_get_dashboard_info_success(mock_info, mcp_server): dashboard.certified_by = None dashboard.certification_details = None dashboard.json_metadata = None - dashboard.position_json = None dashboard.published = True dashboard.is_managed_externally = False dashboard.external_url = None @@ -383,7 +379,6 @@ async def test_get_dashboard_info_by_uuid(mock_find_object, mcp_server): dashboard.certified_by = None dashboard.certification_details = None dashboard.json_metadata = "{}" - dashboard.position_json = "{}" dashboard.published = True dashboard.is_managed_externally = False dashboard.external_url = None @@ -423,7 +418,6 @@ async def test_get_dashboard_info_by_slug(mock_find_object, mcp_server): dashboard.certified_by = None dashboard.certification_details = None dashboard.json_metadata = "{}" - dashboard.position_json = "{}" dashboard.published = True dashboard.is_managed_externally = False dashboard.external_url = None @@ -475,7 +469,6 @@ async def test_list_dashboards_custom_uuid_slug_columns(mock_list, mcp_server): dashboard.certified_by = None dashboard.certification_details = None dashboard.json_metadata = None - dashboard.position_json = None dashboard.is_managed_externally = False dashboard.external_url = None dashboard.thumbnail_url = None @@ -529,21 +522,23 @@ class TestDashboardDefaultColumnFiltering: DASHBOARD_DEFAULT_COLUMNS, ) - # Should have exactly 5 minimal columns - assert len(DASHBOARD_DEFAULT_COLUMNS) == 5 assert set(DASHBOARD_DEFAULT_COLUMNS) == { "id", "dashboard_title", "slug", + "description", + "certified_by", + "certification_details", "url", + "changed_on", "changed_on_humanized", } # Heavy columns should NOT be in defaults assert "charts" not in DASHBOARD_DEFAULT_COLUMNS assert "published" not in DASHBOARD_DEFAULT_COLUMNS - assert "json_metadata" not in DASHBOARD_DEFAULT_COLUMNS - assert "position_json" not in DASHBOARD_DEFAULT_COLUMNS + assert "native_filters" not in DASHBOARD_DEFAULT_COLUMNS + assert "cross_filters_enabled" not in DASHBOARD_DEFAULT_COLUMNS assert "uuid" not in DASHBOARD_DEFAULT_COLUMNS def test_empty_select_columns_default(self): @@ -587,7 +582,6 @@ class TestDashboardDefaultColumnFiltering: dashboard.certified_by = None dashboard.certification_details = None dashboard.json_metadata = None - dashboard.position_json = None dashboard.is_managed_externally = False dashboard.external_url = None dashboard.thumbnail_url = None @@ -610,8 +604,8 @@ class TestDashboardDefaultColumnFiltering: assert "changed_on_humanized" in data["columns_requested"] # Verify heavy columns are NOT in columns_loaded by default - assert "json_metadata" not in data["columns_loaded"] - assert "position_json" not in data["columns_loaded"] + assert "native_filters" not in data["columns_loaded"] + assert "cross_filters_enabled" not in data["columns_loaded"] class TestDashboardSortableColumns: diff --git a/tests/unit_tests/mcp_service/database/__init__.py b/tests/unit_tests/mcp_service/database/__init__.py new file mode 100644 index 00000000000..13a83393a91 --- /dev/null +++ b/tests/unit_tests/mcp_service/database/__init__.py @@ -0,0 +1,16 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. diff --git a/tests/unit_tests/mcp_service/database/tool/__init__.py b/tests/unit_tests/mcp_service/database/tool/__init__.py new file mode 100644 index 00000000000..13a83393a91 --- /dev/null +++ b/tests/unit_tests/mcp_service/database/tool/__init__.py @@ -0,0 +1,16 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. diff --git a/tests/unit_tests/mcp_service/database/tool/test_database_tools.py b/tests/unit_tests/mcp_service/database/tool/test_database_tools.py new file mode 100644 index 00000000000..78ae81c9962 --- /dev/null +++ b/tests/unit_tests/mcp_service/database/tool/test_database_tools.py @@ -0,0 +1,206 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + + +import logging +from unittest.mock import MagicMock, patch + +import pytest +from fastmcp import Client +from fastmcp.exceptions import ToolError + +from superset.mcp_service.app import mcp +from superset.mcp_service.database.schemas import ListDatabasesRequest +from superset.utils import json + +logging.basicConfig(level=logging.DEBUG) +logger = logging.getLogger(__name__) + + +def create_mock_database( + database_id: int = 1, + database_name: str = "examples", + backend: str = "postgresql", + expose_in_sqllab: bool = True, + allow_ctas: bool = False, + allow_cvas: bool = False, + allow_dml: bool = False, + allow_file_upload: bool = False, + allow_run_async: bool = False, +) -> MagicMock: + """Factory function to create mock database objects with sensible defaults.""" + database = MagicMock() + database.id = database_id + database.database_name = database_name + database.backend = backend + database.verbose_name = None + database.expose_in_sqllab = expose_in_sqllab + database.allow_ctas = allow_ctas + database.allow_cvas = allow_cvas + database.allow_dml = allow_dml + database.allow_file_upload = allow_file_upload + database.allow_run_async = allow_run_async + database.cache_timeout = None + database.configuration_method = "sqlalchemy_form" + database.force_ctas_schema = None + database.impersonate_user = False + database.is_managed_externally = False + database.external_url = None + database.extra = '{"metadata_params": {}, "engine_params": {}}' + database.uuid = f"test-database-uuid-{database_id}" + database.changed_by_name = "admin" + database.changed_by = None + database.changed_on = None + database.created_by_name = "admin" + database.created_by = None + database.created_on = None + database.owners = [] + return database + + +@pytest.fixture +def mcp_server(): + return mcp + + +@pytest.fixture(autouse=True) +def mock_auth(): + """Mock authentication for all tests.""" + from unittest.mock import Mock, patch + + with patch("superset.mcp_service.auth.get_user_from_request") as mock_get_user: + mock_user = Mock() + mock_user.id = 1 + mock_user.username = "admin" + mock_get_user.return_value = mock_user + yield mock_get_user + + +@patch("superset.daos.database.DatabaseDAO.list") +@pytest.mark.asyncio +async def test_list_databases_basic(mock_list, mcp_server): + """Test basic database listing functionality.""" + database = create_mock_database() + database._mapping = { + "id": database.id, + "database_name": database.database_name, + "backend": database.backend, + "expose_in_sqllab": database.expose_in_sqllab, + } + mock_list.return_value = ([database], 1) + async with Client(mcp_server) as client: + request = ListDatabasesRequest(page=1, page_size=10) + result = await client.call_tool( + "list_databases", {"request": request.model_dump()} + ) + assert result.content is not None + data = json.loads(result.content[0].text) + assert data["databases"] is not None + assert len(data["databases"]) == 1 + assert data["databases"][0]["id"] == 1 + assert data["databases"][0]["database_name"] == "examples" + + +@patch("superset.daos.database.DatabaseDAO.list") +@pytest.mark.asyncio +async def test_list_databases_with_search(mock_list, mcp_server): + """Test database listing with search functionality.""" + database = create_mock_database(database_name="production_db") + database._mapping = { + "id": database.id, + "database_name": database.database_name, + } + mock_list.return_value = ([database], 1) + async with Client(mcp_server) as client: + request = ListDatabasesRequest(page=1, page_size=10, search="production") + result = await client.call_tool( + "list_databases", {"request": request.model_dump()} + ) + assert result.content is not None + data = json.loads(result.content[0].text) + assert data["databases"] is not None + assert len(data["databases"]) == 1 + assert data["databases"][0]["database_name"] == "production_db" + + +@patch("superset.daos.database.DatabaseDAO.list") +@pytest.mark.asyncio +async def test_list_databases_with_filters(mock_list, mcp_server): + """Test database listing with filters.""" + database = create_mock_database(expose_in_sqllab=True) + database._mapping = { + "id": database.id, + "database_name": database.database_name, + "expose_in_sqllab": database.expose_in_sqllab, + } + mock_list.return_value = ([database], 1) + async with Client(mcp_server) as client: + request = ListDatabasesRequest( + page=1, + page_size=10, + filters=[ + {"col": "expose_in_sqllab", "opr": "eq", "value": True}, + ], + ) + result = await client.call_tool( + "list_databases", {"request": request.model_dump()} + ) + assert result.content is not None + data = json.loads(result.content[0].text) + assert data["databases"] is not None + assert len(data["databases"]) == 1 + + +@patch("superset.daos.database.DatabaseDAO.list") +@pytest.mark.asyncio +async def test_list_databases_api_error(mock_list, mcp_server): + """Test error handling when DAO raises an exception.""" + mock_list.side_effect = ToolError("Database error") + async with Client(mcp_server) as client: + request = ListDatabasesRequest(page=1, page_size=10) + with pytest.raises(ToolError) as excinfo: # noqa: PT012 + await client.call_tool("list_databases", {"request": request.model_dump()}) + assert "Database error" in str(excinfo.value) + + +@patch("superset.daos.database.DatabaseDAO.find_by_id") +@pytest.mark.asyncio +async def test_get_database_info_basic(mock_find, mcp_server): + """Test basic get database info functionality.""" + database = create_mock_database() + mock_find.return_value = database + async with Client(mcp_server) as client: + result = await client.call_tool( + "get_database_info", {"request": {"identifier": 1}} + ) + assert result.content is not None + data = json.loads(result.content[0].text) + assert data["id"] == 1 + assert data["database_name"] == "examples" + assert data["backend"] == "postgresql" + + +@patch("superset.daos.database.DatabaseDAO.find_by_id") +@pytest.mark.asyncio +async def test_get_database_info_not_found(mock_find, mcp_server): + """Test get database info when database does not exist.""" + mock_find.return_value = None + async with Client(mcp_server) as client: + result = await client.call_tool( + "get_database_info", {"request": {"identifier": 999}} + ) + assert result.data["error_type"] == "not_found" diff --git a/tests/unit_tests/mcp_service/dataset/tool/test_dataset_tools.py b/tests/unit_tests/mcp_service/dataset/tool/test_dataset_tools.py index 48742973863..c64801df22f 100644 --- a/tests/unit_tests/mcp_service/dataset/tool/test_dataset_tools.py +++ b/tests/unit_tests/mcp_service/dataset/tool/test_dataset_tools.py @@ -25,7 +25,10 @@ from fastmcp import Client from fastmcp.exceptions import ToolError from superset.mcp_service.app import mcp -from superset.mcp_service.dataset.schemas import ListDatasetsRequest +from superset.mcp_service.dataset.schemas import ( + CreateVirtualDatasetRequest, + ListDatasetsRequest, +) from superset.utils import json logging.basicConfig(level=logging.DEBUG) @@ -46,6 +49,8 @@ def create_mock_dataset( dataset.table_name = table_name dataset.schema = schema dataset.description = "desc" + dataset.certified_by = None + dataset.certification_details = None dataset.changed_by_name = "admin" dataset.changed_on = None dataset.changed_on_humanized = None @@ -106,6 +111,8 @@ async def test_list_datasets_basic(mock_list, mcp_server): dataset.table_name = "Test DatasetInfo" dataset.schema = "main" dataset.description = "desc" + dataset.certified_by = None + dataset.certification_details = None dataset.changed_by_name = "admin" dataset.changed_on = None dataset.changed_on_humanized = None @@ -212,6 +219,8 @@ async def test_list_datasets_custom_uuid_columns(mock_list, mcp_server): dataset.table_name = "custom_dataset" dataset.schema = "public" dataset.description = "desc" + dataset.certified_by = None + dataset.certification_details = None dataset.changed_by_name = "admin" dataset.changed_on = None dataset.changed_on_humanized = None @@ -290,6 +299,8 @@ async def test_list_datasets_with_filters(mock_list, mcp_server): dataset.table_name = "Filtered Dataset" dataset.schema = "main" dataset.description = "desc" + dataset.certified_by = None + dataset.certification_details = None dataset.changed_by_name = "admin" dataset.changed_on = None dataset.changed_on_humanized = None @@ -391,6 +402,8 @@ async def test_list_datasets_with_string_filters(mock_list, mcp_server): dataset.table_name = "String Filter Dataset" dataset.schema = "main" dataset.description = "desc" + dataset.certified_by = None + dataset.certification_details = None dataset.changed_by_name = "admin" dataset.changed_on = None dataset.changed_on_humanized = None @@ -468,6 +481,8 @@ async def test_list_datasets_with_search(mock_list, mcp_server): dataset.database_name = "test_db" dataset.database = None dataset.description = "A test dataset" + dataset.certified_by = None + dataset.certification_details = None dataset.changed_by = "admin" dataset.changed_by_name = "admin" dataset.changed_on = None @@ -559,6 +574,8 @@ async def test_list_datasets_simple_with_search(mock_list, mcp_server): dataset.database_name = "analytics_db" dataset.database = None dataset.description = "Another test dataset" + dataset.certified_by = None + dataset.certification_details = None dataset.changed_by = "user" dataset.changed_by_name = "user" dataset.changed_on = None @@ -648,6 +665,8 @@ async def test_list_datasets_simple_basic(mock_list, mcp_server): dataset.table_name = "Test DatasetInfo" dataset.schema = "main" dataset.description = "desc" + dataset.certified_by = None + dataset.certification_details = None dataset.changed_by_name = "admin" dataset.changed_on = None dataset.changed_on_humanized = None @@ -743,6 +762,8 @@ async def test_list_datasets_simple_with_filters(mock_list, mcp_server): dataset.table_name = "Sales Dataset" dataset.schema = "main" dataset.description = "desc" + dataset.certified_by = None + dataset.certification_details = None dataset.changed_by_name = "admin" dataset.changed_on = None dataset.changed_on_humanized = None @@ -853,6 +874,8 @@ async def test_get_dataset_info_success(mock_info, mcp_server): dataset.table_name = "Test DatasetInfo" dataset.schema = "main" dataset.description = "desc" + dataset.certified_by = None + dataset.certification_details = None dataset.changed_by_name = "admin" dataset.changed_on = None dataset.changed_on_humanized = None @@ -990,6 +1013,8 @@ async def test_get_dataset_info_includes_columns_and_metrics(mock_info, mcp_serv dataset.database = MagicMock() dataset.database.database_name = "examples" dataset.description = "desc" + dataset.certified_by = None + dataset.certification_details = None dataset.changed_by_name = "admin" dataset.changed_on = None dataset.changed_on_humanized = None @@ -1080,6 +1105,8 @@ async def test_list_datasets_includes_columns_and_metrics(mock_list, mcp_server) dataset.database = MagicMock() dataset.database.database_name = "examples" dataset.description = "desc" + dataset.certified_by = None + dataset.certification_details = None dataset.changed_by_name = "admin" dataset.changed_on = None dataset.changed_on_humanized = None @@ -1154,6 +1181,8 @@ async def test_get_dataset_info_by_uuid(mock_find_object, mcp_server): dataset.table_name = "Test Dataset UUID" dataset.schema = "main" dataset.description = "desc" + dataset.certified_by = None + dataset.certification_details = None dataset.changed_by_name = "admin" dataset.changed_on = None dataset.changed_on_humanized = None @@ -1190,6 +1219,36 @@ async def test_get_dataset_info_by_uuid(mock_find_object, mcp_server): assert data["table_name"] == "Test Dataset UUID" +class TestDatasetCertificationSerialization: + """Test certification fields flow through dataset serialization.""" + + def test_serialize_dataset_with_certification_fields(self): + """Serializes non-None certification values.""" + from superset.mcp_service.dataset.schemas import serialize_dataset_object + + dataset = create_mock_dataset() + dataset.certified_by = "Analytics Engineering" + dataset.certification_details = "Production-ready, SLA-backed" + + result = serialize_dataset_object(dataset) + + assert result is not None + assert result.certified_by == "Analytics Engineering" + assert result.certification_details == "Production-ready, SLA-backed" + + def test_serialize_dataset_with_none_certification(self): + """serialize_dataset_object handles None certification fields.""" + from superset.mcp_service.dataset.schemas import serialize_dataset_object + + dataset = create_mock_dataset() + + result = serialize_dataset_object(dataset) + + assert result is not None + assert result.certified_by is None + assert result.certification_details is None + + class TestDatasetDefaultColumnFiltering: """Test default column filtering behavior for datasets.""" @@ -1197,12 +1256,14 @@ class TestDatasetDefaultColumnFiltering: """Test that minimal default columns are properly defined.""" from superset.mcp_service.common.schema_discovery import DATASET_DEFAULT_COLUMNS - # Should have exactly 4 minimal columns - assert len(DATASET_DEFAULT_COLUMNS) == 4 assert set(DATASET_DEFAULT_COLUMNS) == { "id", "table_name", "schema", + "description", + "certified_by", + "certification_details", + "changed_on", "changed_on_humanized", } assert "uuid" not in DATASET_DEFAULT_COLUMNS @@ -1210,7 +1271,6 @@ class TestDatasetDefaultColumnFiltering: # Heavy columns should NOT be in defaults assert "columns" not in DATASET_DEFAULT_COLUMNS assert "metrics" not in DATASET_DEFAULT_COLUMNS - assert "description" not in DATASET_DEFAULT_COLUMNS assert "database_name" not in DATASET_DEFAULT_COLUMNS @patch("superset.daos.dataset.DatasetDAO.list") @@ -1236,6 +1296,12 @@ class TestDatasetDefaultColumnFiltering: "id", "table_name", "schema", + "database_name", + "database", + "description", + "certified_by", + "certification_details", + "changed_on", "changed_on_humanized", } @@ -1319,7 +1385,21 @@ class TestDatasetDefaultColumnFiltering: dataset_item = data["datasets"][0] # Verify ONLY default columns are present in the response item - expected_keys = {"id", "table_name", "schema", "changed_on_humanized"} + # Note: "database" is in DEFAULT_DATASET_COLUMNS to trigger eager + # loading of the relationship, but it's not a field on DatasetInfo + # so it won't appear in the serialized output. "database_name" IS + # a field and will appear. + expected_keys = { + "id", + "table_name", + "schema", + "database_name", + "description", + "certified_by", + "certification_details", + "changed_on", + "changed_on_humanized", + } actual_keys = set(dataset_item.keys()) # The response should only contain the default columns, NOT all columns @@ -1332,10 +1412,7 @@ class TestDatasetDefaultColumnFiltering: # Verify non-default columns are NOT present (not even with null values) non_default_columns = [ - "description", - "database_name", "changed_by", - "changed_on", "columns", "metrics", ] @@ -1432,3 +1509,291 @@ class TestDatasetSortableColumns: data = json.loads(result.content[0].text) assert data["count"] == 0 assert data["datasets"] == [] + + +# --------------------------------------------------------------------------- +# create_virtual_dataset tests +# --------------------------------------------------------------------------- + + +def _make_mock_db(database_name: str = "examples") -> MagicMock: + """Create a mock database object with a configurable database name.""" + db = MagicMock() + db.database_name = database_name + return db + + +def _make_mock_virtual_dataset( + id: int = 21, + table_name: str = "Customer Revenue", + column_names: list[str] | None = None, +) -> MagicMock: + """Create a mock virtual dataset object with configurable columns.""" + if column_names is None: + column_names = ["name", "revenue"] + dataset = MagicMock() + dataset.id = id + dataset.table_name = table_name + dataset.columns = [MagicMock(column_name=c) for c in column_names] + return dataset + + +# --- Schema tests --- + + +def test_create_virtual_dataset_request_valid() -> None: + req = CreateVirtualDatasetRequest( + database_id=1, + sql="SELECT a.name, COUNT(*) FROM a JOIN b ON a.id = b.a_id GROUP BY a.name", + dataset_name="Customer Revenue", + ) + assert req.database_id == 1 + assert req.dataset_name == "Customer Revenue" + assert req.schema_name is None + + +def test_create_virtual_dataset_request_empty_sql_fails() -> None: + from pydantic import ValidationError + + with pytest.raises(ValidationError, match="sql must not be empty"): + CreateVirtualDatasetRequest(database_id=1, sql=" ", dataset_name="Test") + + +def test_create_virtual_dataset_request_empty_name_fails() -> None: + from pydantic import ValidationError + + with pytest.raises(ValidationError, match="dataset_name must not be empty"): + CreateVirtualDatasetRequest(database_id=1, sql="SELECT 1", dataset_name=" ") + + +def test_create_virtual_dataset_request_name_too_long() -> None: + from pydantic import ValidationError + + with pytest.raises(ValidationError): + CreateVirtualDatasetRequest( + database_id=1, sql="SELECT 1", dataset_name="a" * 251 + ) + + +def test_create_virtual_dataset_request_optional_fields() -> None: + req = CreateVirtualDatasetRequest( + database_id=1, + sql="SELECT 1", + dataset_name="Test", + schema_name="public", + catalog="main", + description="A virtual dataset", + ) + assert req.schema_name == "public" + assert req.catalog == "main" + assert req.description == "A virtual dataset" + + +# --- Tool logic tests --- + + +@pytest.mark.asyncio +async def test_create_virtual_dataset_success(mcp_server: object) -> None: + """Happy path: dataset created, columns and URL returned.""" + mock_dataset = _make_mock_virtual_dataset( + id=21, table_name="Customer Revenue", column_names=["name", "revenue"] + ) + mock_command = MagicMock() + mock_command.run.return_value = mock_dataset + + with ( + patch( + "superset.commands.dataset.create.CreateDatasetCommand", + return_value=mock_command, + ), + patch( + "superset.mcp_service.utils.url_utils.get_superset_base_url", + return_value="http://localhost:8088", + ), + ): + async with Client(mcp_server) as client: + sql = ( + "SELECT a.name, SUM(b.revenue) FROM a" + " JOIN b ON a.id = b.a_id GROUP BY a.name" + ) + request = CreateVirtualDatasetRequest( + database_id=1, + sql=sql, + dataset_name="Customer Revenue", + ) + result = await client.call_tool( + "create_virtual_dataset", {"request": request.model_dump()} + ) + data = json.loads(result.content[0].text) + + assert data["id"] == 21 + assert data["dataset_name"] == "Customer Revenue" + assert data["columns"] == ["name", "revenue"] + assert "datasource_type=table" in data["url"] + assert "datasource_id=21" in data["url"] + assert data["error"] is None + + +@pytest.mark.asyncio +async def test_create_virtual_dataset_db_not_found(mcp_server: object) -> None: + """When the database ID does not exist, CreateDatasetCommand raises + DatasetInvalidError containing DatabaseNotFoundValidationError.""" + from superset.commands.dataset.exceptions import ( + DatabaseNotFoundValidationError, + DatasetInvalidError, + ) + + invalid_exc = DatasetInvalidError() + invalid_exc.append(DatabaseNotFoundValidationError()) + mock_command = MagicMock() + mock_command.run.side_effect = invalid_exc + + with patch( + "superset.commands.dataset.create.CreateDatasetCommand", + return_value=mock_command, + ): + async with Client(mcp_server) as client: + request = CreateVirtualDatasetRequest( + database_id=999, sql="SELECT 1", dataset_name="Test" + ) + result = await client.call_tool( + "create_virtual_dataset", {"request": request.model_dump()} + ) + data = json.loads(result.content[0].text) + + assert data["id"] is None + assert data["columns"] == [] + assert data["error"] is not None + + +@pytest.mark.asyncio +async def test_create_virtual_dataset_invalid_error(mcp_server: object) -> None: + """DatasetInvalidError is caught and returned as an error response.""" + from marshmallow.exceptions import ValidationError as MarshmallowValidationError + + from superset.commands.dataset.exceptions import DatasetInvalidError + + invalid_exc = DatasetInvalidError() + invalid_exc.append( + MarshmallowValidationError( + {"table_name": ["Dataset with this name already exists"]} + ) + ) + mock_command = MagicMock() + mock_command.run.side_effect = invalid_exc + + with patch( + "superset.commands.dataset.create.CreateDatasetCommand", + return_value=mock_command, + ): + async with Client(mcp_server) as client: + request = CreateVirtualDatasetRequest( + database_id=1, sql="SELECT 1", dataset_name="Test" + ) + result = await client.call_tool( + "create_virtual_dataset", {"request": request.model_dump()} + ) + data = json.loads(result.content[0].text) + + assert data["id"] is None + assert data["columns"] == [] + assert data["error"] is not None + + +@pytest.mark.asyncio +async def test_create_virtual_dataset_create_failed(mcp_server: object) -> None: + """DatasetCreateFailedError is caught and returned as an error response.""" + from superset.commands.dataset.exceptions import DatasetCreateFailedError + + mock_command = MagicMock() + mock_command.run.side_effect = DatasetCreateFailedError() + + with patch( + "superset.commands.dataset.create.CreateDatasetCommand", + return_value=mock_command, + ): + async with Client(mcp_server) as client: + request = CreateVirtualDatasetRequest( + database_id=1, sql="SELECT 1", dataset_name="Test" + ) + result = await client.call_tool( + "create_virtual_dataset", {"request": request.model_dump()} + ) + data = json.loads(result.content[0].text) + + assert data["id"] is None + assert data["columns"] == [] + assert data["error"] is not None + assert "Failed to create dataset" in data["error"] + + +@pytest.mark.asyncio +async def test_create_virtual_dataset_permission_denied(mcp_server: object) -> None: + """SQL access denied surfaces as DatasetInvalidError with id=None.""" + from superset.commands.dataset.exceptions import ( + DatasetDataAccessIsNotAllowed, + DatasetInvalidError, + ) + + invalid_exc = DatasetInvalidError() + invalid_exc.append(DatasetDataAccessIsNotAllowed("Access denied to schema public")) + mock_command = MagicMock() + mock_command.run.side_effect = invalid_exc + + with patch( + "superset.commands.dataset.create.CreateDatasetCommand", + return_value=mock_command, + ): + async with Client(mcp_server) as client: + request = CreateVirtualDatasetRequest( + database_id=1, + sql="SELECT * FROM sensitive_table", + dataset_name="Restricted", + ) + result = await client.call_tool( + "create_virtual_dataset", {"request": request.model_dump()} + ) + data = json.loads(result.content[0].text) + + assert data["id"] is None + assert data["columns"] == [] + assert data["error"] is not None + + +@pytest.mark.asyncio +async def test_create_virtual_dataset_optional_fields_forwarded( + mcp_server: object, +) -> None: + """schema_name, catalog, and description are forwarded to CreateDatasetCommand.""" + mock_dataset = _make_mock_virtual_dataset(column_names=["col1"]) + mock_command_instance = MagicMock() + mock_command_instance.run.return_value = mock_dataset + mock_command_cls = MagicMock(return_value=mock_command_instance) + + with ( + patch( + "superset.commands.dataset.create.CreateDatasetCommand", + mock_command_cls, + ), + patch( + "superset.mcp_service.utils.url_utils.get_superset_base_url", + return_value="http://localhost:8088", + ), + ): + async with Client(mcp_server) as client: + request = CreateVirtualDatasetRequest( + database_id=1, + sql="SELECT col1 FROM t", + dataset_name="My Dataset", + schema="public", + catalog="main", + description="A test dataset", + ) + await client.call_tool( + "create_virtual_dataset", {"request": request.model_dump()} + ) + + props = mock_command_cls.call_args[0][0] + assert props["schema"] == "public" + assert props["catalog"] == "main" + assert props["description"] == "A test dataset" diff --git a/tests/unit_tests/mcp_service/explore/tool/test_generate_explore_link.py b/tests/unit_tests/mcp_service/explore/tool/test_generate_explore_link.py index 0a8771e48ba..fb8aee539b0 100644 --- a/tests/unit_tests/mcp_service/explore/tool/test_generate_explore_link.py +++ b/tests/unit_tests/mcp_service/explore/tool/test_generate_explore_link.py @@ -312,15 +312,19 @@ class TestGenerateExploreLink: ) mock_create_form_data.assert_called_once() + @patch("superset.daos.dataset.DatasetDAO.find_by_id") @patch( "superset.mcp_service.commands.create_form_data.MCPCreateFormDataCommand.run" ) @pytest.mark.asyncio async def test_generate_explore_link_cache_failure_fallback( - self, mock_create_form_data, mcp_server + self, mock_create_form_data, mock_find_dataset, mcp_server ): """Test fallback when form_data cache creation fails.""" - mock_create_form_data.side_effect = Exception("Cache storage failed") + mock_find_dataset.return_value = _mock_dataset(id=1) + from superset.commands.exceptions import CommandException + + mock_create_form_data.side_effect = CommandException("Cache storage failed") config = TableChartConfig( chart_type="table", columns=[ColumnRef(name="test_col")] @@ -339,16 +343,18 @@ class TestGenerateExploreLink: == "http://localhost:9001/explore/?datasource_type=table&datasource_id=1" ) + @patch("superset.daos.dataset.DatasetDAO.find_by_id") @patch( "superset.mcp_service.commands.create_form_data.MCPCreateFormDataCommand.run" ) @pytest.mark.asyncio async def test_generate_explore_link_database_lock_fallback( - self, mock_create_form_data, mcp_server + self, mock_create_form_data, mock_find_dataset, mcp_server ): """Test fallback when database is locked.""" from sqlalchemy.exc import OperationalError + mock_find_dataset.return_value = _mock_dataset(id=5) mock_create_form_data.side_effect = OperationalError( "database is locked", None, None ) @@ -584,15 +590,19 @@ class TestGenerateExploreLink: ) mock_create_form_data.assert_called_once() + @patch("superset.daos.dataset.DatasetDAO.find_by_id") @patch( "superset.mcp_service.commands.create_form_data.MCPCreateFormDataCommand.run" ) @pytest.mark.asyncio async def test_fallback_url_different_datasets( - self, mock_create_form_data, mcp_server + self, mock_create_form_data, mock_find_dataset, mcp_server ): """Test fallback URLs are correct for different dataset IDs.""" - mock_create_form_data.side_effect = Exception( + mock_find_dataset.return_value = _mock_dataset(id=1) + from superset.commands.exceptions import CommandException + + mock_create_form_data.side_effect = CommandException( "Always fail for fallback testing" ) @@ -612,9 +622,13 @@ class TestGenerateExploreLink: assert result.data["error"] is None assert result.data["url"] == expected_url + @patch("superset.daos.dataset.DatasetDAO.find_by_id") @pytest.mark.asyncio - async def test_generate_explore_link_tool_exception_handling(self, mcp_server): + async def test_generate_explore_link_tool_exception_handling( + self, mock_find_dataset, mcp_server + ): """Test that tool-level exceptions are properly handled and return error.""" + mock_find_dataset.return_value = _mock_dataset(id=1) import sys # Get the actual module object from sys.modules (not via __init__.py which @@ -708,6 +722,55 @@ class TestGenerateExploreLink: # Verify datasource field format: "{dataset_id}__table" assert result.data["form_data"].get("datasource") == "1__table" + @patch("superset.daos.dataset.DatasetDAO.find_by_id") + @pytest.mark.asyncio + async def test_generate_explore_link_nonexistent_dataset( + self, mock_find_dataset, mcp_server + ): + """Test nonexistent dataset_id returns error instead of broken URL.""" + mock_find_dataset.return_value = None + + config = TableChartConfig( + chart_type="table", columns=[ColumnRef(name="test_col")] + ) + request = GenerateExploreLinkRequest(dataset_id="99999", config=config) + + async with Client(mcp_server) as client: + result = await client.call_tool( + "generate_explore_link", {"request": request.model_dump()} + ) + + assert result.data["url"] == "" + assert result.data["form_data"] == {} + assert result.data["form_data_key"] is None + assert "Dataset not found: 99999" in result.data["error"] + assert "list_datasets" in result.data["error"] + + @patch("superset.daos.dataset.DatasetDAO.find_by_id") + @pytest.mark.asyncio + async def test_generate_explore_link_nonexistent_uuid_dataset( + self, mock_find_dataset, mcp_server + ): + """Test that nonexistent UUID dataset_id returns structured error.""" + mock_find_dataset.return_value = None + + config = TableChartConfig( + chart_type="table", columns=[ColumnRef(name="test_col")] + ) + request = GenerateExploreLinkRequest( + dataset_id="00000000-0000-0000-0000-000000000000", config=config + ) + + async with Client(mcp_server) as client: + result = await client.call_tool( + "generate_explore_link", {"request": request.model_dump()} + ) + + assert result.data["url"] == "" + assert result.data["form_data"] == {} + assert result.data["form_data_key"] is None + assert "Dataset not found" in result.data["error"] + class TestGenerateExploreLinkColumnNormalization: """Tests that generate_explore_link normalizes column names. @@ -816,8 +879,12 @@ class TestGenerateExploreLinkColumnNormalization: assert form_data["x_axis"] == "OrderDate" # filter subject normalized to match x-axis adhoc_filters = form_data.get("adhoc_filters", []) - assert len(adhoc_filters) == 1 + # User filter + auto-added TEMPORAL_RANGE for temporal x-axis + assert len(adhoc_filters) == 2 assert adhoc_filters[0]["subject"] == "OrderDate" + assert adhoc_filters[0]["operator"] == ">" + assert adhoc_filters[1]["operator"] == "TEMPORAL_RANGE" + assert adhoc_filters[1]["subject"] == "OrderDate" @patch( "superset.mcp_service.chart.validation.dataset_validator.DatasetValidator._get_dataset_context" diff --git a/tests/unit_tests/mcp_service/sql_lab/tool/test_execute_sql.py b/tests/unit_tests/mcp_service/sql_lab/tool/test_execute_sql.py index 764e1bf2cee..e1d3dee8e39 100644 --- a/tests/unit_tests/mcp_service/sql_lab/tool/test_execute_sql.py +++ b/tests/unit_tests/mcp_service/sql_lab/tool/test_execute_sql.py @@ -23,6 +23,7 @@ and response conversion logic. """ import logging +from decimal import Decimal from typing import Any from unittest.mock import MagicMock, Mock, patch @@ -33,6 +34,7 @@ from fastmcp.exceptions import ToolError from superset_core.queries.types import QueryResult, QueryStatus, StatementResult from superset.mcp_service.app import mcp +from superset.mcp_service.sql_lab.schemas import ColumnInfo logging.basicConfig(level=logging.DEBUG) logger = logging.getLogger(__name__) @@ -236,7 +238,7 @@ class TestExecuteSql: mock_security_manager, # noqa: PT019 mcp_server, ): - """Test error when database is not found.""" + """Test graceful error when database is not found.""" # mock_security_manager is patched but not used (error happens first) del mock_security_manager # Silence unused variable warning mock_db.session.query.return_value.filter_by.return_value.first.return_value = ( @@ -250,8 +252,10 @@ class TestExecuteSql: } async with Client(mcp_server) as client: - with pytest.raises(ToolError, match="Database with ID 999 not found"): - await client.call_tool("execute_sql", {"request": request}) + result = await client.call_tool("execute_sql", {"request": request}) + data = result.structured_content + assert data["success"] is False + assert "Database with ID 999 not found" in data["error"] @patch("superset.security_manager", new_callable=MagicMock) @patch("superset.db") @@ -273,8 +277,10 @@ class TestExecuteSql: } async with Client(mcp_server) as client: - with pytest.raises(ToolError, match="Access denied to database"): - await client.call_tool("execute_sql", {"request": request}) + result = await client.call_tool("execute_sql", {"request": request}) + data = result.structured_content + assert data["success"] is False + assert "Access denied to database" in data["error"] @patch("superset.security_manager") @patch("superset.db") @@ -883,3 +889,332 @@ class TestExecuteSql: options = call_args[0][1] assert options.cache is not None assert options.cache.force_refresh is True + + @patch("superset.security_manager") + @patch("superset.db") + @pytest.mark.asyncio + async def test_execute_sql_bytes_in_dataframe( + self, mock_db, mock_security_manager, mcp_server + ): + """Test that bytes/memoryview values in DataFrame are sanitized for JSON. + + Regression test: execute_sql fails with 'encoding without a string + argument' when queries return binary/bytea data. + """ + mock_database = _mock_database() + df = pd.DataFrame( + [ + { + "id": 1, + "name": "test", + "utf8_data": b"hello world", + "binary_data": b"\x00\x01\x02\xff", + }, + ] + ) + mock_database.execute.return_value = QueryResult( + status=QueryStatus.SUCCESS, + statements=[ + StatementResult( + original_sql="SELECT * FROM files", + executed_sql="SELECT * FROM files", + data=df, + row_count=1, + execution_time_ms=5.0, + ) + ], + query_id=None, + total_execution_time_ms=5.0, + is_cached=False, + ) + mock_db.session.query.return_value.filter_by.return_value.first.return_value = ( + mock_database + ) + mock_security_manager.can_access_database.return_value = True + + request = { + "database_id": 1, + "sql": "SELECT * FROM files", + "limit": 10, + } + + async with Client(mcp_server) as client: + result = await client.call_tool("execute_sql", {"request": request}) + + data = result.structured_content + assert data["success"] is True + assert data["row_count"] == 1 + row = data["rows"][0] + # UTF-8 decodable bytes should become string + assert row["utf8_data"] == "hello world" + # Non-UTF-8 bytes should become hex + assert row["binary_data"] == "000102ff" + + @patch("superset.security_manager") + @patch("superset.db") + @pytest.mark.asyncio + async def test_execute_sql_decimal_in_dataframe( + self, mock_db, mock_security_manager, mcp_server + ): + """Test that Decimal values in DataFrame are converted to float for JSON. + + Regression test: execute_sql fails with 'encoding without a string + argument' when queries return Decimal types (common with SUM/AVG). + """ + mock_database = _mock_database() + df = pd.DataFrame( + [ + { + "id": 1, + "price": Decimal("19.99"), + "total": Decimal("1234567.89"), + }, + ] + ) + mock_database.execute.return_value = QueryResult( + status=QueryStatus.SUCCESS, + statements=[ + StatementResult( + original_sql="SELECT * FROM orders", + executed_sql="SELECT * FROM orders", + data=df, + row_count=1, + execution_time_ms=5.0, + ) + ], + query_id=None, + total_execution_time_ms=5.0, + is_cached=False, + ) + mock_db.session.query.return_value.filter_by.return_value.first.return_value = ( + mock_database + ) + mock_security_manager.can_access_database.return_value = True + + request = { + "database_id": 1, + "sql": "SELECT * FROM orders", + "limit": 10, + } + + async with Client(mcp_server) as client: + result = await client.call_tool("execute_sql", {"request": request}) + + data = result.structured_content + assert data["success"] is True + assert data["row_count"] == 1 + row = data["rows"][0] + assert row["price"] == 19.99 + assert row["total"] == 1234567.89 + assert isinstance(row["price"], float) + + +class TestSanitizeRowValues: + """Unit tests for _sanitize_row_values helper function.""" + + def test_sanitize_utf8_bytes(self): + from superset.mcp_service.sql_lab.tool.execute_sql import _sanitize_row_values + + rows = [{"data": b"hello"}] + _sanitize_row_values(rows) + assert rows[0]["data"] == "hello" + + def test_sanitize_non_utf8_bytes(self): + from superset.mcp_service.sql_lab.tool.execute_sql import _sanitize_row_values + + rows = [{"data": b"\x00\xff"}] + _sanitize_row_values(rows) + assert rows[0]["data"] == "00ff" + + def test_sanitize_memoryview(self): + from superset.mcp_service.sql_lab.tool.execute_sql import _sanitize_row_values + + rows = [{"data": memoryview(b"test")}] + _sanitize_row_values(rows) + assert rows[0]["data"] == "test" + + def test_sanitize_decimal(self): + from superset.mcp_service.sql_lab.tool.execute_sql import _sanitize_row_values + + rows = [{"price": Decimal("19.99"), "count": Decimal("42")}] + _sanitize_row_values(rows) + assert rows[0]["price"] == 19.99 + assert isinstance(rows[0]["price"], float) + assert rows[0]["count"] == 42.0 + + def test_sanitize_custom_type_uses_str(self): + from superset.mcp_service.sql_lab.tool.execute_sql import _sanitize_row_values + + class CustomType: + def __str__(self): + return "custom_value" + + rows = [{"data": CustomType()}] + _sanitize_row_values(rows) + assert rows[0]["data"] == "custom_value" + + def test_preserves_json_serializable_types(self): + from superset.mcp_service.sql_lab.tool.execute_sql import _sanitize_row_values + + rows = [ + { + "str_val": "hello", + "int_val": 42, + "float_val": 3.14, + "bool_val": True, + "none_val": None, + "list_val": [1, 2], + "dict_val": {"a": 1}, + } + ] + original = [dict(row) for row in rows] + _sanitize_row_values(rows) + assert rows == original + + def test_sanitize_empty_rows(self): + from superset.mcp_service.sql_lab.tool.execute_sql import _sanitize_row_values + + rows: list[dict[str, Any]] = [] + _sanitize_row_values(rows) + assert rows == [] + + def test_sanitize_mixed_types_in_single_row(self): + from superset.mcp_service.sql_lab.tool.execute_sql import _sanitize_row_values + + rows = [ + { + "id": 1, + "name": "test", + "price": Decimal("9.99"), + "blob": b"\x00\x01\x02\xff", + } + ] + _sanitize_row_values(rows) + assert rows[0]["id"] == 1 + assert rows[0]["name"] == "test" + assert rows[0]["price"] == 9.99 + assert rows[0]["blob"] == "000102ff" + + +class TestExecuteSqlOAuth2: + """Tests for OAuth2 error handling in execute_sql.""" + + @patch("superset.security_manager") + @patch("superset.db") + @pytest.mark.asyncio + async def test_execute_sql_oauth2_redirect_error( + self, mock_db, mock_security_manager, mcp_server + ): + """Test that OAuth2RedirectError is caught and returns a clear message.""" + from superset.exceptions import OAuth2RedirectError + + mock_database = _mock_database() + mock_database.execute.side_effect = OAuth2RedirectError( + url="https://oauth.example.com/authorize", + tab_id="test-tab-id", + redirect_uri="https://superset.example.com/callback", + ) + mock_db.session.query.return_value.filter_by.return_value.first.return_value = ( + mock_database + ) + mock_security_manager.can_access_database.return_value = True + + request = { + "database_id": 1, + "sql": "SELECT 1", + "limit": 100, + } + + async with Client(mcp_server) as client: + result = await client.call_tool("execute_sql", {"request": request}) + + data = result.structured_content + assert data["success"] is False + assert "OAuth" in data["error"] + assert "https://oauth.example.com/authorize" in data["error"] + assert data["error_type"] == "OAUTH2_REDIRECT" + + @patch("superset.security_manager") + @patch("superset.db") + @pytest.mark.asyncio + async def test_execute_sql_oauth2_error( + self, mock_db, mock_security_manager, mcp_server + ): + """Test that OAuth2Error is caught and returns a clear message.""" + from superset.exceptions import OAuth2Error + + mock_database = _mock_database() + mock_database.execute.side_effect = OAuth2Error( + "Unable to determine the OAuth2 redirect URI." + ) + mock_db.session.query.return_value.filter_by.return_value.first.return_value = ( + mock_database + ) + mock_security_manager.can_access_database.return_value = True + + request = { + "database_id": 1, + "sql": "SELECT 1", + "limit": 100, + } + + async with Client(mcp_server) as client: + result = await client.call_tool("execute_sql", {"request": request}) + + data = result.structured_content + assert data["success"] is False + assert "configuration" in data["error"] + assert data["error_type"] == "OAUTH2_REDIRECT_ERROR" + + +class TestColumnInfoIsNullable: + """Tests for ColumnInfo.is_nullable coercion (Athena returns 'UNKNOWN').""" + + def test_unknown_string_becomes_none(self): + assert ( + ColumnInfo(name="c", type="int", is_nullable="UNKNOWN").is_nullable is None + ) + + def test_arbitrary_string_becomes_none(self): + assert ColumnInfo(name="c", type="int", is_nullable="maybe").is_nullable is None + + def test_true_bool(self): + assert ColumnInfo(name="c", type="int", is_nullable=True).is_nullable is True + + def test_false_bool(self): + assert ColumnInfo(name="c", type="int", is_nullable=False).is_nullable is False + + def test_none(self): + assert ColumnInfo(name="c", type="int", is_nullable=None).is_nullable is None + + def test_default_is_none(self): + assert ColumnInfo(name="c", type="int").is_nullable is None + + def test_true_string(self): + assert ColumnInfo(name="c", type="int", is_nullable="true").is_nullable is True + + def test_false_string(self): + assert ( + ColumnInfo(name="c", type="int", is_nullable="false").is_nullable is False + ) + + def test_one_string(self): + assert ColumnInfo(name="c", type="int", is_nullable="1").is_nullable is True + + def test_zero_string(self): + assert ColumnInfo(name="c", type="int", is_nullable="0").is_nullable is False + + def test_integer_one(self): + assert ColumnInfo(name="c", type="int", is_nullable=1).is_nullable is True + + def test_integer_zero(self): + assert ColumnInfo(name="c", type="int", is_nullable=0).is_nullable is False + + def test_integer_two_becomes_none(self): + assert ColumnInfo(name="c", type="int", is_nullable=2).is_nullable is None + + def test_model_validate_unknown(self): + col = ColumnInfo.model_validate( + {"name": "c", "type": "int", "is_nullable": "UNKNOWN"} + ) + assert col.is_nullable is None diff --git a/tests/unit_tests/mcp_service/sql_lab/tool/test_save_sql_query.py b/tests/unit_tests/mcp_service/sql_lab/tool/test_save_sql_query.py index 469ca9fd43c..9bffcf66c88 100644 --- a/tests/unit_tests/mcp_service/sql_lab/tool/test_save_sql_query.py +++ b/tests/unit_tests/mcp_service/sql_lab/tool/test_save_sql_query.py @@ -228,9 +228,8 @@ class TestSaveSqlQueryToolLogic: from superset import db, etc.). We patch at the import source so that when the function runs, it picks up our mocks. - The @parse_request decorator injects ctx via get_context() and strips - __wrapped__, so we mock get_context and call the decorated function - directly (without unwrapping). + With passthrough decorators, we call the tool function directly + and pass a mock Context as the second argument. """ @pytest.mark.anyio @@ -248,6 +247,9 @@ class TestSaveSqlQueryToolLogic: mock_sq.id = 42 mock_sq.label = "Revenue Query" mock_sq.sql = "SELECT SUM(revenue) FROM sales" + mock_sq.db_id = 1 + mock_sq.schema = "" + mock_sq.description = "" mock_sq.catalog = None request = SaveSqlQueryRequest( @@ -277,10 +279,6 @@ class TestSaveSqlQueryToolLogic: ) with ( - patch( - "fastmcp.server.dependencies.get_context", - return_value=mock_ctx, - ), patch("superset.db", mock_db_session), patch("superset.security_manager", mock_sm), patch("superset.daos.query.SavedQueryDAO", mock_dao), @@ -291,7 +289,7 @@ class TestSaveSqlQueryToolLogic: patch("flask.g", mock_g), patch.object(mod, "event_logger", mock_event_logger), ): - result = await mod.save_sql_query(request) + result = await mod.save_sql_query(request, mock_ctx) assert result.id == 42 assert result.label == "Revenue Query" @@ -333,10 +331,6 @@ class TestSaveSqlQueryToolLogic: ) with ( - patch( - "fastmcp.server.dependencies.get_context", - return_value=mock_ctx, - ), patch("superset.db", mock_db_session), patch("flask.g", mock_g), patch.object(mod, "event_logger", mock_event_logger), @@ -344,7 +338,7 @@ class TestSaveSqlQueryToolLogic: from superset.exceptions import SupersetErrorException with pytest.raises(SupersetErrorException, match="not found"): - await mod.save_sql_query(request) + await mod.save_sql_query(request, mock_ctx) finally: _restore_modules(saved) @@ -382,10 +376,6 @@ class TestSaveSqlQueryToolLogic: ) with ( - patch( - "fastmcp.server.dependencies.get_context", - return_value=mock_ctx, - ), patch("superset.db", mock_db_session), patch("superset.security_manager", mock_sm), patch("flask.g", mock_g), @@ -394,7 +384,7 @@ class TestSaveSqlQueryToolLogic: from superset.exceptions import SupersetSecurityException with pytest.raises(SupersetSecurityException, match="Access denied"): - await mod.save_sql_query(request) + await mod.save_sql_query(request, mock_ctx) finally: _restore_modules(saved) @@ -412,6 +402,9 @@ class TestSaveSqlQueryToolLogic: mock_sq.id = 10 mock_sq.label = "Test" mock_sq.sql = "SELECT 1" + mock_sq.db_id = 1 + mock_sq.schema = "public" + mock_sq.description = "" mock_sq.catalog = None request = SaveSqlQueryRequest( @@ -443,10 +436,6 @@ class TestSaveSqlQueryToolLogic: ) with ( - patch( - "fastmcp.server.dependencies.get_context", - return_value=mock_ctx, - ), patch("superset.db", mock_db_session), patch("superset.security_manager", mock_sm), patch("superset.daos.query.SavedQueryDAO", mock_dao), @@ -457,7 +446,7 @@ class TestSaveSqlQueryToolLogic: patch("flask.g", mock_g), patch.object(mod, "event_logger", mock_event_logger), ): - result = await mod.save_sql_query(request) + result = await mod.save_sql_query(request, mock_ctx) assert result.id == 10 call_attrs = mock_dao.create.call_args[1]["attributes"] diff --git a/tests/unit_tests/mcp_service/system/tool/test_get_schema.py b/tests/unit_tests/mcp_service/system/tool/test_get_schema.py index 1d4e857be33..20abfdd7999 100644 --- a/tests/unit_tests/mcp_service/system/tool/test_get_schema.py +++ b/tests/unit_tests/mcp_service/system/tool/test_get_schema.py @@ -153,16 +153,18 @@ class TestModelSchemaInfo: def test_chart_default_columns(self): """Test chart default columns include required minimal set.""" - required_columns = { + assert set(CHART_DEFAULT_COLUMNS) == { "id", "slice_name", "viz_type", + "description", + "certified_by", + "certification_details", "url", + "changed_on", "changed_on_humanized", } - assert required_columns.issubset(set(CHART_DEFAULT_COLUMNS)) - # These should NOT be in defaults - assert "description" not in CHART_DEFAULT_COLUMNS + # Heavy columns should NOT be in defaults assert "form_data" not in CHART_DEFAULT_COLUMNS assert "uuid" not in CHART_DEFAULT_COLUMNS @@ -269,28 +271,6 @@ class TestGetSchemaToolViaClient: assert "dashboard_title" in info["sortable_columns"] assert "changed_on" in info["sortable_columns"] - @patch( - "superset.mcp_service.utils.schema_utils._is_parse_request_enabled", - return_value=True, - ) - @patch("superset.daos.chart.ChartDAO.get_filterable_columns_and_operators") - @pytest.mark.asyncio - async def test_get_schema_with_json_string_request( - self, mock_filters, mock_parse_enabled, mcp_server - ): - """Test get_schema accepts JSON string request (Claude Code compatibility).""" - mock_filters.return_value = {"slice_name": ["eq"]} - - async with Client(mcp_server) as client: - # Send request as JSON string (Claude Code bug workaround) - result = await client.call_tool( - "get_schema", {"request": '{"model_type": "chart"}'} - ) - - assert result.content is not None - data = json.loads(result.content[0].text) - assert data["schema_info"]["model_type"] == "chart" - @patch("superset.daos.chart.ChartDAO.get_filterable_columns_and_operators") @pytest.mark.asyncio async def test_get_schema_select_columns_have_metadata( @@ -313,12 +293,17 @@ class TestGetSchemaToolViaClient: assert id_col["type"] == "int" assert id_col["is_default"] is True - # Find a non-default column (description is on the model) + # description is now a default column desc_col = next( (c for c in select_cols if c["name"] == "description"), None ) assert desc_col is not None - assert desc_col["is_default"] is False + assert desc_col["is_default"] is True + + # Find a non-default column (uuid is on the model but not default) + uuid_col = next((c for c in select_cols if c["name"] == "uuid"), None) + assert uuid_col is not None + assert uuid_col["is_default"] is False class TestGetSchemaEdgeCases: diff --git a/tests/unit_tests/mcp_service/test_auth_api_key.py b/tests/unit_tests/mcp_service/test_auth_api_key.py new file mode 100644 index 00000000000..e1bbf13bf43 --- /dev/null +++ b/tests/unit_tests/mcp_service/test_auth_api_key.py @@ -0,0 +1,222 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +"""Tests for API key authentication in get_user_from_request().""" + +from unittest.mock import MagicMock, patch + +import pytest +from flask import g + +from superset.mcp_service.auth import get_user_from_request + + +@pytest.fixture +def mock_user(): + user = MagicMock() + user.username = "api_key_user" + return user + + +@pytest.fixture +def _enable_api_keys(app): + """Enable FAB API key auth and clear MCP_DEV_USERNAME so the API key + path is exercised instead of falling through to the dev-user fallback.""" + app.config["FAB_API_KEY_ENABLED"] = True + old_dev = app.config.pop("MCP_DEV_USERNAME", None) + yield + app.config.pop("FAB_API_KEY_ENABLED", None) + if old_dev is not None: + app.config["MCP_DEV_USERNAME"] = old_dev + + +@pytest.fixture +def _disable_api_keys(app): + app.config["FAB_API_KEY_ENABLED"] = False + old_dev = app.config.pop("MCP_DEV_USERNAME", None) + yield + app.config.pop("FAB_API_KEY_ENABLED", None) + if old_dev is not None: + app.config["MCP_DEV_USERNAME"] = old_dev + + +# -- Valid API key -> user loaded -- + + +@pytest.mark.usefixtures("_enable_api_keys") +def test_valid_api_key_returns_user(app, mock_user) -> None: + """A valid API key should authenticate and return the user.""" + mock_sm = MagicMock() + mock_sm._extract_api_key_from_request.return_value = "sst_abc123" + mock_sm.validate_api_key.return_value = mock_user + + with app.test_request_context(headers={"Authorization": "Bearer sst_abc123"}): + g.user = None + app.appbuilder = MagicMock() + app.appbuilder.sm = mock_sm + + with patch( + "superset.mcp_service.auth.load_user_with_relationships", + return_value=mock_user, + ): + result = get_user_from_request() + + assert result.username == "api_key_user" + mock_sm.validate_api_key.assert_called_once_with("sst_abc123") + + +# -- Invalid API key -> PermissionError -- + + +@pytest.mark.usefixtures("_enable_api_keys") +def test_invalid_api_key_raises(app) -> None: + """An invalid API key should raise PermissionError.""" + mock_sm = MagicMock() + mock_sm._extract_api_key_from_request.return_value = "sst_bad_key" + mock_sm.validate_api_key.return_value = None + + with app.test_request_context(headers={"Authorization": "Bearer sst_bad_key"}): + g.user = None + app.appbuilder = MagicMock() + app.appbuilder.sm = mock_sm + + with pytest.raises(PermissionError, match="Invalid or expired API key"): + get_user_from_request() + + +# -- API key disabled -> falls through to next auth method -- + + +@pytest.mark.usefixtures("_disable_api_keys") +def test_api_key_disabled_skips_auth(app) -> None: + """When FAB_API_KEY_ENABLED is False, API key auth is skipped entirely.""" + mock_sm = MagicMock() + + with app.test_request_context(headers={"Authorization": "Bearer sst_abc123"}): + g.user = None + app.appbuilder = MagicMock() + app.appbuilder.sm = mock_sm + + # Without API key auth or MCP_DEV_USERNAME, should raise ValueError + # about no authenticated user (not about invalid API key) + with pytest.raises(ValueError, match="No authenticated user found"): + get_user_from_request() + + # SecurityManager API key methods should never be called + mock_sm._extract_api_key_from_request.assert_not_called() + + +# -- No request context -> API key auth skipped -- + + +@pytest.mark.usefixtures("_enable_api_keys") +def test_no_request_context_skips_api_key_auth(app) -> None: + """Without a request context, API key auth should be skipped + (e.g., during MCP tool discovery with only an app context).""" + mock_sm = MagicMock() + + with app.app_context(): + g.user = None + app.appbuilder = MagicMock() + app.appbuilder.sm = mock_sm + + # Explicitly mock has_request_context to False because the test + # framework's app fixture may implicitly provide a request context. + with patch("superset.mcp_service.auth.has_request_context", return_value=False): + with pytest.raises(ValueError, match="No authenticated user found"): + get_user_from_request() + + mock_sm._extract_api_key_from_request.assert_not_called() + + +# -- g.user fallback when no higher-priority auth succeeds -- + + +@pytest.mark.usefixtures("_disable_api_keys") +def test_g_user_fallback_when_no_jwt_or_api_key(app, mock_user) -> None: + """When no JWT or API key auth succeeds and MCP_DEV_USERNAME is not set, + g.user (set by external middleware) is used as fallback.""" + with app.test_request_context(): + g.user = mock_user + + result = get_user_from_request() + + assert result.username == "api_key_user" + + +# -- FAB version without _extract_api_key_from_request -- + + +@pytest.mark.usefixtures("_enable_api_keys") +def test_fab_without_extract_method_skips_gracefully(app) -> None: + """If FAB SecurityManager lacks _extract_api_key_from_request, + API key auth should be skipped with a debug log, not crash.""" + mock_sm = MagicMock(spec=[]) # empty spec = no attributes + + with app.test_request_context(): + g.user = None + app.appbuilder = MagicMock() + app.appbuilder.sm = mock_sm + + with pytest.raises(ValueError, match="No authenticated user found"): + get_user_from_request() + + +# -- FAB version without validate_api_key -- + + +@pytest.mark.usefixtures("_enable_api_keys") +def test_fab_without_validate_method_raises(app) -> None: + """If FAB has _extract_api_key_from_request but not validate_api_key, + should raise PermissionError about unavailable validation.""" + mock_sm = MagicMock(spec=["_extract_api_key_from_request"]) + mock_sm._extract_api_key_from_request.return_value = "sst_abc123" + + with app.test_request_context(headers={"Authorization": "Bearer sst_abc123"}): + g.user = None + app.appbuilder = MagicMock() + app.appbuilder.sm = mock_sm + + with pytest.raises( + PermissionError, match="API key validation is not available" + ): + get_user_from_request() + + +# -- Relationship reload fallback -- + + +@pytest.mark.usefixtures("_enable_api_keys") +def test_relationship_reload_failure_returns_original_user(app, mock_user) -> None: + """If load_user_with_relationships fails, the original user from + validate_api_key should be returned as fallback.""" + mock_sm = MagicMock() + mock_sm._extract_api_key_from_request.return_value = "sst_abc123" + mock_sm.validate_api_key.return_value = mock_user + + with app.test_request_context(headers={"Authorization": "Bearer sst_abc123"}): + g.user = None + app.appbuilder = MagicMock() + app.appbuilder.sm = mock_sm + + with patch( + "superset.mcp_service.auth.load_user_with_relationships", + return_value=None, + ): + result = get_user_from_request() + + assert result is mock_user diff --git a/tests/unit_tests/mcp_service/test_auth_user_resolution.py b/tests/unit_tests/mcp_service/test_auth_user_resolution.py new file mode 100644 index 00000000000..658e131f18b --- /dev/null +++ b/tests/unit_tests/mcp_service/test_auth_user_resolution.py @@ -0,0 +1,456 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +"""Tests for MCP user resolution priority and stale g.user prevention.""" + +from unittest.mock import MagicMock, patch + +import pytest +from flask import g + +from superset.mcp_service.auth import ( + _resolve_user_from_jwt_context, + get_user_from_request, + mcp_auth_hook, +) +from superset.mcp_service.mcp_config import default_user_resolver + + +def _make_mock_user(username: str = "testuser") -> MagicMock: + """Create a mock User with required attributes.""" + user = MagicMock() + user.username = username + user.roles = [] + user.groups = [] + return user + + +def _make_access_token( + claims: dict[str, str] | None = None, **kwargs: str +) -> MagicMock: + """Create a mock AccessToken matching FastMCP's format.""" + token = MagicMock() + token.claims = claims or {} + token.client_id = kwargs.get("client_id", "") + token.scopes = kwargs.get("scopes", []) + # Remove auto-created attributes so getattr fallbacks work correctly + for attr in ("subject", "payload"): + if attr not in kwargs: + delattr(token, attr) + for attr in kwargs: + setattr(token, attr, kwargs[attr]) + return token + + +# -- _resolve_user_from_jwt_context -- + + +def test_jwt_context_resolves_correct_user(app) -> None: + """JWT context with valid claims resolves the correct DB user.""" + mock_user = _make_mock_user("alice") + token = _make_access_token(claims={"sub": "alice"}) + + with app.app_context(): + with ( + patch("fastmcp.server.dependencies.get_access_token", return_value=token), + patch( + "superset.mcp_service.auth.load_user_with_relationships", + return_value=mock_user, + ), + ): + result = _resolve_user_from_jwt_context(app) + + assert result is not None + assert result.username == "alice" + + +def test_jwt_context_returns_none_when_no_token(app) -> None: + """No JWT token present returns None (fall through to next source).""" + with app.app_context(): + with patch("fastmcp.server.dependencies.get_access_token", return_value=None): + result = _resolve_user_from_jwt_context(app) + + assert result is None + + +def test_jwt_context_raises_for_unknown_user(app) -> None: + """JWT resolves a username not in DB — raises ValueError (fail closed).""" + token = _make_access_token(claims={"sub": "nonexistent"}) + + with app.app_context(): + with ( + patch("fastmcp.server.dependencies.get_access_token", return_value=token), + patch( + "superset.mcp_service.auth.load_user_with_relationships", + return_value=None, + ), + ): + with pytest.raises(ValueError, match="not found in Superset database"): + _resolve_user_from_jwt_context(app) + + +def test_jwt_context_raises_when_no_username_in_claims(app) -> None: + """JWT present but claims have no extractable username — fails closed.""" + token = _make_access_token(claims={"iss": "some-issuer"}) + + with app.app_context(): + with patch("fastmcp.server.dependencies.get_access_token", return_value=token): + with pytest.raises(ValueError, match="no username could be extracted"): + _resolve_user_from_jwt_context(app) + + +def test_jwt_context_uses_custom_resolver(app) -> None: + """Custom MCP_USER_RESOLVER config is used when set.""" + mock_user = _make_mock_user("custom_user") + token = _make_access_token(claims={"custom_field": "custom_user"}) + custom_resolver = MagicMock(return_value="custom_user") + + with app.app_context(): + app.config["MCP_USER_RESOLVER"] = custom_resolver + try: + with ( + patch( + "fastmcp.server.dependencies.get_access_token", return_value=token + ), + patch( + "superset.mcp_service.auth.load_user_with_relationships", + return_value=mock_user, + ), + ): + result = _resolve_user_from_jwt_context(app) + finally: + app.config.pop("MCP_USER_RESOLVER", None) + + assert result is not None + assert result.username == "custom_user" + custom_resolver.assert_called_once_with(app, token) + + +def test_jwt_context_email_fallback_lookup(app) -> None: + """When resolver returns an email, tries email lookup after username miss.""" + mock_user = _make_mock_user("alice") + token = _make_access_token(claims={"email": "alice@example.com"}) + + def _load_side_effect(username=None, email=None): + if email == "alice@example.com": + return mock_user + return None + + with app.app_context(): + with ( + patch("fastmcp.server.dependencies.get_access_token", return_value=token), + patch( + "superset.mcp_service.auth.load_user_with_relationships", + side_effect=_load_side_effect, + ), + ): + result = _resolve_user_from_jwt_context(app) + + assert result is not None + assert result.username == "alice" + + +# -- get_user_from_request priority order -- + + +def test_jwt_takes_priority_over_stale_g_user(app) -> None: + """Core regression test: JWT user wins over stale g.user.""" + stale_user = _make_mock_user("stale_bob") + jwt_user = _make_mock_user("jwt_alice") + token = _make_access_token(claims={"sub": "jwt_alice"}) + + with app.app_context(): + g.user = stale_user + with ( + patch("fastmcp.server.dependencies.get_access_token", return_value=token), + patch( + "superset.mcp_service.auth.load_user_with_relationships", + return_value=jwt_user, + ), + ): + result = get_user_from_request() + + assert result.username == "jwt_alice" + + +def test_dev_username_fallback_when_no_jwt(app) -> None: + """MCP_DEV_USERNAME used when no JWT context available.""" + mock_user = _make_mock_user("dev_admin") + + with app.app_context(): + app.config["MCP_DEV_USERNAME"] = "dev_admin" + try: + with ( + patch( + "fastmcp.server.dependencies.get_access_token", return_value=None + ), + patch( + "superset.mcp_service.auth.load_user_with_relationships", + return_value=mock_user, + ), + ): + result = get_user_from_request() + finally: + app.config.pop("MCP_DEV_USERNAME", None) + + assert result.username == "dev_admin" + + +def test_g_user_fallback_when_no_jwt_and_no_dev_username(app) -> None: + """g.user used as last-resort fallback (Preset middleware compatibility).""" + preset_user = _make_mock_user("preset_user") + + with app.app_context(): + app.config.pop("MCP_DEV_USERNAME", None) + g.user = preset_user + with patch("fastmcp.server.dependencies.get_access_token", return_value=None): + result = get_user_from_request() + + assert result.username == "preset_user" + + +def test_raises_when_no_auth_source(app) -> None: + """ValueError raised when no auth source is available.""" + with app.app_context(): + app.config.pop("MCP_DEV_USERNAME", None) + g.pop("user", None) + with patch("fastmcp.server.dependencies.get_access_token", return_value=None): + with pytest.raises(ValueError, match="No authenticated user found"): + get_user_from_request() + + +def test_dev_username_not_found_raises(app) -> None: + """MCP_DEV_USERNAME configured but user not in DB raises ValueError.""" + with app.app_context(): + app.config["MCP_DEV_USERNAME"] = "ghost" + try: + with ( + patch( + "fastmcp.server.dependencies.get_access_token", return_value=None + ), + patch( + "superset.mcp_service.auth.load_user_with_relationships", + return_value=None, + ), + ): + with pytest.raises(ValueError, match="not found"): + get_user_from_request() + finally: + app.config.pop("MCP_DEV_USERNAME", None) + + +# -- g.user clearing in mcp_auth_hook -- + + +def test_mcp_auth_hook_clears_stale_g_user(app) -> None: + """mcp_auth_hook clears g.user before setting up user context. + + Uses a side_effect that asserts g.user was cleared before user + resolution runs, so the test fails if g.pop("user") is removed. + """ + stale_user = _make_mock_user("stale") + fresh_user = _make_mock_user("fresh") + + def dummy_tool(): + """Dummy tool.""" + return g.user.username + + wrapped = mcp_auth_hook(dummy_tool) + + def _assert_cleared_then_return(): + """Verify stale g.user was cleared before returning fresh user.""" + assert not hasattr(g, "user") or g.user is None, ( + "g.user should have been cleared before get_user_from_request() " + f"but found g.user={getattr(g, 'user', '')}" + ) + return fresh_user + + with app.app_context(): + g.user = stale_user + # Explicitly mock has_request_context to False because the test + # framework's autouse app_context fixture may implicitly provide + # a request context in some CI environments. + with ( + patch("flask.has_request_context", return_value=False), + patch( + "superset.mcp_service.auth.get_user_from_request", + side_effect=lambda: _assert_cleared_then_return(), + ), + ): + result = wrapped() + + assert result == "fresh" + + +def test_mcp_auth_hook_clears_stale_g_user_async(app) -> None: + """mcp_auth_hook clears g.user before setting up user context (async). + + Uses a side_effect that asserts g.user was cleared before user + resolution runs, so the test fails if g.pop("user") is removed. + """ + import asyncio + + stale_user = _make_mock_user("stale") + fresh_user = _make_mock_user("fresh") + + async def dummy_tool(): + """Dummy tool.""" + return g.user.username + + wrapped = mcp_auth_hook(dummy_tool) + + def _assert_cleared_then_return(): + """Verify stale g.user was cleared before returning fresh user.""" + assert not hasattr(g, "user") or g.user is None, ( + "g.user should have been cleared before get_user_from_request() " + f"but found g.user={getattr(g, 'user', '')}" + ) + return fresh_user + + with app.app_context(): + g.user = stale_user + with ( + patch("flask.has_request_context", return_value=False), + patch( + "superset.mcp_service.auth.get_user_from_request", + side_effect=lambda: _assert_cleared_then_return(), + ), + ): + result = asyncio.run(wrapped()) + + assert result == "fresh" + + +def test_mcp_auth_hook_preserves_g_user_in_request_context(app) -> None: + """g.user is NOT cleared when a request context is active (middleware compat). + + Uses a side_effect that asserts g.user is still the middleware-set + user when get_user_from_request() is called, proving the hook did + NOT clear it. + """ + middleware_user = _make_mock_user("middleware_user") + + def dummy_tool(): + """Dummy tool.""" + return g.user.username + + wrapped = mcp_auth_hook(dummy_tool) + + def _assert_preserved_then_return(): + """Verify g.user was preserved (not cleared) before returning.""" + assert hasattr(g, "user"), ( + "g.user should be preserved in request context but was removed" + ) + assert g.user is middleware_user, ( + "g.user should be preserved in request context but was changed; " + f"g.user={g.user}" + ) + return middleware_user + + with app.test_request_context(): + g.user = middleware_user + with patch( + "superset.mcp_service.auth.get_user_from_request", + side_effect=lambda: _assert_preserved_then_return(), + ): + result = wrapped() + + assert result == "middleware_user" + + +# -- default_user_resolver -- + + +def test_default_resolver_extracts_sub_from_claims() -> None: + """Extracts 'sub' claim as last-resort from AccessToken.claims dict.""" + token = _make_access_token(claims={"sub": "alice"}) + assert default_user_resolver(None, token) == "alice" + + +def test_default_resolver_extracts_preferred_username() -> None: + """Extracts 'preferred_username' claim (common OIDC claim).""" + token = _make_access_token(claims={"preferred_username": "alice"}) + assert default_user_resolver(None, token) == "alice" + + +def test_default_resolver_extracts_email_from_claims() -> None: + """Falls back to 'email' claim when 'sub' is absent.""" + token = _make_access_token(claims={"email": "alice@example.com"}) + assert default_user_resolver(None, token) == "alice@example.com" + + +def test_default_resolver_extracts_username_from_claims() -> None: + """Falls back to 'username' claim.""" + token = _make_access_token(claims={"username": "alice"}) + assert default_user_resolver(None, token) == "alice" + + +def test_default_resolver_falls_back_to_subject_attr() -> None: + """Falls back to legacy .subject attribute when claims empty.""" + token = _make_access_token(claims={}, subject="legacy_user") + assert default_user_resolver(None, token) == "legacy_user" + + +def test_default_resolver_falls_back_to_client_id() -> None: + """Falls back to .client_id when claims empty and no subject.""" + token = _make_access_token(claims={}, client_id="service-account") + assert default_user_resolver(None, token) == "service-account" + + +def test_default_resolver_returns_none_for_empty_token() -> None: + """Returns None when no claims or attributes have a username.""" + token = _make_access_token(claims={}, client_id="") + assert default_user_resolver(None, token) is None + + +def test_default_resolver_preferred_username_takes_priority() -> None: + """'preferred_username' takes priority over 'sub' and 'email' in claims.""" + token = _make_access_token( + claims={ + "sub": "opaque-id-123", + "preferred_username": "alice", + "email": "alice@example.com", + } + ) + assert default_user_resolver(None, token) == "alice" + + +def test_setup_user_context_propagates_valueerror(app) -> None: + """ValueError from get_user_from_request propagates — no g.user fallback. + + This is a security-critical test: when JWT resolution explicitly fails + (user not in DB), the request must be denied. The auth layer must NOT + silently fall back to g.user from middleware. + + Uses test_request_context() so g.user is preserved (not cleared by + the no-request-context guard), validating that the ValueError still + propagates even when middleware has set g.user. + """ + from superset.mcp_service.auth import _setup_user_context + + fallback_user = _make_mock_user("middleware_user") + + with app.test_request_context(): + g.user = fallback_user + with patch( + "superset.mcp_service.auth.get_user_from_request", + side_effect=ValueError("User 'ghost' not found"), + ): + with pytest.raises(ValueError, match="User 'ghost' not found"): + _setup_user_context() + # g.user should be cleared after ValueError (no misleading audit) + assert not hasattr(g, "user") or g.user is None diff --git a/tests/unit_tests/mcp_service/test_g_user_race_condition.py b/tests/unit_tests/mcp_service/test_g_user_race_condition.py new file mode 100644 index 00000000000..5e9d3aac2b8 --- /dev/null +++ b/tests/unit_tests/mcp_service/test_g_user_race_condition.py @@ -0,0 +1,164 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +""" +Tests for g.user isolation across concurrent MCP tool calls. + +The MCP server pushes a parent app_context at startup (__main__.py). +asyncio.create_task() copies the ContextVar VALUE — a reference to the +SAME AppContext object. Without pushing a fresh app_context() per tool +call, concurrent tasks share one g namespace and g.user mutations race. + +These tests verify that: +- Always pushing a new app_context() per task isolates g.user (SAFE) +- Reusing a shared parent context via nullcontext() causes races (UNSAFE) +- When a request context is active, nullcontext() is safe (middleware path) +""" + +import asyncio +from types import SimpleNamespace + +import pytest +from flask import Flask, g + + +def _make_user(user_id: int, username: str) -> SimpleNamespace: + return SimpleNamespace(id=user_id, username=username) + + +ALICE = _make_user(1, "alice") +BOB = _make_user(2, "bob") + + +def _get_user_id() -> int | None: + """Mirrors superset.utils.core.get_user_id.""" + try: + return g.user.id + except AttributeError: + return None + + +@pytest.mark.asyncio +async def test_fresh_app_context_per_task_isolates_g_user(): + """ + Each task pushes its own app_context(). g.user is isolated. + This is the fixed code path in _get_app_context_manager() when + no request context is active (app-context-only mode). + """ + app = Flask(__name__) + + async def tool_call(user, results, key): + with app.app_context(): + g.user = user + await asyncio.sleep(0) # yield to other tasks + results[key] = _get_user_id() + + # Parent context exists (like __main__.py:138) + with app.app_context(): + for _ in range(200): + results: dict[str, int | None] = {} + await asyncio.gather( + tool_call(ALICE, results, "alice"), + tool_call(BOB, results, "bob"), + ) + assert results["alice"] == ALICE.id + assert results["bob"] == BOB.id + + +@pytest.mark.asyncio +async def test_nullcontext_shared_context_causes_race(): + """ + Both tasks reuse the parent's app context (nullcontext path). + g.user is shared — one task overwrites the other's identity. + Uses asyncio.Event for deterministic interleaving. + """ + app = Flask(__name__) + + alice_set = asyncio.Event() + bob_set = asyncio.Event() + + async def alice_task(results): + g.user = ALICE + alice_set.set() # Signal: Alice has set g.user + await bob_set.wait() # Wait for Bob to overwrite g.user + results["alice"] = _get_user_id() + + async def bob_task(results): + await alice_set.wait() # Wait for Alice to set g.user first + g.user = BOB # Overwrite the shared g.user + bob_set.set() # Signal: Bob has overwritten + results["bob"] = _get_user_id() + + with app.app_context(): + results: dict[str, int | None] = {} + await asyncio.gather( + alice_task(results), + bob_task(results), + ) + # Alice reads Bob's ID because they share the same g + assert results["alice"] == BOB.id, ( + "Expected Alice to see Bob's ID due to shared g" + ) + assert results["bob"] == BOB.id + + +@pytest.mark.asyncio +async def test_request_context_preserves_g_user(): + """ + When a request context is active (middleware set g.user), each task + pushes its own test_request_context. The per-task app_context + + request_context provides isolation even with nullcontext() in the + auth hook. + """ + app = Flask(__name__) + + async def tool_call(user, results, key): + with app.app_context(): + with app.test_request_context(path="/mcp"): + g.user = user + await asyncio.sleep(0) + results[key] = _get_user_id() + + with app.app_context(): + for _ in range(200): + results: dict[str, int | None] = {} + await asyncio.gather( + tool_call(ALICE, results, "alice"), + tool_call(BOB, results, "bob"), + ) + assert results["alice"] == ALICE.id + assert results["bob"] == BOB.id + + +@pytest.mark.asyncio +async def test_high_contention_isolation(): + """10 concurrent users, 50 iterations — stress test.""" + app = Flask(__name__) + users = [_make_user(i, f"user_{i}") for i in range(10)] + + async def tool_call(user, results, key): + with app.app_context(): + g.user = user + await asyncio.sleep(0) + await asyncio.sleep(0) + results[key] = _get_user_id() + + with app.app_context(): + for _ in range(50): + results: dict[str, int | None] = {} + await asyncio.gather(*(tool_call(u, results, u.username) for u in users)) + for u in users: + assert results[u.username] == u.id diff --git a/tests/unit_tests/mcp_service/test_middleware.py b/tests/unit_tests/mcp_service/test_middleware.py index d380320b790..bcc164e048f 100644 --- a/tests/unit_tests/mcp_service/test_middleware.py +++ b/tests/unit_tests/mcp_service/test_middleware.py @@ -207,6 +207,106 @@ class TestResponseSizeGuardMiddleware: call_args = mock_event_logger.log.call_args assert call_args.kwargs["action"] == "mcp_response_size_exceeded" + @pytest.mark.asyncio + async def test_truncates_info_tool_instead_of_blocking(self) -> None: + """Should truncate info tool responses instead of blocking them.""" + middleware = ResponseSizeGuardMiddleware(token_limit=500) + + context = MagicMock() + context.message.name = "get_dataset_info" + context.message.params = {} + + # Large info tool response with a big description + large_response = { + "id": 1, + "table_name": "test", + "description": "x" * 50000, + } + call_next = AsyncMock(return_value=large_response) + + with ( + patch("superset.mcp_service.middleware.get_user_id", return_value=1), + patch("superset.mcp_service.middleware.event_logger"), + ): + result = await middleware.on_call_tool(context, call_next) + + # Should return truncated response, not raise ToolError + assert isinstance(result, dict) + assert result["id"] == 1 + assert result["_response_truncated"] is True + assert "[truncated" in result["description"] + + @pytest.mark.asyncio + async def test_truncates_chart_info_with_large_form_data(self) -> None: + """Should truncate get_chart_info with large form_data.""" + middleware = ResponseSizeGuardMiddleware(token_limit=500) + + context = MagicMock() + context.message.name = "get_chart_info" + context.message.params = {} + + large_response = { + "id": 1, + "slice_name": "My Chart", + "form_data": {f"key_{i}": f"value_{i}" for i in range(100)}, + } + call_next = AsyncMock(return_value=large_response) + + with ( + patch("superset.mcp_service.middleware.get_user_id", return_value=1), + patch("superset.mcp_service.middleware.event_logger"), + ): + result = await middleware.on_call_tool(context, call_next) + + assert isinstance(result, dict) + assert result["id"] == 1 + assert result["_response_truncated"] is True + + @pytest.mark.asyncio + async def test_still_blocks_non_info_tools(self) -> None: + """Should still block non-info tools that exceed limit.""" + middleware = ResponseSizeGuardMiddleware(token_limit=100) + + context = MagicMock() + context.message.name = "list_charts" # Not an info tool + context.message.params = {} + + large_response = {"data": "x" * 10000} + call_next = AsyncMock(return_value=large_response) + + with ( + patch("superset.mcp_service.middleware.get_user_id", return_value=1), + patch("superset.mcp_service.middleware.event_logger"), + pytest.raises(ToolError), + ): + await middleware.on_call_tool(context, call_next) + + @pytest.mark.asyncio + async def test_logs_truncation_event(self) -> None: + """Should log mcp_response_truncated event on successful truncation.""" + middleware = ResponseSizeGuardMiddleware(token_limit=500) + + context = MagicMock() + context.message.name = "get_dashboard_info" + context.message.params = {} + + large_response = { + "id": 1, + "description": "x" * 50000, + } + call_next = AsyncMock(return_value=large_response) + + with ( + patch("superset.mcp_service.middleware.get_user_id", return_value=1), + patch("superset.mcp_service.middleware.event_logger") as mock_event_logger, + ): + await middleware.on_call_tool(context, call_next) + + # Should log truncation event (not size_exceeded) + mock_event_logger.log.assert_called() + call_args = mock_event_logger.log.call_args + assert call_args.kwargs["action"] == "mcp_response_truncated" + class TestCreateResponseSizeGuardMiddleware: """Test create_response_size_guard_middleware factory function.""" diff --git a/tests/unit_tests/mcp_service/test_middleware_logging.py b/tests/unit_tests/mcp_service/test_middleware_logging.py index 1f48d531ccc..50d23449707 100644 --- a/tests/unit_tests/mcp_service/test_middleware_logging.py +++ b/tests/unit_tests/mcp_service/test_middleware_logging.py @@ -102,6 +102,34 @@ class TestLoggingMiddlewareOnCallTool: assert call_kwargs["curated_payload"]["success"] is False assert call_kwargs["duration_ms"] >= 0 + @patch("superset.mcp_service.middleware.event_logger") + @patch("superset.mcp_service.middleware.get_user_id", return_value=42) + @pytest.mark.asyncio + async def test_on_call_tool_logs_failure_on_tool_error( + self, mock_get_user_id, mock_event_logger + ): + """on_call_tool records success=False when GlobalErrorHandler raises ToolError. + + This simulates the real middleware chain: GlobalErrorHandler catches + tool exceptions and re-raises them as ToolError. Since LoggingMiddleware + sits between GlobalErrorHandler and StructuredContentStripper, it + catches the ToolError directly. + """ + from fastmcp.exceptions import ToolError + + middleware = LoggingMiddleware() + ctx = _make_context(name="get_chart_info") + call_next = AsyncMock(side_effect=ToolError("Chart 999999 not found")) + + with pytest.raises(ToolError, match="Chart 999999 not found"): + await middleware.on_call_tool(ctx, call_next) + + mock_event_logger.log.assert_called_once() + call_kwargs = mock_event_logger.log.call_args[1] + assert call_kwargs["curated_payload"]["success"] is False + assert call_kwargs["curated_payload"]["tool"] == "get_chart_info" + assert call_kwargs["duration_ms"] >= 0 + @patch("superset.mcp_service.middleware.event_logger") @patch("superset.mcp_service.middleware.get_user_id", return_value=42) @pytest.mark.asyncio @@ -205,3 +233,132 @@ class TestExtractContextInfo: _, _, _, slice_id, _, _ = middleware._extract_context_info(ctx) assert slice_id == 66 + + +class TestIsErrorResponse: + """Tests for LoggingMiddleware._is_error_response().""" + + def test_detects_error_schema_response(self): + """Detects ToolResult containing a serialized error schema + (ChartError, DashboardError, etc.) via "error_type" field.""" + from fastmcp.tools.tool import ToolResult + from mcp import types as mt + + middleware = LoggingMiddleware() + error_json = ( + '{"error": "Chart 999 not found",' + ' "error_type": "not_found",' + ' "timestamp": "2026-04-09T00:00:00Z"}' + ) + result = ToolResult(content=[mt.TextContent(type="text", text=error_json)]) + assert middleware._is_error_response(result) is True + + def test_success_response_not_detected_as_error(self): + """Normal ToolResult is not detected as error.""" + from fastmcp.tools.tool import ToolResult + from mcp import types as mt + + middleware = LoggingMiddleware() + result = ToolResult( + content=[mt.TextContent(type="text", text="Successfully retrieved data")] + ) + assert middleware._is_error_response(result) is False + + def test_empty_content_not_detected_as_error(self): + """ToolResult with empty content is not detected as error.""" + from fastmcp.tools.tool import ToolResult + + middleware = LoggingMiddleware() + assert middleware._is_error_response(ToolResult(content=[])) is False + + @patch("superset.mcp_service.middleware.event_logger") + @patch("superset.mcp_service.middleware.get_user_id", return_value=42) + @pytest.mark.asyncio + async def test_on_call_tool_logs_failure_for_error_schema( + self, mock_get_user_id, mock_event_logger + ): + """on_call_tool logs success=False when tool returns an + error schema (e.g. ChartError).""" + from fastmcp.tools.tool import ToolResult + from mcp import types as mt + + middleware = LoggingMiddleware() + ctx = _make_context(name="get_chart_info") + + error_json = ( + '{"error": "Chart 999999 not found",' + ' "error_type": "not_found",' + ' "timestamp": "2026-04-09T00:00:00Z"}' + ) + error_result = ToolResult( + content=[mt.TextContent(type="text", text=error_json)] + ) + call_next = AsyncMock(return_value=error_result) + + result = await middleware.on_call_tool(ctx, call_next) + + assert result == error_result + mock_event_logger.log.assert_called_once() + call_kwargs = mock_event_logger.log.call_args[1] + assert call_kwargs["curated_payload"]["success"] is False + assert call_kwargs["curated_payload"]["tool"] == "get_chart_info" + + +class TestMiddlewareChainOrder: + """Test that the middleware order from server.py logs failures correctly. + + If the order is wrong (StructuredContentStripper innermost), + it swallows exceptions before LoggingMiddleware can see them, + causing success=True for failures. + """ + + @patch("superset.mcp_service.middleware.event_logger") + @patch("superset.mcp_service.middleware.get_user_id", return_value=42) + @pytest.mark.asyncio + async def test_real_middleware_chain_logs_exception_as_failure( + self, mock_get_user_id, mock_event_logger + ): + """Tool exception is logged as success=False through the + real middleware chain from build_middleware_list().""" + from functools import partial + + from fastmcp.tools.tool import ToolResult + + from superset.mcp_service.server import build_middleware_list + + middleware_list = build_middleware_list() + + async def failing_tool(context: Any) -> Any: + raise ValueError("chart not found") + + # Build chain same way FastMCP does + chain = failing_tool + for mw in reversed(middleware_list): + chain = partial(mw, call_next=chain) + + ctx = _make_context(name="get_chart_info") + result = await chain(ctx) + + # StructuredContentStripper (outermost) must catch the re-raised + # exception and convert it to a safe ToolResult with "Error:" text. + # If it's not outermost, the exception would leak to the MCP SDK. + assert isinstance(result, ToolResult) + assert result.content[0].text.startswith("Error:") + + # LoggingMiddleware must log + # success=False. If the middleware order is wrong + # (StructuredContentStripper innermost), this would be + # success=True because the exception gets swallowed + # before LoggingMiddleware sees it. + log_calls = [ + c + for c in mock_event_logger.log.call_args_list + if c[1].get("action") == "mcp_tool_call" + ] + assert len(log_calls) == 1 + assert log_calls[0][1]["curated_payload"]["success"] is False, ( + "Middleware order is wrong: StructuredContentStripper is " + "swallowing exceptions before LoggingMiddleware can detect " + "them. Ensure StructuredContentStripper is outermost " + "(first added) in build_middleware_list()." + ) diff --git a/tests/unit_tests/mcp_service/test_tool_search_transform.py b/tests/unit_tests/mcp_service/test_tool_search_transform.py index 7adfeacad47..74de17b88e5 100644 --- a/tests/unit_tests/mcp_service/test_tool_search_transform.py +++ b/tests/unit_tests/mcp_service/test_tool_search_transform.py @@ -25,9 +25,15 @@ from fastmcp.server.transforms.search import BM25SearchTransform, RegexSearchTra from superset.mcp_service.mcp_config import MCP_TOOL_SEARCH_CONFIG from superset.mcp_service.server import ( _apply_tool_search_transform, + _compact_schema, + _create_search_result_serializer, + _extract_parameter_names, _fix_call_tool_arguments, + _normalize_call_tool_arguments, _serialize_tools_without_output_schema, + _truncate_description, ) +from superset.utils import json def test_tool_search_config_defaults(): @@ -39,6 +45,7 @@ def test_tool_search_config_defaults(): assert "get_instance_info" in MCP_TOOL_SEARCH_CONFIG["always_visible"] assert MCP_TOOL_SEARCH_CONFIG["search_tool_name"] == "search_tools" assert MCP_TOOL_SEARCH_CONFIG["call_tool_name"] == "call_tool" + assert MCP_TOOL_SEARCH_CONFIG["include_schemas"] is False def test_apply_bm25_transform(): @@ -171,3 +178,673 @@ def test_serialize_tools_handles_no_output_schema(): assert len(result) == 1 assert result[0]["name"] == "simple_tool" assert "outputSchema" not in result[0] + + +# -- _normalize_call_tool_arguments tests -- + + +def test_normalize_serializes_dict_with_anyof_string(): + """Dict value is JSON-serialized when schema has anyOf with string type.""" + arguments = {"request": {"dataset_id": 1, "config": {"key": "val"}}} + schema = { + "properties": { + "request": { + "anyOf": [ + {"type": "string"}, + {"$ref": "#/$defs/SomeModel"}, + ] + } + } + } + + result = _normalize_call_tool_arguments(arguments, schema) + + assert isinstance(result["request"], str) + assert json.loads(result["request"]) == { + "dataset_id": 1, + "config": {"key": "val"}, + } + + +def test_normalize_serializes_dict_with_oneof_string(): + """Dict value is JSON-serialized when schema has oneOf with string type.""" + arguments = {"request": {"name": "test"}} + schema = { + "properties": { + "request": { + "oneOf": [ + {"type": "string"}, + {"type": "object"}, + ] + } + } + } + + result = _normalize_call_tool_arguments(arguments, schema) + + assert isinstance(result["request"], str) + + +def test_normalize_leaves_dict_without_string_variant(): + """Dict value is left as-is when schema has no string variant.""" + arguments = {"config": {"key": "val"}} + schema = { + "properties": { + "config": { + "anyOf": [ + {"type": "object"}, + {"type": "null"}, + ] + } + } + } + + result = _normalize_call_tool_arguments(arguments, schema) + + assert isinstance(result["config"], dict) + assert result["config"] == {"key": "val"} + + +def test_normalize_leaves_non_dict_unchanged(): + """Non-dict/list values pass through unchanged.""" + arguments = {"name": "test", "count": 42, "flag": True} + schema = { + "properties": { + "name": {"type": "string"}, + "count": {"type": "integer"}, + "flag": {"type": "boolean"}, + } + } + + result = _normalize_call_tool_arguments(arguments, schema) + + assert result == {"name": "test", "count": 42, "flag": True} + + +def test_normalize_returns_none_for_none_arguments(): + """None arguments returns None.""" + result = _normalize_call_tool_arguments(None, {"properties": {}}) + + assert result is None + + +def test_normalize_returns_arguments_for_non_dict_schema(): + """Non-dict schema returns arguments unchanged.""" + arguments = {"request": {"key": "val"}} + + result = _normalize_call_tool_arguments(arguments, None) + + assert result is arguments + + +def test_normalize_serializes_list_with_anyof_string(): + """List value is JSON-serialized when schema has anyOf with string type.""" + arguments = {"items": [1, 2, 3]} + schema = { + "properties": { + "items": { + "anyOf": [ + {"type": "string"}, + {"type": "array", "items": {"type": "integer"}}, + ] + } + } + } + + result = _normalize_call_tool_arguments(arguments, schema) + + assert isinstance(result["items"], str) + assert json.loads(result["items"]) == [1, 2, 3] + + +def test_normalize_ignores_keys_not_in_schema(): + """Dict values for keys not in schema properties are left unchanged.""" + arguments = {"unknown_key": {"nested": True}} + schema = {"properties": {"other_key": {"type": "string"}}} + + result = _normalize_call_tool_arguments(arguments, schema) + + assert isinstance(result["unknown_key"], dict) + + +# -- _compact_schema tests -- + + +def test_compact_schema_removes_defs(): + """$defs section is stripped from the schema.""" + schema = { + "type": "object", + "properties": { + "filters": {"items": {"$ref": "#/$defs/MyFilter"}, "type": "array"}, + }, + "$defs": { + "MyFilter": { + "type": "object", + "properties": {"col": {"type": "string"}}, + } + }, + } + + result = _compact_schema(schema) + + assert "$defs" not in result + assert result["properties"]["filters"]["items"] == {"type": "object"} + + +def test_compact_schema_replaces_ref_with_object(): + """Direct $ref is replaced with {"type": "object"}.""" + schema = {"$ref": "#/$defs/SomeModel"} + + result = _compact_schema(schema) + + assert result == {"type": "object"} + + +def test_compact_schema_preserves_ref_description(): + """$ref replacement preserves sibling description if present.""" + schema = {"$ref": "#/$defs/SomeModel", "description": "A model"} + + result = _compact_schema(schema) + + assert result == {"type": "object", "description": "A model"} + + +def test_compact_schema_simplifies_optional_ref(): + """anyOf with $ref and null is collapsed to the non-null variant.""" + schema = { + "anyOf": [ + {"$ref": "#/$defs/SomeModel"}, + {"type": "null"}, + ], + "description": "Optional model", + } + + result = _compact_schema(schema) + + assert "anyOf" not in result + assert result["type"] == "object" + assert result["description"] == "Optional model" + + +def test_compact_schema_simplifies_optional_primitive(): + """anyOf with primitive type and null is collapsed.""" + schema = { + "anyOf": [ + {"type": "integer"}, + {"type": "null"}, + ], + "default": None, + } + + result = _compact_schema(schema) + + assert "anyOf" not in result + assert result["type"] == "integer" + assert result["default"] is None + + +def test_compact_schema_preserves_multi_variant_anyof(): + """anyOf with >2 variants or no null is left unchanged.""" + schema = { + "anyOf": [ + {"type": "integer"}, + {"type": "string"}, + ] + } + + result = _compact_schema(schema) + + assert "anyOf" in result + assert len(result["anyOf"]) == 2 + + +def test_compact_schema_nested_in_items(): + """$ref nested inside items/properties is also replaced.""" + schema = { + "type": "object", + "properties": { + "filters": { + "type": "array", + "items": {"$ref": "#/$defs/Filter"}, + }, + "name": {"type": "string"}, + }, + } + + result = _compact_schema(schema) + + assert result["properties"]["filters"]["items"] == {"type": "object"} + assert result["properties"]["name"] == {"type": "string"} + + +def test_compact_schema_passthrough_simple(): + """Simple schema without $defs/$ref passes through unchanged.""" + schema = { + "type": "object", + "properties": { + "page": {"type": "integer", "default": 1}, + "search": {"type": "string"}, + }, + } + + result = _compact_schema(schema) + + assert result == schema + + +def test_compact_schema_handles_non_dict(): + """Non-dict inputs pass through unchanged.""" + assert _compact_schema("hello") == "hello" + assert _compact_schema(42) == 42 + assert _compact_schema(None) is None + assert _compact_schema([1, 2]) == [1, 2] + + +# -- _truncate_description tests -- + + +def test_truncate_description_short_text(): + """Short text is returned as-is.""" + assert _truncate_description("Hello world", 300) == "Hello world" + + +def test_truncate_description_cuts_at_sentence(): + """Long text is cut at the last sentence boundary.""" + text = "First sentence. Second sentence. Third sentence that is quite long." + result = _truncate_description(text, 40) + assert result == "First sentence. Second sentence." + + +def test_truncate_description_ellipsis_fallback(): + """When no sentence boundary, truncates with ellipsis.""" + text = "A very long single sentence without periods that goes on and on" + result = _truncate_description(text, 30) + assert result.endswith("...") + assert len(result) <= 33 # 30 + "..." + + +def test_truncate_description_empty(): + """Empty string returns empty.""" + assert _truncate_description("", 300) == "" + + +def test_truncate_description_zero_max(): + """Zero max_length produces ellipsis; the serializer skips calling this.""" + text = "Some text" + # _truncate_description(text, 0) truncates to 0 chars and appends "...". + # The caller (_create_search_result_serializer) skips calling it when + # max_desc=0 so this edge case only matters for direct callers. + result = _truncate_description(text, 0) + assert result == "..." + + +# -- _create_search_result_serializer tests -- + + +def _make_mock_tool(name, description, input_schema): + """Helper to create a mock tool for serializer tests.""" + mock_tool = MagicMock() + mock_mcp_tool = MagicMock() + mock_mcp_tool.model_dump.return_value = { + "name": name, + "description": description, + "inputSchema": input_schema, + } + mock_tool.to_mcp_tool.return_value = mock_mcp_tool + return mock_tool + + +def test_create_serializer_compacts_schemas(): + """Compact serializer strips $defs and replaces $ref.""" + tool = _make_mock_tool( + "list_charts", + "List charts with filtering.", + { + "type": "object", + "properties": { + "filters": { + "type": "array", + "items": {"$ref": "#/$defs/ChartFilter"}, + } + }, + "$defs": { + "ChartFilter": { + "type": "object", + "properties": {"col": {"type": "string"}}, + } + }, + }, + ) + + serializer = _create_search_result_serializer( + {"include_schemas": True, "compact_schemas": True} + ) + result = serializer([tool]) + + assert len(result) == 1 + schema = result[0]["inputSchema"] + assert "$defs" not in schema + assert schema["properties"]["filters"]["items"] == {"type": "object"} + + +def test_create_serializer_truncates_descriptions(): + """Compact serializer truncates long tool descriptions.""" + long_desc = "Short intro. " + "x" * 500 + tool = _make_mock_tool( + "generate_chart", + long_desc, + {"type": "object"}, + ) + + serializer = _create_search_result_serializer({"max_description_length": 50}) + result = serializer([tool]) + + assert len(result[0]["description"]) <= 53 # 50 + potential "..." + + +def test_create_serializer_disabled(): + """When compact_schemas=False and max_description_length=0, no compaction.""" + tool = _make_mock_tool( + "test_tool", + "A long description " * 20, + { + "type": "object", + "$defs": {"Model": {"type": "object"}}, + }, + ) + + serializer = _create_search_result_serializer( + {"include_schemas": True, "compact_schemas": False, "max_description_length": 0} + ) + result = serializer([tool]) + + # $defs should still be present (compaction disabled) + assert "$defs" in result[0]["inputSchema"] + # Description should not be truncated + assert result[0]["description"] == "A long description " * 20 + + +def test_create_serializer_compact_false_disables_truncation(): + """compact_schemas=False also disables description truncation by default.""" + long_desc = "A very long description. " * 30 + tool = _make_mock_tool( + "test_tool", + long_desc, + {"type": "object", "$defs": {"Model": {"type": "object"}}}, + ) + + serializer = _create_search_result_serializer( + {"include_schemas": True, "compact_schemas": False} + ) + result = serializer([tool]) + + # $defs should still be present (compaction disabled) + assert "$defs" in result[0]["inputSchema"] + # Description should NOT be truncated (max_desc defaults to 0 when compact=False) + assert result[0]["description"] == long_desc + + +def test_create_serializer_compact_false_explicit_truncation(): + """compact_schemas=False with explicit max_description_length still truncates.""" + long_desc = "First sentence. " + "x" * 500 + tool = _make_mock_tool( + "test_tool", + long_desc, + {"type": "object", "$defs": {"Model": {"type": "object"}}}, + ) + + serializer = _create_search_result_serializer( + { + "include_schemas": True, + "compact_schemas": False, + "max_description_length": 200, + } + ) + result = serializer([tool]) + + # $defs should still be present (compaction disabled) + assert "$defs" in result[0]["inputSchema"] + # Description SHOULD be truncated (explicitly requested) + assert len(result[0]["description"]) <= 203 + + +def test_create_serializer_uses_config_defaults(): + """Empty config defaults to summary mode (include_schemas=False). + + The new default omits inputSchema and adds parameters_hint instead. + Descriptions are still truncated to 300 chars. + """ + long_desc = "First sentence. " + "x" * 500 + tool = _make_mock_tool( + "test_tool", + long_desc, + { + "type": "object", + "$defs": {"Model": {"type": "object"}}, + "properties": {"x": {"$ref": "#/$defs/Model"}}, + }, + ) + + serializer = _create_search_result_serializer({}) + result = serializer([tool]) + + # Summary mode: no inputSchema, parameters_hint present + assert "inputSchema" not in result[0] + assert result[0]["parameters_hint"] == "x" + # Description still truncated to default 300 + assert len(result[0]["description"]) <= 303 + + +def test_apply_transform_uses_compact_serializer(): + """_apply_tool_search_transform wires _create_search_result_serializer.""" + mock_mcp = MagicMock() + config = { + "strategy": "bm25", + "max_results": 5, + "always_visible": ["health_check"], + "search_tool_name": "search_tools", + "call_tool_name": "call_tool", + "compact_schemas": True, + "max_description_length": 200, + } + + _apply_tool_search_transform(mock_mcp, config) + + mock_mcp.add_transform.assert_called_once() + transform = mock_mcp.add_transform.call_args[0][0] + # The serializer should NOT be the plain _serialize_tools_without_output_schema + assert ( + transform._search_result_serializer + is not _serialize_tools_without_output_schema + ) + + +# -- _extract_parameter_names tests -- + + +def test_extract_parameter_names_basic(): + """Returns comma-separated top-level property names.""" + schema = { + "type": "object", + "properties": { + "page": {"type": "integer"}, + "page_size": {"type": "integer"}, + "search": {"type": "string"}, + }, + } + + result = _extract_parameter_names(schema) + + assert result == "page, page_size, search" + + +def test_extract_parameter_names_empty_properties(): + """Returns empty string when properties dict is empty.""" + schema = {"type": "object", "properties": {}} + + result = _extract_parameter_names(schema) + + assert result == "" + + +def test_extract_parameter_names_no_properties_key(): + """Returns empty string when properties key is absent.""" + schema = {"type": "object"} + + result = _extract_parameter_names(schema) + + assert result == "" + + +def test_extract_parameter_names_with_refs(): + """Extracts names regardless of the shape of property values.""" + schema = { + "type": "object", + "properties": { + "filters": {"type": "array", "items": {"$ref": "#/$defs/ChartFilter"}}, + "select_columns": {"type": "array"}, + }, + "$defs": {"ChartFilter": {"type": "object"}}, + } + + result = _extract_parameter_names(schema) + + assert result == "filters, select_columns" + + +# -- _create_search_result_serializer summary mode (include_schemas=False) -- + + +def test_create_serializer_summary_mode_strips_input_schema(): + """When include_schemas=False, inputSchema is absent from results.""" + tool = _make_mock_tool( + "list_charts", + "List charts.", + { + "type": "object", + "properties": { + "page": {"type": "integer"}, + "search": {"type": "string"}, + }, + }, + ) + + serializer = _create_search_result_serializer({"include_schemas": False}) + result = serializer([tool]) + + assert len(result) == 1 + assert "inputSchema" not in result[0] + assert result[0]["name"] == "list_charts" + + +def test_create_serializer_summary_mode_adds_parameters_hint(): + """When include_schemas=False, parameters_hint lists top-level param names.""" + tool = _make_mock_tool( + "list_charts", + "List charts.", + { + "type": "object", + "properties": { + "page": {"type": "integer"}, + "page_size": {"type": "integer"}, + "search": {"type": "string"}, + }, + }, + ) + + serializer = _create_search_result_serializer({"include_schemas": False}) + result = serializer([tool]) + + assert result[0]["parameters_hint"] == "page, page_size, search" + + +def test_create_serializer_summary_mode_no_hint_when_no_properties(): + """When inputSchema has no properties, parameters_hint is absent.""" + tool = _make_mock_tool( + "health_check", + "Health check.", + {"type": "object"}, + ) + + serializer = _create_search_result_serializer({"include_schemas": False}) + result = serializer([tool]) + + assert "inputSchema" not in result[0] + assert "parameters_hint" not in result[0] + + +def test_create_serializer_summary_mode_truncates_description(): + """Summary mode still truncates descriptions to max_description_length.""" + long_desc = "First sentence. " + "x" * 500 + tool = _make_mock_tool( + "list_charts", + long_desc, + {"type": "object", "properties": {"page": {"type": "integer"}}}, + ) + + serializer = _create_search_result_serializer( + {"include_schemas": False, "max_description_length": 50} + ) + result = serializer([tool]) + + assert len(result[0]["description"]) <= 53 + + +def test_create_serializer_summary_mode_is_default(): + """Empty config defaults to summary mode (include_schemas=False).""" + tool = _make_mock_tool( + "list_charts", + "List charts.", + { + "type": "object", + "properties": {"page": {"type": "integer"}}, + }, + ) + + serializer = _create_search_result_serializer({}) + result = serializer([tool]) + + assert "inputSchema" not in result[0] + assert "parameters_hint" in result[0] + + +def test_create_serializer_include_schemas_true_restores_full_schema(): + """include_schemas=True preserves inputSchema in results.""" + schema = { + "type": "object", + "properties": {"page": {"type": "integer"}}, + "$defs": {"Model": {"type": "object"}}, + } + tool = _make_mock_tool("list_charts", "List charts.", schema) + + serializer = _create_search_result_serializer( + {"include_schemas": True, "compact_schemas": False, "max_description_length": 0} + ) + result = serializer([tool]) + + assert "inputSchema" in result[0] + assert "parameters_hint" not in result[0] + assert "$defs" in result[0]["inputSchema"] + + +def test_create_serializer_include_schemas_true_with_compact(): + """include_schemas=True + compact_schemas=True still compacts the schema.""" + schema = { + "type": "object", + "properties": { + "filters": {"type": "array", "items": {"$ref": "#/$defs/ChartFilter"}} + }, + "$defs": {"ChartFilter": {"type": "object"}}, + } + tool = _make_mock_tool("list_charts", "List charts.", schema) + + serializer = _create_search_result_serializer( + {"include_schemas": True, "compact_schemas": True} + ) + result = serializer([tool]) + + assert "inputSchema" in result[0] + assert "$defs" not in result[0]["inputSchema"] + assert result[0]["inputSchema"]["properties"]["filters"]["items"] == { + "type": "object" + } diff --git a/tests/unit_tests/mcp_service/utils/test_schema_utils.py b/tests/unit_tests/mcp_service/utils/test_schema_utils.py index ab3450e6182..cac25c5d665 100644 --- a/tests/unit_tests/mcp_service/utils/test_schema_utils.py +++ b/tests/unit_tests/mcp_service/utils/test_schema_utils.py @@ -28,7 +28,6 @@ from superset.mcp_service.utils.schema_utils import ( parse_json_or_model, parse_json_or_model_list, parse_json_or_passthrough, - parse_request, ) @@ -343,224 +342,3 @@ class TestEdgeCases: """Should handle special characters in CSV strings.""" result = parse_json_or_list("item-1, item_2, item.3", "items") assert result == ["item-1", "item_2", "item.3"] - - -class TestParseRequestDecorator: - """Test parse_request decorator for MCP tools.""" - - class RequestModel(BaseModel): - """Test request model.""" - - name: str - count: int - - @pytest.fixture(autouse=True) - def _enable_parse_request(self): - """Ensure MCP_PARSE_REQUEST_ENABLED=True for all parsing tests.""" - from unittest.mock import patch - - with patch( - "superset.mcp_service.utils.schema_utils._is_parse_request_enabled", - return_value=True, - ): - yield - - def test_decorator_with_json_string_async(self): - """Should parse JSON string request in async function.""" - from unittest.mock import MagicMock, patch - - @parse_request(self.RequestModel) - async def async_tool(request, ctx=None): - return f"{request.name}:{request.count}" - - import asyncio - - mock_ctx = MagicMock() - with patch("fastmcp.server.dependencies.get_context", return_value=mock_ctx): - result = asyncio.run(async_tool('{"name": "test", "count": 5}')) - assert result == "test:5" - - def test_decorator_with_json_string_sync(self): - """Should parse JSON string request in sync function.""" - from unittest.mock import MagicMock, patch - - @parse_request(self.RequestModel) - def sync_tool(request, ctx=None): - return f"{request.name}:{request.count}" - - mock_ctx = MagicMock() - with patch("fastmcp.server.dependencies.get_context", return_value=mock_ctx): - result = sync_tool('{"name": "test", "count": 5}') - assert result == "test:5" - - def test_decorator_with_dict_async(self): - """Should handle dict request in async function.""" - from unittest.mock import MagicMock, patch - - @parse_request(self.RequestModel) - async def async_tool(request, ctx=None): - return f"{request.name}:{request.count}" - - import asyncio - - mock_ctx = MagicMock() - with patch("fastmcp.server.dependencies.get_context", return_value=mock_ctx): - result = asyncio.run(async_tool({"name": "test", "count": 5})) - assert result == "test:5" - - def test_decorator_with_dict_sync(self): - """Should handle dict request in sync function.""" - from unittest.mock import MagicMock, patch - - @parse_request(self.RequestModel) - def sync_tool(request, ctx=None): - return f"{request.name}:{request.count}" - - mock_ctx = MagicMock() - with patch("fastmcp.server.dependencies.get_context", return_value=mock_ctx): - result = sync_tool({"name": "test", "count": 5}) - assert result == "test:5" - - def test_decorator_with_model_instance_async(self): - """Should pass through model instance in async function.""" - from unittest.mock import MagicMock, patch - - @parse_request(self.RequestModel) - async def async_tool(request, ctx=None): - return f"{request.name}:{request.count}" - - import asyncio - - mock_ctx = MagicMock() - instance = self.RequestModel(name="test", count=5) - with patch("fastmcp.server.dependencies.get_context", return_value=mock_ctx): - result = asyncio.run(async_tool(instance)) - assert result == "test:5" - - def test_decorator_with_model_instance_sync(self): - """Should pass through model instance in sync function.""" - from unittest.mock import MagicMock, patch - - @parse_request(self.RequestModel) - def sync_tool(request, ctx=None): - return f"{request.name}:{request.count}" - - mock_ctx = MagicMock() - instance = self.RequestModel(name="test", count=5) - with patch("fastmcp.server.dependencies.get_context", return_value=mock_ctx): - result = sync_tool(instance) - assert result == "test:5" - - def test_decorator_preserves_function_signature_async(self): - """Should preserve original async function signature.""" - from unittest.mock import MagicMock, patch - - @parse_request(self.RequestModel) - async def async_tool(request, ctx=None, extra=None): - return f"{request.name}:{request.count}:{extra}" - - import asyncio - - mock_ctx = MagicMock() - with patch("fastmcp.server.dependencies.get_context", return_value=mock_ctx): - result = asyncio.run( - async_tool('{"name": "test", "count": 5}', extra="data") - ) - assert result == "test:5:data" - - def test_decorator_preserves_function_signature_sync(self): - """Should preserve original sync function signature.""" - from unittest.mock import MagicMock, patch - - @parse_request(self.RequestModel) - def sync_tool(request, ctx=None, extra=None): - return f"{request.name}:{request.count}:{extra}" - - mock_ctx = MagicMock() - with patch("fastmcp.server.dependencies.get_context", return_value=mock_ctx): - result = sync_tool('{"name": "test", "count": 5}', extra="data") - assert result == "test:5:data" - - def test_decorator_raises_tool_error_for_invalid_data_async(self): - """Should raise ToolError with field details for invalid data.""" - from unittest.mock import MagicMock, patch - - from fastmcp.exceptions import ToolError - - @parse_request(self.RequestModel) - async def async_tool(request, ctx=None): - return f"{request.name}:{request.count}" - - import asyncio - - mock_ctx = MagicMock() - with patch("fastmcp.server.dependencies.get_context", return_value=mock_ctx): - with pytest.raises(ToolError, match="Required fields for RequestModel"): - asyncio.run(async_tool('{"name": "test"}')) # Missing count - - def test_decorator_raises_tool_error_for_invalid_data_sync(self): - """Should raise ToolError with field details for invalid data.""" - from unittest.mock import MagicMock, patch - - from fastmcp.exceptions import ToolError - - @parse_request(self.RequestModel) - def sync_tool(request, ctx=None): - return f"{request.name}:{request.count}" - - mock_ctx = MagicMock() - with patch("fastmcp.server.dependencies.get_context", return_value=mock_ctx): - with pytest.raises(ToolError, match="Required fields for RequestModel"): - sync_tool('{"name": "test"}') # Missing count - - def test_decorator_with_complex_model_async(self): - """Should handle complex nested models in async function.""" - from unittest.mock import MagicMock, patch - - class NestedModel(BaseModel): - """Nested model.""" - - value: int - - class ComplexModel(BaseModel): - """Complex request model.""" - - name: str - nested: NestedModel - - @parse_request(ComplexModel) - async def async_tool(request, ctx=None): - return f"{request.name}:{request.nested.value}" - - import asyncio - - mock_ctx = MagicMock() - json_str = '{"name": "test", "nested": {"value": 42}}' - with patch("fastmcp.server.dependencies.get_context", return_value=mock_ctx): - result = asyncio.run(async_tool(json_str)) - assert result == "test:42" - - def test_decorator_with_complex_model_sync(self): - """Should handle complex nested models in sync function.""" - from unittest.mock import MagicMock, patch - - class NestedModel(BaseModel): - """Nested model.""" - - value: int - - class ComplexModel(BaseModel): - """Complex request model.""" - - name: str - nested: NestedModel - - @parse_request(ComplexModel) - def sync_tool(request, ctx=None): - return f"{request.name}:{request.nested.value}" - - mock_ctx = MagicMock() - json_str = '{"name": "test", "nested": {"value": 42}}' - with patch("fastmcp.server.dependencies.get_context", return_value=mock_ctx): - result = sync_tool(json_str) - assert result == "test:42" diff --git a/tests/unit_tests/mcp_service/utils/test_token_utils.py b/tests/unit_tests/mcp_service/utils/test_token_utils.py index 649536019bb..2e540902c93 100644 --- a/tests/unit_tests/mcp_service/utils/test_token_utils.py +++ b/tests/unit_tests/mcp_service/utils/test_token_utils.py @@ -24,6 +24,11 @@ from typing import Any, List from pydantic import BaseModel from superset.mcp_service.utils.token_utils import ( + _replace_collections_with_summaries, + _summarize_large_dicts, + _truncate_lists, + _truncate_strings, + _truncate_strings_recursive, CHARS_PER_TOKEN, estimate_response_tokens, estimate_token_count, @@ -31,6 +36,8 @@ from superset.mcp_service.utils.token_utils import ( format_size_limit_error, generate_size_reduction_suggestions, get_response_size_bytes, + INFO_TOOLS, + truncate_oversized_response, ) @@ -222,7 +229,25 @@ class TestGenerateSizeReductionSuggestions: estimated_tokens=50000, token_limit=25000, ) - assert any("LIMIT" in s or "limit" in s.lower() for s in suggestions) + combined = " ".join(suggestions) + # Should suggest SQL LIMIT clause + assert "LIMIT" in combined + # Should suggest the tool's limit parameter + assert "'limit' parameter" in combined.lower() or "limit=" in combined.lower() + + def test_execute_sql_with_limit_param_no_duplicate_suggestion(self) -> None: + """When limit param is already set, should not suggest adding it again.""" + suggestions = generate_size_reduction_suggestions( + tool_name="execute_sql", + params={"sql": "SELECT * FROM table", "limit": 500}, + estimated_tokens=50000, + token_limit=25000, + ) + combined = " ".join(suggestions) + # Should still suggest SQL LIMIT + assert "LIMIT" in combined + # Should suggest reducing the existing limit (from general suggestion) + assert "500" in combined or "limit" in combined.lower() def test_tool_specific_suggestions_list_charts(self) -> None: """Should provide chart-specific suggestions for list_charts.""" @@ -356,3 +381,259 @@ class TestCalculatedSuggestions: # Should mention ~66% reduction needed (int truncation of 66.6%) combined = " ".join(suggestions) assert "66%" in combined + + +class TestInfoToolsSet: + """Test the INFO_TOOLS constant.""" + + def test_info_tools_contains_expected_tools(self) -> None: + """Should contain all info tools.""" + assert "get_chart_info" in INFO_TOOLS + assert "get_dataset_info" in INFO_TOOLS + assert "get_dashboard_info" in INFO_TOOLS + assert "get_instance_info" in INFO_TOOLS + + def test_info_tools_does_not_contain_list_tools(self) -> None: + """Should not contain list or write tools.""" + assert "list_charts" not in INFO_TOOLS + assert "execute_sql" not in INFO_TOOLS + assert "generate_chart" not in INFO_TOOLS + + +class TestTruncateStrings: + """Test _truncate_strings helper.""" + + def test_truncates_long_strings(self) -> None: + """Should truncate strings exceeding max_chars.""" + data: dict[str, Any] = {"description": "x" * 1000, "name": "short"} + notes: list[str] = [] + changed = _truncate_strings(data, notes, max_chars=500) + assert changed is True + assert len(data["description"]) < 1000 + assert "[truncated from 1000 chars]" in data["description"] + assert data["name"] == "short" + assert len(notes) == 1 + + def test_does_not_truncate_short_strings(self) -> None: + """Should not truncate strings within limit.""" + data: dict[str, Any] = {"name": "hello", "id": 123} + notes: list[str] = [] + changed = _truncate_strings(data, notes, max_chars=500) + assert changed is False + assert data["name"] == "hello" + assert len(notes) == 0 + + +class TestTruncateStringsRecursive: + """Test _truncate_strings_recursive helper.""" + + def test_truncates_nested_strings_in_list_items(self) -> None: + """Should truncate strings inside list items (e.g. charts[i].description).""" + data: dict[str, Any] = { + "id": 1, + "charts": [ + {"id": 1, "description": "x" * 1000}, + {"id": 2, "description": "short"}, + ], + } + notes: list[str] = [] + changed = _truncate_strings_recursive(data, notes, max_chars=500) + assert changed is True + assert "[truncated" in data["charts"][0]["description"] + assert data["charts"][1]["description"] == "short" + assert len(notes) == 1 + assert "charts[0].description" in notes[0] + + def test_truncates_nested_strings_in_dicts(self) -> None: + """Should truncate strings inside nested dicts.""" + data: dict[str, Any] = { + "filter_state": { + "dataMask": {"some_filter": "y" * 2000}, + }, + } + notes: list[str] = [] + changed = _truncate_strings_recursive(data, notes, max_chars=500) + assert changed is True + assert "[truncated" in data["filter_state"]["dataMask"]["some_filter"] + + def test_respects_depth_limit(self) -> None: + """Should stop recursing at depth 10.""" + # Build a deeply nested structure (15 levels) + data: dict[str, Any] = {"level": "x" * 1000} + current = data + for _ in range(15): + current["nested"] = {"level": "x" * 1000} + current = current["nested"] + notes: list[str] = [] + _truncate_strings_recursive(data, notes, max_chars=500) + # Should truncate levels 0-10 but stop before 15 + assert len(notes) <= 11 + + def test_handles_empty_structures(self) -> None: + """Should handle empty dicts and lists gracefully.""" + data: dict[str, Any] = {"items": [], "meta": {}, "name": "ok"} + notes: list[str] = [] + changed = _truncate_strings_recursive(data, notes, max_chars=500) + assert changed is False + + def test_dashboard_with_many_charts_edge_case(self) -> None: + """Simulate a dashboard with 30 charts each having long descriptions.""" + data: dict[str, Any] = { + "id": 1, + "dashboard_title": "Big Dashboard", + "charts": [ + {"id": i, "slice_name": f"Chart {i}", "description": "d" * 2000} + for i in range(30) + ], + } + notes: list[str] = [] + changed = _truncate_strings_recursive(data, notes, max_chars=500) + assert changed is True + # All 30 chart descriptions should be truncated + assert len(notes) == 30 + for chart in data["charts"]: + assert len(chart["description"]) < 2000 + assert "[truncated" in chart["description"] + + +class TestTruncateLists: + """Test _truncate_lists helper.""" + + def test_truncates_long_lists(self) -> None: + """Should truncate lists exceeding max_items without inline markers.""" + data: dict[str, Any] = { + "columns": [{"name": f"col_{i}"} for i in range(50)], + "tags": [1, 2], + } + notes: list[str] = [] + changed = _truncate_lists(data, notes, max_items=10) + assert changed is True + # Exactly 10 items — no marker appended (preserves type contract) + assert len(data["columns"]) == 10 + assert all(isinstance(c, dict) and "name" in c for c in data["columns"]) + assert data["tags"] == [1, 2] # Not truncated + assert len(notes) == 1 + assert "50" in notes[0] + + def test_does_not_truncate_short_lists(self) -> None: + """Should not truncate lists within limit.""" + data: dict[str, Any] = {"items": [1, 2, 3]} + notes: list[str] = [] + changed = _truncate_lists(data, notes, max_items=10) + assert changed is False + + +class TestSummarizeLargeDicts: + """Test _summarize_large_dicts helper.""" + + def test_summarizes_large_dicts(self) -> None: + """Should replace large dicts with key summaries.""" + big_dict = {f"key_{i}": f"value_{i}" for i in range(30)} + data: dict[str, Any] = {"form_data": big_dict, "id": 1} + notes: list[str] = [] + changed = _summarize_large_dicts(data, notes, max_keys=20) + assert changed is True + assert data["form_data"]["_truncated"] is True + assert "30 keys" in data["form_data"]["_message"] + assert data["id"] == 1 + + def test_does_not_summarize_small_dicts(self) -> None: + """Should not summarize dicts within limit.""" + data: dict[str, Any] = {"params": {"a": 1, "b": 2}} + notes: list[str] = [] + changed = _summarize_large_dicts(data, notes, max_keys=20) + assert changed is False + + +class TestReplaceCollectionsWithSummaries: + """Test _replace_collections_with_summaries helper.""" + + def test_replaces_lists_and_dicts(self) -> None: + """Should clear non-empty collections to reduce size.""" + data: dict[str, Any] = { + "columns": [1, 2, 3], + "params": {"a": 1}, + "name": "test", + "empty": [], + } + notes: list[str] = [] + changed = _replace_collections_with_summaries(data, notes) + assert changed is True + # Lists become empty lists (preserves type) + assert data["columns"] == [] + # Dicts become empty dicts (preserves type) + assert data["params"] == {} + # Scalars unchanged + assert data["name"] == "test" + # Empty collections unchanged + assert data["empty"] == [] + assert len(notes) == 2 + + +class TestTruncateOversizedResponse: + """Test truncate_oversized_response function.""" + + def test_no_truncation_needed(self) -> None: + """Should return original data when under limit.""" + response = {"id": 1, "name": "test"} + result, was_truncated, notes = truncate_oversized_response(response, 10000) + assert was_truncated is False + assert notes == [] + + def test_truncates_large_string_fields(self) -> None: + """Should truncate long strings to fit.""" + response = { + "id": 1, + "description": "x" * 50000, # Very large description + } + result, was_truncated, notes = truncate_oversized_response(response, 500) + assert was_truncated is True + assert isinstance(result, dict) + assert "[truncated" in result["description"] + assert any("description" in n for n in notes) + + def test_truncates_large_lists(self) -> None: + """Should truncate lists when strings alone are not enough.""" + response = { + "id": 1, + "columns": [{"name": f"col_{i}", "type": "VARCHAR"} for i in range(200)], + } + result, was_truncated, notes = truncate_oversized_response(response, 500) + assert was_truncated is True + assert isinstance(result, dict) + # Should have been truncated + assert len(result["columns"]) < 200 + + def test_handles_pydantic_model(self) -> None: + """Should handle Pydantic model input.""" + + class FakeInfo(BaseModel): + id: int = 1 + description: str = "x" * 5000 + + response = FakeInfo() + result, was_truncated, notes = truncate_oversized_response(response, 200) + assert was_truncated is True + assert isinstance(result, dict) + + def test_returns_non_dict_unchanged(self) -> None: + """Should return non-dict/model responses unchanged.""" + result, was_truncated, notes = truncate_oversized_response("just a string", 100) + assert was_truncated is False + assert result == "just a string" + + def test_progressive_truncation(self) -> None: + """Should progressively apply truncation phases.""" + # Build a response that's quite large + response = { + "id": 1, + "description": "x" * 2000, + "css": "y" * 2000, + "columns": [{"name": f"col_{i}"} for i in range(100)], + "form_data": {f"key_{i}": f"val_{i}" for i in range(50)}, + } + result, was_truncated, notes = truncate_oversized_response(response, 300) + assert was_truncated is True + assert isinstance(result, dict) + assert result["id"] == 1 # Scalar fields preserved + assert len(notes) > 0 diff --git a/tests/unit_tests/models/helpers_test.py b/tests/unit_tests/models/helpers_test.py index c0354844737..75d89519c78 100644 --- a/tests/unit_tests/models/helpers_test.py +++ b/tests/unit_tests/models/helpers_test.py @@ -20,7 +20,7 @@ from __future__ import annotations from contextlib import contextmanager -from typing import TYPE_CHECKING +from typing import cast, TYPE_CHECKING from unittest.mock import patch import pytest @@ -33,6 +33,7 @@ from sqlalchemy.sql.elements import ColumnElement from superset.superset_typing import AdhocColumn if TYPE_CHECKING: + from superset.jinja_context import BaseTemplateProcessor from superset.models.core import Database @@ -1429,6 +1430,79 @@ def test_adhoc_column_to_sqla_with_column_reference(database: Database) -> None: assert "Customer Name" in result_str or '"Customer Name"' in result_str +def test_virtual_dataset_calculated_column_selected_via_templated_adhoc_dimension( + database: Database, +) -> None: + """ + Calculated columns on virtual datasets must be resolvable when the column + reference comes from a templated `sqlExpression` (e.g. using `filter_values`). + + Regression test for cases where selecting a calculated column by name would + fall back to treating the resolved name as a bare identifier (breaking SQL + execution because the calculated expression is not present in the virtual + dataset's FROM subquery). + """ + from superset.connectors.sqla.models import SqlaTable, TableColumn + + table = SqlaTable( + database=database, + schema=None, + table_name="virtual_t", + # Non-empty `sql` makes the table a virtual dataset. + sql="SELECT random_value, category FROM t", + columns=[ + TableColumn(column_name="random_value", type="INTEGER"), + TableColumn(column_name="category", type="TEXT"), + TableColumn( + column_name="gt_or_lt_50", + type="TEXT", + expression=( + "CASE WHEN random_value > 50 THEN 'GT 50' ELSE 'LT 50' END" + ), + ), + ], + ) + + class DummyTemplateProcessor: + def process_template(self, sql_expression: str) -> str: + # Only resolve the templated column name; leave other expressions + # (like the calculated column's CASE expression) untouched. + if "filter_values('aggregation')" in sql_expression: + return "gt_or_lt_50" + return sql_expression + + adhoc_col: AdhocColumn = { + "sqlExpression": ( + "{{ filter_values('aggregation')[0] if " + "filter_values('aggregation') else \"'Total'\" }}" + ), + "label": "breakdown_by", + "isColumnReference": True, + } + + result = table.adhoc_column_to_sqla( + adhoc_col, + template_processor=cast("BaseTemplateProcessor", DummyTemplateProcessor()), + ) + assert result is not None + + # The calculated column expression should be inlined (not treated as a bare + # identifier), so SQL must contain the CASE expression. + with database.get_sqla_engine() as engine: + sql = str( + result.compile( + dialect=engine.dialect, + compile_kwargs={"literal_binds": True}, + ) + ) + + assert "CASE WHEN" in sql + assert "random_value" in sql + assert "'GT 50'" in sql + assert "'LT 50'" in sql + assert "gt_or_lt_50" not in sql + + def test_adhoc_column_to_sqla_preserves_column_type_for_time_grain( database: Database, ) -> None: @@ -1856,3 +1930,112 @@ def test_extras_having_is_parenthesized( assert "(COUNT(*) > 0 OR 1 = 1)" in sql, ( f"extras.having should be wrapped in parentheses. Generated SQL: {sql}" ) + + +def _run_probe( + database: Database, + type_probe_needs_row: bool = False, +) -> str: + """ + Run adhoc_column_to_sqla with a mocked get_columns_description and + return the SQL that was passed to it. + + ``type_probe_needs_row`` is patched onto the database's real engine spec + class so every other method keeps working normally. + """ + from unittest.mock import patch + + from superset.connectors.sqla.models import SqlaTable, TableColumn + + table = SqlaTable( + database=database, + schema=None, + table_name="t", + columns=[TableColumn(column_name="a", type="INTEGER")], + ) + + adhoc_col: AdhocColumn = { + "sqlExpression": "round(a / 50) * 50 / 1000", + "label": "Duration", + "columnType": "BASE_AXIS", + "timeGrain": "P1D", + } + captured: list[str] = [] + + def fake_get_columns_description( + db: object, + catalog: object, + schema: object, + sql: str, + ) -> list[dict[str, object]]: + captured.append(sql) + return [ + { + "column_name": "Duration", + "name": "Duration", + "type": "FLOAT", + "is_dttm": False, + } + ] + + spec_cls = database.db_engine_spec + with ( + patch( + "superset.connectors.sqla.models.get_columns_description", + side_effect=fake_get_columns_description, + ), + patch.object(spec_cls, "type_probe_needs_row", type_probe_needs_row), + ): + result = table.adhoc_column_to_sqla(adhoc_col) + + assert result is not None + assert len(captured) == 1, "Expected exactly one type-probe query" + return captured[0] + + +def test_adhoc_column_type_probe_uses_where_false(database: Database) -> None: + """ + Most engines populate cursor.description from query-plan metadata, so the + probe uses WHERE FALSE — zero rows read, no table scan. + + SQLAlchemy renders sa.false() differently per dialect: + - Most databases: WHERE false + - SQLite: WHERE 0 = 1 + """ + probe_sql = _run_probe(database, type_probe_needs_row=False).lower() + + always_false_patterns = ("where false", "where 0 = 1") + assert any(p in probe_sql for p in always_false_patterns), ( + f"Probe query should use a WHERE false condition to avoid scanning the " + f"table, but got: {probe_sql}" + ) + assert "limit" not in probe_sql, ( + f"WHERE false probe must not contain LIMIT, but got: {probe_sql}" + ) + + +def test_adhoc_column_type_probe_uses_limit_1_for_row_dependent_engines( + database: Database, +) -> None: + """ + Druid and Pinot build cursor.description by inspecting the first returned + row. WHERE FALSE yields no rows, so description stays None. These engines + must fall back to LIMIT 1. + """ + from superset.db_engine_specs.druid import DruidEngineSpec + from superset.db_engine_specs.pinot import PinotEngineSpec + + for spec_cls in (DruidEngineSpec, PinotEngineSpec): + assert spec_cls.type_probe_needs_row is True, ( + f"{spec_cls.__name__} must declare type_probe_needs_row = True" + ) + + probe_sql = _run_probe(database, type_probe_needs_row=True).lower() + + assert "limit 1" in probe_sql, ( + f"Row-dependent engines: probe should use LIMIT 1, got: {probe_sql}" + ) + always_false_patterns = ("where false", "where 0 = 1") + assert not any(p in probe_sql for p in always_false_patterns), ( + f"Row-dependent engines: probe must NOT use WHERE false, got: {probe_sql}" + ) diff --git a/tests/unit_tests/models/slice_test.py b/tests/unit_tests/models/slice_test.py index dc6bbb86552..e55d00dede0 100644 --- a/tests/unit_tests/models/slice_test.py +++ b/tests/unit_tests/models/slice_test.py @@ -85,3 +85,41 @@ class TestSlice: """Test id_or_uuid_filter returns correct BinaryExpression.""" result = id_or_uuid_filter(input_value) assert result is not None + + def test_datasource_url_returns_none_when_datasource_lacks_explore_url(self): + """datasource_url() must not raise when the datasource has no explore_url. + + Charts whose datasource resolves to a Query (or any other type without + explore_url) used to raise AttributeError, which caused the entire chart + list API response to fail instead of just skipping that one chart. + """ + slc = Slice() + slc.id = 1 + + # Simulate a datasource object that does NOT have explore_url (e.g. Query) + mock_datasource = MagicMock(spec=[]) # spec=[] means no attributes at all + slc.table = mock_datasource + + result = slc.datasource_url() + assert result is None + + def test_datasource_url_returns_explore_url_when_present(self): + """datasource_url() returns the datasource explore_url when it exists.""" + slc = Slice() + slc.id = 1 + + mock_table = MagicMock() + mock_table.explore_url = "/explore/?datasource_type=table&datasource_id=1" + slc.table = mock_table + + result = slc.datasource_url() + assert result == "/explore/?datasource_type=table&datasource_id=1" + + def test_datasource_url_returns_none_when_no_datasource(self): + """datasource_url() returns None when there is no datasource.""" + slc = Slice() + slc.id = 1 + slc.table = None + + result = slc.datasource_url() + assert result is None diff --git a/tests/unit_tests/models/test_no_filter_time_range.py b/tests/unit_tests/models/test_no_filter_time_range.py new file mode 100644 index 00000000000..09139920897 --- /dev/null +++ b/tests/unit_tests/models/test_no_filter_time_range.py @@ -0,0 +1,307 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +"""Tests that 'No filter' temporal range produces no WHERE 1 = 1 clause.""" + +from __future__ import annotations + +from datetime import datetime, timezone + +from flask import Flask +from pytest_mock import MockerFixture + +from superset.connectors.sqla.models import SqlaTable, SqlMetric, TableColumn +from superset.models.core import Database +from superset.superset_typing import QueryObjectDict + + +def _make_dataset(mocker: MockerFixture) -> SqlaTable: + database = Database( + id=1, + database_name="test_db", + sqlalchemy_uri="sqlite://", + ) + columns = [ + TableColumn(column_name="time_start", is_dttm=1, type="TIMESTAMP"), + TableColumn(column_name="value", type="INTEGER"), + ] + dataset = SqlaTable( + table_name="test_table", + columns=columns, + main_dttm_col="time_start", + database=database, + metrics=[SqlMetric(metric_name="count", expression="COUNT(*)")], + ) + mocker.patch( + "superset.connectors.sqla.models.security_manager.get_guest_rls_filters", + return_value=[], + ) + mocker.patch( + "superset.connectors.sqla.models.security_manager.is_guest_user", + return_value=False, + ) + return dataset + + +def test_get_time_filter_returns_none_when_no_bounds( + mocker: MockerFixture, + app: Flask, +) -> None: + """get_time_filter returns None when both start_dttm and end_dttm are None.""" + dataset = _make_dataset(mocker) + time_col = dataset.columns[0] + + with app.test_request_context(): + result = dataset.get_time_filter( + time_col=time_col, + start_dttm=None, + end_dttm=None, + ) + + assert result is None + + +def test_get_time_filter_returns_clause_with_partial_bounds( + mocker: MockerFixture, + app: Flask, +) -> None: + """get_time_filter returns a clause when at least one bound is set.""" + dataset = _make_dataset(mocker) + time_col = dataset.columns[0] + + with app.test_request_context(): + result_start_only = dataset.get_time_filter( + time_col=time_col, + start_dttm=datetime(2024, 1, 1, tzinfo=timezone.utc), + end_dttm=None, + ) + result_end_only = dataset.get_time_filter( + time_col=time_col, + start_dttm=None, + end_dttm=datetime(2024, 1, 31, tzinfo=timezone.utc), + ) + result_both = dataset.get_time_filter( + time_col=time_col, + start_dttm=datetime(2024, 1, 1, tzinfo=timezone.utc), + end_dttm=datetime(2024, 1, 31, tzinfo=timezone.utc), + ) + + assert result_start_only is not None + assert result_end_only is not None + assert result_both is not None + + +def test_no_filter_temporal_range_omits_where_1_equals_1( + mocker: MockerFixture, + app: Flask, +) -> None: + """Generating SQL with 'No filter' time range must not produce WHERE 1 = 1.""" + dataset = _make_dataset(mocker) + + # "No filter" is represented as None/None for from_dttm/to_dttm + query_obj: QueryObjectDict = { + "granularity": "time_start", + "from_dttm": None, + "to_dttm": None, + "is_timeseries": False, + "filter": [ + { + "col": "time_start", + "op": "TEMPORAL_RANGE", + "val": "No filter", + } + ], + "metrics": ["count"], + "columns": [], + } + + with app.test_request_context(): + sqla_query = dataset.get_query_str_extended(query_obj, mutate=False) + generated_sql = sqla_query.sql + + assert "1 = 1" not in generated_sql, ( + f"Expected no '1 = 1' in generated SQL when 'No filter' is selected, " + f"but got: {generated_sql}" + ) + + +def test_no_filter_with_other_filters_preserved( + mocker: MockerFixture, + app: Flask, +) -> None: + """'No filter' time range + regular filter => WHERE has only the regular filter.""" + dataset = _make_dataset(mocker) + + query_obj: QueryObjectDict = { + "granularity": "time_start", + "from_dttm": None, + "to_dttm": None, + "is_timeseries": False, + "filter": [ + { + "col": "time_start", + "op": "TEMPORAL_RANGE", + "val": "No filter", + }, + { + "col": "value", + "op": "==", + "val": "42", + }, + ], + "metrics": ["count"], + "columns": [], + } + + with app.test_request_context(): + sqla_query = dataset.get_query_str_extended(query_obj, mutate=False) + generated_sql = sqla_query.sql + + assert "1 = 1" not in generated_sql, ( + f"Expected no '1 = 1' with 'No filter' + regular filter, got: {generated_sql}" + ) + # Regular filter should still be present + assert "value" in generated_sql.lower(), ( + f"Regular filter on 'value' should still appear in SQL, got: {generated_sql}" + ) + + +def test_actual_from_to_dttm_produces_time_filter( + mocker: MockerFixture, + app: Flask, +) -> None: + """Generating SQL with actual from/to dates applies a WHERE time filter.""" + dataset = _make_dataset(mocker) + + query_obj: QueryObjectDict = { + "granularity": "time_start", + "from_dttm": datetime(2024, 1, 1, tzinfo=timezone.utc), + "to_dttm": datetime(2024, 1, 31, tzinfo=timezone.utc), + "is_timeseries": False, + "filter": [], + "metrics": ["count"], + "columns": [], + } + + with app.test_request_context(): + sqla_query = dataset.get_query_str_extended(query_obj, mutate=False) + generated_sql = sqla_query.sql + + assert "1 = 1" not in generated_sql, ( + f"Expected no '1 = 1' in SQL with actual date bounds, got: {generated_sql}" + ) + assert "time_start" in generated_sql.lower(), ( + f"Expected time_start column in WHERE clause, got: {generated_sql}" + ) + + +def test_series_limit_with_time_filter_includes_time_in_inner_subquery( + mocker: MockerFixture, + app: Flask, +) -> None: + """Series limit subquery includes time filter when actual dates are provided. + + This covers the branch at helpers.py where inner_time_filter is set to + [_inner_filter] when get_time_filter returns a non-None clause inside the + series-limit subquery block. + """ + database = Database( + id=1, + database_name="test_db", + sqlalchemy_uri="sqlite://", + ) + columns = [ + TableColumn(column_name="time_start", is_dttm=1, type="TIMESTAMP"), + TableColumn(column_name="category", type="VARCHAR(100)"), + TableColumn(column_name="value", type="INTEGER"), + ] + dataset = SqlaTable( + table_name="test_table", + columns=columns, + main_dttm_col="time_start", + database=database, + metrics=[SqlMetric(metric_name="count", expression="COUNT(*)")], + ) + mocker.patch( + "superset.connectors.sqla.models.security_manager.get_guest_rls_filters", + return_value=[], + ) + mocker.patch( + "superset.connectors.sqla.models.security_manager.is_guest_user", + return_value=False, + ) + + query_obj: QueryObjectDict = { + "granularity": "time_start", + "from_dttm": datetime(2024, 1, 1, tzinfo=timezone.utc), + "to_dttm": datetime(2024, 1, 31, tzinfo=timezone.utc), + "is_timeseries": True, + "groupby": ["category"], + "series_limit": 5, + "filter": [], + "metrics": ["count"], + "columns": [], + } + + with app.test_request_context(): + sqla_query = dataset.get_query_str_extended(query_obj, mutate=False) + generated_sql = sqla_query.sql + + assert "1 = 1" not in generated_sql, ( + f"Expected no '1 = 1' in generated SQL with series_limit and actual dates, " + f"but got: {generated_sql}" + ) + assert "time_start" in generated_sql.lower(), ( + f"Expected time_start to appear in SQL (inner subquery WHERE), " + f"but got: {generated_sql}" + ) + + +def test_temporal_range_filter_with_actual_dates_produces_time_filter( + mocker: MockerFixture, + app: Flask, +) -> None: + """TEMPORAL_RANGE filter with actual dates applies a WHERE time filter.""" + dataset = _make_dataset(mocker) + + query_obj: QueryObjectDict = { + "granularity": None, + "from_dttm": None, + "to_dttm": None, + "is_timeseries": False, + "filter": [ + { + "col": "time_start", + "op": "TEMPORAL_RANGE", + "val": "2024-01-01T00:00:00 : 2024-01-31T00:00:00", + } + ], + "metrics": ["count"], + "columns": [], + } + + with app.test_request_context(): + sqla_query = dataset.get_query_str_extended(query_obj, mutate=False) + generated_sql = sqla_query.sql + + assert "time_start" in generated_sql.lower(), ( + f"Expected time_start in WHERE clause for TEMPORAL_RANGE with dates, " + f"got: {generated_sql}" + ) + assert "1 = 1" not in generated_sql, ( + f"Expected no '1 = 1' for TEMPORAL_RANGE with actual dates, " + f"got: {generated_sql}" + ) diff --git a/tests/unit_tests/reports/api_test.py b/tests/unit_tests/reports/api_test.py new file mode 100644 index 00000000000..6656b623bb1 --- /dev/null +++ b/tests/unit_tests/reports/api_test.py @@ -0,0 +1,52 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +from typing import Any +from unittest.mock import patch + +import prison + +from superset.exceptions import SupersetException +from tests.unit_tests.conftest import with_feature_flags + + +@with_feature_flags(ALERT_REPORTS=True) +@patch("superset.reports.api.get_channels_with_search") +def test_slack_channels_success( + mock_search: Any, + client: Any, + full_api_access: None, +) -> None: + mock_search.return_value = [{"id": "C123", "name": "general"}] + params = prison.dumps({}) + rv = client.get(f"/api/v1/report/slack_channels/?q={params}") + assert rv.status_code == 200 + data = rv.json + assert data["result"] == [{"id": "C123", "name": "general"}] + + +@with_feature_flags(ALERT_REPORTS=True) +@patch("superset.reports.api.get_channels_with_search") +def test_slack_channels_handles_superset_exception( + mock_search: Any, + client: Any, + full_api_access: None, +) -> None: + mock_search.side_effect = SupersetException("Slack API error") + params = prison.dumps({}) + rv = client.get(f"/api/v1/report/slack_channels/?q={params}") + assert rv.status_code == 422 + assert "Slack API error" in rv.json["message"] diff --git a/tests/unit_tests/reports/dao_test.py b/tests/unit_tests/reports/dao_test.py new file mode 100644 index 00000000000..d77c42a9fd7 --- /dev/null +++ b/tests/unit_tests/reports/dao_test.py @@ -0,0 +1,91 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +from unittest.mock import MagicMock, patch + + +@patch("superset.daos.report.get_user_id", return_value=1) +@patch("superset.daos.report.db") +def test_validate_unique_creation_method_duplicate_returns_false( + mock_db: MagicMock, + mock_uid: MagicMock, +) -> None: + from superset.daos.report import ReportScheduleDAO + + # Simulate that a matching report already exists + mock_db.session.query.return_value.filter_by.return_value.filter.return_value = ( + MagicMock() + ) + mock_db.session.query.return_value.scalar.return_value = True + assert ReportScheduleDAO.validate_unique_creation_method(dashboard_id=1) is False + + +@patch("superset.daos.report.get_user_id", return_value=1) +@patch("superset.daos.report.db") +def test_validate_unique_creation_method_no_duplicate_returns_true( + mock_db: MagicMock, + mock_uid: MagicMock, +) -> None: + from superset.daos.report import ReportScheduleDAO + + mock_db.session.query.return_value.filter_by.return_value.filter.return_value = ( + MagicMock() + ) + mock_db.session.query.return_value.scalar.return_value = False + assert ReportScheduleDAO.validate_unique_creation_method(dashboard_id=1) is True + + +@patch("superset.daos.report.db") +def test_find_last_error_notification_returns_none_after_success( + mock_db: MagicMock, +) -> None: + from superset.daos.report import ReportScheduleDAO + + schedule = MagicMock() + error_log = MagicMock() + success_log = MagicMock() + + # Build the query chain so each .query().filter().order_by().first() call + # returns a different result. The DAO calls db.session.query() twice: + # 1st call finds the error marker log + # 2nd call finds a non-error log after it (success happened since last error email) + query_mock = MagicMock() + mock_db.session.query.return_value = query_mock + chain = query_mock.filter.return_value.order_by.return_value + chain.first.side_effect = [error_log, success_log] + + result = ReportScheduleDAO.find_last_error_notification(schedule) + # Success log exists after error → should return None (no re-notification needed) + assert result is None + + +@patch("superset.daos.report.db") +def test_find_last_error_notification_returns_log_when_only_errors( + mock_db: MagicMock, +) -> None: + from superset.daos.report import ReportScheduleDAO + + schedule = MagicMock() + error_log = MagicMock() + + query_mock = MagicMock() + mock_db.session.query.return_value = query_mock + chain = query_mock.filter.return_value.order_by.return_value + # 1st call: error marker log found; 2nd call: no success log after it + chain.first.side_effect = [error_log, None] + + result = ReportScheduleDAO.find_last_error_notification(schedule) + assert result is error_log diff --git a/tests/unit_tests/reports/filters_test.py b/tests/unit_tests/reports/filters_test.py new file mode 100644 index 00000000000..308eb439136 --- /dev/null +++ b/tests/unit_tests/reports/filters_test.py @@ -0,0 +1,64 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +from unittest.mock import MagicMock, patch + + +@patch("superset.reports.filters.security_manager", new_callable=MagicMock) +def test_report_schedule_filter_admin_sees_all(mock_sm: MagicMock) -> None: + from superset.reports.filters import ReportScheduleFilter + + mock_sm.can_access_all_datasources.return_value = True + query = MagicMock() + f = ReportScheduleFilter("id", MagicMock()) + result = f.apply(query, None) + assert result is query + query.filter.assert_not_called() + + +@patch("superset.reports.filters.security_manager", new_callable=MagicMock) +@patch("superset.reports.filters.db") +def test_report_schedule_filter_non_admin_filtered( + mock_db: MagicMock, mock_sm: MagicMock +) -> None: + from superset.reports.filters import ReportScheduleFilter + + mock_sm.can_access_all_datasources.return_value = False + mock_sm.user_model.get_user_id.return_value = 1 + mock_sm.user_model.id = 1 + query = MagicMock() + f = ReportScheduleFilter("id", MagicMock()) + f.apply(query, None) + query.filter.assert_called_once() + + +def test_report_schedule_all_text_filter_empty_noop() -> None: + from superset.reports.filters import ReportScheduleAllTextFilter + + query = MagicMock() + f = ReportScheduleAllTextFilter("name", MagicMock()) + result = f.apply(query, "") + assert result is query + query.filter.assert_not_called() + + +def test_report_schedule_all_text_filter_applies_ilike() -> None: + from superset.reports.filters import ReportScheduleAllTextFilter + + query = MagicMock() + f = ReportScheduleAllTextFilter("name", MagicMock()) + f.apply(query, "test") + query.filter.assert_called_once() diff --git a/tests/unit_tests/reports/model_test.py b/tests/unit_tests/reports/model_test.py index 19e12e14072..5f44307a08a 100644 --- a/tests/unit_tests/reports/model_test.py +++ b/tests/unit_tests/reports/model_test.py @@ -14,7 +14,6 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. -import pytest from superset.reports.models import ReportSchedule @@ -37,9 +36,14 @@ def test_get_native_filters_params(): } } - assert report_schedule.get_native_filters_params() == ( - "(filter_id:(extraFormData:(filters:!((col:column_name,op:IN,val:!(value1,value2)))),filterState:(label:column_name,validateStatus:!f,value:!(value1,value2)),id:filter_id,ownState:()))" + result, warnings = report_schedule.get_native_filters_params() + expected = ( + "(filter_id:(extraFormData:(filters:!((col:column_name,op:IN," + "val:!(value1,value2)))),filterState:(label:column_name," + "validateStatus:!f,value:!(value1,value2)),id:filter_id,ownState:()))" ) + assert result == expected + assert warnings == [] def test_get_native_filters_params_multiple_filters(): @@ -66,9 +70,17 @@ def test_get_native_filters_params_multiple_filters(): } } - assert report_schedule.get_native_filters_params() == ( - "(filter_id_1:(extraFormData:(filters:!((col:column_name_1,op:IN,val:!(value1,value2)))),filterState:(label:column_name_1,validateStatus:!f,value:!(value1,value2)),id:filter_id_1,ownState:()),filter_id_2:(extraFormData:(filters:!((col:column_name_2,op:IN,val:!(value3,value4)))),filterState:(label:column_name_2,validateStatus:!f,value:!(value3,value4)),id:filter_id_2,ownState:()))" + result, warnings = report_schedule.get_native_filters_params() + expected = ( + "(filter_id_1:(extraFormData:(filters:!((col:column_name_1,op:IN," + "val:!(value1,value2)))),filterState:(label:column_name_1," + "validateStatus:!f,value:!(value1,value2)),id:filter_id_1,ownState:())," + "filter_id_2:(extraFormData:(filters:!((col:column_name_2,op:IN," + "val:!(value3,value4)))),filterState:(label:column_name_2," + "validateStatus:!f,value:!(value3,value4)),id:filter_id_2,ownState:()))" ) + assert result == expected + assert warnings == [] def test_report_generate_native_filter_no_values(): @@ -79,11 +91,12 @@ def test_report_generate_native_filter_no_values(): native_filter_id = "filter_id" column_name = "column_name" filter_type = "filter_select" - values = None + values: list[str | None] = [] - assert report_schedule._generate_native_filter( + result, warning = report_schedule._generate_native_filter( native_filter_id, filter_type, column_name, values - ) == { + ) + assert result == { "filter_id": { "id": "filter_id", "extraFormData": { @@ -97,11 +110,13 @@ def test_report_generate_native_filter_no_values(): "ownState": {}, } } + assert warning is None -def test_get_native_filters_params_invalid_structure(): +def test_get_native_filters_params_missing_filter_values(): """ - Test the ``get_native_filters_params`` method with invalid structure. + Test the ``get_native_filters_params`` method with missing filterValues. + Should handle gracefully by using empty list as default. """ report_schedule = ReportSchedule() report_schedule.extra = { @@ -111,29 +126,85 @@ def test_get_native_filters_params_invalid_structure(): "nativeFilterId": "filter_id", "columnName": "column_name", "filterType": "filter_select", - # Missing "filterValues" key + # Missing "filterValues" key - should default to [] } ] } } - with pytest.raises(KeyError, match="'filterValues'"): - report_schedule.get_native_filters_params() + # Should not raise, should handle gracefully with empty filterValues + result, warnings = report_schedule.get_native_filters_params() + assert "filter_id" in result + assert "column_name" in result + assert warnings == [] -# todo(hugh): how do we want to handle this case? -# def test_report_generate_native_filter_invalid_filter_id(): -# """ -# Test the ``_generate_native_filter`` method with invalid filter id. -# """ -# report_schedule = ReportSchedule() -# native_filter_id = None -# column_name = "column_name" -# values = ["value1", "value2"] +def test_get_native_filters_params_explicit_none_values(): + """ + Test the ``get_native_filters_params`` method with explicit None values. + Should handle gracefully by coercing None to empty string/list. + """ + report_schedule = ReportSchedule() + report_schedule.extra = { + "dashboard": { + "nativeFilters": [ + { + "nativeFilterId": "filter_id", + "columnName": None, # Explicit None + "filterType": "filter_select", + "filterValues": None, # Explicit None + } + ] + } + } -# assert report_schedule._generate_native_filter( -# native_filter_id, column_name, values -# ) == {} + # Should not raise TypeError, should handle gracefully + result, warnings = report_schedule.get_native_filters_params() + assert "filter_id" in result + assert warnings == [] + + +def test_get_native_filters_params_missing_required_fields(): + """ + Test the ``get_native_filters_params`` method with missing required fields. + Filters missing nativeFilterId or filterType should be skipped. + """ + report_schedule = ReportSchedule() + report_schedule.extra = { + "dashboard": { + "nativeFilters": [ + { + # Missing nativeFilterId - should be skipped + "filterType": "filter_select", + "columnName": "column_name", + "filterValues": ["value1"], + }, + { + # Missing filterType - should be skipped + "nativeFilterId": "filter_2", + "columnName": "column_name", + "filterValues": ["value2"], + }, + { + # Valid filter - should be processed + "nativeFilterId": "filter_3", + "filterType": "filter_select", + "columnName": "column_name", + "filterValues": ["value3"], + }, + ] + } + } + + result, warnings = report_schedule.get_native_filters_params() + # Only the valid filter should be in the result + assert "filter_3" in result + assert "filter_2" not in result + assert "value1" not in result + assert "value3" in result + # Two malformed filters should generate two warnings + assert len(warnings) == 2 + assert all("Skipping malformed native filter" in w for w in warnings) def test_report_generate_native_filter(): @@ -146,9 +217,10 @@ def test_report_generate_native_filter(): column_name = "column_name" values = ["value1", "value2"] - assert report_schedule._generate_native_filter( + result, warning = report_schedule._generate_native_filter( native_filter_id, filter_type, column_name, values - ) == { + ) + assert result == { "filter_id": { "extraFormData": { "filters": [ @@ -164,6 +236,7 @@ def test_report_generate_native_filter(): "ownState": {}, } } + assert warning is None def test_get_native_filters_params_empty(): @@ -173,7 +246,9 @@ def test_get_native_filters_params_empty(): report_schedule = ReportSchedule() report_schedule.extra = {} - assert report_schedule.get_native_filters_params() == "()" + result, warnings = report_schedule.get_native_filters_params() + assert result == "()" + assert warnings == [] def test_get_native_filters_params_no_native_filters(): @@ -183,7 +258,9 @@ def test_get_native_filters_params_no_native_filters(): report_schedule = ReportSchedule() report_schedule.extra = {"dashboard": {"nativeFilters": []}} - assert report_schedule.get_native_filters_params() == "()" + result, warnings = report_schedule.get_native_filters_params() + assert result == "()" + assert warnings == [] def test_report_generate_native_filter_empty_values(): @@ -196,9 +273,10 @@ def test_report_generate_native_filter_empty_values(): column_name = "column_name" values = [] - assert report_schedule._generate_native_filter( + result, warning = report_schedule._generate_native_filter( native_filter_id, filter_type, column_name, values - ) == { + ) + assert result == { "filter_id": { "extraFormData": { "filters": [{"col": "column_name", "op": "IN", "val": []}] @@ -212,6 +290,7 @@ def test_report_generate_native_filter_empty_values(): "ownState": {}, } } + assert warning is None def test_report_generate_native_filter_no_column_name(): @@ -224,9 +303,10 @@ def test_report_generate_native_filter_no_column_name(): column_name = "" values = ["value1", "value2"] - assert report_schedule._generate_native_filter( + result, warning = report_schedule._generate_native_filter( native_filter_id, filter_type, column_name, values - ) == { + ) + assert result == { "filter_id": { "extraFormData": { "filters": [{"col": "", "op": "IN", "val": ["value1", "value2"]}] @@ -240,3 +320,431 @@ def test_report_generate_native_filter_no_column_name(): "ownState": {}, } } + assert warning is None + + +def test_report_generate_native_filter_select_null_column(): + report_schedule = ReportSchedule() + result, warning = report_schedule._generate_native_filter( + "F1", "filter_select", None, ["US"] + ) + assert result["F1"]["extraFormData"]["filters"][0]["col"] == "" + assert result["F1"]["filterState"]["label"] == "" + assert warning is None + + +def test_generate_native_filter_time_normal(): + report_schedule = ReportSchedule() + result, warning = report_schedule._generate_native_filter( + "F2", "filter_time", "ignored", ["Last week"] + ) + assert result == { + "F2": { + "id": "F2", + "extraFormData": {"time_range": "Last week"}, + "filterState": {"value": "Last week"}, + "ownState": {}, + } + } + assert warning is None + + +def test_generate_native_filter_timegrain_normal(): + report_schedule = ReportSchedule() + result, warning = report_schedule._generate_native_filter( + "F3", "filter_timegrain", "ignored", ["P1D"] + ) + assert result == { + "F3": { + "id": "F3", + "extraFormData": {"time_grain_sqla": "P1D"}, + "filterState": {"value": ["P1D"]}, + "ownState": {}, + } + } + assert warning is None + + +def test_generate_native_filter_timecolumn_normal(): + """filter_timecolumn is the only branch missing 'id' in its output.""" + report_schedule = ReportSchedule() + result, warning = report_schedule._generate_native_filter( + "F4", "filter_timecolumn", "ignored", ["ds"] + ) + assert result == { + "F4": { + "extraFormData": {"granularity_sqla": "ds"}, + "filterState": {"value": ["ds"]}, + } + } + assert "id" not in result["F4"] + assert warning is None + + +def test_generate_native_filter_range_normal(): + report_schedule = ReportSchedule() + result, warning = report_schedule._generate_native_filter( + "F5", "filter_range", "price", [10, 100] + ) + assert result == { + "F5": { + "id": "F5", + "extraFormData": { + "filters": [ + {"col": "price", "op": ">=", "val": 10}, + {"col": "price", "op": "<=", "val": 100}, + ] + }, + "filterState": { + "value": [10, 100], + "label": "10 ≤ x ≤ 100", + }, + "ownState": {}, + } + } + assert warning is None + + +def test_generate_native_filter_range_min_only(): + report_schedule = ReportSchedule() + result, warning = report_schedule._generate_native_filter( + "F5", "filter_range", "price", [10] + ) + assert result["F5"]["extraFormData"]["filters"] == [ + {"col": "price", "op": ">=", "val": 10} + ] + assert result["F5"]["filterState"]["label"] == "x ≥ 10" + assert result["F5"]["filterState"]["value"] == [10, None] + assert warning is None + + +def test_generate_native_filter_range_max_only(): + report_schedule = ReportSchedule() + result, warning = report_schedule._generate_native_filter( + "F5", "filter_range", "price", [None, 100] + ) + assert result["F5"]["extraFormData"]["filters"] == [ + {"col": "price", "op": "<=", "val": 100} + ] + assert result["F5"]["filterState"]["label"] == "x ≤ 100" + assert warning is None + + +def test_report_generate_native_filter_unknown_filter_type(): + """ + Test the ``_generate_native_filter`` method with an unknown filter type. + Should return empty dict and a warning message. + """ + report_schedule = ReportSchedule() + native_filter_id = "filter_id" + filter_type = "filter_unknown" + column_name = "column_name" + values = ["value1", "value2"] + + result, warning = report_schedule._generate_native_filter( + native_filter_id, filter_type, column_name, values + ) + assert result == {} + assert warning is not None + assert "unrecognized filter type" in warning + assert "filter_unknown" in warning + assert "filter_id" in warning + + +def test_get_native_filters_params_null_native_filters(): + report_schedule = ReportSchedule() + report_schedule.extra = {"dashboard": {"nativeFilters": None}} + result, warnings = report_schedule.get_native_filters_params() + assert result == "()" + assert warnings == [] + + +def test_get_native_filters_params_rison_quote_escaping(): + report_schedule = ReportSchedule() + report_schedule.extra = { + "dashboard": { + "nativeFilters": [ + { + "nativeFilterId": "F1", + "filterType": "filter_select", + "columnName": "name", + "filterValues": ["O'Brien"], + } + ] + } + } + result, warnings = report_schedule.get_native_filters_params() + assert "'" not in result + assert "%27" in result + assert warnings == [] + + +def test_get_native_filters_params_unknown_filter_type(): + """ + Test the ``get_native_filters_params`` method with an unknown filter type. + Should skip the filter and include a warning. + """ + report_schedule = ReportSchedule() + report_schedule.extra = { + "dashboard": { + "nativeFilters": [ + { + "nativeFilterId": "filter_1", + "filterType": "filter_unknown_type", + "columnName": "column_name", + "filterValues": ["value1"], + }, + { + "nativeFilterId": "filter_2", + "filterType": "filter_select", + "columnName": "column_name", + "filterValues": ["value2"], + }, + ] + } + } + + result, warnings = report_schedule.get_native_filters_params() + # The unknown filter should be skipped, valid filter should be present + assert "filter_2" in result + assert "filter_1" not in result + assert "value2" in result + # Should have one warning for the unknown filter type + assert len(warnings) == 1 + assert "unrecognized filter type" in warnings[0] + assert "filter_unknown_type" in warnings[0] + + +def test_report_generate_native_filter_time_empty_values(): + """ + Test filter_time with empty values returns empty dict and warning. + """ + report_schedule = ReportSchedule() + result, warning = report_schedule._generate_native_filter( + "filter_id", "filter_time", "column_name", [] + ) + assert result == {} + assert warning is not None + assert "filter_time" in warning + assert "empty filterValues" in warning + assert "filter_id" in warning + + +def test_report_generate_native_filter_timegrain_empty_values(): + """ + Test filter_timegrain with empty values returns empty dict and warning. + """ + report_schedule = ReportSchedule() + result, warning = report_schedule._generate_native_filter( + "filter_id", "filter_timegrain", "column_name", [] + ) + assert result == {} + assert warning is not None + assert "filter_timegrain" in warning + assert "empty filterValues" in warning + assert "filter_id" in warning + + +def test_report_generate_native_filter_timecolumn_empty_values(): + """ + Test filter_timecolumn with empty values returns empty dict and warning. + """ + report_schedule = ReportSchedule() + result, warning = report_schedule._generate_native_filter( + "filter_id", "filter_timecolumn", "column_name", [] + ) + assert result == {} + assert warning is not None + assert "filter_timecolumn" in warning + assert "empty filterValues" in warning + assert "filter_id" in warning + + +def test_report_generate_native_filter_range_empty_values(): + """ + Test filter_range with empty values returns empty dict and warning. + """ + report_schedule = ReportSchedule() + result, warning = report_schedule._generate_native_filter( + "filter_id", "filter_range", "column_name", [] + ) + assert result == {} + assert warning is not None + assert "filter_range" in warning + assert "empty filterValues" in warning + assert "filter_id" in warning + + +def test_get_native_filters_params_time_filters_empty_values(): + """ + Test get_native_filters_params with time filters having empty values. + Should skip those filters and include warnings. + """ + report_schedule = ReportSchedule() + report_schedule.extra = { + "dashboard": { + "nativeFilters": [ + { + "nativeFilterId": "time_filter", + "filterType": "filter_time", + "columnName": "time_col", + "filterValues": [], # Empty values + }, + { + "nativeFilterId": "timegrain_filter", + "filterType": "filter_timegrain", + "columnName": "grain_col", + "filterValues": None, # None values (coerced to []) + }, + { + "nativeFilterId": "select_filter", + "filterType": "filter_select", + "columnName": "select_col", + "filterValues": ["value1"], # Valid filter + }, + ] + } + } + + result, warnings = report_schedule.get_native_filters_params() + # The time filters should be skipped, select filter should be present + assert "select_filter" in result + assert "time_filter" not in result + assert "timegrain_filter" not in result + assert "value1" in result + # Should have two warnings for the empty time filters + assert len(warnings) == 2 + assert any("filter_time" in w for w in warnings) + assert any("filter_timegrain" in w for w in warnings) + + +def test_get_native_filters_params_missing_filter_id_key(): + report_schedule = ReportSchedule() + report_schedule.extra = { + "dashboard": { + "nativeFilters": [ + { + "filterType": "filter_select", + "columnName": "col", + "filterValues": ["v"], + # Missing "nativeFilterId" key — skipped by defensive guard + } + ] + } + } + result, warnings = report_schedule.get_native_filters_params() + assert result == "()" + assert len(warnings) == 1 + assert "Skipping malformed native filter" in warnings[0] + + +def test_generate_native_filter_empty_filter_id(): + """Empty native_filter_id triggers the ``or ""`` fallback branches.""" + report_schedule = ReportSchedule() + result, warning = report_schedule._generate_native_filter( + "", "filter_select", "col", ["x"] + ) + assert "" in result + assert result[""]["id"] == "" + assert warning is None + + +def test_generate_native_filter_range_zero_min(): + """Zero min_val should produce a two-sided label, not a max-only label.""" + report_schedule = ReportSchedule() + result, _ = report_schedule._generate_native_filter( + "F5", "filter_range", "price", [0, 100] + ) + assert result["F5"]["extraFormData"]["filters"] == [ + {"col": "price", "op": ">=", "val": 0}, + {"col": "price", "op": "<=", "val": 100}, + ] + # Label generation correctly treats zero as a valid bound + assert result["F5"]["filterState"]["label"] == "0 ≤ x ≤ 100" + + +def test_generate_native_filter_range_zero_max(): + """Zero max_val should produce a two-sided label, not a min-only label.""" + report_schedule = ReportSchedule() + result, _ = report_schedule._generate_native_filter( + "F5", "filter_range", "price", [10, 0] + ) + assert result["F5"]["extraFormData"]["filters"] == [ + {"col": "price", "op": ">=", "val": 10}, + {"col": "price", "op": "<=", "val": 0}, + ] + assert result["F5"]["filterState"]["label"] == "10 ≤ x ≤ 0" + + +def test_generate_native_filter_range_both_zero(): + """Both values zero should produce a two-sided label, not an empty string.""" + report_schedule = ReportSchedule() + result, _ = report_schedule._generate_native_filter( + "F5", "filter_range", "price", [0, 0] + ) + assert result["F5"]["extraFormData"]["filters"] == [ + {"col": "price", "op": ">=", "val": 0}, + {"col": "price", "op": "<=", "val": 0}, + ] + assert result["F5"]["filterState"]["label"] == "0 ≤ x ≤ 0" + + +def test_get_native_filters_params_missing_filter_type(): + """Missing filterType skips the filter and emits a warning.""" + report_schedule = ReportSchedule() + report_schedule.extra = { + "dashboard": { + "nativeFilters": [ + { + "nativeFilterId": "F1", + "columnName": "col", + "filterValues": ["v"], + } + ] + } + } + result, warnings = report_schedule.get_native_filters_params() + assert result == "()" + assert len(warnings) == 1 + assert "Skipping malformed native filter" in warnings[0] + + +def test_get_native_filters_params_missing_column_name(): + """Missing columnName defaults to empty string via .get() fallback.""" + report_schedule = ReportSchedule() + report_schedule.extra = { + "dashboard": { + "nativeFilters": [ + { + "nativeFilterId": "F1", + "filterType": "filter_select", + "filterValues": ["v"], + } + ] + } + } + result, warnings = report_schedule.get_native_filters_params() + assert "F1" in result + assert warnings == [] + + +def test_generate_native_filter_range_null_column(): + """Range filter with None column_name falls back to empty string.""" + report_schedule = ReportSchedule() + result, warning = report_schedule._generate_native_filter( + "F5", "filter_range", None, [10, 100] + ) + assert result["F5"]["extraFormData"]["filters"][0]["col"] == "" + assert result["F5"]["extraFormData"]["filters"][1]["col"] == "" + assert warning is None + + +def test_generate_native_filter_time_empty_id(): + """Empty string filter ID for filter_time uses the ``or ""`` fallback.""" + report_schedule = ReportSchedule() + result, warning = report_schedule._generate_native_filter( + "", "filter_time", "ignored", ["Last week"] + ) + assert "" in result + assert result[""]["id"] == "" + assert warning is None diff --git a/tests/unit_tests/reports/notifications/slack_tests.py b/tests/unit_tests/reports/notifications/slack_tests.py index 1457544ec7e..d1a1a99d95e 100644 --- a/tests/unit_tests/reports/notifications/slack_tests.py +++ b/tests/unit_tests/reports/notifications/slack_tests.py @@ -377,3 +377,57 @@ def test_send_slack_no_feature_flag( ``` """, ) + + +@patch("superset.reports.notifications.slackv2.g") +@patch("superset.reports.notifications.slackv2.get_slack_client") +def test_slackv2_send_without_channels_raises( + slack_client_mock: MagicMock, + flask_global_mock: MagicMock, + mock_header_data, +) -> None: + from superset.reports.models import ReportRecipients, ReportRecipientType + from superset.reports.notifications.base import NotificationContent + from superset.reports.notifications.exceptions import NotificationParamException + + flask_global_mock.logs_context = {} + content = NotificationContent(name="test", header_data=mock_header_data) + notification = SlackV2Notification( + recipient=ReportRecipients( + type=ReportRecipientType.SLACKV2, + recipient_config_json='{"target": ""}', + ), + content=content, + ) + with pytest.raises(NotificationParamException, match="No recipients"): + notification.send() + + +@patch("superset.reports.notifications.slackv2.g") +@patch("superset.reports.notifications.slackv2.get_slack_client") +def test_slack_mixin_get_body_truncates_large_table( + slack_client_mock: MagicMock, + flask_global_mock: MagicMock, + mock_header_data, +) -> None: + from superset.reports.models import ReportRecipients, ReportRecipientType + from superset.reports.notifications.base import NotificationContent + + flask_global_mock.logs_context = {} + # Create a large DataFrame that exceeds the 4000-char message limit + large_df = pd.DataFrame({"col_" + str(i): range(100) for i in range(10)}) + content = NotificationContent( + name="test", + header_data=mock_header_data, + embedded_data=large_df, + description="desc", + ) + notification = SlackV2Notification( + recipient=ReportRecipients( + type=ReportRecipientType.SLACKV2, + recipient_config_json='{"target": "some_channel"}', + ), + content=content, + ) + body = notification._get_body(content=content) + assert "(table was truncated)" in body diff --git a/tests/unit_tests/reports/schemas_test.py b/tests/unit_tests/reports/schemas_test.py index cf72149070b..49e7fdfe649 100644 --- a/tests/unit_tests/reports/schemas_test.py +++ b/tests/unit_tests/reports/schemas_test.py @@ -19,7 +19,7 @@ import pytest from marshmallow import ValidationError from pytest_mock import MockerFixture -from superset.reports.schemas import ReportSchedulePostSchema +from superset.reports.schemas import ReportSchedulePostSchema, ReportSchedulePutSchema def test_report_post_schema_custom_width_validation(mocker: MockerFixture) -> None: @@ -75,3 +75,184 @@ def test_report_post_schema_custom_width_validation(mocker: MockerFixture) -> No assert excinfo.value.messages == { "custom_width": ["Screenshot width must be between 100px and 200px"] } + + +MINIMAL_POST_PAYLOAD = { + "type": "Report", + "name": "A report", + "crontab": "* * * * *", + "timezone": "America/Los_Angeles", +} + +CUSTOM_WIDTH_CONFIG = { + "ALERT_REPORTS_MIN_CUSTOM_SCREENSHOT_WIDTH": 600, + "ALERT_REPORTS_MAX_CUSTOM_SCREENSHOT_WIDTH": 2400, +} + + +@pytest.mark.parametrize( + "schema_class,payload_base", + [ + (ReportSchedulePostSchema, MINIMAL_POST_PAYLOAD), + (ReportSchedulePutSchema, {}), + ], + ids=["post", "put"], +) +@pytest.mark.parametrize( + "width,should_pass", + [ + (599, False), + (600, True), + (2400, True), + (2401, False), + (None, True), + ], +) +def test_custom_width_boundary_values( + mocker: MockerFixture, + schema_class: type, + payload_base: dict[str, object], + width: int | None, + should_pass: bool, +) -> None: + mocker.patch("flask.current_app.config", CUSTOM_WIDTH_CONFIG) + schema = schema_class() + payload = {**payload_base, "custom_width": width} + + if should_pass: + schema.load(payload) + else: + with pytest.raises(ValidationError) as exc: + schema.load(payload) + assert "custom_width" in exc.value.messages + + +def test_working_timeout_validation(mocker: MockerFixture) -> None: + mocker.patch("flask.current_app.config", CUSTOM_WIDTH_CONFIG) + post_schema = ReportSchedulePostSchema() + put_schema = ReportSchedulePutSchema() + + # POST: working_timeout=0 and -1 are invalid (min=1) + with pytest.raises(ValidationError) as exc: + post_schema.load({**MINIMAL_POST_PAYLOAD, "working_timeout": 0}) + assert "working_timeout" in exc.value.messages + + with pytest.raises(ValidationError) as exc: + post_schema.load({**MINIMAL_POST_PAYLOAD, "working_timeout": -1}) + assert "working_timeout" in exc.value.messages + + # POST: working_timeout=1 is valid + post_schema.load({**MINIMAL_POST_PAYLOAD, "working_timeout": 1}) + + # PUT: working_timeout=None is valid (allow_none=True) + put_schema.load({"working_timeout": None}) + + +def test_log_retention_post_vs_put_parity(mocker: MockerFixture) -> None: + mocker.patch("flask.current_app.config", CUSTOM_WIDTH_CONFIG) + post_schema = ReportSchedulePostSchema() + put_schema = ReportSchedulePutSchema() + + # POST: log_retention=0 is invalid (min=1) + with pytest.raises(ValidationError) as exc: + post_schema.load({**MINIMAL_POST_PAYLOAD, "log_retention": 0}) + assert "log_retention" in exc.value.messages + + # POST: log_retention=1 is valid + post_schema.load({**MINIMAL_POST_PAYLOAD, "log_retention": 1}) + + # PUT: log_retention=0 is valid (min=0) + put_schema.load({"log_retention": 0}) + + +def test_report_type_disallows_database(mocker: MockerFixture) -> None: + mocker.patch("flask.current_app.config", CUSTOM_WIDTH_CONFIG) + schema = ReportSchedulePostSchema() + + with pytest.raises(ValidationError) as exc: + schema.load({**MINIMAL_POST_PAYLOAD, "database": 1}) + assert "database" in exc.value.messages + + +def test_alert_type_allows_database(mocker: MockerFixture) -> None: + """Alert type should accept database; only Report type blocks it.""" + mocker.patch("flask.current_app.config", CUSTOM_WIDTH_CONFIG) + schema = ReportSchedulePostSchema() + result = schema.load({**MINIMAL_POST_PAYLOAD, "type": "Alert", "database": 1}) + assert result["database"] == 1 + + +# --------------------------------------------------------------------------- +# Phase 1b gap closure: crontab validator, name length, PUT parity +# --------------------------------------------------------------------------- + + +@pytest.mark.parametrize( + "crontab,should_pass", + [ + ("* * * * *", True), + ("0 0 * * 0", True), + ("*/5 * * * *", True), + ("not a cron", False), + ("* * * *", False), # too few fields + ("", False), + ], + ids=["every-min", "weekly", "every-5", "invalid-text", "too-few-fields", "empty"], +) +def test_crontab_validation( + mocker: MockerFixture, + crontab: str, + should_pass: bool, +) -> None: + mocker.patch("flask.current_app.config", CUSTOM_WIDTH_CONFIG) + schema = ReportSchedulePostSchema() + payload = {**MINIMAL_POST_PAYLOAD, "crontab": crontab} + + if should_pass: + result = schema.load(payload) + assert result["crontab"] == crontab + else: + with pytest.raises(ValidationError) as exc: + schema.load(payload) + assert "crontab" in exc.value.messages + + +def test_name_empty_rejected(mocker: MockerFixture) -> None: + mocker.patch("flask.current_app.config", CUSTOM_WIDTH_CONFIG) + schema = ReportSchedulePostSchema() + + with pytest.raises(ValidationError) as exc: + schema.load({**MINIMAL_POST_PAYLOAD, "name": ""}) + assert "name" in exc.value.messages + + +def test_name_at_max_length_accepted(mocker: MockerFixture) -> None: + mocker.patch("flask.current_app.config", CUSTOM_WIDTH_CONFIG) + schema = ReportSchedulePostSchema() + long_name = "x" * 150 + result = schema.load({**MINIMAL_POST_PAYLOAD, "name": long_name}) + assert result["name"] == long_name + + +def test_name_over_max_length_rejected(mocker: MockerFixture) -> None: + mocker.patch("flask.current_app.config", CUSTOM_WIDTH_CONFIG) + schema = ReportSchedulePostSchema() + + with pytest.raises(ValidationError) as exc: + schema.load({**MINIMAL_POST_PAYLOAD, "name": "x" * 151}) + assert "name" in exc.value.messages + + +def test_put_schema_allows_database_on_report_type(mocker: MockerFixture) -> None: + """PUT schema lacks validate_report_references — database on Report type is + accepted (documents current behavior; POST schema correctly rejects this).""" + mocker.patch("flask.current_app.config", CUSTOM_WIDTH_CONFIG) + put_schema = ReportSchedulePutSchema() + result = put_schema.load({"type": "Report", "database": 1}) + assert result["database"] == 1 + + # POST schema rejects it (verify the asymmetry) + post_schema = ReportSchedulePostSchema() + with pytest.raises(ValidationError) as exc: + post_schema.load({**MINIMAL_POST_PAYLOAD, "database": 1}) + assert "database" in exc.value.messages diff --git a/tests/unit_tests/result_set_test.py b/tests/unit_tests/result_set_test.py index da5dcdafabc..25df635247d 100644 --- a/tests/unit_tests/result_set_test.py +++ b/tests/unit_tests/result_set_test.py @@ -185,3 +185,62 @@ def test_get_column_description_from_empty_data_using_cursor_description( ) assert any(col.get("column_name") == "__time" for col in result_set.columns) logger.exception.assert_not_called() + + +def test_empty_column_names_get_synthetic_names() -> None: + """ + SQL Server returns an empty-string column name in cursor.description for + any un-aliased expression (e.g. ``SELECT COUNT(*) FROM t``). An empty + field name is illegal in NumPy structured arrays and PyArrow tables. + + SupersetResultSet must replace empty column names with synthetic names + so queries like ``SELECT COUNT(*) FROM t`` succeed on MSSQL. + + Regression test for https://github.com/apache/superset/issues/23848 + """ + data = [(42,)] + description = [("", 3, None, None, None, None, None)] + result_set = SupersetResultSet(data, description, BaseEngineSpec) # type: ignore + + assert result_set.columns[0]["column_name"] == "_col_0" + df = result_set.to_pandas_df() + assert list(df.columns) == ["_col_0"] + assert df["_col_0"].iloc[0] == 42 + + +def test_multiple_empty_column_names_get_unique_synthetic_names() -> None: + """ + When several columns have empty names (e.g. ``SELECT COUNT(*), SUM(x)`` + on MSSQL), each must receive a distinct synthetic name. + """ + data = [(10, 20)] + description = [ + ("", 3, None, None, None, None, None), + ("", 3, None, None, None, None, None), + ] + result_set = SupersetResultSet(data, description, BaseEngineSpec) # type: ignore + + col_names = [c["column_name"] for c in result_set.columns] + assert len(col_names) == 2 + assert len(set(col_names)) == 2 # all unique + df = result_set.to_pandas_df() + assert df.iloc[0].tolist() == [10, 20] + + +def test_empty_column_names_do_not_rename_explicit_synthetic_names() -> None: + """ + Synthetic names assigned to empty columns must not collide with explicit + user-selected names that already look like Superset fallbacks. + """ + data = [(10, 20)] + description = [ + ("", 3, None, None, None, None, None), + ("_col_0", 3, None, None, None, None, None), + ] + result_set = SupersetResultSet(data, description, BaseEngineSpec) # type: ignore + + col_names = [c["column_name"] for c in result_set.columns] + assert col_names == ["_col_1", "_col_0"] + df = result_set.to_pandas_df() + assert list(df.columns) == ["_col_1", "_col_0"] + assert df.iloc[0].tolist() == [10, 20] diff --git a/tests/unit_tests/security/api_test.py b/tests/unit_tests/security/api_test.py index 869b3308478..b26d2687990 100644 --- a/tests/unit_tests/security/api_test.py +++ b/tests/unit_tests/security/api_test.py @@ -14,6 +14,8 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. +from typing import Any + import pytest from superset.extensions import csrf @@ -24,12 +26,13 @@ from superset.extensions import csrf [{"WTF_CSRF_ENABLED": True}], indirect=True, ) -def test_csrf_not_exempt(app_context: None) -> None: +def test_csrf_exempt_blueprints(app_context: None) -> None: """ - Test that REST API is not exempt from CSRF. + Test that only FAB security API blueprints (which use token-based auth) + are exempt from CSRF protection. """ assert {blueprint.name for blueprint in csrf._exempt_blueprints} == { - "GroupApi", + "SupersetGroupApi", "MenuApi", "SecurityApi", "OpenApi", @@ -39,3 +42,21 @@ def test_csrf_not_exempt(app_context: None) -> None: "PermissionApi", "ViewMenuApi", } + + +@pytest.mark.parametrize( + "app", + [ + { + "WTF_CSRF_ENABLED": True, + "FAB_API_KEY_ENABLED": True, + } + ], + indirect=True, +) +def test_csrf_exempt_blueprints_with_api_key(app: Any, app_context: None) -> None: + """ + Test that ApiKeyApi blueprint is CSRF-exempt when FAB_API_KEY_ENABLED + config is enabled. + """ + assert "ApiKeyApi" in {blueprint.name for blueprint in csrf._exempt_blueprints} diff --git a/tests/unit_tests/security/audit_log_test.py b/tests/unit_tests/security/audit_log_test.py new file mode 100644 index 00000000000..276da24a6f9 --- /dev/null +++ b/tests/unit_tests/security/audit_log_test.py @@ -0,0 +1,251 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +from unittest.mock import MagicMock, patch + +from flask_appbuilder.security.sqla.models import Group, Role, User + +from superset.security.manager import ( + _log_audit_event, + SupersetGroupApi, + SupersetRoleApi, + SupersetSecurityManager, + SupersetUserApi, +) + + +@patch("superset.extensions.event_logger") +@patch("superset.security.manager.get_user_id", return_value=1) +def test_log_audit_event_calls_event_logger( + mock_get_user_id: MagicMock, + mock_event_logger: MagicMock, +) -> None: + """_log_audit_event delegates to the configured event_logger.""" + _log_audit_event("TestAction", {"key": "value"}) + + mock_event_logger.log.assert_called_once_with( + user_id=1, + action="TestAction", + dashboard_id=None, + duration_ms=None, + slice_id=None, + referrer=None, + curated_payload=None, + curated_form_data=None, + records=[{"key": "value"}], + ) + + +@patch("superset.extensions.event_logger") +@patch("superset.security.manager.get_user_id", return_value=1) +def test_log_audit_event_handles_logger_error( + mock_get_user_id: MagicMock, + mock_event_logger: MagicMock, +) -> None: + """_log_audit_event does not raise on event_logger errors.""" + mock_event_logger.log.side_effect = Exception("Logger error") + # Should not raise + _log_audit_event("TestAction", {"key": "value"}) + + +# --- Role CRUD --- + + +@patch("superset.security.manager._log_audit_event") +def test_role_api_post_add_logs_event(mock_log: MagicMock) -> None: + """SupersetRoleApi.post_add logs a RoleCreated event.""" + api = SupersetRoleApi.__new__(SupersetRoleApi) + role = MagicMock(spec=Role) + role.name = "TestRole" + role.id = 42 + api.post_add(role) + mock_log.assert_called_once_with( + "RoleCreated", {"role_name": "TestRole", "role_id": 42} + ) + + +@patch("superset.security.manager._log_audit_event") +def test_role_api_post_update_logs_event(mock_log: MagicMock) -> None: + """SupersetRoleApi.post_update logs a RoleUpdated event.""" + api = SupersetRoleApi.__new__(SupersetRoleApi) + role = MagicMock(spec=Role) + role.name = "TestRole" + role.id = 42 + api.post_update(role) + mock_log.assert_called_once_with( + "RoleUpdated", {"role_name": "TestRole", "role_id": 42} + ) + + +@patch("superset.security.manager._log_audit_event") +def test_role_api_post_delete_logs_event(mock_log: MagicMock) -> None: + """SupersetRoleApi.post_delete logs a RoleDeleted event.""" + api = SupersetRoleApi.__new__(SupersetRoleApi) + role = MagicMock(spec=Role) + role.name = "TestRole" + role.id = 42 + api.post_delete(role) + mock_log.assert_called_once_with( + "RoleDeleted", {"role_name": "TestRole", "role_id": 42} + ) + + +# --- User CRUD --- + + +@patch("superset.security.manager._log_audit_event") +def test_user_api_post_add_logs_event(mock_log: MagicMock) -> None: + """SupersetUserApi.post_add logs a UserCreated event.""" + api = SupersetUserApi.__new__(SupersetUserApi) + user = MagicMock(spec=User) + user.username = "testuser" + user.id = 7 + user.email = "test@example.com" + api.post_add(user) + mock_log.assert_called_once_with( + "UserCreated", + { + "target_username": "testuser", + "target_user_id": 7, + "email": "test@example.com", + }, + ) + + +@patch("superset.security.manager._log_audit_event") +def test_user_api_post_update_logs_event(mock_log: MagicMock) -> None: + """SupersetUserApi.post_update logs a UserUpdated event.""" + api = SupersetUserApi.__new__(SupersetUserApi) + user = MagicMock(spec=User) + user.username = "testuser" + user.id = 7 + user.email = "test@example.com" + user.active = True + api.post_update(user) + mock_log.assert_called_once_with( + "UserUpdated", + { + "target_username": "testuser", + "target_user_id": 7, + "email": "test@example.com", + "active": True, + }, + ) + + +@patch("superset.security.manager._log_audit_event") +def test_user_api_post_delete_logs_event(mock_log: MagicMock) -> None: + """SupersetUserApi.post_delete logs a UserDeleted event.""" + api = SupersetUserApi.__new__(SupersetUserApi) + user = MagicMock(spec=User) + user.username = "testuser" + user.id = 7 + api.post_delete(user) + mock_log.assert_called_once_with( + "UserDeleted", + {"target_username": "testuser", "target_user_id": 7}, + ) + + +# --- Group CRUD --- + + +@patch("superset.security.manager._log_audit_event") +def test_group_api_post_add_logs_event(mock_log: MagicMock) -> None: + """SupersetGroupApi.post_add logs a GroupCreated event.""" + api = SupersetGroupApi.__new__(SupersetGroupApi) + group = MagicMock(spec=Group) + group.name = "TestGroup" + group.id = 10 + api.post_add(group) + mock_log.assert_called_once_with( + "GroupCreated", {"group_name": "TestGroup", "group_id": 10} + ) + + +@patch("superset.security.manager._log_audit_event") +def test_group_api_post_update_logs_event(mock_log: MagicMock) -> None: + """SupersetGroupApi.post_update logs a GroupUpdated event.""" + api = SupersetGroupApi.__new__(SupersetGroupApi) + group = MagicMock(spec=Group) + group.name = "TestGroup" + group.id = 10 + api.post_update(group) + mock_log.assert_called_once_with( + "GroupUpdated", {"group_name": "TestGroup", "group_id": 10} + ) + + +@patch("superset.security.manager._log_audit_event") +def test_group_api_post_delete_logs_event(mock_log: MagicMock) -> None: + """SupersetGroupApi.post_delete logs a GroupDeleted event.""" + api = SupersetGroupApi.__new__(SupersetGroupApi) + group = MagicMock(spec=Group) + group.name = "TestGroup" + group.id = 10 + api.post_delete(group) + mock_log.assert_called_once_with( + "GroupDeleted", {"group_name": "TestGroup", "group_id": 10} + ) + + +# --- Login / Logout --- + + +@patch("superset.security.manager._log_audit_event") +def test_on_user_login_logs_event(mock_log: MagicMock) -> None: + """on_user_login logs a UserLoggedIn event.""" + sm = SupersetSecurityManager.__new__(SupersetSecurityManager) + user = MagicMock(spec=User) + user.username = "testuser" + user.id = 7 + + sm.on_user_login(user) + + mock_log.assert_called_once_with( + "UserLoggedIn", {"username": "testuser", "user_id": 7} + ) + + +@patch("superset.security.manager._log_audit_event") +def test_on_user_login_failed_logs_event(mock_log: MagicMock) -> None: + """on_user_login_failed logs a UserLoginFailed event.""" + sm = SupersetSecurityManager.__new__(SupersetSecurityManager) + user = MagicMock(spec=User) + user.username = "testuser" + user.id = 7 + + sm.on_user_login_failed(user) + + mock_log.assert_called_once_with( + "UserLoginFailed", {"username": "testuser", "user_id": 7} + ) + + +@patch("superset.security.manager._log_audit_event") +def test_on_user_logout_logs_event(mock_log: MagicMock) -> None: + """on_user_logout logs a UserLoggedOut event.""" + sm = SupersetSecurityManager.__new__(SupersetSecurityManager) + user = MagicMock(spec=User) + user.username = "testuser" + user.id = 7 + + sm.on_user_logout(user) + + mock_log.assert_called_once_with( + "UserLoggedOut", {"username": "testuser", "user_id": 7} + ) diff --git a/tests/unit_tests/sql/dialects/opensearch_tests.py b/tests/unit_tests/sql/dialects/opensearch_tests.py new file mode 100644 index 00000000000..c68c343a7ad --- /dev/null +++ b/tests/unit_tests/sql/dialects/opensearch_tests.py @@ -0,0 +1,240 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +import pytest +import sqlglot + +from superset.sql.dialects.opensearch import OpenSearch + + +def test_opensearch_dialect_registered() -> None: + """ + Test that OpenSearch dialect is properly registered for odelasticsearch. + """ + from superset.sql.parse import SQLGLOT_DIALECTS + + assert "odelasticsearch" in SQLGLOT_DIALECTS + assert SQLGLOT_DIALECTS["odelasticsearch"] == OpenSearch + + +def test_double_quotes_as_identifiers() -> None: + """ + Test that double quotes are treated as identifiers, not string literals. + """ + sql = 'SELECT "AvgTicketPrice" FROM "flights"' + ast = sqlglot.parse_one(sql, OpenSearch) + + assert ( + OpenSearch().generate(expression=ast, pretty=True) + == """ +SELECT + "AvgTicketPrice" +FROM "flights" + """.strip() + ) + + +def test_single_quotes_for_strings() -> None: + """ + Test that single quotes are used for string literals. + """ + sql = "SELECT * FROM flights WHERE Carrier = 'Kibana Airlines'" + ast = sqlglot.parse_one(sql, OpenSearch) + + assert ( + OpenSearch().generate(expression=ast, pretty=True) + == """ +SELECT + * +FROM flights +WHERE + Carrier = 'Kibana Airlines' + """.strip() + ) + + +def test_backticks_as_identifiers() -> None: + """ + Test that backticks work as identifiers (MySQL-style). + Backticks are normalized to double quotes in output. + """ + sql = "SELECT `AvgTicketPrice` FROM `flights`" + ast = sqlglot.parse_one(sql, OpenSearch) + + assert ( + OpenSearch().generate(expression=ast, pretty=True) + == """ +SELECT + "AvgTicketPrice" +FROM "flights" + """.strip() + ) + + +def test_mixed_identifier_quotes() -> None: + """ + Test mixing double quotes and backticks for identifiers. + """ + sql = 'SELECT "AvgTicketPrice" AS `AvgTicketPrice` FROM `default`.`flights`' + ast = sqlglot.parse_one(sql, OpenSearch) + + assert ( + OpenSearch().generate(expression=ast, pretty=True) + == """ +SELECT + "AvgTicketPrice" AS "AvgTicketPrice" +FROM "default"."flights" + """.strip() + ) + + +@pytest.mark.parametrize( + "sql, expected", + [ + ( + 'SELECT COUNT(*) FROM "flights" WHERE "Cancelled" = true', + """ +SELECT + COUNT(*) +FROM "flights" +WHERE + "Cancelled" = TRUE + """.strip(), + ), + ( + 'SELECT "Carrier", SUM("AvgTicketPrice") FROM "flights" GROUP BY "Carrier"', + """ +SELECT + "Carrier", + SUM("AvgTicketPrice") +FROM "flights" +GROUP BY + "Carrier" + """.strip(), + ), + ( + "SELECT * FROM \"flights\" WHERE \"DestCountry\" IN ('US', 'CA', 'MX')", + """ +SELECT + * +FROM "flights" +WHERE + "DestCountry" IN ('US', 'CA', 'MX') + """.strip(), + ), + ], +) +def test_various_queries(sql: str, expected: str) -> None: + """ + Test various SQL queries with OpenSearch dialect. + """ + ast = sqlglot.parse_one(sql, OpenSearch) + assert OpenSearch().generate(expression=ast, pretty=True) == expected + + +def test_aggregate_functions() -> None: + """ + Test aggregate functions with quoted identifiers. + """ + sql = """ +SELECT + "Carrier", + COUNT(*), + AVG("AvgTicketPrice"), + MAX("FlightDelayMin") +FROM "flights" +GROUP BY "Carrier" + """ + ast = sqlglot.parse_one(sql, OpenSearch) + + assert ( + OpenSearch().generate(expression=ast, pretty=True) + == """ +SELECT + "Carrier", + COUNT(*), + AVG("AvgTicketPrice"), + MAX("FlightDelayMin") +FROM "flights" +GROUP BY + "Carrier" + """.strip() + ) + + +def test_subquery_with_quoted_identifiers() -> None: + """ + Test subqueries with quoted identifiers. + """ + sql = 'SELECT * FROM (SELECT "Carrier", "AvgTicketPrice" FROM "flights") AS "sub"' + ast = sqlglot.parse_one(sql, OpenSearch) + + assert ( + OpenSearch().generate(expression=ast, pretty=True) + == """ +SELECT + * +FROM ( + SELECT + "Carrier", + "AvgTicketPrice" + FROM "flights" +) AS "sub" + """.strip() + ) + + +def test_order_by_with_quoted_identifiers() -> None: + """ + Test ORDER BY clause with quoted identifiers. + """ + sql = ( + 'SELECT "Carrier", "AvgTicketPrice" FROM "flights" ' + 'ORDER BY "AvgTicketPrice" DESC, "Carrier" ASC' + ) + ast = sqlglot.parse_one(sql, OpenSearch) + + assert ( + OpenSearch().generate(expression=ast, pretty=True) + == """ +SELECT + "Carrier", + "AvgTicketPrice" +FROM "flights" +ORDER BY + "AvgTicketPrice" DESC, + "Carrier" ASC + """.strip() + ) + + +def test_limit_clause() -> None: + """ + Test LIMIT clause with quoted identifiers. + """ + sql = 'SELECT * FROM "flights" LIMIT 10' + ast = sqlglot.parse_one(sql, OpenSearch) + + assert ( + OpenSearch().generate(expression=ast, pretty=True) + == """ +SELECT + * +FROM "flights" +LIMIT 10 + """.strip() + ) diff --git a/tests/unit_tests/sql/execution/test_executor.py b/tests/unit_tests/sql/execution/test_executor.py index 18b814a54d9..fdaf52f8a79 100644 --- a/tests/unit_tests/sql/execution/test_executor.py +++ b/tests/unit_tests/sql/execution/test_executor.py @@ -644,6 +644,78 @@ def test_execute_error( assert "Database error" in result.error_message +def test_execute_oauth2_redirect_error_propagates( + mocker: MockerFixture, database: Database, app_context: None +) -> None: + """Test that OAuth2RedirectError propagates instead of being swallowed.""" + from superset.exceptions import OAuth2RedirectError + + mock_conn = MagicMock() + mock_cursor = MagicMock() + mock_conn.cursor.return_value = mock_cursor + mock_conn.__enter__ = MagicMock(return_value=mock_conn) + mock_conn.__exit__ = MagicMock(return_value=False) + mocker.patch.object(database, "get_raw_connection", return_value=mock_conn) + mocker.patch.object( + database, "mutate_sql_based_on_config", side_effect=lambda sql, **kw: sql + ) + mocker.patch.object( + database.db_engine_spec, + "execute", + side_effect=OAuth2RedirectError( + url="https://oauth.example.com/authorize", + tab_id="test-tab", + redirect_uri="https://superset.example.com/callback", + ), + ) + mocker.patch.dict( + current_app.config, + { + "SQL_QUERY_MUTATOR": None, + "SQLLAB_TIMEOUT": 30, + "SQL_MAX_ROW": None, + "QUERY_LOGGER": None, + }, + ) + + with pytest.raises(OAuth2RedirectError): + database.execute("SELECT 1") + + +def test_execute_oauth2_error_propagates( + mocker: MockerFixture, database: Database, app_context: None +) -> None: + """Test that OAuth2Error propagates instead of being swallowed.""" + from superset.exceptions import OAuth2Error + + mock_conn = MagicMock() + mock_cursor = MagicMock() + mock_conn.cursor.return_value = mock_cursor + mock_conn.__enter__ = MagicMock(return_value=mock_conn) + mock_conn.__exit__ = MagicMock(return_value=False) + mocker.patch.object(database, "get_raw_connection", return_value=mock_conn) + mocker.patch.object( + database, "mutate_sql_based_on_config", side_effect=lambda sql, **kw: sql + ) + mocker.patch.object( + database.db_engine_spec, + "execute", + side_effect=OAuth2Error("No configuration found for OAuth2"), + ) + mocker.patch.dict( + current_app.config, + { + "SQL_QUERY_MUTATOR": None, + "SQLLAB_TIMEOUT": 30, + "SQL_MAX_ROW": None, + "QUERY_LOGGER": None, + }, + ) + + with pytest.raises(OAuth2Error): + database.execute("SELECT 1") + + # ============================================================================= # Async Execution Tests # ============================================================================= diff --git a/tests/unit_tests/sql/parse_tests.py b/tests/unit_tests/sql/parse_tests.py index 274acc01b5a..78b00f4487d 100644 --- a/tests/unit_tests/sql/parse_tests.py +++ b/tests/unit_tests/sql/parse_tests.py @@ -2693,9 +2693,19 @@ def test_is_valid_cvas(sql: str, engine: str, expected: bool) -> None: ), # Compact format ( "col = 'abc' -- comment", - "col = 'abc'", + "col = 'abc' /* comment */", "base", - ), # Comments removed for compact format + ), # Line comments converted to block comments + ( + "TRUE /* precise_count_distinct=true */", + "TRUE /* precise_count_distinct=true */", + "base", + ), # Block comments preserved + ( + "col > 1 /* hint=value */", + "col > 1 /* hint=value */", + "base", + ), # Block comments preserved ("col = 'col1 = 1) AND (col2 = 2'", "col = 'col1 = 1) AND (col2 = 2'", "base"), ("col = 'select 1; select 2'", "col = 'select 1; select 2'", "base"), ("col = 'abc -- comment'", "col = 'abc -- comment'", "base"), diff --git a/tests/unit_tests/sql/test_firebolt_dialect.py b/tests/unit_tests/sql/test_firebolt_dialect.py new file mode 100644 index 00000000000..88d9e89e680 --- /dev/null +++ b/tests/unit_tests/sql/test_firebolt_dialect.py @@ -0,0 +1,62 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +"""Tests for Firebolt dialect support in sqlglot.""" + +from superset.sql.parse import SQLScript + + +def test_firebolt_exclude_syntax() -> None: + """Test that Firebolt EXCLUDE syntax is preserved (not transformed to EXCEPT).""" + sql = "SELECT g.* EXCLUDE (source_file_timestamp) FROM public.games g" + script = SQLScript(sql, "firebolt") + + generated = script.format() + assert "EXCLUDE" in generated + assert "EXCEPT" not in generated + assert "source_file_timestamp" in generated + + +def test_firebolt_exclude_multiple_columns() -> None: + """Test EXCLUDE with multiple columns.""" + sql = "SELECT * EXCLUDE (col1, col2, col3) FROM my_table" + script = SQLScript(sql, "firebolt") + + generated = script.format() + assert "EXCLUDE" in generated + assert "EXCEPT" not in generated + assert "col1" in generated + assert "col2" in generated + assert "col3" in generated + + +def test_firebolt_sql_parsing() -> None: + """Test that Firebolt SQL can be parsed without errors.""" + sql = "SELECT * FROM my_table LIMIT 10" + script = SQLScript(sql, "firebolt") + assert len(script.statements) == 1 + assert not script.has_mutation() + + +def test_firebolt_not_in_parenthesized() -> None: + """Test that NOT IN is properly parenthesized in Firebolt.""" + sql = "SELECT * FROM my_table WHERE id NOT IN (1, 2, 3)" + script = SQLScript(sql, "firebolt") + + generated = script.format() + assert "NOT" in generated + assert "IN" in generated diff --git a/tests/unit_tests/sql_validators/#sqlite_test.py# b/tests/unit_tests/sql_validators/#sqlite_test.py# new file mode 100644 index 00000000000..ae5a07f4d2a --- /dev/null +++ b/tests/unit_tests/sql_validators/#sqlite_test.py# @@ -0,0 +1,303 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +from __future__ import annotations + +import subprocess +from subprocess import CompletedProcess +from unittest.mock import MagicMock, patch + +from superset.sql_validators.sqlite import SQLiteSQLValidator + + +def _mock_result( + returncode: int, + stderr: str = "", + stdout: str = "", +) -> CompletedProcess[str]: + return CompletedProcess( + args=[], + returncode=returncode, + stdout=stdout, + stderr=stderr, + ) + + +def test_valid_syntax() -> None: + mock_database = MagicMock() + sql = "SELECT 1, col FROM my_table" + + with ( + patch( + "superset.sql_validators.sqlite.get_binary_path", + return_value="syntaqlite", + ), + patch( + "superset.sql_validators.sqlite.subprocess.run", + return_value=_mock_result(returncode=0), + ) as run, + ): + annotations = SQLiteSQLValidator.validate( + sql=sql, + catalog=None, + schema="", + database=mock_database, + ) + + assert annotations == [] + run.assert_called_once() + command = run.call_args.args[0] + assert "--input" not in command + assert "-e" not in command + assert run.call_args.kwargs["input"] == sql + + +def test_invalid_syntax_single_error() -> None: + mock_database = MagicMock() + stderr = ( + 'error: near "SELEC": syntax error\n' + " --> :1:1\n" + " |\n" + "1 | SELEC * FROM foo\n" + " | ^~~~~\n" + ) + + with ( + patch( + "superset.sql_validators.sqlite.get_binary_path", return_value="syntaqlite" + ), + patch( + "superset.sql_validators.sqlite.subprocess.run", + return_value=_mock_result(returncode=1, stderr=stderr), + ), + ): + annotations = SQLiteSQLValidator.validate( + sql="SELEC * FROM foo", + catalog=None, + schema="", + database=mock_database, + ) + + assert len(annotations) == 1 + annotation = annotations[0] + assert annotation.line_number == 1 + assert annotation.start_column == 1 + # "SELEC" is 5 chars → caret + 4 tildes → end_column = 1 + 1 + 4 = 6 + assert annotation.end_column == 6 + assert "SELEC" in annotation.message + + +def test_invalid_syntax_multiple_errors() -> None: + mock_database = MagicMock() + stderr = ( + 'error: near "SELEC": syntax error\n' + " --> :1:1\n" + " |\n" + "1 | SELEC * FROM foo; SELEC * FROM bar\n" + " | ^~~~~\n\n" + 'error: near "SELEC": syntax error\n' + " --> :1:20\n" + " |\n" + "1 | SELEC * FROM foo; SELEC * FROM bar\n" + " | ^~~~~\n" + ) + + with ( + patch( + "superset.sql_validators.sqlite.get_binary_path", return_value="syntaqlite" + ), + patch( + "superset.sql_validators.sqlite.subprocess.run", + return_value=_mock_result(returncode=1, stderr=stderr), + ), + ): + annotations = SQLiteSQLValidator.validate( + sql="SELEC * FROM foo; SELEC * FROM bar", + catalog=None, + schema="", + database=mock_database, + ) + + assert len(annotations) == 2 + assert "SELEC" in annotations[0].message + # "SELEC" is 5 chars → caret + 4 tildes → end_column = start_column + 1 + 4 + assert isinstance(annotations[0].start_column, int) + assert isinstance(annotations[0].start_column, int) + assert annotations[0].end_column == annotations[0].start_column + 5 + assert annotations[1].end_column == annotations[1].start_column + 5 + + +def test_multiline_error_reports_correct_line() -> None: + mock_database = MagicMock() + stderr = ( + 'error: near "SELEC": syntax error\n' + " --> :2:1\n" + " |\n" + "2 | SELEC * FROM foo\n" + " | ^~~~~\n" + ) + + with ( + patch( + "superset.sql_validators.sqlite.get_binary_path", return_value="syntaqlite" + ), + patch( + "superset.sql_validators.sqlite.subprocess.run", + return_value=_mock_result(returncode=1, stderr=stderr), + ), + ): + annotations = SQLiteSQLValidator.validate( + sql="SELECT 1;\nSELEC * FROM foo", + catalog=None, + schema="", + database=mock_database, + ) + + assert len(annotations) == 1 + assert annotations[0].line_number == 2 + + +def test_empty_sql() -> None: + mock_database = MagicMock() + + with ( + patch( + "superset.sql_validators.sqlite.get_binary_path", return_value="syntaqlite" + ), + patch( + "superset.sql_validators.sqlite.subprocess.run", + return_value=_mock_result(returncode=0), + ), + ): + annotations = SQLiteSQLValidator.validate( + sql="", + catalog=None, + schema="", + database=mock_database, + ) + + assert annotations == [] + + +def test_valid_complex_query() -> None: + mock_database = MagicMock() + + with ( + patch( + "superset.sql_validators.sqlite.get_binary_path", return_value="syntaqlite" + ), + patch( + "superset.sql_validators.sqlite.subprocess.run", + return_value=_mock_result(returncode=0), + ), + ): + annotations = SQLiteSQLValidator.validate( + sql=( + "SELECT a, COUNT(*) AS cnt " + "FROM my_table " + "WHERE b > 10 " + "GROUP BY a " + "HAVING cnt > 1 " + "ORDER BY cnt DESC " + "LIMIT 100" + ), + catalog=None, + schema="", + database=mock_database, + ) + + assert annotations == [] + + +def test_annotation_to_dict() -> None: + mock_database = MagicMock() + stderr = ( + 'error: near "SELEC": syntax error\n' + " --> :1:1\n" + " |\n" + "1 | SELEC * FROM foo\n" + " | ^~~~~\n" + ) + + with ( + patch( + "superset.sql_validators.sqlite.get_binary_path", return_value="syntaqlite" + ), + patch( + "superset.sql_validators.sqlite.subprocess.run", + return_value=_mock_result(returncode=1, stderr=stderr), + ), + ): + annotations = SQLiteSQLValidator.validate( + sql="SELEC * FROM foo", + catalog=None, + schema="", + database=mock_database, + ) + + assert len(annotations) == 1 + result = annotations[0].to_dict() + assert "line_number" in result + assert "start_column" in result + assert "end_column" in result + assert "message" in result + + +def test_missing_syntaqlite_returns_annotation() -> None: + mock_database = MagicMock() + with patch("superset.sql_validators.sqlite.get_binary_path", None): + annotations = SQLiteSQLValidator.validate( + sql="SELECT 1", + catalog=None, + schema="", + database=mock_database, + ) + + assert len(annotations) == 1 + assert "syntaqlite is not installed" in annotations[0].message + assert annotations[0].line_number is None + + +def test_timeout_returns_annotation() -> None: + mock_database = MagicMock() + + with ( + patch( + "superset.sql_validators.sqlite.get_binary_path", return_value="syntaqlite" + ), + patch( + "superset.sql_validators.sqlite.subprocess.run", + side_effect=subprocess.TimeoutExpired(cmd="syntaqlite", timeout=10), + ), + ): + annotations = SQLiteSQLValidator.validate( + sql="SELECT 1", + catalog=None, + schema="", + database=mock_database, + ) + + assert len(annotations) == 1 + assert "timed out" in annotations[0].message + assert annotations[0].line_number is None + + +def test_get_validator_by_name() -> None: + from superset.sql_validators import get_validator_by_name + + validator = get_validator_by_name("SQLiteSQLValidator") + assert validator is SQLiteSQLValidator diff --git a/tests/unit_tests/sql_validators/sqlite_test.py b/tests/unit_tests/sql_validators/sqlite_test.py new file mode 100644 index 00000000000..a950580eafc --- /dev/null +++ b/tests/unit_tests/sql_validators/sqlite_test.py @@ -0,0 +1,303 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +from __future__ import annotations + +import subprocess +from subprocess import CompletedProcess +from unittest.mock import MagicMock, patch + +from superset.sql_validators.sqlite import SQLiteSQLValidator + + +def _mock_result( + returncode: int, + stderr: str = "", + stdout: str = "", +) -> CompletedProcess[str]: + return CompletedProcess( + args=[], + returncode=returncode, + stdout=stdout, + stderr=stderr, + ) + + +def test_valid_syntax() -> None: + mock_database = MagicMock() + sql = "SELECT 1, col FROM my_table" + + with ( + patch( + "superset.sql_validators.sqlite.get_binary_path", + return_value="syntaqlite", + ), + patch( + "superset.sql_validators.sqlite.subprocess.run", + return_value=_mock_result(returncode=0), + ) as run, + ): + annotations = SQLiteSQLValidator.validate( + sql=sql, + catalog=None, + schema="", + database=mock_database, + ) + + assert annotations == [] + run.assert_called_once() + command = run.call_args.args[0] + assert "--input" not in command + assert "-e" not in command + assert run.call_args.kwargs["input"] == sql + + +def test_invalid_syntax_single_error() -> None: + mock_database = MagicMock() + stderr = ( + 'error: near "SELEC": syntax error\n' + " --> :1:1\n" + " |\n" + "1 | SELEC * FROM foo\n" + " | ^~~~~\n" + ) + + with ( + patch( + "superset.sql_validators.sqlite.get_binary_path", return_value="syntaqlite" + ), + patch( + "superset.sql_validators.sqlite.subprocess.run", + return_value=_mock_result(returncode=1, stderr=stderr), + ), + ): + annotations = SQLiteSQLValidator.validate( + sql="SELEC * FROM foo", + catalog=None, + schema="", + database=mock_database, + ) + + assert len(annotations) == 1 + annotation = annotations[0] + assert annotation.line_number == 1 + assert annotation.start_column == 1 + # "SELEC" is 5 chars → caret + 4 tildes → end_column = 1 + 1 + 4 = 6 + assert annotation.end_column == 6 + assert "SELEC" in annotation.message + + +def test_invalid_syntax_multiple_errors() -> None: + mock_database = MagicMock() + stderr = ( + 'error: near "SELEC": syntax error\n' + " --> :1:1\n" + " |\n" + "1 | SELEC * FROM foo; SELEC * FROM bar\n" + " | ^~~~~\n\n" + 'error: near "SELEC": syntax error\n' + " --> :1:20\n" + " |\n" + "1 | SELEC * FROM foo; SELEC * FROM bar\n" + " | ^~~~~\n" + ) + + with ( + patch( + "superset.sql_validators.sqlite.get_binary_path", return_value="syntaqlite" + ), + patch( + "superset.sql_validators.sqlite.subprocess.run", + return_value=_mock_result(returncode=1, stderr=stderr), + ), + ): + annotations = SQLiteSQLValidator.validate( + sql="SELEC * FROM foo; SELEC * FROM bar", + catalog=None, + schema="", + database=mock_database, + ) + + assert len(annotations) == 2 + assert "SELEC" in annotations[0].message + # "SELEC" is 5 chars → caret + 4 tildes → end_column = start_column + 1 + 4 + assert isinstance(annotations[0].start_column, int) + assert annotations[0].end_column == annotations[0].start_column + 5 + assert isinstance(annotations[1].start_column, int) + assert annotations[1].end_column == annotations[1].start_column + 5 + + +def test_multiline_error_reports_correct_line() -> None: + mock_database = MagicMock() + stderr = ( + 'error: near "SELEC": syntax error\n' + " --> :2:1\n" + " |\n" + "2 | SELEC * FROM foo\n" + " | ^~~~~\n" + ) + + with ( + patch( + "superset.sql_validators.sqlite.get_binary_path", return_value="syntaqlite" + ), + patch( + "superset.sql_validators.sqlite.subprocess.run", + return_value=_mock_result(returncode=1, stderr=stderr), + ), + ): + annotations = SQLiteSQLValidator.validate( + sql="SELECT 1;\nSELEC * FROM foo", + catalog=None, + schema="", + database=mock_database, + ) + + assert len(annotations) == 1 + assert annotations[0].line_number == 2 + + +def test_empty_sql() -> None: + mock_database = MagicMock() + + with ( + patch( + "superset.sql_validators.sqlite.get_binary_path", return_value="syntaqlite" + ), + patch( + "superset.sql_validators.sqlite.subprocess.run", + return_value=_mock_result(returncode=0), + ), + ): + annotations = SQLiteSQLValidator.validate( + sql="", + catalog=None, + schema="", + database=mock_database, + ) + + assert annotations == [] + + +def test_valid_complex_query() -> None: + mock_database = MagicMock() + + with ( + patch( + "superset.sql_validators.sqlite.get_binary_path", return_value="syntaqlite" + ), + patch( + "superset.sql_validators.sqlite.subprocess.run", + return_value=_mock_result(returncode=0), + ), + ): + annotations = SQLiteSQLValidator.validate( + sql=( + "SELECT a, COUNT(*) AS cnt " + "FROM my_table " + "WHERE b > 10 " + "GROUP BY a " + "HAVING cnt > 1 " + "ORDER BY cnt DESC " + "LIMIT 100" + ), + catalog=None, + schema="", + database=mock_database, + ) + + assert annotations == [] + + +def test_annotation_to_dict() -> None: + mock_database = MagicMock() + stderr = ( + 'error: near "SELEC": syntax error\n' + " --> :1:1\n" + " |\n" + "1 | SELEC * FROM foo\n" + " | ^~~~~\n" + ) + + with ( + patch( + "superset.sql_validators.sqlite.get_binary_path", return_value="syntaqlite" + ), + patch( + "superset.sql_validators.sqlite.subprocess.run", + return_value=_mock_result(returncode=1, stderr=stderr), + ), + ): + annotations = SQLiteSQLValidator.validate( + sql="SELEC * FROM foo", + catalog=None, + schema="", + database=mock_database, + ) + + assert len(annotations) == 1 + result = annotations[0].to_dict() + assert "line_number" in result + assert "start_column" in result + assert "end_column" in result + assert "message" in result + + +def test_missing_syntaqlite_returns_annotation() -> None: + mock_database = MagicMock() + with patch("superset.sql_validators.sqlite.get_binary_path", None): + annotations = SQLiteSQLValidator.validate( + sql="SELECT 1", + catalog=None, + schema="", + database=mock_database, + ) + + assert len(annotations) == 1 + assert "syntaqlite is not installed" in annotations[0].message + assert annotations[0].line_number is None + + +def test_timeout_returns_annotation() -> None: + mock_database = MagicMock() + + with ( + patch( + "superset.sql_validators.sqlite.get_binary_path", return_value="syntaqlite" + ), + patch( + "superset.sql_validators.sqlite.subprocess.run", + side_effect=subprocess.TimeoutExpired(cmd="syntaqlite", timeout=10), + ), + ): + annotations = SQLiteSQLValidator.validate( + sql="SELECT 1", + catalog=None, + schema="", + database=mock_database, + ) + + assert len(annotations) == 1 + assert "timed out" in annotations[0].message + assert annotations[0].line_number is None + + +def test_get_validator_by_name() -> None: + from superset.sql_validators import get_validator_by_name + + validator = get_validator_by_name("SQLiteSQLValidator") + assert validator is SQLiteSQLValidator diff --git a/tests/unit_tests/utils/oauth2_tests.py b/tests/unit_tests/utils/oauth2_tests.py index f04ae26e7c2..c74b2f9570b 100644 --- a/tests/unit_tests/utils/oauth2_tests.py +++ b/tests/unit_tests/utils/oauth2_tests.py @@ -33,6 +33,7 @@ from superset.utils.oauth2 import ( generate_code_challenge, generate_code_verifier, get_oauth2_access_token, + get_oauth2_redirect_uri, refresh_oauth2_token, ) @@ -136,6 +137,7 @@ def test_refresh_oauth2_token_deletes_token_on_oauth2_exception( refresh_oauth2_token(DUMMY_OAUTH2_CONFIG, 1, 1, db_engine_spec, token) db.session.delete.assert_called_with(token) + db.session.flush.assert_called_once() def test_refresh_oauth2_token_keeps_token_on_other_exception( @@ -335,3 +337,105 @@ def test_encode_decode_oauth2_state( assert "code_verifier" not in decoded assert decoded["database_id"] == 1 assert decoded["user_id"] == 2 + + +def test_get_oauth2_access_token_lock_not_acquired_no_error_log( + mocker: MockerFixture, + caplog: pytest.LogCaptureFixture, +) -> None: + """ + Test that when a distributed lock can't be acquired, no error is logged and + the function returns None instead of raising. + + This scenario occurs when a dashboard with multiple charts from the same + OAuth2-enabled DB has an expired token: simultaneous requests compete for + the lock, and only the first one wins. The rest should silently return None. + """ + import logging + + from superset.exceptions import AcquireDistributedLockFailedException + + mocker.patch("time.sleep") # avoid backoff delays in tests + + db = mocker.patch("superset.utils.oauth2.db") + db_engine_spec = mocker.MagicMock() + token = mocker.MagicMock() + token.access_token = "access-token" # noqa: S105 + token.access_token_expiration = datetime(2024, 1, 1) + token.refresh_token = "refresh-token" # noqa: S105 + db.session.query().filter_by().one_or_none.return_value = token + + mocker.patch( + "superset.utils.oauth2.refresh_oauth2_token", + side_effect=AcquireDistributedLockFailedException("Lock not available"), + ) + + with freeze_time("2024-01-02"): + with caplog.at_level(logging.DEBUG): + result = get_oauth2_access_token({}, 1, 1, db_engine_spec) + + assert result is None + assert not any(record.levelno >= logging.ERROR for record in caplog.records) + + +def test_get_oauth2_redirect_uri_from_config(mocker: MockerFixture) -> None: + """ + Test that get_oauth2_redirect_uri returns the configured value when set. + """ + custom_uri = "https://proxy.example.com/oauth2/" + mocker.patch( + "flask.current_app.config", + {"DATABASE_OAUTH2_REDIRECT_URI": custom_uri}, + ) + assert get_oauth2_redirect_uri() == custom_uri + + +def test_get_oauth2_redirect_uri_falls_back_to_url_for(mocker: MockerFixture) -> None: + """ + Test that get_oauth2_redirect_uri falls back to url_for when config is not set. + """ + fallback_uri = "http://localhost:8088/api/v1/database/oauth2/" + mocker.patch("flask.current_app.config", {}) + mocker.patch( + "superset.utils.oauth2.url_for", + return_value=fallback_uri, + ) + assert get_oauth2_redirect_uri() == fallback_uri + + +def test_get_oauth2_redirect_uri_raises_on_build_error( + mocker: MockerFixture, +) -> None: + """ + Test that get_oauth2_redirect_uri raises OAuth2Error when url_for raises + BuildError (e.g. in headless/MCP contexts). + """ + from werkzeug.routing import BuildError + + from superset.exceptions import OAuth2Error + + mocker.patch("flask.current_app.config", {}) + mocker.patch( + "superset.utils.oauth2.url_for", + side_effect=BuildError("DatabaseRestApi.oauth2", {}, ("GET",)), + ) + with pytest.raises(OAuth2Error): + get_oauth2_redirect_uri() + + +def test_get_oauth2_redirect_uri_raises_on_runtime_error( + mocker: MockerFixture, +) -> None: + """ + Test that get_oauth2_redirect_uri raises OAuth2Error when url_for raises + RuntimeError (e.g. no request context and no SERVER_NAME). + """ + from superset.exceptions import OAuth2Error + + mocker.patch("flask.current_app.config", {}) + mocker.patch( + "superset.utils.oauth2.url_for", + side_effect=RuntimeError("Unable to build URL outside of request context"), + ) + with pytest.raises(OAuth2Error): + get_oauth2_redirect_uri() diff --git a/tests/unit_tests/utils/webdriver_test.py b/tests/unit_tests/utils/webdriver_test.py index ab3490e0956..ad55f4b68e6 100644 --- a/tests/unit_tests/utils/webdriver_test.py +++ b/tests/unit_tests/utils/webdriver_test.py @@ -530,6 +530,7 @@ class TestWebDriverPlaywrightFallback: "SCREENSHOT_PLAYWRIGHT_DEFAULT_TIMEOUT": 30000, "SCREENSHOT_PLAYWRIGHT_WAIT_EVENT": "networkidle", "SCREENSHOT_SELENIUM_HEADSTART": 5, + "SCREENSHOT_SELENIUM_ANIMATION_WAIT": 1, "SCREENSHOT_LOCATE_WAIT": 10, "SCREENSHOT_LOAD_WAIT": 10, "SCREENSHOT_WAIT_FOR_ERROR_MODAL_VISIBLE": 10, @@ -546,8 +547,10 @@ class TestWebDriverPlaywrightFallback: "http://example.com", "test-element", mock_user ) - # Should handle timeout gracefully and return None - assert result is None + # page.goto() timeout is caught and logged without aborting; execution + # continues to the element waits, which succeed here, so a screenshot + # is taken and returned (not None). + assert result is not None mock_logger.exception.assert_called() exception_call = mock_logger.exception.call_args[0][0] assert "Web event %s not detected" in exception_call @@ -640,10 +643,10 @@ class TestWebDriverPlaywrightErrorHandling: @patch("superset.utils.webdriver.PLAYWRIGHT_AVAILABLE", True) @patch("superset.utils.webdriver.sync_playwright") @patch("superset.utils.webdriver.logger") - def test_get_screenshot_logs_multiple_timeouts( + def test_get_screenshot_raises_on_element_wait_timeout( self, mock_logger, mock_sync_playwright ): - """Test that multiple timeout scenarios are logged appropriately.""" + """Test that PlaywrightTimeout propagates when waiting for page elements.""" from superset.utils.webdriver import PlaywrightTimeout mock_user = MagicMock() @@ -663,9 +666,10 @@ class TestWebDriverPlaywrightErrorHandling: mock_browser.new_context.return_value = mock_context mock_context.new_page.return_value = mock_page - # Mock locator to raise timeout on element wait + # Keep a reference to the exact instance so we can verify identity below. + timeout = PlaywrightTimeout() mock_page.locator.return_value = mock_element - mock_element.wait_for.side_effect = PlaywrightTimeout() + mock_element.wait_for.side_effect = timeout with patch("superset.utils.webdriver.app") as mock_app: mock_app.config = { @@ -686,10 +690,15 @@ class TestWebDriverPlaywrightErrorHandling: mock_auth.return_value = mock_context driver = WebDriverPlaywright("chrome") - result = driver.get_screenshot( - "http://example.com", "test-element", mock_user - ) + with pytest.raises(PlaywrightTimeout) as exc_info: + driver.get_screenshot( + "http://example.com", "test-element", mock_user + ) - assert result is None - # Should log timeout for element wait - assert mock_logger.exception.call_count >= 1 + # The exact injected instance must propagate — guards against the + # fallback alias (PlaywrightTimeout = Exception when playwright is + # not installed) accepting unrelated exceptions. + assert exc_info.value is timeout + mock_logger.exception.assert_any_call( + "Timed out requesting url %s", "http://example.com" + ) diff --git a/tests/unit_tests/views/datasource/views_test.py b/tests/unit_tests/views/datasource/views_test.py new file mode 100644 index 00000000000..3a54932d80c --- /dev/null +++ b/tests/unit_tests/views/datasource/views_test.py @@ -0,0 +1,314 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +"""Unit tests for resource-level authorization in superset/views/datasource/views.py. + +Tests use ``inspect.unwrap`` to call the underlying view logic directly, +bypassing the Flask-AppBuilder permission decorator machinery. +""" + +import inspect +from unittest.mock import MagicMock, patch + +import pytest + +from superset.errors import ErrorLevel, SupersetError, SupersetErrorType +from superset.exceptions import SupersetSecurityException +from superset.utils import json as superset_json + + +def _security_exception() -> SupersetSecurityException: + return SupersetSecurityException( + SupersetError( + message="Access denied", + error_type=SupersetErrorType.DATASOURCE_SECURITY_ACCESS_ERROR, + level=ErrorLevel.WARNING, + ) + ) + + +def _get_view_func(name: str): + """Return the unwrapped body of a Datasource view method.""" + from superset.views.datasource.views import Datasource + + return inspect.unwrap(getattr(Datasource, name)) + + +def _view_self() -> MagicMock: + """Create a minimal stand-in for a Datasource view instance.""" + self = MagicMock() + self.json_response = MagicMock(return_value="ok") + return self + + +# --------------------------------------------------------------------------- +# Datasource.get +# --------------------------------------------------------------------------- + + +@patch("superset.views.datasource.views.security_manager", new_callable=MagicMock) +@patch("superset.views.datasource.views.DatasourceDAO.get_datasource") +def test_get_raises_when_access_denied( + mock_get_datasource: MagicMock, + mock_security_manager: MagicMock, +) -> None: + """raise_for_access is called and propagates for unauthorised callers.""" + mock_datasource = MagicMock() + mock_get_datasource.return_value = mock_datasource + mock_security_manager.raise_for_access.side_effect = _security_exception() + + raw_get = _get_view_func("get") + with pytest.raises(SupersetSecurityException): + raw_get(_view_self(), "table", 1) + + mock_security_manager.raise_for_access.assert_called_once_with( + datasource=mock_datasource + ) + + +@patch("superset.views.datasource.views.sanitize_datasource_data") +@patch("superset.views.datasource.views.security_manager", new_callable=MagicMock) +@patch("superset.views.datasource.views.DatasourceDAO.get_datasource") +def test_get_succeeds_for_authorised_user( + mock_get_datasource: MagicMock, + mock_security_manager: MagicMock, + mock_sanitize: MagicMock, +) -> None: + """raise_for_access is called without raising; sanitized data is returned.""" + mock_datasource = MagicMock() + mock_datasource.data = {"id": 1} + mock_get_datasource.return_value = mock_datasource + mock_security_manager.raise_for_access.return_value = None + mock_sanitize.return_value = {"id": 1} + + view = _view_self() + raw_get = _get_view_func("get") + raw_get(view, "table", 1) + + mock_security_manager.raise_for_access.assert_called_once_with( + datasource=mock_datasource + ) + view.json_response.assert_called_once_with({"id": 1}) + + +# --------------------------------------------------------------------------- +# Datasource.external_metadata +# --------------------------------------------------------------------------- + + +@patch("superset.views.datasource.views.security_manager", new_callable=MagicMock) +@patch("superset.views.datasource.views.DatasourceDAO.get_datasource") +def test_external_metadata_raises_when_access_denied( + mock_get_datasource: MagicMock, + mock_security_manager: MagicMock, +) -> None: + mock_datasource = MagicMock() + mock_get_datasource.return_value = mock_datasource + mock_security_manager.raise_for_access.side_effect = _security_exception() + + raw_fn = _get_view_func("external_metadata") + with pytest.raises(SupersetSecurityException): + raw_fn(_view_self(), "table", 1) + + mock_security_manager.raise_for_access.assert_called_once_with( + datasource=mock_datasource + ) + + +@patch("superset.views.datasource.views.security_manager", new_callable=MagicMock) +@patch("superset.views.datasource.views.DatasourceDAO.get_datasource") +def test_external_metadata_succeeds_for_authorised_user( + mock_get_datasource: MagicMock, + mock_security_manager: MagicMock, +) -> None: + mock_datasource = MagicMock() + mock_datasource.external_metadata.return_value = [{"name": "col1"}] + mock_get_datasource.return_value = mock_datasource + mock_security_manager.raise_for_access.return_value = None + + view = _view_self() + raw_fn = _get_view_func("external_metadata") + raw_fn(view, "table", 1) + + mock_security_manager.raise_for_access.assert_called_once_with( + datasource=mock_datasource + ) + view.json_response.assert_called_once_with([{"name": "col1"}]) + + +# --------------------------------------------------------------------------- +# Datasource.external_metadata_by_name +# --------------------------------------------------------------------------- + + +@patch("superset.views.datasource.views.security_manager", new_callable=MagicMock) +@patch("superset.views.datasource.views.SqlaTable.get_datasource_by_name") +@patch("superset.views.datasource.views.ExternalMetadataSchema") +def test_external_metadata_by_name_known_datasource_raises_when_access_denied( + mock_schema_cls: MagicMock, + mock_get_by_name: MagicMock, + mock_security_manager: MagicMock, +) -> None: + """When a datasource exists, raise_for_access(datasource=...) is enforced.""" + params = { + "database_name": "mydb", + "schema_name": "public", + "table_name": "private_table", + } + mock_schema_cls.return_value.load.return_value = params + + mock_datasource = MagicMock() + mock_get_by_name.return_value = mock_datasource + mock_security_manager.raise_for_access.side_effect = _security_exception() + + raw_fn = _get_view_func("external_metadata_by_name") + with pytest.raises(SupersetSecurityException): + raw_fn(_view_self(), rison=params) + + mock_security_manager.raise_for_access.assert_called_once_with( + datasource=mock_datasource + ) + + +@patch("superset.views.datasource.views.security_manager", new_callable=MagicMock) +@patch("superset.views.datasource.views.SqlaTable.get_datasource_by_name") +@patch("superset.views.datasource.views.ExternalMetadataSchema") +@patch("superset.views.datasource.views.db") +def test_external_metadata_by_name_no_datasource_raises_when_access_denied( + mock_db: MagicMock, + mock_schema_cls: MagicMock, + mock_get_by_name: MagicMock, + mock_security_manager: MagicMock, +) -> None: + """When no datasource exists, raise_for_access(database=..., table=...) runs.""" + params = { + "database_name": "mydb", + "schema_name": "public", + "table_name": "new_table", + } + mock_schema_cls.return_value.load.return_value = params + mock_get_by_name.return_value = None + + mock_database = MagicMock() + mock_db.session.query.return_value.filter_by.return_value.one.return_value = ( + mock_database + ) + mock_security_manager.raise_for_access.side_effect = _security_exception() + + raw_fn = _get_view_func("external_metadata_by_name") + with pytest.raises(SupersetSecurityException): + raw_fn(_view_self(), rison=params) + + mock_security_manager.raise_for_access.assert_called_once() + call_kwargs = mock_security_manager.raise_for_access.call_args.kwargs + assert call_kwargs["database"] is mock_database + assert call_kwargs["table"].table == "new_table" + assert call_kwargs["table"].schema == "public" + + +# --------------------------------------------------------------------------- +# Datasource.save — ownership bypass prevention +# --------------------------------------------------------------------------- + + +@patch("superset.views.datasource.views.security_manager", new_callable=MagicMock) +@patch("superset.views.datasource.views.DatasourceDAO.get_datasource") +def test_save_always_checks_ownership_even_without_owners_field( + mock_get_datasource: MagicMock, + mock_security_manager: MagicMock, +) -> None: + """Ownership check runs even when 'owners' is absent from the payload.""" + mock_orm = MagicMock() + mock_orm.owner_class = MagicMock() # not None — model supports ownership + mock_get_datasource.return_value = mock_orm + mock_security_manager.raise_for_ownership.side_effect = SupersetSecurityException( + SupersetError( + message="Not an owner", + error_type=SupersetErrorType.DATASOURCE_SECURITY_ACCESS_ERROR, + level=ErrorLevel.WARNING, + ) + ) + + from flask import Flask + + from superset.commands.dataset.exceptions import DatasetForbiddenError + + raw_save = _get_view_func("save") + app = Flask(__name__) + with app.test_request_context( + "/datasource/save/", + method="POST", + data={ + "data": superset_json.dumps( + { + "id": 1, + "type": "table", + "database": {"id": 1}, + "columns": [], + # 'owners' intentionally omitted + } + ) + }, + ): + with pytest.raises(DatasetForbiddenError): + raw_save(_view_self()) + + mock_security_manager.raise_for_ownership.assert_called_once_with(mock_orm) + + +@patch("superset.views.datasource.views.security_manager", new_callable=MagicMock) +@patch("superset.views.datasource.views.DatasourceDAO.get_datasource") +def test_save_non_owner_with_owners_field_is_rejected( + mock_get_datasource: MagicMock, + mock_security_manager: MagicMock, +) -> None: + """A non-owner cannot use the save endpoint even when supplying an owners list.""" + mock_orm = MagicMock() + mock_orm.owner_class = MagicMock() + mock_get_datasource.return_value = mock_orm + mock_security_manager.raise_for_ownership.side_effect = SupersetSecurityException( + SupersetError( + message="Not an owner", + error_type=SupersetErrorType.DATASOURCE_SECURITY_ACCESS_ERROR, + level=ErrorLevel.WARNING, + ) + ) + + from flask import Flask + + from superset.commands.dataset.exceptions import DatasetForbiddenError + + raw_save = _get_view_func("save") + app = Flask(__name__) + with app.test_request_context( + "/datasource/save/", + method="POST", + data={ + "data": superset_json.dumps( + { + "id": 1, + "type": "table", + "database": {"id": 1}, + "columns": [], + "owners": [99], # attacker-supplied owners list + } + ) + }, + ): + with pytest.raises(DatasetForbiddenError): + raw_save(_view_self()) + + mock_security_manager.raise_for_ownership.assert_called_once_with(mock_orm)