diff options
Diffstat
131 files changed, 379 insertions, 23540 deletions
diff --git a/.dockerignore b/.dockerignore deleted file mode 100644 --- a/.dockerignore +++ /dev/null @@ -1,15 +0,0 @@ -# SPDX-FileCopyrightText: 2026 Nikolay Govorov -# SPDX-License-Identifier: AGPL-3.0-or-later - -.git -.github -.task -build -cmd/mirum-agent/.zig-cache -cmd/mirum-agent/zig-out -cmd/mirum-agent/zig-pkg -cmd/mirum-server/apipb -cmd/mirum-server/static -cmd/mirum-server/web/gen -cmd/mirum-server/web/node_modules -internal/protocol/wirepb diff --git a/.editorconfig b/.editorconfig deleted file mode 100644 --- a/.editorconfig +++ /dev/null @@ -1,21 +0,0 @@ -# SPDX-FileCopyrightText: 2026 Nikolay Govorov -# SPDX-License-Identifier: AGPL-3.0-or-later - -root = true - -[*] -charset = utf-8 -end_of_line = lf -insert_final_newline = true -trim_trailing_whitespace = true -indent_style = space -indent_size = 2 - -[*.go] -indent_style = tab - -[{Makefile,*.mk}] -indent_style = tab - -[*.md] -trim_trailing_whitespace = false diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml deleted file mode 100644 --- a/.github/workflows/build.yml +++ /dev/null @@ -1,193 +0,0 @@ -# SPDX-FileCopyrightText: 2026 Nikolay Govorov -# SPDX-License-Identifier: AGPL-3.0-or-later - -# NOTE: Don't extend it; keep the logic in the Taskfile. -# We're using GitHub Actions as a temporary solution -# until Mirum can handle its own maintenance. - -name: Build - -on: - push: - branches: [main] - tags: ["v*"] - pull_request: - branches: [main] - -jobs: - build: - name: Build and publish Nightly - runs-on: ubuntu-latest - concurrency: - group: >- - package-publish-mirum-${{ github.ref == 'refs/heads/main' && 'nightly' - || startsWith(github.ref, 'refs/tags/v') && 'stable' || github.ref }} - cancel-in-progress: false - permissions: - contents: write - steps: - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - with: - fetch-depth: 0 - - - uses: jdx/mise-action@5228313ee0372e111a38da051671ca30fc5a96db # v3 - with: - version: 2026.7.5 - experimental: true - install: false - - - name: Install dependencies - run: mise bootstrap --locked --yes --update - - - name: Build and package - run: task package GIT_REF=${{ github.ref }} GPG_KEY_ID=${{ vars.GPG_KEY_ID }} - env: - GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} - GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} - APK_PRIVATE_KEY: ${{ secrets.APK_PRIVATE_KEY }} - PACKAGE_KEY_VERSION: ${{ vars.PACKAGE_KEY_VERSION }} - - - name: Lint - run: task lint - - - name: Tests - run: task test - - - name: Upload container binaries - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 - with: - name: container-binaries - path: | - build/mirum-server-linux-amd64 - build/mirum-server-linux-arm64 - build/mirum-worker-linux-amd64 - build/mirum-worker-linux-arm64 - if-no-files-found: error - - - name: Publish - if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') - run: | - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - . build/dist/build.env - task publish:release CHANNEL=$CHANNEL VERSION=$VERSION - mise run publish -- \ - --service mirum \ - --channel "$CHANNEL" \ - --input build/dist \ - deb rpm apk - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} - GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} - GPG_KEY_ID: ${{ vars.GPG_KEY_ID }} - APK_PRIVATE_KEY: ${{ secrets.APK_PRIVATE_KEY }} - PACKAGE_KEY_VERSION: ${{ vars.PACKAGE_KEY_VERSION }} - S3_BUCKET: ${{ vars.S3_BUCKET }} - S3_ENDPOINT: ${{ vars.S3_ENDPOINT }} - S3_PUBLIC_URL: ${{ vars.S3_PUBLIC_URL }} - S3_ACCESS_KEY_ID: ${{ secrets.S3_ACCESS_KEY_ID }} - S3_SECRET_ACCESS_KEY: ${{ secrets.S3_SECRET_ACCESS_KEY }} - - oci: - name: OCI artifacts - needs: [build] - runs-on: ubuntu-latest - permissions: - contents: read - packages: write - steps: - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - - - uses: jdx/mise-action@5228313ee0372e111a38da051671ca30fc5a96db # v3 - with: - version: 2026.7.5 - experimental: true - install: false - - - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 - with: - name: container-binaries - path: .container - - - uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3 - - - uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4 - - - name: Determine artifact version - id: artifact - shell: bash - run: | - base_version=$(cat VERSION) - if [[ "$GITHUB_REF" == refs/heads/main ]]; then - version="$base_version-nightly.$(git log -1 --format=%ct)" - elif [[ "$GITHUB_REF" == refs/tags/v* ]]; then - version="${GITHUB_REF_NAME#v}" - if [[ "$version" != "$base_version" ]]; then - echo "Tag version $version does not match VERSION $base_version" >&2 - exit 1 - fi - else - version="$base_version-pr.$GITHUB_RUN_NUMBER" - fi - echo "version=$version" >> "$GITHUB_OUTPUT" - - - name: Log in to GHCR - if: github.event_name != 'pull_request' - uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Log Helm in to GHCR - if: github.event_name != 'pull_request' - env: - GHCR_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - printf '%s' "$GHCR_TOKEN" | mise x helm@4.1.1 -- \ - helm registry login ghcr.io --username "$GITHUB_ACTOR" --password-stdin - - - name: Build and publish OCI artifacts - shell: bash - run: | - version="${{ steps.artifact.outputs.version }}" - image_output=() - chart_output=() - - if [[ "${{ github.event_name }}" != pull_request ]]; then - image_output+=(--push) - chart_output+=(--push "oci://ghcr.io/${GITHUB_REPOSITORY_OWNER,,}/charts") - fi - - for service in server worker; do - image="ghcr.io/${GITHUB_REPOSITORY_OWNER,,}/mirum-$service" - tags=( - --tag "$image:sha-$GITHUB_SHA" - --tag "$image:$version" - ) - labels=( - --label "org.opencontainers.image.revision=$GITHUB_SHA" - --label "org.opencontainers.image.source=$GITHUB_SERVER_URL/$GITHUB_REPOSITORY" - --label "org.opencontainers.image.version=$version" - ) - - if [[ "$GITHUB_REF" == refs/heads/main ]]; then - tags+=(--tag "$image:nightly") - elif [[ "$GITHUB_REF" == refs/tags/v* ]]; then - tags+=(--tag "$image:latest") - fi - - mise run container -- \ - --context . \ - --file "cmd/mirum-$service/Dockerfile" \ - --platform linux/amd64,linux/arm64 \ - --cache-scope "mirum-$service" \ - "${tags[@]}" "${labels[@]}" "${image_output[@]}" - done - - mise run chart -- \ - --chart charts/mirum \ - --version "$version" \ - --app-version "$version" \ - "${chart_output[@]}" diff --git a/.github/workflows/cla.yml b/.github/workflows/cla.yml new file mode 100644 --- /dev/null +++ b/.github/workflows/cla.yml @@ -0,0 +1,30 @@ +# SPDX-FileCopyrightText: 2026 Nikolay Govorov <me@govorov.online> +# SPDX-License-Identifier: AGPL-3.0-or-later + +name: "CLA Assistant" + +on: + issue_comment: + types: [created] + pull_request_target: + types: [opened, closed, synchronize] + +jobs: + CLAAssistant: + runs-on: ubuntu-latest + permissions: + actions: write + contents: write + statuses: write + pull-requests: write + steps: + - name: "CLA Assistant" + if: (github.event.comment.body == 'recheck' || github.event.comment.body == 'I have read the CLA Document and I hereby sign the CLA') || github.event_name == 'pull_request_target' + uses: contributor-assistant/github-action@ca4a40a7d1004f18d9960b404b97e5f30a505a08 # v2.6.1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + branch: "main" + allowlist: mrdimidium + path-to-signatures: "LICENSES/cla.json" + path-to-document: "https://github.com/dimidiumlabs/mirum/blob/main/CLA.md" diff --git a/.github/workflows/legal.yml b/.github/workflows/legal.yml deleted file mode 100644 --- a/.github/workflows/legal.yml +++ /dev/null @@ -1,35 +0,0 @@ -# SPDX-FileCopyrightText: 2026 Nikolay Govorov -# SPDX-License-Identifier: AGPL-3.0-or-later - -name: Legal - -on: - push: - branches: [main] - tags: ["v*"] - pull_request: - branches: [main] - -permissions: - contents: read - -jobs: - legal: - name: Legal checks - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - with: - fetch-depth: 0 - - - uses: jdx/mise-action@5228313ee0372e111a38da051671ca30fc5a96db # v3 - with: - version: 2026.7.5 - experimental: true - install: false - - - name: Check contribution sign-off - run: mise run signoff - - - name: Check licensing policy - run: mise run licenses diff --git a/.gitignore b/.gitignore index 437dc77..d893ba1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,21 +1,5 @@ -# SPDX-FileCopyrightText: 2026 Nikolay Govorov +# Copyright (c) 2026 Nikolay Govorov # SPDX-License-Identifier: AGPL-3.0-or-later -/dev /build -/.container /.task -mise.local.toml -mise.*.local.toml -cmd/mirum-server/static -cmd/mirum-server/web/node_modules - -# zig build artifacts -.zig-cache/ -zig-out/ -zig-pkg/ - -# grpc generated code -/internal/protocol/wirepb -/cmd/mirum-server/apipb -/cmd/mirum-server/web/gen diff --git a/.mailmap b/.mailmap deleted file mode 100644 --- a/.mailmap +++ /dev/null @@ -1,5 +0,0 @@ -# Add new entries in alphabetical order - -Nikolay Govorov <me@govorov.online> -Nikolay Govorov <mr@dimidiumlabs.io> -pauline nemchak <doublewebstandards@gmail.com> diff --git a/CLA.md b/CLA.md index d0140e8..e1d891a 100644 --- a/CLA.md +++ b/CLA.md @@ -1,45 +1,43 @@ -# Mirum Individual Contributor License Agreement +# Recluse Grant and Contributor License Agreement -Version 1.0 +> This agreement is based on the Harmony Combined Contributor Agreement +> Version 1.0 licensed under a [Creative Commons Attribution 3.0 Unported License](http://creativecommons.org/licenses/by/3.0/). -> This agreement is based on the Harmony Individual Contributor License -> Agreement Version 1.0 licensed under a -> [Creative Commons Attribution 3.0 Unported License](https://creativecommons.org/licenses/by/3.0/). - -Thank you for your interest in contributing to Mirum (the "Project"). In this -Agreement, "We" and "Us" mean Nikolay Govorov. - -This contributor agreement ("Agreement") documents the rights granted by -contributors to Us. To make this document effective, You must personally add -the following trailers to every commit You Submit: - -```text -CLA-Version: 1.0 -Signed-off-by: Your Name <your.email@example.com> -``` - -The name and email address in `Signed-off-by` must identify You and match the -commit author. By Submitting a commit containing these trailers, You -electronically sign and accept this Agreement. No other person or automated -system may add the `Signed-off-by` trailer on Your behalf. This is a legally -binding document, so please read it carefully before agreeing to it. +Thank you for your interest in contributing to Recluse ("We" or "Us"). In order +to clarify the intellectual property license granted with Contributions from any +person or entity, We must have a Contributor License Agreement ("CLA") on file +that has been signed, accepted or otherwise agreed to by each contributor, +indicating agreement to the license terms below. This license is for your +protection as a contributor as well as the protection of the Us and our users; +it does not change your rights to use your own Contributions for any other +purpose. ## 1. Definitions -"You" means the individual who Submits a Contribution to Us. +"You" (Individual) means the individual who Submits a Contribution to Us. + +"You" (Entity) means any Legal Entity on behalf of whom a Contribution has been +received by Us. "Legal Entity" means an entity which is not a natural person. +"Affiliates" means other Legal Entities that control, are controlled by, or +under common control with that Legal Entity. For the purposes of this +definition, "control" means (i) the power, direct or indirect, to cause the +direction or management of such Legal Entity, whether by contract or otherwise, +(ii) ownership of fifty percent (50%) or more of the outstanding shares or +securities which vote to elect the management or other persons who direct such +Legal Entity or (iii) beneficial ownership of such entity. "Contribution" means any work of authorship that is Submitted by You to Us in -which You own or assert ownership of the Copyright. If You do not own the -Copyright in the entire work of authorship, please follow the instructions in -Section 3(d). +which You own or assert ownership of the Copyright. "Copyright" means all rights protecting works of authorship owned or controlled -by You, including copyright, moral and neighboring rights, as appropriate, for -the full term of their existence including any extensions by You. +by You [or Your Affiliates], including copyright, moral and neighboring rights, +as appropriate, for the full term of their existence including any extensions by +You. "Material" means the work of authorship which is made available by Us to third -parties as part of the Project. After You Submit the Contribution, it may be -included in the Material. +parties. When this Agreement covers more than one software project, the Material +means the work of authorship to which the Contribution was Submitted. After You +Submit the Contribution, it may be included in the Material. "Submit" means any form of electronic, verbal, or written communication sent to Us or our representatives, including but not limited to electronic mailing @@ -71,10 +69,40 @@ modify, display, perform and distribute the Contribution as part of the Material; provided that this license is conditioned upon compliance with Section 2.3. +### 2.1 Copyright Assignment + +(a) At the time the Contribution is Submitted, You assign to Us all right, +title, and interest worldwide in all Copyright covering the Contribution; +provided that this transfer is conditioned upon compliance with Section 2.3. + +(b) To the extent that any of the rights in Section 2.1(a) cannot be assigned by +You to Us, You grant to Us a perpetual, worldwide, exclusive, royalty-free, +transferable, irrevocable license under such non-assigned rights, with rights to +sublicense through multiple tiers of sublicensees, to practice such non-assigned +rights, including, but not limited to, the right to reproduce, modify, display, +perform and distribute the Contribution; provided that this license is +conditioned upon compliance with Section 2.3. + +(c) To the extent that any of the rights in Section 2.1(a) can neither be +assigned nor licensed by You to Us, You irrevocably waive and agree never to +assert such rights against Us, any of our successors in interest, or any of our +licensees, either direct or indirect; provided that this agreement not to assert +is conditioned upon compliance with Section 2.3. + +(d) Upon such transfer of rights to Us, to the maximum extent possible, We +immediately grant to You a perpetual, worldwide, non-exclusive, royalty-free, +transferable, irrevocable license under such rights covering the Contribution, +with rights to sublicense through multiple tiers of sublicensees, to reproduce, +modify, display, perform, and distribute the Contribution. The intention of the +parties is that this license will be as broad as possible and to provide You +with rights as similar as possible to the owner of the rights that You +transferred. This license back is limited to the Contribution and does not +provide any rights to the Material. + ### 2.2 Patent License For patent claims including, without limitation, method, process, and apparatus -claims which You own, control or have the right to grant, +claims which You [or Your Affiliates] own, control or have the right to grant, now or in the future, You grant to Us a perpetual, worldwide, non-exclusive, transferable, royalty-free, irrevocable patent license, with the right to sublicense these rights to multiple tiers of sublicensees, to make, have made, @@ -106,7 +134,7 @@ the Material and may decide to include any Contribution We consider appropriate. ### 2.6 Reservation of Rights -Any rights not expressly licensed under this section are expressly +Any rights not expressly [assigned or] licensed under this section are expressly reserved by You. ## 3. Agreement @@ -115,32 +143,31 @@ You confirm that: (a) You have the legal authority to enter into this Agreement. -(b) You own the Copyright and patent claims covering the +(b) You [or Your Affiliates] own the Copyright and patent claims covering the Contribution which are required to grant the rights under Section 2. -(c) The grant of rights under Section 2 does not violate any grant +(c) (Individual) The grant of rights under Section 2 does not violate any grant of rights which You have made to third parties, including Your employer. If You are an employee, You have had Your employer approve this Agreement or sign the Entity version of this document. If You are less than eighteen years old, please have Your parents or guardian sign the Agreement. -(d) If You do not own the Copyright in the entire work of authorship Submitted, -You have clearly identified the third-party work, its source, and its license in -the Submission. +(c) (Entity) The grant of rights under Section 2 does not violate any grant of +rights which You or Your Affiliates have made to third parties. ## 4. Disclaimer EXCEPT FOR THE EXPRESS WARRANTIES IN SECTION 3, THE CONTRIBUTION IS PROVIDED "AS IS". MORE PARTICULARLY, ALL EXPRESS OR IMPLIED WARRANTIES INCLUDING, WITHOUT LIMITATION, ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR -PURPOSE AND NON-INFRINGEMENT ARE EXPRESSLY DISCLAIMED BY YOU TO US. -TO THE EXTENT THAT ANY SUCH WARRANTIES CANNOT BE DISCLAIMED, SUCH WARRANTY +PURPOSE AND NON-INFRINGEMENT ARE EXPRESSLY DISCLAIMED BY YOU TO US [AND BY US TO +YOU]. TO THE EXTENT THAT ANY SUCH WARRANTIES CANNOT BE DISCLAIMED, SUCH WARRANTY IS LIMITED IN DURATION TO THE MINIMUM PERIOD PERMITTED BY LAW. ## 5. Consequential Damage Waiver -TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, IN NO EVENT WILL YOU BE -LIABLE FOR ANY LOSS OF PROFITS, LOSS OF ANTICIPATED SAVINGS, LOSS OF DATA, +TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, IN NO EVENT WILL YOU [OR US] +BE LIABLE FOR ANY LOSS OF PROFITS, LOSS OF ANTICIPATED SAVINGS, LOSS OF DATA, INDIRECT, SPECIAL, INCIDENTAL, CONSEQUENTIAL AND EXEMPLARY DAMAGES ARISING OUT OF THIS AGREEMENT REGARDLESS OF THE LEGAL OR EQUITABLE THEORY (CONTRACT, TORT OR OTHERWISE) UPON WHICH THE CLAIM IS BASED. diff --git a/CODEOWNERS b/CODEOWNERS deleted file mode 100644 --- a/CODEOWNERS +++ /dev/null @@ -1,5 +0,0 @@ -# SPDX-FileCopyrightText: 2026 Nikolay Govorov -# SPDX-License-Identifier: AGPL-3.0-or-later - -* @mrdimidium -cmd/mirum-server/web/ @PaulineNemchak diff --git a/LICENSE.go b/LICENSE.go deleted file mode 100644 --- a/LICENSE.go +++ /dev/null @@ -1,17 +0,0 @@ -// SPDX-FileCopyrightText: 2026 Nikolay Govorov -// SPDX-License-Identifier: AGPL-3.0-or-later - -//go:build !licensegen - -// This package is a small hack for embedding LICENSE into a project. -// Go sensibly prohibits embedding files from parent folders, -// so this file is only needed to bypass the restriction. -package mirum - -import _ "embed" - -//go:embed LICENSE -var License string - -//go:embed build/licenses.json -var Licenses []byte diff --git a/LICENSES/CC-BY-3.0.txt b/LICENSES/CC-BY-3.0.txt deleted file mode 100644 --- a/LICENSES/CC-BY-3.0.txt +++ /dev/null @@ -1,319 +0,0 @@ -Creative Commons Legal Code - -Attribution 3.0 Unported - - CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE - LEGAL SERVICES. DISTRIBUTION OF THIS LICENSE DOES NOT CREATE AN - ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS - INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES - REGARDING THE INFORMATION PROVIDED, AND DISCLAIMS LIABILITY FOR - DAMAGES RESULTING FROM ITS USE. - -License - -THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE -COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY -COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS -AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED. - -BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE -TO BE BOUND BY THE TERMS OF THIS LICENSE. TO THE EXTENT THIS LICENSE MAY -BE CONSIDERED TO BE A CONTRACT, THE LICENSOR GRANTS YOU THE RIGHTS -CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND -CONDITIONS. - -1. Definitions - - a. "Adaptation" means a work based upon the Work, or upon the Work and - other pre-existing works, such as a translation, adaptation, - derivative work, arrangement of music or other alterations of a - literary or artistic work, or phonogram or performance and includes - cinematographic adaptations or any other form in which the Work may be - recast, transformed, or adapted including in any form recognizably - derived from the original, except that a work that constitutes a - Collection will not be considered an Adaptation for the purpose of - this License. For the avoidance of doubt, where the Work is a musical - work, performance or phonogram, the synchronization of the Work in - timed-relation with a moving image ("synching") will be considered an - Adaptation for the purpose of this License. - b. "Collection" means a collection of literary or artistic works, such as - encyclopedias and anthologies, or performances, phonograms or - broadcasts, or other works or subject matter other than works listed - in Section 1(f) below, which, by reason of the selection and - arrangement of their contents, constitute intellectual creations, in - which the Work is included in its entirety in unmodified form along - with one or more other contributions, each constituting separate and - independent works in themselves, which together are assembled into a - collective whole. A work that constitutes a Collection will not be - considered an Adaptation (as defined above) for the purposes of this - License. - c. "Distribute" means to make available to the public the original and - copies of the Work or Adaptation, as appropriate, through sale or - other transfer of ownership. - d. "Licensor" means the individual, individuals, entity or entities that - offer(s) the Work under the terms of this License. - e. "Original Author" means, in the case of a literary or artistic work, - the individual, individuals, entity or entities who created the Work - or if no individual or entity can be identified, the publisher; and in - addition (i) in the case of a performance the actors, singers, - musicians, dancers, and other persons who act, sing, deliver, declaim, - play in, interpret or otherwise perform literary or artistic works or - expressions of folklore; (ii) in the case of a phonogram the producer - being the person or legal entity who first fixes the sounds of a - performance or other sounds; and, (iii) in the case of broadcasts, the - organization that transmits the broadcast. - f. "Work" means the literary and/or artistic work offered under the terms - of this License including without limitation any production in the - literary, scientific and artistic domain, whatever may be the mode or - form of its expression including digital form, such as a book, - pamphlet and other writing; a lecture, address, sermon or other work - of the same nature; a dramatic or dramatico-musical work; a - choreographic work or entertainment in dumb show; a musical - composition with or without words; a cinematographic work to which are - assimilated works expressed by a process analogous to cinematography; - a work of drawing, painting, architecture, sculpture, engraving or - lithography; a photographic work to which are assimilated works - expressed by a process analogous to photography; a work of applied - art; an illustration, map, plan, sketch or three-dimensional work - relative to geography, topography, architecture or science; a - performance; a broadcast; a phonogram; a compilation of data to the - extent it is protected as a copyrightable work; or a work performed by - a variety or circus performer to the extent it is not otherwise - considered a literary or artistic work. - g. "You" means an individual or entity exercising rights under this - License who has not previously violated the terms of this License with - respect to the Work, or who has received express permission from the - Licensor to exercise rights under this License despite a previous - violation. - h. "Publicly Perform" means to perform public recitations of the Work and - to communicate to the public those public recitations, by any means or - process, including by wire or wireless means or public digital - performances; to make available to the public Works in such a way that - members of the public may access these Works from a place and at a - place individually chosen by them; to perform the Work to the public - by any means or process and the communication to the public of the - performances of the Work, including by public digital performance; to - broadcast and rebroadcast the Work by any means including signs, - sounds or images. - i. "Reproduce" means to make copies of the Work by any means including - without limitation by sound or visual recordings and the right of - fixation and reproducing fixations of the Work, including storage of a - protected performance or phonogram in digital form or other electronic - medium. - -2. Fair Dealing Rights. Nothing in this License is intended to reduce, -limit, or restrict any uses free from copyright or rights arising from -limitations or exceptions that are provided for in connection with the -copyright protection under copyright law or other applicable laws. - -3. License Grant. Subject to the terms and conditions of this License, -Licensor hereby grants You a worldwide, royalty-free, non-exclusive, -perpetual (for the duration of the applicable copyright) license to -exercise the rights in the Work as stated below: - - a. to Reproduce the Work, to incorporate the Work into one or more - Collections, and to Reproduce the Work as incorporated in the - Collections; - b. to create and Reproduce Adaptations provided that any such Adaptation, - including any translation in any medium, takes reasonable steps to - clearly label, demarcate or otherwise identify that changes were made - to the original Work. For example, a translation could be marked "The - original work was translated from English to Spanish," or a - modification could indicate "The original work has been modified."; - c. to Distribute and Publicly Perform the Work including as incorporated - in Collections; and, - d. to Distribute and Publicly Perform Adaptations. - e. For the avoidance of doubt: - - i. Non-waivable Compulsory License Schemes. In those jurisdictions in - which the right to collect royalties through any statutory or - compulsory licensing scheme cannot be waived, the Licensor - reserves the exclusive right to collect such royalties for any - exercise by You of the rights granted under this License; - ii. Waivable Compulsory License Schemes. In those jurisdictions in - which the right to collect royalties through any statutory or - compulsory licensing scheme can be waived, the Licensor waives the - exclusive right to collect such royalties for any exercise by You - of the rights granted under this License; and, - iii. Voluntary License Schemes. The Licensor waives the right to - collect royalties, whether individually or, in the event that the - Licensor is a member of a collecting society that administers - voluntary licensing schemes, via that society, from any exercise - by You of the rights granted under this License. - -The above rights may be exercised in all media and formats whether now -known or hereafter devised. The above rights include the right to make -such modifications as are technically necessary to exercise the rights in -other media and formats. Subject to Section 8(f), all rights not expressly -granted by Licensor are hereby reserved. - -4. Restrictions. The license granted in Section 3 above is expressly made -subject to and limited by the following restrictions: - - a. You may Distribute or Publicly Perform the Work only under the terms - of this License. You must include a copy of, or the Uniform Resource - Identifier (URI) for, this License with every copy of the Work You - Distribute or Publicly Perform. You may not offer or impose any terms - on the Work that restrict the terms of this License or the ability of - the recipient of the Work to exercise the rights granted to that - recipient under the terms of the License. You may not sublicense the - Work. You must keep intact all notices that refer to this License and - to the disclaimer of warranties with every copy of the Work You - Distribute or Publicly Perform. When You Distribute or Publicly - Perform the Work, You may not impose any effective technological - measures on the Work that restrict the ability of a recipient of the - Work from You to exercise the rights granted to that recipient under - the terms of the License. This Section 4(a) applies to the Work as - incorporated in a Collection, but this does not require the Collection - apart from the Work itself to be made subject to the terms of this - License. If You create a Collection, upon notice from any Licensor You - must, to the extent practicable, remove from the Collection any credit - as required by Section 4(b), as requested. If You create an - Adaptation, upon notice from any Licensor You must, to the extent - practicable, remove from the Adaptation any credit as required by - Section 4(b), as requested. - b. If You Distribute, or Publicly Perform the Work or any Adaptations or - Collections, You must, unless a request has been made pursuant to - Section 4(a), keep intact all copyright notices for the Work and - provide, reasonable to the medium or means You are utilizing: (i) the - name of the Original Author (or pseudonym, if applicable) if supplied, - and/or if the Original Author and/or Licensor designate another party - or parties (e.g., a sponsor institute, publishing entity, journal) for - attribution ("Attribution Parties") in Licensor's copyright notice, - terms of service or by other reasonable means, the name of such party - or parties; (ii) the title of the Work if supplied; (iii) to the - extent reasonably practicable, the URI, if any, that Licensor - specifies to be associated with the Work, unless such URI does not - refer to the copyright notice or licensing information for the Work; - and (iv) , consistent with Section 3(b), in the case of an Adaptation, - a credit identifying the use of the Work in the Adaptation (e.g., - "French translation of the Work by Original Author," or "Screenplay - based on original Work by Original Author"). The credit required by - this Section 4 (b) may be implemented in any reasonable manner; - provided, however, that in the case of a Adaptation or Collection, at - a minimum such credit will appear, if a credit for all contributing - authors of the Adaptation or Collection appears, then as part of these - credits and in a manner at least as prominent as the credits for the - other contributing authors. For the avoidance of doubt, You may only - use the credit required by this Section for the purpose of attribution - in the manner set out above and, by exercising Your rights under this - License, You may not implicitly or explicitly assert or imply any - connection with, sponsorship or endorsement by the Original Author, - Licensor and/or Attribution Parties, as appropriate, of You or Your - use of the Work, without the separate, express prior written - permission of the Original Author, Licensor and/or Attribution - Parties. - c. Except as otherwise agreed in writing by the Licensor or as may be - otherwise permitted by applicable law, if You Reproduce, Distribute or - Publicly Perform the Work either by itself or as part of any - Adaptations or Collections, You must not distort, mutilate, modify or - take other derogatory action in relation to the Work which would be - prejudicial to the Original Author's honor or reputation. Licensor - agrees that in those jurisdictions (e.g. Japan), in which any exercise - of the right granted in Section 3(b) of this License (the right to - make Adaptations) would be deemed to be a distortion, mutilation, - modification or other derogatory action prejudicial to the Original - Author's honor and reputation, the Licensor will waive or not assert, - as appropriate, this Section, to the fullest extent permitted by the - applicable national law, to enable You to reasonably exercise Your - right under Section 3(b) of this License (right to make Adaptations) - but not otherwise. - -5. Representations, Warranties and Disclaimer - -UNLESS OTHERWISE MUTUALLY AGREED TO BY THE PARTIES IN WRITING, LICENSOR -OFFERS THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY -KIND CONCERNING THE WORK, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, -INCLUDING, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTIBILITY, -FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF -LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS, -WHETHER OR NOT DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION -OF IMPLIED WARRANTIES, SO SUCH EXCLUSION MAY NOT APPLY TO YOU. - -6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE -LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR -ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES -ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS -BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. - -7. Termination - - a. This License and the rights granted hereunder will terminate - automatically upon any breach by You of the terms of this License. - Individuals or entities who have received Adaptations or Collections - from You under this License, however, will not have their licenses - terminated provided such individuals or entities remain in full - compliance with those licenses. Sections 1, 2, 5, 6, 7, and 8 will - survive any termination of this License. - b. Subject to the above terms and conditions, the license granted here is - perpetual (for the duration of the applicable copyright in the Work). - Notwithstanding the above, Licensor reserves the right to release the - Work under different license terms or to stop distributing the Work at - any time; provided, however that any such election will not serve to - withdraw this License (or any other license that has been, or is - required to be, granted under the terms of this License), and this - License will continue in full force and effect unless terminated as - stated above. - -8. Miscellaneous - - a. Each time You Distribute or Publicly Perform the Work or a Collection, - the Licensor offers to the recipient a license to the Work on the same - terms and conditions as the license granted to You under this License. - b. Each time You Distribute or Publicly Perform an Adaptation, Licensor - offers to the recipient a license to the original Work on the same - terms and conditions as the license granted to You under this License. - c. If any provision of this License is invalid or unenforceable under - applicable law, it shall not affect the validity or enforceability of - the remainder of the terms of this License, and without further action - by the parties to this agreement, such provision shall be reformed to - the minimum extent necessary to make such provision valid and - enforceable. - d. No term or provision of this License shall be deemed waived and no - breach consented to unless such waiver or consent shall be in writing - and signed by the party to be charged with such waiver or consent. - e. This License constitutes the entire agreement between the parties with - respect to the Work licensed here. There are no understandings, - agreements or representations with respect to the Work not specified - here. Licensor shall not be bound by any additional provisions that - may appear in any communication from You. This License may not be - modified without the mutual written agreement of the Licensor and You. - f. The rights granted under, and the subject matter referenced, in this - License were drafted utilizing the terminology of the Berne Convention - for the Protection of Literary and Artistic Works (as amended on - September 28, 1979), the Rome Convention of 1961, the WIPO Copyright - Treaty of 1996, the WIPO Performances and Phonograms Treaty of 1996 - and the Universal Copyright Convention (as revised on July 24, 1971). - These rights and subject matter take effect in the relevant - jurisdiction in which the License terms are sought to be enforced - according to the corresponding provisions of the implementation of - those treaty provisions in the applicable national law. If the - standard suite of rights granted under applicable copyright law - includes additional rights not granted under this License, such - additional rights are deemed to be included in the License; this - License is not intended to restrict the license of any rights under - applicable law. - - -Creative Commons Notice - - Creative Commons is not a party to this License, and makes no warranty - whatsoever in connection with the Work. Creative Commons will not be - liable to You or any party on any legal theory for any damages - whatsoever, including without limitation any general, special, - incidental or consequential damages arising in connection to this - license. Notwithstanding the foregoing two (2) sentences, if Creative - Commons has expressly identified itself as the Licensor hereunder, it - shall have all rights and obligations of Licensor. - - Except for the limited purpose of indicating to the public that the - Work is licensed under the CCPL, Creative Commons does not authorize - the use by either party of the trademark "Creative Commons" or any - related trademark or logo of Creative Commons without the prior - written consent of Creative Commons. Any permitted use will be in - compliance with Creative Commons' then-current trademark usage - guidelines, as may be published on its website or otherwise made - available upon request from time to time. For the avoidance of doubt, - this trademark restriction does not form part of this License. - - Creative Commons may be contacted at https://creativecommons.org/. diff --git a/LICENSES/CC-BY-4.0.txt b/LICENSES/CC-BY-4.0.txt deleted file mode 100644 --- a/LICENSES/CC-BY-4.0.txt +++ /dev/null @@ -1,156 +0,0 @@ -Creative Commons Attribution 4.0 International - - Creative Commons Corporation (“Creative Commons”) is not a law firm and does not provide legal services or legal advice. Distribution of Creative Commons public licenses does not create a lawyer-client or other relationship. Creative Commons makes its licenses and related information available on an “as-is” basis. Creative Commons gives no warranties regarding its licenses, any material licensed under their terms and conditions, or any related information. Creative Commons disclaims all liability for damages resulting from their use to the fullest extent possible. - -Using Creative Commons Public Licenses - -Creative Commons public licenses provide a standard set of terms and conditions that creators and other rights holders may use to share original works of authorship and other material subject to copyright and certain other rights specified in the public license below. The following considerations are for informational purposes only, are not exhaustive, and do not form part of our licenses. - -Considerations for licensors: Our public licenses are intended for use by those authorized to give the public permission to use material in ways otherwise restricted by copyright and certain other rights. Our licenses are irrevocable. Licensors should read and understand the terms and conditions of the license they choose before applying it. Licensors should also secure all rights necessary before applying our licenses so that the public can reuse the material as expected. Licensors should clearly mark any material not subject to the license. This includes other CC-licensed material, or material used under an exception or limitation to copyright. More considerations for licensors. - -Considerations for the public: By using one of our public licenses, a licensor grants the public permission to use the licensed material under specified terms and conditions. If the licensor’s permission is not necessary for any reason–for example, because of any applicable exception or limitation to copyright–then that use is not regulated by the license. Our licenses grant only permissions under copyright and certain other rights that a licensor has authority to grant. Use of the licensed material may still be restricted for other reasons, including because others have copyright or other rights in the material. A licensor may make special requests, such as asking that all changes be marked or described. Although not required by our licenses, you are encouraged to respect those requests where reasonable. More considerations for the public. - -Creative Commons Attribution 4.0 International Public License - -By exercising the Licensed Rights (defined below), You accept and agree to be bound by the terms and conditions of this Creative Commons Attribution 4.0 International Public License ("Public License"). To the extent this Public License may be interpreted as a contract, You are granted the Licensed Rights in consideration of Your acceptance of these terms and conditions, and the Licensor grants You such rights in consideration of benefits the Licensor receives from making the Licensed Material available under these terms and conditions. - -Section 1 – Definitions. - - a. Adapted Material means material subject to Copyright and Similar Rights that is derived from or based upon the Licensed Material and in which the Licensed Material is translated, altered, arranged, transformed, or otherwise modified in a manner requiring permission under the Copyright and Similar Rights held by the Licensor. For purposes of this Public License, where the Licensed Material is a musical work, performance, or sound recording, Adapted Material is always produced where the Licensed Material is synched in timed relation with a moving image. - - b. Adapter's License means the license You apply to Your Copyright and Similar Rights in Your contributions to Adapted Material in accordance with the terms and conditions of this Public License. - - c. Copyright and Similar Rights means copyright and/or similar rights closely related to copyright including, without limitation, performance, broadcast, sound recording, and Sui Generis Database Rights, without regard to how the rights are labeled or categorized. For purposes of this Public License, the rights specified in Section 2(b)(1)-(2) are not Copyright and Similar Rights. - - d. Effective Technological Measures means those measures that, in the absence of proper authority, may not be circumvented under laws fulfilling obligations under Article 11 of the WIPO Copyright Treaty adopted on December 20, 1996, and/or similar international agreements. - - e. Exceptions and Limitations means fair use, fair dealing, and/or any other exception or limitation to Copyright and Similar Rights that applies to Your use of the Licensed Material. - - f. Licensed Material means the artistic or literary work, database, or other material to which the Licensor applied this Public License. - - g. Licensed Rights means the rights granted to You subject to the terms and conditions of this Public License, which are limited to all Copyright and Similar Rights that apply to Your use of the Licensed Material and that the Licensor has authority to license. - - h. Licensor means the individual(s) or entity(ies) granting rights under this Public License. - - i. Share means to provide material to the public by any means or process that requires permission under the Licensed Rights, such as reproduction, public display, public performance, distribution, dissemination, communication, or importation, and to make material available to the public including in ways that members of the public may access the material from a place and at a time individually chosen by them. - - j. Sui Generis Database Rights means rights other than copyright resulting from Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, as amended and/or succeeded, as well as other essentially equivalent rights anywhere in the world. - - k. You means the individual or entity exercising the Licensed Rights under this Public License. Your has a corresponding meaning. - -Section 2 – Scope. - - a. License grant. - - 1. Subject to the terms and conditions of this Public License, the Licensor hereby grants You a worldwide, royalty-free, non-sublicensable, non-exclusive, irrevocable license to exercise the Licensed Rights in the Licensed Material to: - - A. reproduce and Share the Licensed Material, in whole or in part; and - - B. produce, reproduce, and Share Adapted Material. - - 2. Exceptions and Limitations. For the avoidance of doubt, where Exceptions and Limitations apply to Your use, this Public License does not apply, and You do not need to comply with its terms and conditions. - - 3. Term. The term of this Public License is specified in Section 6(a). - - 4. Media and formats; technical modifications allowed. The Licensor authorizes You to exercise the Licensed Rights in all media and formats whether now known or hereafter created, and to make technical modifications necessary to do so. The Licensor waives and/or agrees not to assert any right or authority to forbid You from making technical modifications necessary to exercise the Licensed Rights, including technical modifications necessary to circumvent Effective Technological Measures. For purposes of this Public License, simply making modifications authorized by this Section 2(a)(4) never produces Adapted Material. - - 5. Downstream recipients. - - A. Offer from the Licensor – Licensed Material. Every recipient of the Licensed Material automatically receives an offer from the Licensor to exercise the Licensed Rights under the terms and conditions of this Public License. - - B. No downstream restrictions. You may not offer or impose any additional or different terms or conditions on, or apply any Effective Technological Measures to, the Licensed Material if doing so restricts exercise of the Licensed Rights by any recipient of the Licensed Material. - - 6. No endorsement. Nothing in this Public License constitutes or may be construed as permission to assert or imply that You are, or that Your use of the Licensed Material is, connected with, or sponsored, endorsed, or granted official status by, the Licensor or others designated to receive attribution as provided in Section 3(a)(1)(A)(i). - -b. Other rights. - - 1. Moral rights, such as the right of integrity, are not licensed under this Public License, nor are publicity, privacy, and/or other similar personality rights; however, to the extent possible, the Licensor waives and/or agrees not to assert any such rights held by the Licensor to the limited extent necessary to allow You to exercise the Licensed Rights, but not otherwise. - - 2. Patent and trademark rights are not licensed under this Public License. - - 3. To the extent possible, the Licensor waives any right to collect royalties from You for the exercise of the Licensed Rights, whether directly or through a collecting society under any voluntary or waivable statutory or compulsory licensing scheme. In all other cases the Licensor expressly reserves any right to collect such royalties. - -Section 3 – License Conditions. - -Your exercise of the Licensed Rights is expressly made subject to the following conditions. - - a. Attribution. - - 1. If You Share the Licensed Material (including in modified form), You must: - - A. retain the following if it is supplied by the Licensor with the Licensed Material: - - i. identification of the creator(s) of the Licensed Material and any others designated to receive attribution, in any reasonable manner requested by the Licensor (including by pseudonym if designated); - - ii. a copyright notice; - - iii. a notice that refers to this Public License; - - iv. a notice that refers to the disclaimer of warranties; - - v. a URI or hyperlink to the Licensed Material to the extent reasonably practicable; - - B. indicate if You modified the Licensed Material and retain an indication of any previous modifications; and - - C. indicate the Licensed Material is licensed under this Public License, and include the text of, or the URI or hyperlink to, this Public License. - - 2. You may satisfy the conditions in Section 3(a)(1) in any reasonable manner based on the medium, means, and context in which You Share the Licensed Material. For example, it may be reasonable to satisfy the conditions by providing a URI or hyperlink to a resource that includes the required information. - - 3. If requested by the Licensor, You must remove any of the information required by Section 3(a)(1)(A) to the extent reasonably practicable. - - 4. If You Share Adapted Material You produce, the Adapter's License You apply must not prevent recipients of the Adapted Material from complying with this Public License. - -Section 4 – Sui Generis Database Rights. - -Where the Licensed Rights include Sui Generis Database Rights that apply to Your use of the Licensed Material: - - a. for the avoidance of doubt, Section 2(a)(1) grants You the right to extract, reuse, reproduce, and Share all or a substantial portion of the contents of the database; - - b. if You include all or a substantial portion of the database contents in a database in which You have Sui Generis Database Rights, then the database in which You have Sui Generis Database Rights (but not its individual contents) is Adapted Material; and - - c. You must comply with the conditions in Section 3(a) if You Share all or a substantial portion of the contents of the database. -For the avoidance of doubt, this Section 4 supplements and does not replace Your obligations under this Public License where the Licensed Rights include other Copyright and Similar Rights. - -Section 5 – Disclaimer of Warranties and Limitation of Liability. - - a. Unless otherwise separately undertaken by the Licensor, to the extent possible, the Licensor offers the Licensed Material as-is and as-available, and makes no representations or warranties of any kind concerning the Licensed Material, whether express, implied, statutory, or other. This includes, without limitation, warranties of title, merchantability, fitness for a particular purpose, non-infringement, absence of latent or other defects, accuracy, or the presence or absence of errors, whether or not known or discoverable. Where disclaimers of warranties are not allowed in full or in part, this disclaimer may not apply to You. - - b. To the extent possible, in no event will the Licensor be liable to You on any legal theory (including, without limitation, negligence) or otherwise for any direct, special, indirect, incidental, consequential, punitive, exemplary, or other losses, costs, expenses, or damages arising out of this Public License or use of the Licensed Material, even if the Licensor has been advised of the possibility of such losses, costs, expenses, or damages. Where a limitation of liability is not allowed in full or in part, this limitation may not apply to You. - - c. The disclaimer of warranties and limitation of liability provided above shall be interpreted in a manner that, to the extent possible, most closely approximates an absolute disclaimer and waiver of all liability. - -Section 6 – Term and Termination. - - a. This Public License applies for the term of the Copyright and Similar Rights licensed here. However, if You fail to comply with this Public License, then Your rights under this Public License terminate automatically. - - b. Where Your right to use the Licensed Material has terminated under Section 6(a), it reinstates: - - 1. automatically as of the date the violation is cured, provided it is cured within 30 days of Your discovery of the violation; or - - 2. upon express reinstatement by the Licensor. - - c. For the avoidance of doubt, this Section 6(b) does not affect any right the Licensor may have to seek remedies for Your violations of this Public License. - - d. For the avoidance of doubt, the Licensor may also offer the Licensed Material under separate terms or conditions or stop distributing the Licensed Material at any time; however, doing so will not terminate this Public License. - - e. Sections 1, 5, 6, 7, and 8 survive termination of this Public License. - -Section 7 – Other Terms and Conditions. - - a. The Licensor shall not be bound by any additional or different terms or conditions communicated by You unless expressly agreed. - - b. Any arrangements, understandings, or agreements regarding the Licensed Material not stated herein are separate from and independent of the terms and conditions of this Public License. - -Section 8 – Interpretation. - - a. For the avoidance of doubt, this Public License does not, and shall not be interpreted to, reduce, limit, restrict, or impose conditions on any use of the Licensed Material that could lawfully be made without permission under this Public License. - - b. To the extent possible, if any provision of this Public License is deemed unenforceable, it shall be automatically reformed to the minimum extent necessary to make it enforceable. If the provision cannot be reformed, it shall be severed from this Public License without affecting the enforceability of the remaining terms and conditions. - - c. No term or condition of this Public License will be waived and no failure to comply consented to unless expressly agreed to by the Licensor. - - d. Nothing in this Public License constitutes or may be interpreted as a limitation upon, or waiver of, any privileges and immunities that apply to the Licensor or You, including from the legal processes of any jurisdiction or authority. - -Creative Commons is not a party to its public licenses. Notwithstanding, Creative Commons may elect to apply one of its public licenses to material it publishes and in those instances will be considered the “Licensor.” Except for the limited purpose of indicating that material is shared under a Creative Commons public license or as otherwise permitted by the Creative Commons policies published at creativecommons.org/policies, Creative Commons does not authorize the use of the trademark “Creative Commons” or any other trademark or logo of Creative Commons without its prior written consent including, without limitation, in connection with any unauthorized modifications to any of its public licenses or any other arrangements, understandings, or agreements concerning use of licensed material. For the avoidance of doubt, this paragraph does not form part of the public licenses. - -Creative Commons may be contacted at creativecommons.org. diff --git a/LICENSES/MIT.txt b/LICENSES/MIT.txt deleted file mode 100644 --- a/LICENSES/MIT.txt +++ /dev/null @@ -1,18 +0,0 @@ -MIT License - -Copyright (c) <year> <copyright holders> - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and -associated documentation files (the "Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the -following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial -portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT -LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO -EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE -USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md deleted file mode 100644 --- a/README.md +++ /dev/null @@ -1,242 +0,0 @@ -# Mirum - -> [!CAUTION] -> This document describes the design and rationale of the system. It does -> not reflect the current state — almost everything is still -> unimplemented. For the canonical user-facing API of the configuration -> file, see [`mirumfile.md`](./docs/mirumfile.md). - -An experimental portable CI platform with VM-first isolation, -programmable pipelines, local execution parity and Starlark configs. - -## Development - -Install the language toolchains, build utilities, and required system packages -declared by the repository: - -```console -mise bootstrap -mise exec -- task --list -``` - -`task` remains the runner until the task definitions are migrated to mise. - -## [Why](https://xkcd.com/927/) - -The goal of this project is to build a CI system that's both convenient for tiny -projects and suitable for gigantic C++ codebases like Chromium or llvm. - -For this to work, several key decisions need to be made: - -**Open source.** -You can and should build SaaS, but the user must be able to deploy the entire -system themselves. Self-hosted runners aren't enough. - -**Portability and cross-platform support.** -You should be able to run on at least x64, arm64, riscv64, powerpc, s390x, and -loongarch64, as well as Linux, {Free,Open,Net}BSD, Windows, and macOS. Users -should be able to add exotic features like Haiku and Plan9, or even a custom kernel. - -**Hermitic builds.** -All official platforms should support sealed builds with declarative environment -descriptions (like Docker, yes). Hosted builds should work everywhere. - -**Different deployment models.** -Not everyone can deploy themselves, and not everyone wants to. Offer open-source -SaaS, cloud and self-hosted runners, and fully autonomous solutions. The degree -of autonomy is the user's choice. - -**Dynamic pipelines.** -Don't assume that all tasks are described by a static YAML/TOML configuration. -This is often the case, but you should have a path for dynamic pipelines when they -are needed. - -**Security and Isolation.** -CI is the most security-sensitive platform, affecting testing, deployment to production, -and releases. -CI has access to both the code and the production environment. -Since the "just hide it behind a VPN" option doesn't work for either SaaS or open -source projects where CI must be public, you can't be overly paranoid about architectural -decisions. Consider that the code for your pacemaker might be tested here. - -**Local debugging.** -You should be able to run the build locally, get the console into the sandbox, and -attach a debugger. There's nothing more pointless and merciless than trying to fix -automation than the cycle of "test commit -> push to Git Forge -> hope it works." - -- `mirum task` — run a single task on the host without spinning up a VM, - for fast iteration during development -- `mirum run` — run the pipeline locally with the same VMs but with a local copy - of your code and a debugger -- `mirum ssh` — connect to a failed VM over SSH and debug on real hardware -- `mirum try` — run a build from local changes on the cluster without creating - throwaway commits - -## How - -The architecture is built on two key solutions: -[Virtual Machines](https://en.wikipedia.org/wiki/Virtualization#Hardware_virtualization) -and [Starlark](https://starlark-lang.org/). - -### Virtualization vs. Containers vs. Host - -Modern CI must provide a reproducible and hermetic environment for every task. -Despite the popularity of container isolation, it's a Linux-specific technology -with many limitations. As soon as you need Windows, a specific kernel version, -or even systemd, you're forced to revert to bare, stateful runners you've manually -configured. - -A solution was proposed in Sourcehut: use ephemeral VMs from a snapshot for isolated -builds. Unlike a container, a VM can run any guest system, can emulate inaccessible -architectures, provides a full stack including the kernel, and provides sufficient -isolation to allow a user to access the build machine via SSH. - -The idea is to split the runner into two layers: - -- mirum-agent, a highly portable statically linked C binary that can copy files, - execute bash commands, and collect logs and resources. -- mirum-worker, a full-fledged runtime that launches a disposable VM for each task, - launching and managing mirum-agent within the VM. - -This architecture allows for full support on official platforms, including isolation, -SSH access, and so on. On the other hand, if you're testing an exotic system -(for example, on bare metal without any OS at all), port mirum-agent and you'll -be able to connect to a regular mirum server. You can also offer specialized versions -of mirum-worker for containers/clouds/lambdas, or any custom environment. -Simply run mirum-agent in a sandbox and issue commands to it. - -Using VMs also significantly simplifies infrastructure. -One x64 host can run Linux, Windows, and *BSD, -while one arm64 mac mini can run macOS, Linux, and Windows on arm64. - -### Starlark - -Today, there are two ways to describe pipelines: statically in yaml or by writing -a script in a scripting language like JavaScript/Python/Ruby. - -Mirum occupies a niche between the two and offers Starlark as a configuration language. -Starlark is a specialized embedded programming language developed for the Bazel -build system. On the one hand, you have variable conditions and loops, imports, -objects, and arrays. - -Unlike yaml, you don't have to reinvent the wheel. Settings are variables, tasks -are functions, build matrices are loops, and the reusable actions library is -a simple import. Python syntax is well-known and doesn't require learning your DSL. - -On the other hand, it's not an algorithmically complete language. There are no side -effects, no need for a separate sandbox, and no need to drag in a runtime. The CI -server has total control over Starlark execution. But unlike Kotlin DSL (TeamCity), -Groovy (Jenkins), or Python (Buildbot), Starlark forbids side effects: no network, -no filesystem, no arbitrary imports. Eval is safe for untrusted code -(PRs from external contributors), deterministic, and cacheable. - -## Installation - -Please note that the project is in its infancy and is **not** intended for production use. - -**Debian/Ubuntu:** - -```bash -sudo apt install curl gnupg - -curl -fsSL https://pkg.dimidiumlabs.io/packages.gpg | sudo gpg --dearmor -o /usr/share/keyrings/dimidiumlabs.gpg -echo "deb [signed-by=/usr/share/keyrings/dimidiumlabs.gpg] https://pkg.dimidiumlabs.io/mirum/apt/ nightly main" | sudo tee /etc/apt/sources.list.d/mirum.list -sudo apt update && sudo apt install mirum - -# Start the server -sudo systemctl enable --now mirum-server - -# Start a worker (optional, can run on a different host) -sudo systemctl enable --now mirum-worker@default -``` - -**Fedora/RHEL:** - -```bash -# DNF5 (Fedora 41+, RHEL 10+) -sudo dnf config-manager addrepo --from-repofile=https://pkg.dimidiumlabs.io/mirum/rpm/nightly/mirum-nightly.repo - -# DNF4 (Fedora 40 and older, RHEL 8/9) -sudo curl -o /etc/yum.repos.d/mirum-nightly.repo https://pkg.dimidiumlabs.io/mirum/rpm/nightly/mirum-nightly.repo - -sudo dnf install mirum - -# Start the server -sudo systemctl enable --now mirum-server - -# Start a worker (optional, can run on a different host) -sudo systemctl enable --now mirum-worker@default -``` - -**openSUSE:** - -```bash -sudo rpm --import https://pkg.dimidiumlabs.io/packages.gpg -sudo zypper addrepo https://pkg.dimidiumlabs.io/mirum/rpm/nightly/ mirum-nightly -sudo zypper refresh -sudo zypper install mirum - -# Start the server -sudo systemctl enable --now mirum-server - -# Start a worker (optional, can run on a different host) -sudo systemctl enable --now mirum-worker@default -``` - -**Alpine:** - -```sh -sudo wget -O /etc/apk/keys/packages.0001.rsa.pub https://pkg.dimidiumlabs.io/keys/packages.0001.rsa.pub -echo "https://pkg.dimidiumlabs.io/mirum/apk/nightly" | sudo tee -a /etc/apk/repositories -sudo apk update -sudo apk add mirum - -# Start the server -sudo rc-update add mirum-server default -sudo rc-service mirum-server start - -# Start a worker (optional, can run on a different host). -# mirum-worker is a templated service — symlink it per instance name: -sudo ln -s mirum-worker /etc/init.d/mirum-worker.default -sudo rc-update add mirum-worker.default default -sudo rc-service mirum-worker.default start -``` - -## Contributing - -We welcome your contributions, including code, bug reports, ideas, and success -stories. - -If you are making a contribution for the first time or from a new email, please -add yourself to the `.mailmap`. - -### Signoff - -To include your code, we ask that you read and agree to the [CLA](./CLA.md). To -sign, add a `CLA-Version: 1.0` and a `Signed-off-by` trailer to every commit -(`git commit -s --trailer "CLA-Version: 1.0"`). Each commit in a pull request -must carry a valid `Signed-off-by` line matching the commit author. Please use -your real name. We cannot include code from anonymous contributors. - -AI agents MUST NOT add Signed-off-by tags. Only humans can legally certify the -Contributor License Agreement. - -### AI policy - -You may use AI agents when writing code and documentation. AI is not allowed for -media including images, videos, fonts at all. You must fully read, understand, -and cleanup any code generated by the agent. We ask that you disclose the -agent's use and indicate the tool, model, and extent of contribution. - -Contributions should include an Assisted-by tag in the following format: -`Assisted-by: AGENT_NAME:MODEL_VERSION [TOOL1] [TOOL2]`, for example: -`Assisted-by: Claude:claude-4.6-opus coccinelle sparse` - -Remember, AI agents should make software better, not worse. - -## Licensing - -Mirum source code is licensed under AGPL-3.0-or-later. Documentation is licensed -under CC-BY-4.0. - -The bundled shadcn UI components are licensed under MIT. diff --git a/REUSE.toml b/REUSE.toml index 708bc13..82918bf 100644 --- a/REUSE.toml +++ b/REUSE.toml @@ -1,37 +1,12 @@ -# SPDX-FileCopyrightText: 2026 Nikolay Govorov +# Copyright (c) 2026 Nikolay Govorov # SPDX-License-Identifier: AGPL-3.0-or-later version = 1 [[annotations]] path = [ - ".mailmap", - "VERSION", - "cmd/mirum-agent/build.zig.zon", - "cmd/mirum-server/web/*.json", "go.mod", "go.sum", - "mise.lock", - "packaging/dl/*", - "packaging/logo.svg", - "charts/mirum/templates/*", - "buf.*", ] -SPDX-FileCopyrightText = "2026 Nikolay Govorov" +SPDX-FileCopyrightText = "2026 Nikolay Govorov <me@govorov.online>" SPDX-License-Identifier = "AGPL-3.0-or-later" - -[[annotations]] -path = [ - "CLA.md", -] -SPDX-FileCopyrightText = "2026 Nikolay Govorov" -SPDX-License-Identifier = "CC-BY-3.0" - -[[annotations]] -path = [ - "README.md", - "charts/mirum/README.md", - "docs/**.md", -] -SPDX-FileCopyrightText = "2026 Nikolay Govorov" -SPDX-License-Identifier = "CC-BY-4.0" diff --git a/Taskfile.yml b/Taskfile.yml index 8e9b060..2c923c3 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -1,443 +1,16 @@ -# SPDX-FileCopyrightText: 2026 Nikolay Govorov +# Copyright (c) 2026 Nikolay Govorov # SPDX-License-Identifier: AGPL-3.0-or-later -version: "3" - -output: prefixed - -env: - CGO_ENABLED: "0" # cgo forbidden in mirum, do not change this behavior. - SOURCE_DATE_EPOCH: - sh: 'if [ -z "$(git status --porcelain)" ]; then git log -1 --format=%ct; else date +%s; fi' +version: '3' vars: - DOCKER: "false" - DEV_DB: mirum-local-dev - DIST_DIR: build/dist BUILD_DIR: build - LINUX_PKG_TARGETS: amd64 arm64 riscv64 ppc64le loong64 s390x - # Guest matrix for mirum-agent — a strict superset of the host cross - # matrix. Extend here as more guest targets are supported. - AGENT_TARGETS: >- - linux-amd64 linux-arm64 linux-riscv64 linux-ppc64le linux-loong64 linux-s390x - darwin-amd64 darwin-arm64 - windows-amd64 windows-arm64 - freebsd-amd64 freebsd-arm64 freebsd-riscv64 - netbsd-amd64 netbsd-arm64 - openbsd-amd64 openbsd-arm64 tasks: - # Production build pipeline - web:install: - desc: Install frontend dependencies - run: once - dir: cmd/mirum-server/web - sources: - - package.json - - package-lock.json - generates: - - node_modules/.package-lock.json - cmds: - - npm ci - - proto: - desc: Generate ConnectRPC code from proto files - run: once - deps: [web:install] - sources: - - internal/protocol/proto/*.proto - - internal/protocol/proto/buf.gen.yaml - - cmd/mirum-server/proto/*.proto - - cmd/mirum-server/proto/buf.gen.yaml - - buf.yaml - generates: - - internal/protocol/wirepb/**/*.go - - cmd/mirum-server/apipb/**/*.go - - cmd/mirum-server/web/gen/**/*.ts - cmds: - - rm -rf internal/protocol/wirepb cmd/mirum-server/apipb cmd/mirum-server/web/gen - - cd internal/protocol/proto && buf generate - - cd cmd/mirum-server/proto && buf generate - # api_pb.ts imports file_buf_validate_validate from buf/validate/validate_pb.js. - # buf.validate carries only server-side field annotations that the browser - # never decodes, so replace the 200+ KB generated schema with an empty - # descriptor stub that satisfies the GenFile type. - - mkdir -p cmd/mirum-server/web/gen/buf/validate - - | - cat > cmd/mirum-server/web/gen/buf/validate/validate_pb.ts <<'EOF' - // Stub: see Taskfile.yml proto task. buf.validate is server-only. - import { fileDesc, type GenFile } from "@bufbuild/protobuf/codegenv2" - export const file_buf_validate_validate: GenFile = /*@__PURE__*/ fileDesc("") - EOF - - web:build: - desc: Build the frontend into cmd/mirum-server/static - run: once - dir: cmd/mirum-server/web - deps: [web:install, proto] - sources: - - api/**/* - - gen/**/* - - lib/**/* - - pages/**/* - - components/**/* - - index.css - - vite.config.ts - - tsconfig.json - - tsconfig.app.json - generates: - - ../static/**/* - cmds: - - npm run build - - licenses: - desc: Regenerate third-party license manifest (build/licenses.json) - run: once - deps: [proto, web:build] - sources: - - go.sum - - cmd/mirum-server/web/package-lock.json - - tools/licensegen/**/*.go - - LICENSES.go - - LICENSES/*.txt - generates: - - build/licenses.json - cmds: - - mkdir -p {{.BUILD_DIR}} - - go run -tags licensegen ./tools/licensegen -out {{.BUILD_DIR}}/licenses.json - build: - desc: "Build binaries (override GOOS/GOARCH for cross-compilation)" - deps: [proto, licenses, web:build] - vars: - GOOS: { sh: "echo ${GOOS:-$(go env GOOS)}" } - GOARCH: { sh: "echo ${GOARCH:-$(go env GOARCH)}" } - LDFLAGS: "-s -w" - env: - GOOS: "{{.GOOS}}" - GOARCH: "{{.GOARCH}}" - GOFLAGS: "-trimpath" + desc: Build master binary cmds: - mkdir -p {{.BUILD_DIR}} - - go build -ldflags="{{.LDFLAGS}}" -o {{.BUILD_DIR}}/mirum-server-{{.GOOS}}-{{.GOARCH}} ./cmd/mirum-server - - go build -ldflags="{{.LDFLAGS}}" -o {{.BUILD_DIR}}/mirum-worker-{{.GOOS}}-{{.GOARCH}} ./cmd/mirum-worker - - go build -ldflags="{{.LDFLAGS}}" -o {{.BUILD_DIR}}/mirum-{{.GOOS}}-{{.GOARCH}} ./cmd/mirum - - agent:build: - desc: "Build mirum-agent for one guest target (override GOOS/GOARCH)" - dir: cmd/mirum-agent - vars: - GOOS: { sh: "echo ${GOOS:-$(go env GOOS)}" } - GOARCH: { sh: "echo ${GOARCH:-$(go env GOARCH)}" } - cmds: - - | - case "{{.GOARCH}}" in - amd64) za=x86_64 ;; - arm64) za=aarch64 ;; - riscv64) za=riscv64 ;; - ppc64le) za=powerpc64le ;; - loong64) za=loongarch64 ;; - s390x) za=s390x ;; - *) echo "agent: unsupported arch {{.GOARCH}}" >&2; exit 1 ;; - esac - case "{{.GOOS}}" in - linux) ztriple="${za}-linux" ;; - darwin) ztriple="${za}-macos" ;; - windows) ztriple="${za}-windows-gnu" ;; - freebsd) ztriple="${za}-freebsd" ;; - openbsd) ztriple="${za}-openbsd" ;; - netbsd) ztriple="${za}-netbsd" ;; - *) echo "agent: unsupported os {{.GOOS}}" >&2; exit 1 ;; - esac - ext=""; [ "{{.GOOS}}" = windows ] && ext=.exe - zig build -Dtarget="$ztriple" -Doptimize=ReleaseFast -Dstrip - mkdir -p ../../{{.BUILD_DIR}}/agent - cp "zig-out/bin/mirum-agent${ext}" "../../{{.BUILD_DIR}}/agent/mirum-agent-{{.GOOS}}-{{.GOARCH}}${ext}" - - agent:cross: - desc: Cross-compile mirum-agent for the full guest matrix - cmds: - - rm -rf {{.BUILD_DIR}}/agent - - for: { var: AGENT_TARGETS } - cmd: | - target="{{.ITEM}}" - task agent:build GOOS="${target%-*}" GOARCH="${target##*-}" - - agent:test: - desc: Build and run mirum-agent unit tests - dir: cmd/mirum-agent - cmds: - - zig build test - - cross: - desc: Cross-compile and archive binaries for all supported platforms - deps: [agent:cross] - cmds: - - mkdir -p {{.DIST_DIR}} - - for: [ - darwin-amd64, darwin-arm64, - windows-amd64, windows-arm64, - netbsd-amd64, netbsd-arm64, - openbsd-amd64, openbsd-arm64, - freebsd-amd64, freebsd-arm64, freebsd-riscv64, - linux-amd64, linux-arm64, linux-riscv64, linux-ppc64le, linux-loong64, linux-s390x, - ] - cmd: | - target="{{ .ITEM }}" - os="${target%%-*}" - arch="${target#*-}" - task build GOOS="$os" GOARCH="$arch" - - ext="" - [ "$os" = "windows" ] && ext=".exe" - - staging=$(mktemp -d) - cp README.md "$staging/" - cp -r LICENSES "$staging/" - for cmd in cmd/*/; do - bin=$(basename "$cmd") - [ "$bin" = "mirum-agent" ] && continue - cp "{{.BUILD_DIR}}/${bin}-${os}-${arch}" "${staging}/${bin}${ext}" - done - cp -r "{{.BUILD_DIR}}/agent" "$staging/agent" - - format=tar.gz - [ "$os" = "windows" ] && format=zip - mise run package -- \ - --output "{{.DIST_DIR}}" \ - --archive-root "$staging" \ - --archive-name "mirum-${target}" \ - "$format" - rm -rf "$staging" - - cd {{.DIST_DIR}} && sha256sum *.tar.gz *.zip > SHA256SUMS - - package: - desc: Cross-compile all platforms and build deb/rpm packages for Linux - deps: [cross] - vars: - VERSION: - sh: | - ref="${GIT_REF:-$(git describe --tags --exact-match 2>/dev/null || echo "")}" - if [[ "$ref" == refs/tags/v* ]]; then - echo "${ref#refs/tags/v}" - elif [[ "$ref" == v* ]]; then - echo "${ref#v}" - else - echo "$(cat VERSION)~nightly.$(git log -1 --format=%ct)" - fi - CHANNEL: - sh: | - ref="${GIT_REF:-$(git describe --tags --exact-match 2>/dev/null || echo "")}" - if [[ "$ref" == refs/tags/v* || "$ref" == v* ]]; then echo stable; else echo nightly; fi - env: - VERSION: "{{.VERSION}}" - cmds: - - | - for arch in {{.LINUX_PKG_TARGETS}}; do - mkdir -p {{.BUILD_DIR}}/tmp - for cmd in cmd/*/; do - bin=$(basename "$cmd") - [ "$bin" = "mirum-agent" ] && continue - cp "{{.BUILD_DIR}}/${bin}-linux-${arch}" "{{.BUILD_DIR}}/tmp/${bin}" - done - - formats="deb rpm" - # Alpine does not build for loong64 - if [ "$arch" != "loong64" ]; then - formats="$formats apk" - fi - GPG_KEY_ID="{{.GPG_KEY_ID}}" mise run package -- \ - --version "{{.VERSION}}" \ - --arch "$arch" \ - --output "{{.DIST_DIR}}" \ - $formats - rm -rf {{.BUILD_DIR}}/tmp - done - - printf 'VERSION={{.VERSION}}\nCHANNEL={{.CHANNEL}}\n' > {{.DIST_DIR}}/build.env - - publish:release: - desc: Create or update a GitHub Release - requires: - vars: [CHANNEL, VERSION] - cmds: - - | - if [ "{{.CHANNEL}}" = "stable" ]; then - TAG="v{{.VERSION}}" - NAME="{{.VERSION}}" - else - TAG="nightly" - NAME="nightly" - git tag -f nightly - git push origin --force tag nightly - for asset in $(gh release view nightly --json assets --jq '.assets[].name' 2>/dev/null || true); do - gh release delete-asset nightly "$asset" --yes - done - fi - - gh release create "$TAG" {{.DIST_DIR}}/*.deb {{.DIST_DIR}}/*.rpm {{.DIST_DIR}}/*.apk {{.DIST_DIR}}/*.tar.gz {{.DIST_DIR}}/*.zip {{.DIST_DIR}}/SHA256SUMS \ - --title "$NAME" \ - $( [ "{{.CHANNEL}}" = "nightly" ] && echo "--prerelease" ) \ - --notes "**Version**: {{.VERSION}}" \ - || gh release upload "$TAG" {{.DIST_DIR}}/*.deb {{.DIST_DIR}}/*.rpm {{.DIST_DIR}}/*.apk {{.DIST_DIR}}/*.tar.gz {{.DIST_DIR}}/*.zip {{.DIST_DIR}}/SHA256SUMS --clobber - - # Local dev env - devenv:config: - desc: Write dev/mirum-server.yaml if missing - status: - - test -f dev/mirum-server.yaml - cmds: - - mkdir -p dev - - | - cat > dev/mirum-server.yaml <<EOF - web_addr: ":3000" - grpc_addr: ":2026" - admin_socket: "/tmp/mirum-admin.sock" - database_uri: "postgres://{{.DEV_DB}}:{{.DEV_DB}}@localhost:5432/{{.DEV_DB}}?sslmode=disable" - pepper: "dev-pepper" - webhook_secret: "dev-secret" - grpc_tls: - cert: "dev/server.crt" - key: "dev/server.key" - EOF - - devenv:keys: - desc: Generate dev TLS cert and worker key pair - status: - - test -f dev/server.crt - - test -f dev/worker.key - - test -f dev/worker.pub - cmds: - - mkdir -p dev - - >- - test -f dev/server.crt || - openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:prime256v1 - -keyout dev/server.key -out dev/server.crt - -days 365 -nodes -subj "/CN=mirum-server" - -addext "subjectAltName=DNS:mirum-server,DNS:localhost" - - >- - test -f dev/worker.key || - openssl genpkey -algorithm ed25519 -out dev/worker.key - - >- - test -f dev/worker.pub || - openssl pkey -in dev/worker.key -pubout -out dev/worker.pub - - devenv:db:local: - internal: true - desc: Create the dev postgres role and database on a local install (needs sudo) - status: - - '[ "{{.DOCKER}}" = "true" ] || PGPASSWORD={{.DEV_DB}} psql -h localhost -U {{.DEV_DB}} -d {{.DEV_DB}} -c "SELECT 1" >/dev/null 2>&1' - cmds: - - | - sudo -u postgres psql -tAc "SELECT 1 FROM pg_roles WHERE rolname='{{.DEV_DB}}'" | grep -q 1 || - sudo -u postgres psql -c "CREATE ROLE \"{{.DEV_DB}}\" LOGIN PASSWORD '{{.DEV_DB}}'" - - | - sudo -u postgres psql -tAc "SELECT 1 FROM pg_database WHERE datname='{{.DEV_DB}}'" | grep -q 1 || - sudo -u postgres psql -c "CREATE DATABASE \"{{.DEV_DB}}\" OWNER \"{{.DEV_DB}}\"" - - devenv:db:docker: - internal: true - desc: Start a Postgres Docker container for dev - status: - - '[ "{{.DOCKER}}" != "true" ] || docker exec {{.DEV_DB}} pg_isready -U {{.DEV_DB}} >/dev/null 2>&1 || nc -z localhost 5432 2>/dev/null' - cmds: - - docker run -d - --name {{.DEV_DB}} - -e POSTGRES_USER={{.DEV_DB}} - -e POSTGRES_PASSWORD={{.DEV_DB}} - -e POSTGRES_DB={{.DEV_DB}} - -p 5432:5432 - postgres:18 - - | - echo "Waiting for postgres..." - until docker exec {{.DEV_DB}} pg_isready -U {{.DEV_DB}} >/dev/null 2>&1; do - sleep 1 - done - - devenv:db: - desc: "Create the dev postgres role and database (set DOCKER=true for Docker)" - deps: [devenv:db:local, devenv:db:docker] - - devenv: - desc: Provision a full local dev environment (keys, config, database) - deps: [devenv:keys, devenv:config, devenv:db] - - web:dev: - desc: Run Vite dev server (use with `go run -tags dev ./cmd/mirum-server`) - dir: cmd/mirum-server/web - deps: [web:install, proto] - prefix: vite - env: - NO_COLOR: "1" - CI: "true" - cmds: - - npm run dev - - dev:server: - desc: Build mirum-server with -tags dev and run it against the dev environment - deps: [proto, licenses, devenv] - prefix: mirum-server - cmds: - - mkdir -p {{.BUILD_DIR}} - - go build -tags dev -o {{.BUILD_DIR}}/mirum-server ./cmd/mirum-server - - "{{.BUILD_DIR}}/mirum-server daemon --config dev/mirum-server.yaml" - - run: - desc: Build and run mirum-server against the dev environment - deps: [build, devenv] - vars: - GOOS: { sh: go env GOOS } - GOARCH: { sh: go env GOARCH } - cmds: - - "{{.BUILD_DIR}}/mirum-server-{{.GOOS}}-{{.GOARCH}} daemon --config dev/mirum-server.yaml" - - dev: - desc: Run Vite dev server and mirum-server together (HMR-enabled) - deps: [web:dev, dev:server] - - # Test and lint checks - signoff: - desc: Verify contributor identities and commit sign-offs - cmds: - - mise run signoff - - legal: - desc: Verify contribution and licensing policy - cmds: - - task: signoff - - mise run licenses - - web:lint: - desc: Lint and check the frontend - dir: cmd/mirum-server/web - deps: [web:build] - cmds: - - npm run lint - - npm run size - - go:lint: - desc: Lint and check the go - deps: [proto, web:build, licenses] - cmds: - - gofmt -l . | grep . && exit 1 || true - - go vet ./... - - go tool govulncheck ./... - - go tool golangci-lint run ./... - - lint: - desc: Run static checks - deps: [go:lint, web:lint] - cmds: - - task: go:lint - - task: web:lint - - test: - desc: Run all tests - deps: [build] - env: - CGO_ENABLED: "1" # required for -race - cmds: - - go test -race -count=1 ./... - - task: agent:test + - go build -o {{.BUILD_DIR}}/mirumd ./cmd/mirumd # yaml-language-server: $schema=https://taskfile.dev/schema.json diff --git a/VERSION b/VERSION deleted file mode 100644 --- a/VERSION +++ /dev/null @@ -1 +0,0 @@ -0.1.0 diff --git a/VERSION.go b/VERSION.go deleted file mode 100644 --- a/VERSION.go +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-FileCopyrightText: 2026 Nikolay Govorov -// SPDX-License-Identifier: AGPL-3.0-or-later - -// This package is a small hack for embedding VERSION into a project. -// Go sensibly prohibits embedding files from parent folders, -// so this file is only needed to bypass the restriction. -package mirum - -import _ "embed" - -//go:embed VERSION -var Version string diff --git a/buf.lock b/buf.lock deleted file mode 100644 --- a/buf.lock +++ /dev/null @@ -1,6 +0,0 @@ -# Generated by buf. DO NOT EDIT. -version: v2 -deps: - - name: buf.build/bufbuild/protovalidate - commit: 80ab13bee0bf4272b6161a72bf7034e0 - digest: b5:1aa6a965be5d02d64e1d81954fa2e78ef9d1e33a0c30f92bc2626039006a94deb3a5b05f14ed8893f5c3ffce444ac008f7e968188ad225c4c29c813aa5f2daa1 diff --git a/buf.yaml b/buf.yaml deleted file mode 100644 --- a/buf.yaml +++ /dev/null @@ -1,9 +0,0 @@ -# SPDX-FileCopyrightText: 2026 Nikolay Govorov -# SPDX-License-Identifier: AGPL-3.0-or-later - -version: v2 -modules: - - path: internal/protocol/proto - - path: cmd/mirum-server/proto -deps: - - buf.build/bufbuild/protovalidate diff --git a/charts/mirum/Chart.yaml b/charts/mirum/Chart.yaml deleted file mode 100644 --- a/charts/mirum/Chart.yaml +++ /dev/null @@ -1,10 +0,0 @@ -# SPDX-FileCopyrightText: 2026 Nikolay Govorov -# SPDX-License-Identifier: AGPL-3.0-or-later - -apiVersion: v2 -type: application -version: 0.1.0 - -name: mirum -home: https://github.com/dimidiumlabs/mirum -description: Mirum CI server and workers diff --git a/charts/mirum/templates/_helpers.tpl b/charts/mirum/templates/_helpers.tpl deleted file mode 100644 --- a/charts/mirum/templates/_helpers.tpl +++ /dev/null @@ -1,40 +0,0 @@ -{{/* SPDX-License-Identifier: AGPL-3.0-or-later */}} -{{/* vim: set filetype=helm: */}} -{{- define "mirum.name" -}} -{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} -{{- end -}} - -{{- define "mirum.fullname" -}} -{{- if .Values.fullnameOverride -}} -{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} -{{- else -}} -{{- printf "%s-%s" .Release.Name (include "mirum.name" .) | trunc 63 | trimSuffix "-" -}} -{{- end -}} -{{- end -}} - -{{- define "mirum.labels" -}} -helm.sh/chart: {{ printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" }} -app.kubernetes.io/name: {{ include "mirum.name" . }} -app.kubernetes.io/instance: {{ .Release.Name }} -app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} -app.kubernetes.io/managed-by: {{ .Release.Service }} -{{- end -}} - -{{- define "mirum.componentLabels" -}} -{{ include "mirum.labels" .root }} -app.kubernetes.io/component: {{ .component }} -{{- end -}} - -{{- define "mirum.selectorLabels" -}} -app.kubernetes.io/name: {{ include "mirum.name" .root }} -app.kubernetes.io/instance: {{ .root.Release.Name }} -app.kubernetes.io/component: {{ .component }} -{{- end -}} - -{{- define "mirum.image" -}} -{{- if .image.digest -}} -{{- printf "%s@%s" .image.repository .image.digest -}} -{{- else -}} -{{- printf "%s:%s" .image.repository (default .root.Chart.AppVersion .image.tag) -}} -{{- end -}} -{{- end -}} diff --git a/charts/mirum/templates/server-deployment.yaml b/charts/mirum/templates/server-deployment.yaml deleted file mode 100644 --- a/charts/mirum/templates/server-deployment.yaml +++ /dev/null @@ -1,142 +0,0 @@ -# SPDX-FileCopyrightText: 2026 Nikolay Govorov -# SPDX-License-Identifier: AGPL-3.0-or-later - -{{- if and .Values.server.enabled (ne (int .Values.server.replicaCount) 1) }} -{{- fail "mirum: server.replicaCount must be 1 while the task queue is process-local" }} -{{- end }} -{{- if and .Values.server.enabled .Values.server.postgresqlMtls.enabled (not .Values.server.serviceAccountName) }} -{{- fail "mirum: server.serviceAccountName is required when server.postgresqlMtls.enabled=true" }} -{{- end }} -{{- if .Values.server.enabled }} -apiVersion: apps/v1 -kind: Deployment -metadata: - name: {{ include "mirum.fullname" . }}-server - labels: - {{- include "mirum.componentLabels" (dict "root" . "component" "server") | nindent 4 }} -spec: - replicas: {{ .Values.server.replicaCount }} - strategy: - type: Recreate - selector: - matchLabels: - {{- include "mirum.selectorLabels" (dict "root" . "component" "server") | nindent 6 }} - template: - metadata: - annotations: - {{- with .Values.server.podAnnotations }} - {{- toYaml . | nindent 8 }} - {{- end }} - labels: - {{- include "mirum.selectorLabels" (dict "root" . "component" "server") | nindent 8 }} - {{- with .Values.server.podLabels }} - {{- toYaml . | nindent 8 }} - {{- end }} - spec: - automountServiceAccountToken: false - {{- with .Values.server.serviceAccountName }} - serviceAccountName: {{ . | quote }} - {{- end }} - terminationGracePeriodSeconds: {{ .Values.terminationGracePeriodSeconds }} - securityContext: - runAsNonRoot: true - runAsUser: 10000 - runAsGroup: 10000 - fsGroup: 10000 - fsGroupChangePolicy: OnRootMismatch - {{- with .Values.imagePullSecrets }} - imagePullSecrets: - {{- toYaml . | nindent 8 }} - {{- end }} - containers: - - name: server - image: {{ include "mirum.image" (dict "root" . "image" .Values.server.image) | quote }} - imagePullPolicy: {{ .Values.server.image.pullPolicy }} - args: ["daemon", "--config=/etc/mirum/secret/config.yaml"] - securityContext: - allowPrivilegeEscalation: false - capabilities: - drop: [ALL] - readOnlyRootFilesystem: true - ports: - - name: web - containerPort: {{ .Values.server.webPort }} - - name: grpc - containerPort: {{ .Values.server.grpcPort }} - {{- with .Values.server.extraEnv }} - env: - {{- toYaml . | nindent 12 }} - {{- end }} - startupProbe: - tcpSocket: { port: web } - failureThreshold: 30 - periodSeconds: 2 - readinessProbe: - tcpSocket: { port: web } - livenessProbe: - tcpSocket: { port: web } - periodSeconds: 20 - resources: - {{- toYaml .Values.server.resources | nindent 12 }} - volumeMounts: - - name: config - mountPath: /etc/mirum/secret - readOnly: true - {{- with .Values.server.grpcTls.existingSecret }} - - name: grpc-tls - mountPath: /etc/mirum/grpc-tls - readOnly: true - {{- end }} - {{- if .Values.server.postgresqlMtls.enabled }} - - name: postgresql-tls - mountPath: {{ .Values.server.postgresqlMtls.mountPath }} - readOnly: true - {{- end }} - - name: run - mountPath: /run/mirum-server - volumes: - - name: config - secret: - secretName: {{ .Values.server.existingSecret }} - defaultMode: 0440 - {{- with .Values.server.grpcTls.existingSecret }} - - name: grpc-tls - secret: - secretName: {{ . }} - defaultMode: 0440 - {{- end }} - {{- if .Values.server.postgresqlMtls.enabled }} - - name: postgresql-tls - csi: - driver: csi.cert-manager.io - readOnly: true - volumeAttributes: - csi.cert-manager.io/issuer-name: {{ required "mirum: server.postgresqlMtls.issuerName is required" .Values.server.postgresqlMtls.issuerName | quote }} - csi.cert-manager.io/issuer-kind: {{ .Values.server.postgresqlMtls.issuerKind | quote }} - csi.cert-manager.io/common-name: {{ required "mirum: server.postgresqlMtls.commonName is required" .Values.server.postgresqlMtls.commonName | quote }} - csi.cert-manager.io/uri-sans: {{ .Values.server.postgresqlMtls.uriSan | quote }} - csi.cert-manager.io/key-algorithm: "ECDSA" - csi.cert-manager.io/key-size: "256" - csi.cert-manager.io/key-encoding: "PKCS8" - csi.cert-manager.io/key-usages: "digital signature,client auth" - csi.cert-manager.io/duration: {{ .Values.server.postgresqlMtls.duration | quote }} - csi.cert-manager.io/renew-before: {{ .Values.server.postgresqlMtls.renewBefore | quote }} - csi.cert-manager.io/fs-group: {{ .Values.server.postgresqlMtls.fsGroup | quote }} - {{- end }} - - name: run - emptyDir: {} - {{- with .Values.server.nodeSelector }} - nodeSelector: - {{- toYaml . | nindent 8 }} - {{- end }} - {{- with .Values.server.affinity }} - affinity: - {{- toYaml . | nindent 8 }} - {{- end }} - {{- with .Values.server.tolerations }} - tolerations: - {{- toYaml . | nindent 8 }} - {{- end }} -{{- end }} - -# vim: set filetype=helm: diff --git a/charts/mirum/templates/server-httproute.yaml b/charts/mirum/templates/server-httproute.yaml deleted file mode 100644 --- a/charts/mirum/templates/server-httproute.yaml +++ /dev/null @@ -1,26 +0,0 @@ -# SPDX-FileCopyrightText: 2026 Nikolay Govorov -# SPDX-License-Identifier: AGPL-3.0-or-later - -{{- if and .Values.server.enabled .Values.server.route.enabled }} -apiVersion: gateway.networking.k8s.io/v1 -kind: HTTPRoute -metadata: - name: {{ include "mirum.fullname" . }} - labels: - {{- include "mirum.componentLabels" (dict "root" . "component" "server") | nindent 4 }} -spec: - {{- with .Values.server.route.parentRefs }} - parentRefs: - {{- toYaml . | nindent 4 }} - {{- end }} - {{- with .Values.server.route.hostnames }} - hostnames: - {{- toYaml . | nindent 4 }} - {{- end }} - rules: - - backendRefs: - - name: {{ include "mirum.fullname" . }} - port: {{ .Values.server.webPort }} -{{- end }} - -# vim: set filetype=helm: diff --git a/charts/mirum/templates/server-service.yaml b/charts/mirum/templates/server-service.yaml deleted file mode 100644 --- a/charts/mirum/templates/server-service.yaml +++ /dev/null @@ -1,23 +0,0 @@ -# SPDX-FileCopyrightText: 2026 Nikolay Govorov -# SPDX-License-Identifier: AGPL-3.0-or-later - -{{- if .Values.server.enabled }} -apiVersion: v1 -kind: Service -metadata: - name: {{ include "mirum.fullname" . }} - labels: - {{- include "mirum.componentLabels" (dict "root" . "component" "server") | nindent 4 }} -spec: - selector: - {{- include "mirum.selectorLabels" (dict "root" . "component" "server") | nindent 4 }} - ports: - - name: web - port: {{ .Values.server.webPort }} - targetPort: web - - name: grpc - port: {{ .Values.server.grpcPort }} - targetPort: grpc -{{- end }} - -# vim: set filetype=helm: diff --git a/charts/mirum/templates/server-tcproute.yaml b/charts/mirum/templates/server-tcproute.yaml deleted file mode 100644 --- a/charts/mirum/templates/server-tcproute.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# SPDX-FileCopyrightText: 2026 Nikolay Govorov -# SPDX-License-Identifier: AGPL-3.0-or-later - -{{- if and .Values.server.enabled .Values.server.grpcRoute.enabled }} -apiVersion: gateway.networking.k8s.io/v1 -kind: TCPRoute -metadata: - name: {{ include "mirum.fullname" . }}-grpc - labels: - {{- include "mirum.componentLabels" (dict "root" . "component" "server") | nindent 4 }} -spec: - {{- with .Values.server.grpcRoute.parentRefs }} - parentRefs: - {{- toYaml . | nindent 4 }} - {{- end }} - rules: - - backendRefs: - - name: {{ include "mirum.fullname" . }} - port: {{ .Values.server.grpcPort }} -{{- end }} - -# vim: set filetype=helm: diff --git a/charts/mirum/templates/worker-deployment.yaml b/charts/mirum/templates/worker-deployment.yaml deleted file mode 100644 --- a/charts/mirum/templates/worker-deployment.yaml +++ /dev/null @@ -1,87 +0,0 @@ -# SPDX-FileCopyrightText: 2026 Nikolay Govorov -# SPDX-License-Identifier: AGPL-3.0-or-later - -{{- if .Values.worker.enabled }} -apiVersion: apps/v1 -kind: Deployment -metadata: - name: {{ include "mirum.fullname" . }}-worker - labels: - {{- include "mirum.componentLabels" (dict "root" . "component" "worker") | nindent 4 }} -spec: - replicas: {{ .Values.worker.replicaCount }} - selector: - matchLabels: - {{- include "mirum.selectorLabels" (dict "root" . "component" "worker") | nindent 6 }} - template: - metadata: - annotations: - {{- with .Values.worker.podAnnotations }} - {{- toYaml . | nindent 8 }} - {{- end }} - labels: - {{- include "mirum.selectorLabels" (dict "root" . "component" "worker") | nindent 8 }} - {{- with .Values.worker.podLabels }} - {{- toYaml . | nindent 8 }} - {{- end }} - spec: - automountServiceAccountToken: false - terminationGracePeriodSeconds: {{ .Values.terminationGracePeriodSeconds }} - securityContext: - runAsNonRoot: true - runAsUser: 10000 - runAsGroup: 10000 - fsGroup: 10000 - fsGroupChangePolicy: OnRootMismatch - {{- with .Values.imagePullSecrets }} - imagePullSecrets: - {{- toYaml . | nindent 8 }} - {{- end }} - containers: - - name: worker - image: {{ include "mirum.image" (dict "root" . "image" .Values.worker.image) | quote }} - imagePullPolicy: {{ .Values.worker.image.pullPolicy }} - args: ["--config=/etc/mirum/secret/config.yaml"] - securityContext: - allowPrivilegeEscalation: false - capabilities: - drop: [ALL] - readOnlyRootFilesystem: true - {{- with .Values.worker.extraEnv }} - env: - {{- toYaml . | nindent 12 }} - {{- end }} - resources: - {{- toYaml .Values.worker.resources | nindent 12 }} - volumeMounts: - - name: config - mountPath: /etc/mirum/secret - readOnly: true - - name: workspace - mountPath: /var/lib/mirum-worker - - name: tmp - mountPath: /tmp - volumes: - - name: config - secret: - secretName: {{ .Values.worker.existingSecret }} - defaultMode: 0440 - - name: workspace - emptyDir: {} - - name: tmp - emptyDir: {} - {{- with .Values.worker.nodeSelector }} - nodeSelector: - {{- toYaml . | nindent 8 }} - {{- end }} - {{- with .Values.worker.affinity }} - affinity: - {{- toYaml . | nindent 8 }} - {{- end }} - {{- with .Values.worker.tolerations }} - tolerations: - {{- toYaml . | nindent 8 }} - {{- end }} -{{- end }} - -# vim: set filetype=helm: diff --git a/charts/mirum/values.yaml b/charts/mirum/values.yaml deleted file mode 100644 --- a/charts/mirum/values.yaml +++ /dev/null @@ -1,75 +0,0 @@ -# SPDX-FileCopyrightText: 2026 Nikolay Govorov -# SPDX-License-Identifier: AGPL-3.0-or-later - -imagePullSecrets: [] -nameOverride: "" -fullnameOverride: "" - -server: - enabled: true - replicaCount: 1 - image: - repository: ghcr.io/dimidiumlabs/mirum-server - tag: "" - digest: "" - pullPolicy: IfNotPresent - # Must contain config.yaml and any private files other than the gRPC - # certificate. Keep database_uri, pepper, token, and webhook_secret in this - # Secret rather than Helm values. - existingSecret: mirum-server - # Optional existing ServiceAccount used by the server. The default empty - # value preserves the current Deployment and external database path. - serviceAccountName: "" - postgresqlMtls: - # Opt-in client-certificate mount for passwordless PostgreSQL mTLS. Disabled - # by default; no CSI resources or volume mounts are rendered unless enabled. - enabled: false - mountPath: /etc/mirum/postgresql-tls - issuerName: "" - issuerKind: ClusterIssuer - commonName: "" - uriSan: "spiffe://ddlabs.internal/ns/${POD_NAMESPACE}/sa/${SERVICE_ACCOUNT_NAME}/pod/${POD_UID}" - duration: 2160h - renewBefore: 360h - fsGroup: 10000 - grpcTls: - # Optional cert-manager-style Secret containing tls.crt and tls.key. The - # files are mounted at /etc/mirum/grpc-tls/ for config.yaml to reference. - existingSecret: "" - webPort: 3000 - grpcPort: 2000 - route: - enabled: false - hostnames: [] - parentRefs: [] - grpcRoute: - enabled: false - parentRefs: [] - resources: {} - extraEnv: [] - podAnnotations: {} - podLabels: {} - nodeSelector: {} - tolerations: [] - affinity: {} - -worker: - enabled: false - replicaCount: 1 - image: - repository: ghcr.io/dimidiumlabs/mirum-worker - tag: "" - digest: "" - pullPolicy: IfNotPresent - # Must contain config.yaml, the Ed25519 key referenced by key_file, and an - # optional CA file referenced by tls_ca. - existingSecret: mirum-worker - resources: {} - extraEnv: [] - podAnnotations: {} - podLabels: {} - nodeSelector: {} - tolerations: [] - affinity: {} - -terminationGracePeriodSeconds: 40 diff --git a/cmd/mirum-agent/build.zig b/cmd/mirum-agent/build.zig deleted file mode 100644 --- a/cmd/mirum-agent/build.zig +++ /dev/null @@ -1,113 +0,0 @@ -// SPDX-FileCopyrightText: 2026 Nikolay Govorov -// SPDX-License-Identifier: AGPL-3.0-or-later - -const std = @import("std"); - -pub fn build(b: *std.Build) void { - const target = b.standardTargetOptions(.{ - .default_target = .{ .abi = .musl }, - }); - const optimize = b.standardOptimizeOption(.{}); - - const strip = b.option(bool, "strip", "Strip debug info from the agent binary") orelse false; - - // Strict flags for our own C. Unity is compiled separately, without - // them — a third-party header should not have to satisfy our lint. - const cflags: []const []const u8 = &.{ - "-std=c99", - "-Wall", - "-Wextra", - "-Wconversion", - "-Wshadow", - "-Wstrict-prototypes", - }; - - // libmirum-agent: the agent logic, compiled once and exposed only - // through mirum-agent.h. Both the executable and the tests link it. - const lib = b.addLibrary(.{ - .name = "mirum-agent", - .linkage = .static, - .root_module = b.createModule(.{ - .target = target, - .optimize = optimize, - .strip = strip, - .link_libc = true, - .link_libcpp = false, - }), - }); - lib.root_module.addIncludePath(b.path("")); - lib.root_module.addCSourceFile(.{ - .file = b.path("mirum-agent.c"), - .flags = cflags, - }); - - // mirum-agent: the executable entry point, links the library. - const exe = b.addExecutable(.{ - .name = "mirum-agent", - .root_module = b.createModule(.{ - .target = target, - .optimize = optimize, - .strip = strip, - .link_libc = true, - .link_libcpp = false, - }), - }); - exe.root_module.addIncludePath(b.path("")); - exe.root_module.addCSourceFile(.{ - .file = b.path("main.c"), - .flags = cflags, - }); - exe.root_module.linkLibrary(lib); - - b.installArtifact(exe); - - const run_cmd = b.addRunArtifact(exe); - run_cmd.step.dependOn(b.getInstallStep()); - if (b.args) |args| run_cmd.addArgs(args); - - const run_step = b.step("run", "Run the agent"); - run_step.dependOn(&run_cmd.step); - - // Unity: built as its own library so its sources never see our flags. - const unity_dep = b.dependency("unity", .{}); - const unity = b.addLibrary(.{ - .name = "unity", - .linkage = .static, - .root_module = b.createModule(.{ - .target = target, - .optimize = optimize, - .link_libc = true, - .link_libcpp = false, - }), - }); - unity.root_module.addIncludePath(unity_dep.path("src")); - unity.root_module.addCSourceFile(.{ - .file = unity_dep.path("src/unity.c"), - }); - - // test: links libmirum-agent through its public header only — the - // agent sources are never recompiled into the test binary. Unity's - // headers go on the system path so they bypass our warnings. - const exe_tests = b.addExecutable(.{ - .name = "test", - .root_module = b.createModule(.{ - .target = target, - .optimize = optimize, - .link_libc = true, - .link_libcpp = false, - }), - }); - exe_tests.root_module.addIncludePath(b.path("")); - exe_tests.root_module.addSystemIncludePath(unity_dep.path("src")); - exe_tests.root_module.addCSourceFile(.{ - .file = b.path("test.c"), - .flags = cflags, - }); - exe_tests.root_module.linkLibrary(lib); - exe_tests.root_module.linkLibrary(unity); - - const run_exe_tests = b.addRunArtifact(exe_tests); - - const test_step = b.step("test", "Run tests"); - test_step.dependOn(&run_exe_tests.step); -} diff --git a/cmd/mirum-agent/build.zig.zon b/cmd/mirum-agent/build.zig.zon deleted file mode 100644 --- a/cmd/mirum-agent/build.zig.zon +++ /dev/null @@ -1,17 +0,0 @@ -.{ - .name = .agent, - .version = "0.0.0", - .fingerprint = 0x268b9c9d6e24fb9b, // Changing this has security and trust implications. - .minimum_zig_version = "0.16.0", // keep in sync with github actions - .dependencies = .{ - .unity = .{ - .url = "https://github.com/ThrowTheSwitch/Unity/archive/refs/tags/v2.6.1.tar.gz", - .hash = "N-V-__8AAAQfEgAjW551lOzfHoVAve6KndztgTjCXsJvElP9", - }, - }, - .paths = .{ - "build.zig", "build.zig.zon", - "test.c", "main.c", - "mirum-agent.c", "mirum-agent.h", - }, -} diff --git a/cmd/mirum-agent/main.c b/cmd/mirum-agent/main.c deleted file mode 100644 --- a/cmd/mirum-agent/main.c +++ /dev/null @@ -1,10 +0,0 @@ -// SPDX-FileCopyrightText: 2026 Nikolay Govorov -// SPDX-License-Identifier: AGPL-3.0-or-later - -#include "mirum-agent.h" - -int main(int argc, const char *argv[]) { - mirum_init(); - - return 0; -} diff --git a/cmd/mirum-agent/mirum-agent.c b/cmd/mirum-agent/mirum-agent.c deleted file mode 100644 --- a/cmd/mirum-agent/mirum-agent.c +++ /dev/null @@ -1,10 +0,0 @@ -// SPDX-FileCopyrightText: 2026 Nikolay Govorov -// SPDX-License-Identifier: AGPL-3.0-or-later - -#include <stdio.h> - -#include "mirum-agent.h" - -void mirum_init(void) { - printf("This is %s\n", "mirum-agent"); -} diff --git a/cmd/mirum-agent/mirum-agent.h b/cmd/mirum-agent/mirum-agent.h deleted file mode 100644 --- a/cmd/mirum-agent/mirum-agent.h +++ /dev/null @@ -1,9 +0,0 @@ -// SPDX-FileCopyrightText: 2026 Nikolay Govorov -// SPDX-License-Identifier: AGPL-3.0-or-later - -#ifndef MIRUM_AGENT_H -#define MIRUM_AGENT_H - -void mirum_init(void); - -#endif // MIRUM_AGENT_H diff --git a/cmd/mirum-agent/test.c b/cmd/mirum-agent/test.c deleted file mode 100644 --- a/cmd/mirum-agent/test.c +++ /dev/null @@ -1,22 +0,0 @@ -// SPDX-FileCopyrightText: 2026 Nikolay Govorov -// SPDX-License-Identifier: AGPL-3.0-or-later - -#include "unity.h" - -#include "mirum-agent.h" - -void setUp(void) {} -void tearDown(void) {} - -// Smoke test: the public entry point links and runs without crashing. -// Substantive suites (TLV decode, channel state machine) land with that -// code, exercising mirum-agent.h the same way. -static void test_mirum_init_runs(void) { - mirum_init(); -} - -int main(void) { - UNITY_BEGIN(); - RUN_TEST(test_mirum_init_runs); - return UNITY_END(); -} diff --git a/cmd/mirum-server/Dockerfile b/cmd/mirum-server/Dockerfile deleted file mode 100644 --- a/cmd/mirum-server/Dockerfile +++ /dev/null @@ -1,20 +0,0 @@ -# SPDX-FileCopyrightText: 2026 Nikolay Govorov -# SPDX-License-Identifier: AGPL-3.0-or-later - -FROM gcr.io/distroless/static-debian13:nonroot@sha256:1c2c046bc09ed40fad370b599a0b1ae7987f55b01e247cf27a7c27cd97e5bbc7 - -ARG TARGETARCH - -LABEL org.opencontainers.image.source="https://github.com/dimidiumlabs/mirum" \ - org.opencontainers.image.licenses="AGPL-3.0-or-later" \ - org.opencontainers.image.title="mirum-server" - -COPY --chown=root:root --chmod=0755 .container/mirum-server-linux-${TARGETARCH} /usr/local/bin/mirum-server -COPY LICENSE README.md /usr/share/doc/mirum/ - -USER 10000:10000 -EXPOSE 3000 2000 -ENTRYPOINT ["/usr/local/bin/mirum-server"] -CMD ["daemon", "--config=/etc/mirum/config.yaml"] - -# syntax=docker/dockerfile: diff --git a/cmd/mirum-server/actor.go b/cmd/mirum-server/actor.go deleted file mode 100644 --- a/cmd/mirum-server/actor.go +++ /dev/null @@ -1,252 +0,0 @@ -// SPDX-FileCopyrightText: 2026 Nikolay Govorov -// SPDX-License-Identifier: AGPL-3.0-or-later - -package main - -import ( - "context" - "errors" - "slices" - - "dimidiumlabs/mirum/cmd/mirum-server/apipb" - - "github.com/google/uuid" - "github.com/jackc/pgx/v5" -) - -var ( - ErrPermissionDenied = errors.New("database: permission denied") - ErrUnauthenticated = errors.New("database: authentication required") -) - -var anonPermissions = []apipb.Perm{ - apipb.Perm_PERM_ORG_READ, -} - -var userGlobalPermissions = []apipb.Perm{ - apipb.Perm_PERM_ORG_READ, - apipb.Perm_PERM_ORG_WRITE, - apipb.Perm_PERM_USER_READ, -} - -// rolePermissions is the single source of truth for role → perm bundles. -// RLS checks only tenancy (membership); action authz lives here. -var rolePermissions = map[string][]apipb.Perm{ - "owner": { - apipb.Perm_PERM_ORG_READ, - apipb.Perm_PERM_ORG_WRITE, - apipb.Perm_PERM_ORG_DELETE, - apipb.Perm_PERM_ORG_MEMBER_READ, - apipb.Perm_PERM_ORG_MEMBER_WRITE, - apipb.Perm_PERM_WORKER_READ, - apipb.Perm_PERM_WORKER_WRITE, - }, - "admin": { - apipb.Perm_PERM_ORG_READ, - apipb.Perm_PERM_ORG_WRITE, - apipb.Perm_PERM_ORG_MEMBER_READ, - apipb.Perm_PERM_ORG_MEMBER_WRITE, - apipb.Perm_PERM_WORKER_READ, - apipb.Perm_PERM_WORKER_WRITE, - }, - "member": { - apipb.Perm_PERM_ORG_READ, - apipb.Perm_PERM_ORG_MEMBER_READ, - apipb.Perm_PERM_WORKER_READ, - }, -} - -// Actor is the principal making a database request. It carries identity, -// display metadata, and coarse capability. Zero value is invalid: dbID -// panics, so a missing initialisation cannot silently grant privileges. -// -// Synthetic actors (System/Operator/Anon) live only as Go constants — -// they are not rows in the users table, so they cannot be logged in as -// even if somebody writes a password into the DB. -// -// Authorization is divided into two planes: -// - Tenancy: an actor can only see a subset of resources to which -// they have access (public or through organization membership). -// Any select statement will return only records accessible to the actor. -// - RBAC: what the actor can do with records (create/read/write) is implemented here. -// Any rights we grant here are a strict subset of the Tenancy rights. -// The list of perms can be either explicit (for tokens) or implied (for user roles). -type Actor struct { - kind actorKind - id uuid.UUID - email string - superuser bool -} - -type actorKind uint8 - -const ( - actorInvalid actorKind = iota - actorUser - actorOperator - actorSystem - actorAnon -) - -// ActorKind is the exported form of actorKind for audit sinks and logging. -type ActorKind uint8 - -const ( - KindInvalid ActorKind = iota - KindUser - KindOperator - KindSystem - KindAnon -) - -var ( - anonUUID = uuid.MustParse("ffffffff-ffff-ffff-ffff-ffffffffffff") - systemUUID = uuid.MustParse("00000000-0000-0000-0000-000000000001") - operatorUUID = uuid.MustParse("00000000-0000-0000-0000-000000000002") -) - -// UserActor identifies an authenticated user from a session or token. -func UserActor(id UserID, email string, superuser bool) Actor { - if id.IsZero() { - panic("database: UserActor with nil UUID") - } - if email == "" { - panic("database: UserActor with empty email") - } - return Actor{kind: actorUser, id: id.UUID(), email: email, superuser: superuser} -} - -// OperatorActor is the principal for externally invoked privileged -// operations (admin socket). Distinguishable from System in audit logs. -func OperatorActor() Actor { - return Actor{kind: actorOperator, id: operatorUUID, email: "operator@mirum.local", superuser: true} -} - -// SystemActor is the principal for internal machinery (mTLS handshake, -// session bootstrap, background jobs). Not an operator action. -func SystemActor() Actor { - return Actor{kind: actorSystem, id: systemUUID, email: "system@mirum.local", superuser: true} -} - -// AnonActor is the principal for unauthenticated public requests. -func AnonActor() Actor { - return Actor{kind: actorAnon, id: anonUUID, email: "anonymous@mirum.local"} -} - -func (a Actor) Kind() ActorKind { - switch a.kind { - case actorUser: - return KindUser - case actorOperator: - return KindOperator - case actorSystem: - return KindSystem - case actorAnon: - return KindAnon - } - return KindInvalid -} - -func (a Actor) UserID() UserID { return UserID(a.id) } -func (a Actor) Email() string { return a.email } -func (a Actor) IsSuperuser() bool { return a.superuser } - -// dbID returns the UUID to write into app.user_id. Panics on zero value. -func (a Actor) dbID() uuid.UUID { - if a.id == uuid.Nil { - panic("database: zero-value Actor; use UserActor/SystemActor/OperatorActor/AnonActor") - } - return a.id -} - -// kindString returns the string written into app.actor_kind. -// It must match the values tested by app_issuper() in the SQL migration. -func (a Actor) kindString() string { - switch a.kind { - case actorUser: - return "user" - case actorOperator: - return "operator" - case actorSystem: - return "system" - case actorAnon: - return "anon" - } - panic("database: zero-value Actor; use UserActor/SystemActor/OperatorActor/AnonActor") -} - -// checkGlobal checks a global-scope perm (no specific org). Pure, no DB. -func checkGlobal(actor Actor, perm apipb.Perm) error { - switch actor.kind { - case actorOperator, actorSystem: - return nil - case actorAnon: - if slices.Contains(anonPermissions, perm) { - return nil - } - return ErrUnauthenticated - case actorUser: - if actor.superuser { - return nil - } - if slices.Contains(userGlobalPermissions, perm) { - return nil - } - return ErrPermissionDenied - default: - return ErrPermissionDenied - } -} - -// checkPerm checks an org-scoped perm within an existing transaction. -func checkPerm(ctx context.Context, tx pgx.Tx, actor Actor, orgID OrgID, perm apipb.Perm) error { - switch actor.kind { - case actorOperator, actorSystem: - return nil - case actorAnon: - return ErrUnauthenticated - case actorUser: - if actor.superuser { - return nil - } - var role string - err := tx.QueryRow(ctx, - `SELECT role FROM org_members WHERE org_id = $1 AND user_id = $2`, - orgID, actor.id, - ).Scan(&role) - if err != nil { - return ErrPermissionDenied - } - if !slices.Contains(rolePermissions[role], perm) { - return ErrPermissionDenied - } - return nil - default: - return ErrPermissionDenied - } -} - -// checkSystem checks that actor is the internal system principal. -func checkSystem(actor Actor) error { - if actor.kind == actorSystem { - return nil - } - return ErrPermissionDenied -} - -// checkSelf checks that actor is the target user or superuser. -func checkSelf(actor Actor, targetID UserID) error { - switch actor.kind { - case actorOperator, actorSystem: - return nil - case actorAnon: - return ErrUnauthenticated - case actorUser: - if actor.superuser || targetID == actor.UserID() { - return nil - } - return ErrPermissionDenied - default: - return ErrPermissionDenied - } -} diff --git a/cmd/mirum-server/api_cli.go b/cmd/mirum-server/api_cli.go deleted file mode 100644 --- a/cmd/mirum-server/api_cli.go +++ /dev/null @@ -1,463 +0,0 @@ -// SPDX-FileCopyrightText: 2026 Nikolay Govorov -// SPDX-License-Identifier: AGPL-3.0-or-later - -package main - -// Console CLI is generated from api.proto at startup via protoreflect. -// RPC name is camelCase-split into a cobra path: UserCreate -> "user create", -// OrgMemberAdd -> "org member add". Flags come from request fields, dispatch -// goes through reflect on apipbconnect.ConsoleClient. - -import ( - "context" - "crypto/ed25519" - "crypto/x509" - "encoding/base64" - "fmt" - "os" - "reflect" - "strings" - "unicode" - - "github.com/spf13/cobra" - "github.com/spf13/pflag" - "google.golang.org/protobuf/proto" - "google.golang.org/protobuf/reflect/protoreflect" - "google.golang.org/protobuf/reflect/protoregistry" - "google.golang.org/protobuf/types/known/timestamppb" - - "dimidiumlabs/mirum/cmd/mirum-server/apipb" - "dimidiumlabs/mirum/cmd/mirum-server/apipb/apipbconnect" -) - -// mkClient is called per-invocation so persistent flags (e.g. --socket) are -// already parsed by the time it runs. -func buildConsoleCLI(root *cobra.Command, mkClient func() apipbconnect.ConsoleClient) { - methods := apipb.File_api_proto.Services().ByName("Console").Methods() - for i := 0; i < methods.Len(); i++ { - md := methods.Get(i) - path := splitCamel(string(md.Name())) - parent := ensureGroups(root, path[:len(path)-1]) - parent.AddCommand(buildMethodCmd(md, path[len(path)-1], mkClient)) - } -} - -// "OrgMemberAdd" -> ["org","member","add"]. -func splitCamel(s string) []string { - var parts []string - start := 0 - for i := 1; i < len(s); i++ { - if unicode.IsUpper(rune(s[i])) { - parts = append(parts, strings.ToLower(s[start:i])) - start = i - } - } - return append(parts, strings.ToLower(s[start:])) -} - -func ensureGroups(root *cobra.Command, path []string) *cobra.Command { - parent := root - for _, name := range path { - var next *cobra.Command - for _, c := range parent.Commands() { - if c.Name() == name { - next = c - break - } - } - if next == nil { - next = &cobra.Command{Use: name, Short: "Manage " + name} - parent.AddCommand(next) - } - parent = next - } - return parent -} - -// fieldSetter writes one flag value into the request message. -type fieldSetter func(*pflag.FlagSet, protoreflect.Message) error - -func buildMethodCmd(md protoreflect.MethodDescriptor, leaf string, mkClient func() apipbconnect.ConsoleClient) *cobra.Command { - reqDesc := md.Input() - rpcName := string(md.Name()) - cmd := &cobra.Command{ - Use: leaf, - Short: rpcName, - } - setters := registerRequestFlags(cmd, reqDesc) - cmd.Run = func(c *cobra.Command, _ []string) { - req, err := buildRequest(reqDesc, c.Flags(), setters) - if err != nil { - fmt.Fprintln(os.Stderr, err) - os.Exit(1) - } - resp, err := dispatchConsole(mkClient(), rpcName, req) - if err != nil { - fmt.Fprintln(os.Stderr, err) - os.Exit(1) - } - printResponse(resp) - } - return cmd -} - -func registerRequestFlags(cmd *cobra.Command, desc protoreflect.MessageDescriptor) []fieldSetter { - var setters []fieldSetter - fields := desc.Fields() - for i := 0; i < fields.Len(); i++ { - if s := registerField(cmd, fields.Get(i)); s != nil { - setters = append(setters, s) - } - } - return setters -} - -func registerField(cmd *cobra.Command, fd protoreflect.FieldDescriptor) fieldSetter { - flagName := strings.ReplaceAll(string(fd.Name()), "_", "-") - // Required iff non-optional in the schema; bools default to false, so - // marking them required makes no sense. - required := !fd.HasOptionalKeyword() && fd.Kind() != protoreflect.BoolKind - flags := cmd.Flags() - - markRequired := func() { - if required { - _ = cmd.MarkFlagRequired(flagName) - } - } - - switch fd.Kind() { - case protoreflect.StringKind: - flags.String(flagName, "", string(fd.Name())) - markRequired() - return func(fs *pflag.FlagSet, m protoreflect.Message) error { - v, _ := fs.GetString(flagName) - if v == "" { - return nil - } - m.Set(fd, protoreflect.ValueOfString(v)) - return nil - } - - case protoreflect.BoolKind: - flags.Bool(flagName, false, string(fd.Name())) - return func(fs *pflag.FlagSet, m protoreflect.Message) error { - // Preserve "unset" vs "false" for optional bools. - if fd.HasOptionalKeyword() && !fs.Changed(flagName) { - return nil - } - v, _ := fs.GetBool(flagName) - m.Set(fd, protoreflect.ValueOfBool(v)) - return nil - } - - case protoreflect.BytesKind: - flags.String(flagName, "", string(fd.Name())) - markRequired() - return func(fs *pflag.FlagSet, m protoreflect.Message) error { - v, _ := fs.GetString(flagName) - if v == "" { - return nil - } - b, err := parseBytesFlag(string(fd.Name()), v) - if err != nil { - return fmt.Errorf("--%s: %w", flagName, err) - } - m.Set(fd, protoreflect.ValueOfBytes(b)) - return nil - } - - case protoreflect.EnumKind: - flags.String(flagName, "", string(fd.Name())) - markRequired() - return func(fs *pflag.FlagSet, m protoreflect.Message) error { - v, _ := fs.GetString(flagName) - if v == "" { - return nil - } - n, err := parseEnumFlag(fd.Enum(), v) - if err != nil { - return fmt.Errorf("--%s: %w", flagName, err) - } - m.Set(fd, protoreflect.ValueOfEnum(n)) - return nil - } - - case protoreflect.MessageKind: - return registerMessageField(cmd, fd, flagName, required) - } - - if required { - panic(fmt.Sprintf("consolecli: unhandled required field %s (kind=%s)", fd.FullName(), fd.Kind())) - } - return nil -} - -// Only UserRef/OrgRef are flattened (to the string arm of their oneof); -// PageRequest is skipped; anything else required panics at startup so -// schema changes can't silently send malformed requests. -func registerMessageField(cmd *cobra.Command, fd protoreflect.FieldDescriptor, flagName string, required bool) fieldSetter { - flags := cmd.Flags() - markRequired := func() { - if required { - _ = cmd.MarkFlagRequired(flagName) - } - } - - switch fd.Message().FullName() { - case "mirum.api.UserRef": - flags.String(flagName, "", "user email") - markRequired() - return func(fs *pflag.FlagSet, m protoreflect.Message) error { - v, _ := fs.GetString(flagName) - if v == "" { - return nil - } - ref := &apipb.UserRef{Ref: &apipb.UserRef_Email{Email: v}} - m.Set(fd, protoreflect.ValueOfMessage(ref.ProtoReflect())) - return nil - } - - case "mirum.api.OrgRef": - flags.String(flagName, "", "org slug") - markRequired() - return func(fs *pflag.FlagSet, m protoreflect.Message) error { - v, _ := fs.GetString(flagName) - if v == "" { - return nil - } - ref := &apipb.OrgRef{Ref: &apipb.OrgRef_Slug{Slug: v}} - m.Set(fd, protoreflect.ValueOfMessage(ref.ProtoReflect())) - return nil - } - - case "mirum.api.PageRequest": - return nil - - case "mirum.api.Locale": - langFlag := flagName + "-language" - dfFlag := flagName + "-date-format" - flags.String(langFlag, "", "locale language (e.g. en)") - flags.String(dfFlag, "", "date format (DMY, MDY, YMD)") - return func(fs *pflag.FlagSet, m protoreflect.Message) error { - lang, _ := fs.GetString(langFlag) - dfStr, _ := fs.GetString(dfFlag) - if lang == "" && dfStr == "" { - return nil - } - loc := &apipb.Locale{} - if lang != "" { - loc.Language = &lang - } - if dfStr != "" { - want := strings.ToUpper(dfStr) - enumVals := loc.ProtoReflect().Descriptor().Fields().ByName("date_format").Enum().Values() - var n protoreflect.EnumNumber - var found bool - for i := 0; i < enumVals.Len(); i++ { - ev := enumVals.Get(i) - name := string(ev.Name()) - if idx := strings.LastIndexByte(name, '_'); idx >= 0 && strings.ToUpper(name[idx+1:]) == want { - n = ev.Number() - found = true - break - } - } - if !found { - return fmt.Errorf("--%s: unknown value %q (valid: DMY, MDY, YMD)", dfFlag, dfStr) - } - df := apipb.DateFormat(n) - loc.DateFormat = &df - } - m.Set(fd, protoreflect.ValueOfMessage(loc.ProtoReflect())) - return nil - } - } - - if required { - panic(fmt.Sprintf("consolecli: unhandled required message field %s (type=%s)", fd.FullName(), fd.Message().FullName())) - } - return nil -} - -// Console schema uses bytes only for UUIDs (id / *_id) and ed25519 PKIX keys. -func parseBytesFlag(fieldName, v string) ([]byte, error) { - switch { - case fieldName == "id" || strings.HasSuffix(fieldName, "_id"): - id, err := ParseAnyID(v) - if err != nil { - return nil, fmt.Errorf("invalid id: %w", err) - } - return id[:], nil - case strings.Contains(fieldName, "key"): - der, err := base64.StdEncoding.DecodeString(v) - if err != nil { - return nil, fmt.Errorf("invalid base64: %w", err) - } - pub, err := x509.ParsePKIXPublicKey(der) - if err != nil { - return nil, fmt.Errorf("invalid public key: %w", err) - } - ed, ok := pub.(ed25519.PublicKey) - if !ok { - return nil, fmt.Errorf("not an ed25519 key") - } - return ed, nil - } - return nil, fmt.Errorf("unsupported bytes field %q", fieldName) -} - -// Accepts both short ("admin") and full ("ROLE_ADMIN") forms. -func parseEnumFlag(ed protoreflect.EnumDescriptor, v string) (protoreflect.EnumNumber, error) { - want := strings.ToUpper(v) - values := ed.Values() - if ev := values.ByName(protoreflect.Name(want)); ev != nil { - return ev.Number(), nil - } - prefix := strings.ToUpper(string(ed.Name())) + "_" - if ev := values.ByName(protoreflect.Name(prefix + want)); ev != nil { - return ev.Number(), nil - } - return 0, fmt.Errorf("unknown %s value %q", ed.Name(), v) -} - -func buildRequest(desc protoreflect.MessageDescriptor, flags *pflag.FlagSet, setters []fieldSetter) (proto.Message, error) { - mt, err := protoregistry.GlobalTypes.FindMessageByName(desc.FullName()) - if err != nil { - return nil, fmt.Errorf("find message %s: %w", desc.FullName(), err) - } - m := mt.New() - for _, set := range setters { - if err := set(flags, m); err != nil { - return nil, err - } - } - return m.Interface(), nil -} - -// reflect.New on *connect.Request[T] is equivalent to connect.NewRequest(req): -// Msg is the only public field, the rest are initialised lazily at send time. -func dispatchConsole(client apipbconnect.ConsoleClient, name string, req proto.Message) (proto.Message, error) { - cv := reflect.ValueOf(client) - method := cv.MethodByName(name) - if !method.IsValid() { - return nil, fmt.Errorf("unknown console method %q", name) - } - // method signature: - // func(context.Context, *connect.Request[T]) (*connect.Response[U], error) - reqPtrType := method.Type().In(1) // *connect.Request[T] - reqWrap := reflect.New(reqPtrType.Elem()) - reqWrap.Elem().FieldByName("Msg").Set(reflect.ValueOf(req)) - - out := method.Call([]reflect.Value{ - reflect.ValueOf(context.Background()), - reqWrap, - }) - if errV := out[1]; !errV.IsNil() { - return nil, errV.Interface().(error) - } - return out[0].Elem().FieldByName("Msg").Interface().(proto.Message), nil -} - -// Shape-driven printer: empty → "ok", single bytes id → UUID, single message -// → TSV of scalars, repeated → one TSV line per element, anything else → TSV -// of top-level scalars. PageResponse metadata is ignored. -func printResponse(resp proto.Message) { - m := resp.ProtoReflect() - fields := m.Descriptor().Fields() - - var meaningful []protoreflect.FieldDescriptor - for i := 0; i < fields.Len(); i++ { - f := fields.Get(i) - if f.Kind() == protoreflect.MessageKind && f.Message().FullName() == "mirum.api.PageResponse" { - continue - } - meaningful = append(meaningful, f) - } - - if len(meaningful) == 0 { - fmt.Println("ok") - return - } - if len(meaningful) == 1 { - f := meaningful[0] - v := m.Get(f) - switch { - case f.IsList(): - list := v.List() - for j := 0; j < list.Len(); j++ { - item := list.Get(j) - if f.Kind() == protoreflect.MessageKind { - fmt.Println(formatMessageTSV(item.Message())) - } else { - fmt.Println(formatScalar(f, item)) - } - } - case f.Kind() == protoreflect.MessageKind: - fmt.Println(formatMessageTSV(v.Message())) - default: - fmt.Println(formatScalar(f, v)) - } - return - } - fmt.Println(formatMessageTSV(m)) -} - -func formatMessageTSV(m protoreflect.Message) string { - var parts []string - fields := m.Descriptor().Fields() - for i := 0; i < fields.Len(); i++ { - f := fields.Get(i) - if f.IsList() || f.IsMap() { - continue - } - if f.HasOptionalKeyword() && !m.Has(f) { - continue - } - if f.Kind() == protoreflect.MessageKind { - if f.Message().FullName() == "google.protobuf.Timestamp" { - ts := m.Get(f).Message().Interface().(*timestamppb.Timestamp) - parts = append(parts, ts.AsTime().Format("2006-01-02")) - continue - } - nested := m.Get(f).Message() - if nested.IsValid() { - parts = append(parts, formatMessageTSV(nested)) - } - continue - } - parts = append(parts, formatScalar(f, m.Get(f))) - } - return strings.Join(parts, "\t") -} - -func formatScalar(fd protoreflect.FieldDescriptor, v protoreflect.Value) string { - switch fd.Kind() { - case protoreflect.StringKind: - return v.String() - case protoreflect.BoolKind: - if v.Bool() { - return "true" - } - return "false" - case protoreflect.BytesKind: - return formatBytes(v.Bytes()) - case protoreflect.EnumKind: - ev := fd.Enum().Values().ByNumber(v.Enum()) - if ev == nil { - return fmt.Sprintf("%d", v.Enum()) - } - name := string(ev.Name()) - if idx := strings.IndexByte(name, '_'); idx >= 0 { - name = name[idx+1:] - } - return strings.ToLower(name) - } - return v.String() -} - -func formatBytes(b []byte) string { - if len(b) == 16 { - return FormatAnyID(b) - } - return base64.StdEncoding.EncodeToString(b) -} diff --git a/cmd/mirum-server/cert.go b/cmd/mirum-server/cert.go deleted file mode 100644 --- a/cmd/mirum-server/cert.go +++ /dev/null @@ -1,67 +0,0 @@ -// SPDX-FileCopyrightText: 2026 Nikolay Govorov -// SPDX-License-Identifier: AGPL-3.0-or-later - -package main - -import ( - "crypto/tls" - "log/slog" - "os" - "sync" - "time" -) - -// certReloader serves a TLS cert/key pair and reloads it when either file -// changes on disk (e.g. after a letsencrypt renewal). -type certReloader struct { - certFile string - keyFile string - - mu sync.Mutex - cert *tls.Certificate - certMod time.Time - keyMod time.Time -} - -func newCertReloader(certFile, keyFile string) *certReloader { - r := &certReloader{certFile: certFile, keyFile: keyFile} - if _, err := r.GetCertificate(nil); err != nil { - slog.Warn("initial cert load failed", "cert", certFile, "err", err) - } - return r -} - -// GetCertificate plugs into tls.Config.GetCertificate. On a transient -// reload error it returns the last good pair so a mid-renewal race -// (cert swapped but key still being written) doesn't break handshakes. -func (r *certReloader) GetCertificate(*tls.ClientHelloInfo) (*tls.Certificate, error) { - cs, cerr := os.Stat(r.certFile) - ks, kerr := os.Stat(r.keyFile) - - r.mu.Lock() - defer r.mu.Unlock() - - if cerr == nil && kerr == nil && r.cert != nil && - cs.ModTime().Equal(r.certMod) && ks.ModTime().Equal(r.keyMod) { - return r.cert, nil - } - - cert, err := tls.LoadX509KeyPair(r.certFile, r.keyFile) - if err != nil { - if r.cert != nil { - slog.Warn("cert reload failed, serving cached", "cert", r.certFile, "err", err) - return r.cert, nil - } - return nil, err - } - - r.cert = &cert - if cerr == nil { - r.certMod = cs.ModTime() - } - if kerr == nil { - r.keyMod = ks.ModTime() - } - slog.Info("cert loaded", "cert", r.certFile) - return r.cert, nil -} diff --git a/cmd/mirum-server/cert_test.go b/cmd/mirum-server/cert_test.go deleted file mode 100644 --- a/cmd/mirum-server/cert_test.go +++ /dev/null @@ -1,142 +0,0 @@ -// SPDX-FileCopyrightText: 2026 Nikolay Govorov -// SPDX-License-Identifier: AGPL-3.0-or-later - -package main - -import ( - "crypto/ed25519" - "crypto/rand" - "crypto/x509" - "encoding/pem" - "math/big" - "os" - "path/filepath" - "testing" - "time" -) - -func writeTestPair(t *testing.T, certPath, keyPath string) { - t.Helper() - - pub, priv, err := ed25519.GenerateKey(rand.Reader) - if err != nil { - t.Fatal(err) - } - - serial, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128)) - if err != nil { - t.Fatal(err) - } - - der, err := x509.CreateCertificate(rand.Reader, &x509.Certificate{ - SerialNumber: serial, - NotBefore: time.Now(), - NotAfter: time.Now().Add(time.Hour), - }, &x509.Certificate{SerialNumber: serial}, pub, priv) - if err != nil { - t.Fatal(err) - } - - if err := os.WriteFile(certPath, - pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der}), - 0o644); err != nil { - t.Fatal(err) - } - - keyDER, err := x509.MarshalPKCS8PrivateKey(priv) - if err != nil { - t.Fatal(err) - } - if err := os.WriteFile(keyPath, - pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: keyDER}), - 0o600); err != nil { - t.Fatal(err) - } -} - -func TestCertReloader_Cached(t *testing.T) { - dir := t.TempDir() - certPath := filepath.Join(dir, "cert.pem") - keyPath := filepath.Join(dir, "key.pem") - writeTestPair(t, certPath, keyPath) - - r := newCertReloader(certPath, keyPath) - - first, err := r.GetCertificate(nil) - if err != nil { - t.Fatal(err) - } - second, err := r.GetCertificate(nil) - if err != nil { - t.Fatal(err) - } - if first != second { - t.Fatal("expected same pointer on cache hit") - } -} - -func TestCertReloader_ReloadsOnMtimeChange(t *testing.T) { - dir := t.TempDir() - certPath := filepath.Join(dir, "cert.pem") - keyPath := filepath.Join(dir, "key.pem") - writeTestPair(t, certPath, keyPath) - - r := newCertReloader(certPath, keyPath) - first, err := r.GetCertificate(nil) - if err != nil { - t.Fatal(err) - } - - writeTestPair(t, certPath, keyPath) - future := time.Now().Add(time.Second) - if err := os.Chtimes(certPath, future, future); err != nil { - t.Fatal(err) - } - if err := os.Chtimes(keyPath, future, future); err != nil { - t.Fatal(err) - } - - second, err := r.GetCertificate(nil) - if err != nil { - t.Fatal(err) - } - if first == second { - t.Fatal("expected new pointer after mtime change") - } -} - -func TestCertReloader_FallbackOnReloadError(t *testing.T) { - dir := t.TempDir() - certPath := filepath.Join(dir, "cert.pem") - keyPath := filepath.Join(dir, "key.pem") - writeTestPair(t, certPath, keyPath) - - r := newCertReloader(certPath, keyPath) - good, err := r.GetCertificate(nil) - if err != nil { - t.Fatal(err) - } - - if err := os.WriteFile(certPath, []byte("garbage"), 0o644); err != nil { - t.Fatal(err) - } - future := time.Now().Add(time.Second) - if err := os.Chtimes(certPath, future, future); err != nil { - t.Fatal(err) - } - - fallback, err := r.GetCertificate(nil) - if err != nil { - t.Fatalf("expected last-good fallback, got error: %v", err) - } - if fallback != good { - t.Fatal("expected cached cert on corrupted file") - } -} - -func TestCertReloader_ErrorOnFirstLoad(t *testing.T) { - r := newCertReloader("/nonexistent/cert.pem", "/nonexistent/key.pem") - if _, err := r.GetCertificate(nil); err == nil { - t.Fatal("expected error on missing files") - } -} diff --git a/cmd/mirum-server/config.go b/cmd/mirum-server/config.go deleted file mode 100644 --- a/cmd/mirum-server/config.go +++ /dev/null @@ -1,64 +0,0 @@ -// SPDX-FileCopyrightText: 2026 Nikolay Govorov -// SPDX-License-Identifier: AGPL-3.0-or-later - -package main - -import ( - "fmt" - "os" - - "gopkg.in/yaml.v3" -) - -type tlsConfig struct { - Cert string `yaml:"cert"` - Key string `yaml:"key"` -} - -type appConfig struct { - WebAddr string `yaml:"web_addr"` - GrpcAddr string `yaml:"grpc_addr"` - AdminSocket string `yaml:"admin_socket"` - DatabaseUri string `yaml:"database_uri"` - Pepper string `yaml:"pepper"` - - GrpcTls tlsConfig `yaml:"grpc_tls"` - WebTls *tlsConfig `yaml:"web_tls"` // optional - - TrustedProxies []string `yaml:"trusted_proxies"` // CIDR list, empty = trust RemoteAddr only - - GitHubToken string `yaml:"token"` - WebhookSecret string `yaml:"webhook_secret"` -} - -func getConfig(filename string) (*appConfig, error) { - cfg := &appConfig{ - GrpcAddr: ":2026", - WebAddr: ":3000", - AdminSocket: "/run/mirum-server/admin.sock", - } - - data, err := os.ReadFile(filename) - if err != nil { - return nil, err - } - - if err := yaml.Unmarshal(data, &cfg); err != nil { - return nil, err - } - - if cfg.DatabaseUri == "" { - return nil, fmt.Errorf("error: database_uri is required") - } - if cfg.WebhookSecret == "" { - return nil, fmt.Errorf("error: webhook_secret is required") - } - if cfg.Pepper == "" { - return nil, fmt.Errorf("error: pepper is required") - } - if cfg.GrpcTls.Cert == "" || cfg.GrpcTls.Key == "" { - return nil, fmt.Errorf("error: grpc_tls.cert and grpc_tls.key are required") - } - - return cfg, nil -} diff --git a/cmd/mirum-server/database.go b/cmd/mirum-server/database.go deleted file mode 100644 --- a/cmd/mirum-server/database.go +++ /dev/null @@ -1,1562 +0,0 @@ -// SPDX-FileCopyrightText: 2026 Nikolay Govorov -// SPDX-License-Identifier: AGPL-3.0-or-later - -package main - -import ( - "context" - "crypto/hmac" - "crypto/rand" - "crypto/sha256" - "crypto/subtle" - "encoding/base64" - "encoding/hex" - "errors" - "fmt" - "log/slog" - "net/mail" - "regexp" - "strings" - "time" - - "dimidiumlabs/mirum/cmd/mirum-server/apipb" - "dimidiumlabs/mirum/internal/config" - - sb "github.com/huandu/go-sqlbuilder" - "github.com/jackc/pgerrcode" - "github.com/jackc/pgx/v5" - "github.com/jackc/pgx/v5/pgconn" - "github.com/jackc/pgx/v5/pgxpool" - "github.com/jackc/tern/v2/migrate" - "golang.org/x/crypto/argon2" -) - -var ( - ErrAcquire = errors.New("database: failed to acquire connection") - ErrAlreadyMember = errors.New("database: already a member") - ErrEmailTaken = errors.New("database: email already taken") - ErrInvalidCreds = errors.New("database: invalid credentials") - ErrInvalidEmail = errors.New("database: invalid email") - ErrInvalidRole = errors.New("database: invalid role") - ErrInvalidSlug = errors.New("database: invalid slug") - ErrInvalidDateFormat = errors.New("database: invalid date format") - ErrInvalidTimezone = errors.New("database: invalid timezone") - ErrLastOwner = errors.New("database: last owner") - ErrMigrate = errors.New("database: failed to create migrator") - ErrNotImplemented = errors.New("database: filter not implemented") - ErrNotMember = errors.New("database: not a member") - ErrOpen = errors.New("database: failed to open") - ErrOrgNotFound = errors.New("database: organization not found") - ErrPing = errors.New("database: failed to ping") - ErrReservedEmail = errors.New("database: email uses a reserved domain") - ErrSlugTaken = errors.New("database: slug already taken") - ErrSoleOwner = errors.New("database: sole owner of an organization") - ErrUserNotFound = errors.New("database: user not found") - ErrWorkerNotFound = errors.New("database: worker not found") -) - -// reservedEmailSuffix is the domain carved out for synthetic actors -// (system/operator/anon). Real users cannot register with this suffix. -const reservedEmailSuffix = "@mirum.local" - -const ( - saltLen = 16 - - argonTime = 3 - argonMemory = 64 * 1024 // 64 MB - argonKeyLen = 32 - argonThreads = 2 -) - -var slugRe = regexp.MustCompile(`^[a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)*$`) - -// UserRef identifies a user by ID or email. -type UserRef struct { - id UserID - email string -} - -func UserByID(id UserID) UserRef { return UserRef{id: id} } - -func UserByEmail(email string) UserRef { return UserRef{email: email} } - -func (r UserRef) where() (string, any) { - if !r.id.IsZero() { - return "id", r.id - } - return "email", r.email -} - -// OrgRef identifies an organization by ID or slug. -type OrgRef struct { - id OrgID - slug string -} - -func OrgByID(id OrgID) OrgRef { return OrgRef{id: id} } - -func OrgBySlug(slug string) OrgRef { return OrgRef{slug: slug} } - -func (r OrgRef) IsZero() bool { return r.id.IsZero() && r.slug == "" } - -func (r OrgRef) where() (string, any) { - if !r.id.IsZero() { - return "id", r.id - } - return "slug", r.slug -} - -// DB wraps a pgx connection pool. -type DB struct { - Pool *pgxpool.Pool -} - -type DateFormat int - -const ( - DateFormatDMY DateFormat = 1 - DateFormatMDY DateFormat = 2 - DateFormatYMD DateFormat = 3 -) - -type Locale struct { - Language *string - DateFormat *DateFormat -} - -// User holds info about a user. -type User struct { - ID UserID - Email string - CreatedAt time.Time - Locale *Locale - Timezone *string -} - -// Organization holds info about an organization. -type Organization struct { - ID OrgID - Name string - Slug string - Public bool - CreatedAt time.Time -} - -// OrgMember pairs a user with their role in an organization. -type OrgMember struct { - User User - Role string - JoinedAt time.Time -} - -// Worker holds info about a registered worker. -type Worker struct { - ID WorkerID - OrgID *OrgID - PublicKey []byte - CreatedAt time.Time -} - -// DatabaseOpen connects to PostgreSQL and returns a DB. -func DatabaseOpen(ctx context.Context, dsn string) (*DB, error) { - pool, err := pgxpool.New(ctx, dsn) - if err != nil { - return nil, errors.Join(ErrOpen, err) - } - - if err := pool.Ping(ctx); err != nil { - pool.Close() - return nil, errors.Join(ErrPing, err) - } - - return &DB{Pool: pool}, nil -} - -// Close closes the connection pool. -func (db *DB) Close() { - db.Pool.Close() -} - -// apicall starts a transaction and sets the RLS actor. Both app.user_id -// and app.actor_kind are populated: app_issuper() checks actor_kind for -// System/Operator principals, and app.user_id for real user superusers. -func (db *DB) apicall(ctx context.Context, actor Actor, access, validate, doit func(pgx.Tx) error) error { - tx, err := db.Pool.Begin(ctx) - if err != nil { - return err - } - defer func() { - if err := tx.Rollback(ctx); err != nil && !errors.Is(err, pgx.ErrTxClosed) { - slog.Error("rollback failed", "err", err) - } - }() - - if _, err := tx.Exec(ctx, - `SELECT set_config('app.user_id', $1, true), - set_config('app.actor_kind', $2, true)`, - actor.dbID().String(), actor.kindString(), - ); err != nil { - return err - } - - if err := access(tx); err != nil { - slog.Debug("access denied", "err", err) - return err - } - if validate != nil { - if err := validate(tx); err != nil { - slog.Debug("validation failed", "err", err) - return err - } - } - if err := doit(tx); err != nil { - slog.Debug("exec failed", "err", err) - return err - } - - return tx.Commit(ctx) -} - -// Migrate applies all pending migrations. -func (db *DB) Migrate(ctx context.Context) error { - conn, err := db.Pool.Acquire(ctx) - if err != nil { - return errors.Join(ErrAcquire, err) - } - defer conn.Release() - - migrator, err := migrate.NewMigrator(ctx, conn.Conn(), "schema_version") - if err != nil { - return errors.Join(ErrMigrate, err) - } - - migrator.AppendMigration("create_users", ` - CREATE TYPE locale_settings AS ( - language TEXT, - date_format INTEGER - ); - - CREATE TABLE users ( - id UUID PRIMARY KEY DEFAULT uuidv7(), - email TEXT NOT NULL UNIQUE, - password TEXT NOT NULL, - superuser BOOLEAN NOT NULL DEFAULT false, - created_at TIMESTAMPTZ NOT NULL DEFAULT now(), - deleted_at TIMESTAMPTZ, - - locale locale_settings, - timezone TEXT - ); - - CREATE FUNCTION app_user_id() RETURNS uuid STABLE AS $$ - SELECT current_setting('app.user_id', true)::uuid; - $$ LANGUAGE sql; - - -- app_issuper has two independent branches: - -- (a) runtime setting app.actor_kind is 'system' or 'operator' — - -- set only by apicall from Go for synthetic principals and by - -- DML migrations; cannot be injected via login since there is - -- no matching users row to authenticate against. - -- (b) the current app.user_id resolves to a users row with - -- superuser = true — real support-agent style superusers. - CREATE FUNCTION app_issuper() RETURNS boolean STABLE AS $$ - SELECT - current_setting('app.actor_kind', true) IN ('system', 'operator') - OR EXISTS ( - SELECT 1 FROM users - WHERE id = current_setting('app.user_id', true)::uuid - AND superuser = true - ); - $$ LANGUAGE sql; - `, ` - DROP TYPE locale_settings; - DROP TYPE date_format_t; - DROP FUNCTION app_issuper; - DROP FUNCTION app_user_id; - DROP TABLE users; - `) - - migrator.AppendMigration("create_sessions", ` - CREATE TABLE sessions ( - token TEXT PRIMARY KEY, - user_id UUID NOT NULL REFERENCES users(id), - created_at TIMESTAMPTZ NOT NULL DEFAULT now(), - expires_at TIMESTAMPTZ NOT NULL - ); - CREATE INDEX sessions_expires_at ON sessions (expires_at); - `, ` - DROP TABLE sessions; - `) - - migrator.AppendMigration("create_organizations", ` - CREATE TABLE organizations ( - id UUID PRIMARY KEY DEFAULT uuidv7(), - name TEXT NOT NULL, - slug TEXT NOT NULL UNIQUE, - public BOOLEAN NOT NULL DEFAULT false, - created_at TIMESTAMPTZ NOT NULL DEFAULT now(), - deleted_at TIMESTAMPTZ - ); - `, ` - DROP TABLE organizations; - `) - - migrator.AppendMigration("create_org_members", ` - CREATE TABLE org_members ( - org_id UUID NOT NULL REFERENCES organizations(id), - user_id UUID NOT NULL REFERENCES users(id), - role TEXT NOT NULL DEFAULT 'member' CHECK (role IN ('owner', 'admin', 'member')), - created_at TIMESTAMPTZ NOT NULL DEFAULT now(), - PRIMARY KEY (org_id, user_id) - ); - CREATE INDEX org_members_user_id ON org_members (user_id); - - CREATE FUNCTION is_member(org uuid) RETURNS boolean STABLE AS $$ - SELECT EXISTS ( - SELECT 1 FROM org_members - WHERE org_id = org AND user_id = app_user_id() - ); - $$ LANGUAGE sql; - - CREATE FUNCTION is_authenticated() RETURNS boolean STABLE AS $$ - SELECT current_setting('app.actor_kind', true) NOT IN ('', 'anon'); - $$ LANGUAGE sql; - `, ` - DROP FUNCTION is_authenticated; - DROP FUNCTION is_member; - DROP TABLE org_members; - `) - - migrator.AppendMigration("create_workers", ` - CREATE TABLE workers ( - id UUID PRIMARY KEY DEFAULT uuidv7(), - org_id UUID REFERENCES organizations(id), - public_key BYTEA NOT NULL UNIQUE, - created_at TIMESTAMPTZ NOT NULL DEFAULT now(), - revoked_at TIMESTAMPTZ - ); - `, ` - DROP TABLE workers - `) - - migrator.AppendMigration("rls_users", ` - ALTER TABLE users ENABLE ROW LEVEL SECURITY; - ALTER TABLE users FORCE ROW LEVEL SECURITY; - - CREATE POLICY superuser ON users FOR ALL USING (app_issuper()); - CREATE POLICY self ON users FOR ALL USING (id = app_user_id()); - CREATE POLICY shared_org ON users FOR SELECT USING (EXISTS ( - SELECT 1 FROM org_members target - JOIN org_members mine ON mine.org_id = target.org_id - WHERE target.user_id = users.id - AND mine.user_id = app_user_id() - )); - CREATE POLICY write_auth ON users AS RESTRICTIVE FOR INSERT WITH CHECK (is_authenticated()); - CREATE POLICY update_auth ON users AS RESTRICTIVE FOR UPDATE USING (is_authenticated()); - CREATE POLICY delete_auth ON users AS RESTRICTIVE FOR DELETE USING (is_authenticated()); - `, ` - DROP POLICY write_auth ON users; - DROP POLICY update_auth ON users; - DROP POLICY delete_auth ON users; - DROP POLICY superuser ON users; - DROP POLICY self ON users; - DROP POLICY shared_org ON users; - - ALTER TABLE users DISABLE ROW LEVEL SECURITY; - `) - - migrator.AppendMigration("rls_sessions", ` - ALTER TABLE sessions ENABLE ROW LEVEL SECURITY; - ALTER TABLE sessions FORCE ROW LEVEL SECURITY; - - CREATE POLICY superuser ON sessions FOR ALL USING (app_issuper()); - CREATE POLICY own_sessions ON sessions FOR ALL USING (user_id = app_user_id()); - CREATE POLICY write_auth ON sessions AS RESTRICTIVE FOR INSERT WITH CHECK (is_authenticated()); - CREATE POLICY update_auth ON sessions AS RESTRICTIVE FOR UPDATE USING (is_authenticated()); - CREATE POLICY delete_auth ON sessions AS RESTRICTIVE FOR DELETE USING (is_authenticated()); - `, ` - DROP POLICY write_auth ON sessions; - DROP POLICY update_auth ON sessions; - DROP POLICY delete_auth ON sessions; - DROP POLICY superuser ON sessions; - DROP POLICY own_sessions ON sessions; - - ALTER TABLE sessions DISABLE ROW LEVEL SECURITY; - `) - - migrator.AppendMigration("rls_organizations", ` - ALTER TABLE organizations ENABLE ROW LEVEL SECURITY; - ALTER TABLE organizations FORCE ROW LEVEL SECURITY; - - CREATE POLICY superuser ON organizations FOR ALL USING (app_issuper()); - CREATE POLICY public_org ON organizations FOR ALL USING (public); - CREATE POLICY member_org ON organizations FOR ALL USING (is_member(id)); - CREATE POLICY write_auth ON organizations AS RESTRICTIVE FOR INSERT WITH CHECK (is_authenticated()); - CREATE POLICY update_auth ON organizations AS RESTRICTIVE FOR UPDATE USING (is_authenticated()); - CREATE POLICY delete_auth ON organizations AS RESTRICTIVE FOR DELETE USING (is_authenticated()); - `, ` - DROP POLICY write_auth ON organizations; - DROP POLICY update_auth ON organizations; - DROP POLICY delete_auth ON organizations; - DROP POLICY superuser ON organizations; - DROP POLICY public_org ON organizations; - DROP POLICY member_org ON organizations; - - ALTER TABLE organizations DISABLE ROW LEVEL SECURITY; - `) - - migrator.AppendMigration("rls_org_members", ` - ALTER TABLE org_members ENABLE ROW LEVEL SECURITY; - ALTER TABLE org_members FORCE ROW LEVEL SECURITY; - - CREATE POLICY superuser ON org_members FOR ALL USING (app_issuper()); - - -- Self path (own membership rows) uses a pure column predicate - -- so is_member's inner query can resolve without recursion. - CREATE POLICY self_member ON org_members FOR ALL USING (user_id = app_user_id()); - CREATE POLICY org_member ON org_members FOR ALL USING (is_member(org_id)); - CREATE POLICY write_auth ON org_members AS RESTRICTIVE FOR INSERT WITH CHECK (is_authenticated()); - CREATE POLICY update_auth ON org_members AS RESTRICTIVE FOR UPDATE USING (is_authenticated()); - CREATE POLICY delete_auth ON org_members AS RESTRICTIVE FOR DELETE USING (is_authenticated()); - `, ` - DROP POLICY write_auth ON org_members; - DROP POLICY update_auth ON org_members; - DROP POLICY delete_auth ON org_members; - DROP POLICY superuser ON org_members; - DROP POLICY self_member ON org_members; - DROP POLICY org_member ON org_members; - - ALTER TABLE org_members DISABLE ROW LEVEL SECURITY; - `) - - migrator.AppendMigration("rls_workers", ` - ALTER TABLE workers ENABLE ROW LEVEL SECURITY; - ALTER TABLE workers FORCE ROW LEVEL SECURITY; - - CREATE POLICY superuser ON workers FOR ALL USING (app_issuper()); - CREATE POLICY org_worker ON workers FOR ALL USING ( - org_id IS NOT NULL AND is_member(org_id) - ); - CREATE POLICY write_auth ON workers AS RESTRICTIVE FOR INSERT WITH CHECK (is_authenticated()); - CREATE POLICY update_auth ON workers AS RESTRICTIVE FOR UPDATE USING (is_authenticated()); - CREATE POLICY delete_auth ON workers AS RESTRICTIVE FOR DELETE USING (is_authenticated()); - `, ` - DROP POLICY write_auth ON workers; - DROP POLICY update_auth ON workers; - DROP POLICY delete_auth ON workers; - DROP POLICY superuser ON workers; - DROP POLICY org_worker ON workers; - - ALTER TABLE workers DISABLE ROW LEVEL SECURITY; - `) - - return migrator.Migrate(ctx) -} - -// UserCreate hashes the password with argon2id and inserts a new user. -// The pepper is a server-side secret not stored in the database. -func (db *DB) UserCreate(ctx context.Context, actor Actor, email, password string, pepper []byte) (UserID, error) { - var id UserID - err := db.apicall( - ctx, actor, - func(tx pgx.Tx) error { - if !actor.IsSuperuser() { - return ErrPermissionDenied - } - - return nil - }, - func(tx pgx.Tx) error { - if strings.HasSuffix(strings.ToLower(email), reservedEmailSuffix) { - return ErrReservedEmail - } - - return nil - }, - func(tx pgx.Tx) error { - hash, err := hashPassword(password, pepper) - if err != nil { - return err - } - - if err := tx.QueryRow(ctx, - `INSERT INTO users (email, password) VALUES ($1, $2) RETURNING id`, - email, hash, - ).Scan(&id); err != nil { - var pgErr *pgconn.PgError - if errors.As(err, &pgErr) && pgErr.Code == pgerrcode.UniqueViolation { - return ErrEmailTaken - } - - return err - } - return nil - }, - ) - return id, err -} - -// UserGet returns a user by ref (ID or email). -func (db *DB) UserGet(ctx context.Context, actor Actor, ref UserRef) (*User, error) { - var u User - err := db.apicall( - ctx, actor, - func(tx pgx.Tx) error { return checkGlobal(actor, apipb.Perm_PERM_USER_READ) }, - nil, - func(tx pgx.Tx) error { - col, val := ref.where() - - q := sb.PostgreSQL.NewSelectBuilder() - sql, args := q.Select("id", "email", "created_at", - "(locale).language", "(locale).date_format", "timezone"). - From("users"). - Where(q.Equal(col, val), q.IsNull("deleted_at")). - Build() - - u.Locale = &Locale{} - if err := tx.QueryRow(ctx, sql, args...).Scan( - &u.ID, &u.Email, &u.CreatedAt, &u.Locale.Language, &u.Locale.DateFormat, &u.Timezone, - ); err != nil { - if errors.Is(err, pgx.ErrNoRows) { - return ErrUserNotFound - } - return err - } - - return nil - }, - ) - return &u, err -} - -// UserList returns a page of users and the total count. -func (db *DB) UserList(ctx context.Context, actor Actor, cursor UserID, limit int, filter string) ([]User, int, error) { - var users []User - var total int - err := db.apicall(ctx, actor, - func(tx pgx.Tx) error { return checkGlobal(actor, apipb.Perm_PERM_USER_READ) }, - func(tx pgx.Tx) error { - if filter != "" { - return ErrNotImplemented - } - return nil - }, - func(tx pgx.Tx) error { - if err := tx.QueryRow(ctx, - `SELECT count(*) FROM users WHERE deleted_at IS NULL`, - ).Scan(&total); err != nil { - return err - } - - q := sb.PostgreSQL.NewSelectBuilder() - q.Select("id", "email", "created_at", - "(locale).language", "(locale).date_format", "timezone"). - From("users"). - Where(q.IsNull("deleted_at")). - OrderBy("id"). - Limit(limit) - if !cursor.IsZero() { - q.Where(q.GreaterThan("id", cursor)) - } - - sql, args := q.Build() - rows, err := tx.Query(ctx, sql, args...) - if err != nil { - return err - } - defer rows.Close() - - for rows.Next() { - var u User - u.Locale = &Locale{} - if err := rows.Scan(&u.ID, &u.Email, &u.CreatedAt, &u.Locale.Language, &u.Locale.DateFormat, &u.Timezone); err != nil { - return err - } - users = append(users, u) - } - return rows.Err() - }, - ) - return users, total, err -} - -type UserUpdateParams struct { - Email *string - Password *string - Locale *Locale - Timezone *string -} - -// UserUpdate updates a user's email and/or password. -// Invalidates all sessions when password changes. -func (db *DB) UserUpdate(ctx context.Context, actor Actor, ref UserRef, p UserUpdateParams, pepper []byte) error { - var id UserID - return db.apicall(ctx, actor, - func(tx pgx.Tx) error { - var err error - id, err = resolveUser(ctx, tx, ref) - if err != nil { - return err - } - return checkSelf(actor, id) - }, - func(tx pgx.Tx) error { - if p.Email != nil && strings.HasSuffix(strings.ToLower(*p.Email), reservedEmailSuffix) { - return ErrReservedEmail - } - if p.Timezone != nil { - if _, err := time.LoadLocation(*p.Timezone); err != nil { - return ErrInvalidTimezone - } - } - return nil - }, - func(tx pgx.Tx) error { - ub := sb.PostgreSQL.NewUpdateBuilder() - ub.Update("users") - - var hasSet bool - if p.Email != nil { - ub.SetMore(ub.Assign("email", *p.Email)) - hasSet = true - } - if p.Password != nil { - hash, err := hashPassword(*p.Password, pepper) - if err != nil { - return err - } - ub.SetMore(ub.Assign("password", hash)) - hasSet = true - } - if p.Locale != nil { - ub.SetMore(fmt.Sprintf( - "locale = ROW(COALESCE(%s, (locale).language), COALESCE(%s, (locale).date_format))::locale_settings", - ub.Var(p.Locale.Language), ub.Var(p.Locale.DateFormat), - )) - hasSet = true - } - if p.Timezone != nil { - ub.SetMore(ub.Assign("timezone", *p.Timezone)) - hasSet = true - } - if !hasSet { - return nil - } - ub.Where(ub.Equal("id", id)) - - sql, args := ub.Build() - if _, err := tx.Exec(ctx, sql, args...); err != nil { - var pgErr *pgconn.PgError - if errors.As(err, &pgErr) && pgErr.Code == pgerrcode.UniqueViolation { - return ErrEmailTaken - } - return err - } - - if p.Password != nil { - if _, err := tx.Exec(ctx, `DELETE FROM sessions WHERE user_id = $1`, id); err != nil { - return err - } - } - return nil - }, - ) -} - -// UserDelete soft-deletes a user. -// Fails if the user is the sole owner of any organization. -func (db *DB) UserDelete(ctx context.Context, actor Actor, ref UserRef) error { - var id UserID - return db.apicall(ctx, actor, - func(tx pgx.Tx) error { - var err error - id, err = resolveUser(ctx, tx, ref) - if err != nil { - return err - } - return checkSelf(actor, id) - }, - func(tx pgx.Tx) error { - return checkNotSoleOwner(ctx, tx, id) - }, - func(tx pgx.Tx) error { - if _, err := tx.Exec(ctx, `DELETE FROM sessions WHERE user_id = $1`, id); err != nil { - return err - } - if _, err := tx.Exec(ctx, `DELETE FROM org_members WHERE user_id = $1`, id); err != nil { - return err - } - if _, err := tx.Exec(ctx, - `UPDATE users SET email = id::text, password = '', deleted_at = now() WHERE id = $1`, id, - ); err != nil { - return err - } - return nil - }, - ) -} - -// UserVerifyPassword checks credentials and returns the user ID. -func (db *DB) UserVerifyPassword(ctx context.Context, actor Actor, email, password string, pepper []byte) (UserID, error) { - var id UserID - err := db.apicall(ctx, actor, - func(tx pgx.Tx) error { return checkSystem(actor) }, - nil, - func(tx pgx.Tx) error { - var hash string - if err := tx.QueryRow(ctx, - `SELECT id, password FROM users WHERE email = $1 AND deleted_at IS NULL`, - email, - ).Scan(&id, &hash); err != nil { - return ErrInvalidCreds - } - if !verifyHash(password, hash, pepper) { - return ErrInvalidCreds - } - return nil - }, - ) - return id, err -} - -// UserSessionGet resolves a session token into the Actor it authenticates. -// Returns an invalid zero Actor on any error; callers must check err. -func (db *DB) UserSessionGet(ctx context.Context, actor Actor, token string) (Actor, error) { - var ( - userID UserID - email string - superuser bool - ) - err := db.apicall(ctx, actor, - func(tx pgx.Tx) error { return checkSystem(actor) }, - nil, - func(tx pgx.Tx) error { - var expiresAt time.Time - h := hashToken(token) - - if err := tx.QueryRow(ctx, - `SELECT s.user_id, u.email, u.superuser, s.expires_at - FROM sessions s JOIN users u ON u.id = s.user_id - WHERE s.token = $1 AND s.expires_at > now() AND u.deleted_at IS NULL`, - h, - ).Scan(&userID, &email, &superuser, &expiresAt); err != nil { - return err - } - - if time.Until(expiresAt) < config.SessionTTL/2 { - if _, err := tx.Exec(ctx, - `UPDATE sessions SET expires_at = now() + $2 WHERE token = $1`, - h, config.SessionTTL, - ); err != nil { - return err - } - } - return nil - }, - ) - if err != nil { - return Actor{}, err - } - return UserActor(userID, email, superuser), nil -} - -// UserSessionCreate generates a random token, stores its hash, and returns the token. -func (db *DB) UserSessionCreate(ctx context.Context, actor Actor, userID UserID) (string, error) { - var token string - err := db.apicall(ctx, actor, - func(tx pgx.Tx) error { return checkSystem(actor) }, - nil, - func(tx pgx.Tx) error { - buf := make([]byte, 32) - if _, err := rand.Read(buf); err != nil { - return err - } - token = base64.RawURLEncoding.EncodeToString(buf) - - if _, err := tx.Exec(ctx, - `INSERT INTO sessions (token, user_id, expires_at) VALUES ($1, $2, now() + $3)`, - hashToken(token), userID, config.SessionTTL, - ); err != nil { - return err - } - return nil - }, - ) - return token, err -} - -// UserSessionDelete removes a session (logout). -func (db *DB) UserSessionDelete(ctx context.Context, actor Actor, token string) error { - return db.apicall(ctx, actor, - func(tx pgx.Tx) error { return checkSystem(actor) }, - nil, - func(tx pgx.Tx) error { - _, err := tx.Exec(ctx, `DELETE FROM sessions WHERE token = $1`, hashToken(token)) - return err - }, - ) -} - -// UserSessionPurgeExpired deletes all expired sessions. Runs as SystemActor. -func (db *DB) UserSessionPurgeExpired(ctx context.Context) error { - return db.apicall(ctx, SystemActor(), - func(tx pgx.Tx) error { return checkSystem(SystemActor()) }, - nil, - func(tx pgx.Tx) error { - _, err := tx.Exec(ctx, `DELETE FROM sessions WHERE expires_at < now()`) - return err - }, - ) -} - -// OrgGet returns an org by ref (ID or slug). -func (db *DB) OrgGet(ctx context.Context, actor Actor, ref OrgRef) (*Organization, error) { - var o Organization - err := db.apicall(ctx, actor, - func(tx pgx.Tx) error { return checkGlobal(actor, apipb.Perm_PERM_ORG_READ) }, - nil, - func(tx pgx.Tx) error { - col, val := ref.where() - q := sb.PostgreSQL.NewSelectBuilder() - - sql, args := q.Select("id", "name", "slug", "public", "created_at"). - From("organizations"). - Where(q.Equal(col, val), q.IsNull("deleted_at")). - Build() - - if err := tx.QueryRow(ctx, sql, args...).Scan(&o.ID, &o.Name, &o.Slug, &o.Public, &o.CreatedAt); err != nil { - if errors.Is(err, pgx.ErrNoRows) { - return ErrOrgNotFound - } - return err - } - return nil - }, - ) - return &o, err -} - -// OrgCreate creates an org and adds the owner as the first member. -func (db *DB) OrgCreate(ctx context.Context, actor Actor, name, slug string, public bool, owner UserRef) (OrgID, error) { - var orgID OrgID - err := db.apicall(ctx, actor, - func(tx pgx.Tx) error { return checkGlobal(actor, apipb.Perm_PERM_ORG_WRITE) }, - nil, - func(tx pgx.Tx) error { - userID, err := resolveUser(ctx, tx, owner) - if err != nil { - return err - } - - if err := tx.QueryRow(ctx, - `INSERT INTO organizations (name, slug, public) VALUES ($1, $2, $3) RETURNING id`, - name, slug, public, - ).Scan(&orgID); err != nil { - var pgErr *pgconn.PgError - if errors.As(err, &pgErr) && pgErr.Code == pgerrcode.UniqueViolation { - return ErrSlugTaken - } - return err - } - - _, err = tx.Exec(ctx, - `INSERT INTO org_members (org_id, user_id, role) VALUES ($1, $2, 'owner')`, - orgID, userID, - ) - return err - }, - ) - return orgID, err -} - -// OrgUpdate updates an org's name, slug, and/or public flag. -func (db *DB) OrgUpdate(ctx context.Context, actor Actor, ref OrgRef, name *string, slug *string, public *bool) error { - if name == nil && slug == nil && public == nil { - return nil - } - - var id OrgID - return db.apicall(ctx, actor, - func(tx pgx.Tx) error { - var err error - id, err = resolveOrg(ctx, tx, ref) - if err != nil { - return err - } - return checkPerm(ctx, tx, actor, id, apipb.Perm_PERM_ORG_WRITE) - }, - nil, - func(tx pgx.Tx) error { - ub := sb.PostgreSQL.NewUpdateBuilder() - ub.Update("organizations") - if name != nil { - ub.SetMore(ub.Assign("name", *name)) - } - if slug != nil { - ub.SetMore(ub.Assign("slug", *slug)) - } - if public != nil { - ub.SetMore(ub.Assign("public", *public)) - } - ub.Where(ub.Equal("id", id)) - - sql, args := ub.Build() - tag, err := tx.Exec(ctx, sql, args...) - if err != nil { - var pgErr *pgconn.PgError - if errors.As(err, &pgErr) && pgErr.Code == pgerrcode.UniqueViolation { - return ErrSlugTaken - } - return err - } - if tag.RowsAffected() == 0 { - return ErrOrgNotFound - } - return nil - }, - ) -} - -// OrgDelete soft-deletes an org and removes all members. -func (db *DB) OrgDelete(ctx context.Context, actor Actor, ref OrgRef) error { - var id OrgID - return db.apicall(ctx, actor, - func(tx pgx.Tx) error { - var err error - id, err = resolveOrg(ctx, tx, ref) - if err != nil { - return err - } - return checkPerm(ctx, tx, actor, id, apipb.Perm_PERM_ORG_DELETE) - }, - nil, - func(tx pgx.Tx) error { - if _, err := tx.Exec(ctx, `DELETE FROM org_members WHERE org_id = $1`, id); err != nil { - return err - } - tag, err := tx.Exec(ctx, - `UPDATE organizations SET slug = id::text, deleted_at = now() WHERE id = $1`, id, - ) - if err != nil { - return err - } - if tag.RowsAffected() == 0 { - return ErrOrgNotFound - } - return nil - }, - ) -} - -// OrgList returns a page of orgs and the total count. -func (db *DB) OrgList(ctx context.Context, actor Actor, cursor OrgID, limit int, filter string) ([]Organization, int, error) { - var orgs []Organization - var total int - err := db.apicall(ctx, actor, - func(tx pgx.Tx) error { return checkGlobal(actor, apipb.Perm_PERM_ORG_READ) }, - func(tx pgx.Tx) error { - if filter != "" { - return ErrNotImplemented - } - return nil - }, - func(tx pgx.Tx) error { - if err := tx.QueryRow(ctx, - `SELECT count(*) FROM organizations WHERE deleted_at IS NULL`, - ).Scan(&total); err != nil { - return err - } - - q := sb.PostgreSQL.NewSelectBuilder() - q.Select("id", "name", "slug", "public", "created_at"). - From("organizations"). - Where(q.IsNull("deleted_at")). - OrderBy("id"). - Limit(limit) - if !cursor.IsZero() { - q.Where(q.GreaterThan("id", cursor)) - } - - sql, args := q.Build() - rows, err := tx.Query(ctx, sql, args...) - if err != nil { - return err - } - defer rows.Close() - - for rows.Next() { - var o Organization - if err := rows.Scan(&o.ID, &o.Name, &o.Slug, &o.Public, &o.CreatedAt); err != nil { - return err - } - orgs = append(orgs, o) - } - return rows.Err() - }, - ) - return orgs, total, err -} - -// OrgMemberGet returns a single member's info. -func (db *DB) OrgMemberGet(ctx context.Context, actor Actor, org OrgRef, user UserRef) (*OrgMember, error) { - var m OrgMember - var orgID OrgID - var userID UserID - err := db.apicall(ctx, actor, - func(tx pgx.Tx) error { - var err error - orgID, err = resolveOrg(ctx, tx, org) - if err != nil { - return err - } - return checkPerm(ctx, tx, actor, orgID, apipb.Perm_PERM_ORG_MEMBER_READ) - }, - nil, - func(tx pgx.Tx) error { - var err error - userID, err = resolveUser(ctx, tx, user) - if err != nil { - return err - } - - if err := tx.QueryRow(ctx, - `SELECT u.id, u.email, u.created_at, om.role, om.created_at - FROM org_members om - JOIN users u ON u.id = om.user_id - WHERE om.org_id = $1 AND om.user_id = $2`, orgID, userID, - ).Scan(&m.User.ID, &m.User.Email, &m.User.CreatedAt, &m.Role, &m.JoinedAt); err != nil { - if errors.Is(err, pgx.ErrNoRows) { - return ErrNotMember - } - return err - } - return nil - }, - ) - return &m, err -} - -// OrgMembersList returns a page of members for an org. -func (db *DB) OrgMembersList(ctx context.Context, actor Actor, org OrgRef, cursor UserID, limit int, filter string) ([]OrgMember, int, error) { - var members []OrgMember - var total int - var orgID OrgID - err := db.apicall(ctx, actor, - func(tx pgx.Tx) error { - var err error - orgID, err = resolveOrg(ctx, tx, org) - if err != nil { - return err - } - return checkPerm(ctx, tx, actor, orgID, apipb.Perm_PERM_ORG_MEMBER_READ) - }, - func(tx pgx.Tx) error { - if filter != "" { - return ErrNotImplemented - } - return nil - }, - func(tx pgx.Tx) error { - if err := tx.QueryRow(ctx, - `SELECT count(*) FROM org_members WHERE org_id = $1`, orgID, - ).Scan(&total); err != nil { - return err - } - - q := sb.PostgreSQL.NewSelectBuilder() - q.Select("u.id", "u.email", "u.created_at", "m.role", "m.created_at"). - From("org_members m"). - Join("users u", "u.id = m.user_id"). - Where(q.Equal("m.org_id", orgID), q.IsNull("u.deleted_at")). - OrderBy("u.id"). - Limit(limit) - if !cursor.IsZero() { - q.Where(q.GreaterThan("u.id", cursor)) - } - - sql, args := q.Build() - rows, err := tx.Query(ctx, sql, args...) - if err != nil { - return err - } - defer rows.Close() - - for rows.Next() { - var m OrgMember - if err := rows.Scan(&m.User.ID, &m.User.Email, &m.User.CreatedAt, &m.Role, &m.JoinedAt); err != nil { - return err - } - members = append(members, m) - } - return rows.Err() - }, - ) - return members, total, err -} - -// OrgMemberAdd adds a user to an org with the given role. -func (db *DB) OrgMemberAdd(ctx context.Context, actor Actor, org OrgRef, user UserRef, role string) error { - var orgID OrgID - return db.apicall(ctx, actor, - func(tx pgx.Tx) error { - var err error - orgID, err = resolveOrg(ctx, tx, org) - if err != nil { - return err - } - return checkPerm(ctx, tx, actor, orgID, apipb.Perm_PERM_ORG_MEMBER_WRITE) - }, - nil, - func(tx pgx.Tx) error { - userID, err := resolveUser(ctx, tx, user) - if err != nil { - return err - } - if _, err := tx.Exec(ctx, - `INSERT INTO org_members (org_id, user_id, role) VALUES ($1, $2, $3)`, - orgID, userID, role, - ); err != nil { - var pgErr *pgconn.PgError - if errors.As(err, &pgErr) && pgErr.Code == pgerrcode.UniqueViolation { - return ErrAlreadyMember - } - return err - } - return nil - }, - ) -} - -// OrgMemberUpdateRole changes a member's role. Fails if demoting the last owner. -func (db *DB) OrgMemberUpdateRole(ctx context.Context, actor Actor, org OrgRef, user UserRef, newRole string) error { - var orgID OrgID - var userID UserID - return db.apicall(ctx, actor, - func(tx pgx.Tx) error { - var err error - orgID, err = resolveOrg(ctx, tx, org) - if err != nil { - return err - } - return checkPerm(ctx, tx, actor, orgID, apipb.Perm_PERM_ORG_MEMBER_WRITE) - }, - nil, - func(tx pgx.Tx) error { - var err error - userID, err = resolveUser(ctx, tx, user) - if err != nil { - return err - } - - var currentRole string - if err := tx.QueryRow(ctx, - `SELECT role FROM org_members WHERE org_id = $1 AND user_id = $2 FOR UPDATE`, - orgID, userID, - ).Scan(¤tRole); err != nil { - if errors.Is(err, pgx.ErrNoRows) { - return ErrNotMember - } - return err - } - - if currentRole == "owner" && newRole != "owner" { - var ownerCount int - if err := tx.QueryRow(ctx, - `SELECT count(*) FROM org_members WHERE org_id = $1 AND role = 'owner'`, - orgID, - ).Scan(&ownerCount); err != nil { - return err - } - if ownerCount <= 1 { - return ErrLastOwner - } - } - - _, err = tx.Exec(ctx, - `UPDATE org_members SET role = $1 WHERE org_id = $2 AND user_id = $3`, - newRole, orgID, userID, - ) - return err - }, - ) -} - -// OrgMemberRemove removes a user from an org. Fails if they are the last owner. -func (db *DB) OrgMemberRemove(ctx context.Context, actor Actor, org OrgRef, user UserRef) error { - var orgID OrgID - return db.apicall(ctx, actor, - func(tx pgx.Tx) error { - var err error - orgID, err = resolveOrg(ctx, tx, org) - if err != nil { - return err - } - return checkPerm(ctx, tx, actor, orgID, apipb.Perm_PERM_ORG_MEMBER_WRITE) - }, - nil, - func(tx pgx.Tx) error { - userID, err := resolveUser(ctx, tx, user) - if err != nil { - return err - } - - var role string - if err := tx.QueryRow(ctx, - `SELECT role FROM org_members WHERE org_id = $1 AND user_id = $2 FOR UPDATE`, - orgID, userID, - ).Scan(&role); err != nil { - if errors.Is(err, pgx.ErrNoRows) { - return ErrNotMember - } - return err - } - - if role == "owner" { - var ownerCount int - if err := tx.QueryRow(ctx, - `SELECT count(*) FROM org_members WHERE org_id = $1 AND role = 'owner'`, - orgID, - ).Scan(&ownerCount); err != nil { - return err - } - if ownerCount <= 1 { - return ErrLastOwner - } - } - - _, err = tx.Exec(ctx, - `DELETE FROM org_members WHERE org_id = $1 AND user_id = $2`, - orgID, userID, - ) - return err - }, - ) -} - -// WorkerGet returns a worker by ID. -func (db *DB) WorkerGet(ctx context.Context, actor Actor, id WorkerID) (*Worker, error) { - var w Worker - err := db.apicall(ctx, actor, - func(tx pgx.Tx) error { - var orgID *OrgID - if err := tx.QueryRow(ctx, - `SELECT org_id FROM workers WHERE id = $1 AND revoked_at IS NULL`, id, - ).Scan(&orgID); err != nil { - if errors.Is(err, pgx.ErrNoRows) { - return ErrWorkerNotFound - } - return err - } - if orgID != nil { - return checkPerm(ctx, tx, actor, *orgID, apipb.Perm_PERM_WORKER_READ) - } - if !actor.IsSuperuser() { - return ErrPermissionDenied - } - return nil - }, - nil, - func(tx pgx.Tx) error { - return tx.QueryRow(ctx, - `SELECT id, public_key, org_id, created_at FROM workers WHERE id = $1 AND revoked_at IS NULL`, id, - ).Scan(&w.ID, &w.PublicKey, &w.OrgID, &w.CreatedAt) - }, - ) - return &w, err -} - -// WorkerCreate registers a new worker with the given public key and optional org. -func (db *DB) WorkerCreate(ctx context.Context, actor Actor, publicKey []byte, org *OrgRef) (WorkerID, error) { - var workerID WorkerID - var orgID *OrgID - err := db.apicall(ctx, actor, - func(tx pgx.Tx) error { - if org != nil { - id, err := resolveOrg(ctx, tx, *org) - if err != nil { - return err - } - orgID = &id - return checkPerm(ctx, tx, actor, id, apipb.Perm_PERM_WORKER_WRITE) - } - if !actor.IsSuperuser() { - return ErrPermissionDenied - } - return nil - }, - nil, - func(tx pgx.Tx) error { - return tx.QueryRow(ctx, - `INSERT INTO workers (public_key, org_id) VALUES ($1, $2) RETURNING id`, - publicKey, orgID, - ).Scan(&workerID) - }, - ) - return workerID, err -} - -// WorkerDelete soft-deletes a worker by ID. -func (db *DB) WorkerDelete(ctx context.Context, actor Actor, id WorkerID) error { - return db.apicall(ctx, actor, - func(tx pgx.Tx) error { - var orgID *OrgID - if err := tx.QueryRow(ctx, - `SELECT org_id FROM workers WHERE id = $1 AND revoked_at IS NULL`, id, - ).Scan(&orgID); err != nil { - if errors.Is(err, pgx.ErrNoRows) { - return ErrWorkerNotFound - } - return err - } - if orgID != nil { - return checkPerm(ctx, tx, actor, *orgID, apipb.Perm_PERM_WORKER_WRITE) - } - if !actor.IsSuperuser() { - return ErrPermissionDenied - } - return nil - }, - nil, - func(tx pgx.Tx) error { - tag, err := tx.Exec(ctx, - `UPDATE workers SET revoked_at = now() WHERE id = $1 AND revoked_at IS NULL`, id, - ) - if err != nil { - return err - } - if tag.RowsAffected() == 0 { - return ErrWorkerNotFound - } - return nil - }, - ) -} - -// WorkerList returns a page of workers and the total count. -func (db *DB) WorkerList(ctx context.Context, actor Actor, cursor WorkerID, limit int, filter string) ([]Worker, int, error) { - var workers []Worker - var total int - err := db.apicall(ctx, actor, - func(tx pgx.Tx) error { - if actor.kind == actorAnon { - return ErrUnauthenticated - } - return nil - }, - func(tx pgx.Tx) error { - if filter != "" { - return ErrNotImplemented - } - return nil - }, - func(tx pgx.Tx) error { - if err := tx.QueryRow(ctx, - `SELECT count(*) FROM workers WHERE revoked_at IS NULL`, - ).Scan(&total); err != nil { - return err - } - - q := sb.PostgreSQL.NewSelectBuilder() - q.Select("id", "public_key", "org_id", "created_at"). - From("workers"). - Where(q.IsNull("revoked_at")). - OrderBy("id"). - Limit(limit) - if !cursor.IsZero() { - q.Where(q.GreaterThan("id", cursor)) - } - - sql, args := q.Build() - rows, err := tx.Query(ctx, sql, args...) - if err != nil { - return err - } - defer rows.Close() - - for rows.Next() { - var w Worker - if err := rows.Scan(&w.ID, &w.PublicKey, &w.OrgID, &w.CreatedAt); err != nil { - return err - } - workers = append(workers, w) - } - return rows.Err() - }, - ) - return workers, total, err -} - -// WorkerLookup finds an active worker by its ed25519 public key. -func (db *DB) WorkerLookup(ctx context.Context, actor Actor, publicKey []byte) (*Worker, error) { - var w Worker - err := db.apicall(ctx, actor, - func(tx pgx.Tx) error { return checkSystem(actor) }, - nil, - func(tx pgx.Tx) error { - if err := tx.QueryRow(ctx, - `SELECT id, public_key, org_id, created_at FROM workers WHERE public_key = $1 AND revoked_at IS NULL`, - publicKey, - ).Scan(&w.ID, &w.PublicKey, &w.OrgID, &w.CreatedAt); err != nil { - if errors.Is(err, pgx.ErrNoRows) { - return ErrWorkerNotFound - } - return err - } - return nil - }, - ) - return &w, err -} - -// hashToken returns the hex-encoded SHA-256 of a session token. -func hashToken(token string) string { - h := sha256.Sum256([]byte(token)) - return hex.EncodeToString(h[:]) -} - -// verifyHash parses a PHC-format argon2id string and compares. -// Format: $argon2id$v=19$m=65536,t=3,p=2$<salt>$<key> -func verifyHash(password, encoded string, pepper []byte) bool { - // $argon2id$v=19$m=65536,t=3,p=2$salt$key → 6 parts - parts := strings.Split(encoded, "$") - if len(parts) != 6 || parts[1] != "argon2id" { - return false - } - - var memory, time uint32 - var threads uint8 - if _, err := fmt.Sscanf(parts[3], "m=%d,t=%d,p=%d", &memory, &time, &threads); err != nil { - return false - } - - salt, err := base64.RawStdEncoding.DecodeString(parts[4]) - if err != nil { - return false - } - expectedKey, err := base64.RawStdEncoding.DecodeString(parts[5]) - if err != nil { - return false - } - - mac := hmac.New(sha256.New, pepper) - mac.Write([]byte(password)) - peppered := mac.Sum(nil) - - key := argon2.IDKey(peppered, salt, time, memory, threads, uint32(len(expectedKey))) - - return subtle.ConstantTimeCompare(key, expectedKey) == 1 -} - -// hashPassword produces a PHC-format string: -// $argon2id$v=19$m=65536,t=3,p=2$<salt>$<hash> -func hashPassword(password string, pepper []byte) (string, error) { - salt := make([]byte, saltLen) - if _, err := rand.Read(salt); err != nil { - return "", err - } - - // Apply pepper: HMAC-SHA256(pepper, password) - mac := hmac.New(sha256.New, pepper) - mac.Write([]byte(password)) - peppered := mac.Sum(nil) - - key := argon2.IDKey(peppered, salt, argonTime, argonMemory, argonThreads, argonKeyLen) - - return fmt.Sprintf("$argon2id$v=%d$m=%d,t=%d,p=%d$%s$%s", - argon2.Version, - argonMemory, argonTime, argonThreads, - base64.RawStdEncoding.EncodeToString(salt), - base64.RawStdEncoding.EncodeToString(key), - ), nil -} - -// checkNotSoleOwner returns ErrSoleOwner if the user is the only owner of any org. -func checkNotSoleOwner(ctx context.Context, tx pgx.Tx, userID UserID) error { - var slug string - err := tx.QueryRow(ctx, - `SELECT o.slug FROM org_members m - JOIN organizations o ON o.id = m.org_id - WHERE m.role = 'owner' AND o.deleted_at IS NULL - GROUP BY o.id, o.slug - HAVING count(*) = 1 AND bool_or(m.user_id = $1) - LIMIT 1`, userID, - ).Scan(&slug) - if err == nil { - return ErrSoleOwner - } - if errors.Is(err, pgx.ErrNoRows) { - return nil - } - return err -} - -// resolveUser locks and returns the user ID within a transaction. -func resolveUser(ctx context.Context, tx pgx.Tx, ref UserRef) (UserID, error) { - col, val := ref.where() - q := sb.PostgreSQL.NewSelectBuilder() - - sql, args := q.Select("id").From("users"). - Where(q.Equal(col, val), q.IsNull("deleted_at")). - ForUpdate(). - Build() - - var id UserID - if err := tx.QueryRow(ctx, sql, args...).Scan(&id); err != nil { - var zero UserID - if errors.Is(err, pgx.ErrNoRows) { - return zero, ErrUserNotFound - } - - return zero, err - } - - return id, nil -} - -// resolveOrg locks and returns the org ID within a transaction. -func resolveOrg(ctx context.Context, tx pgx.Tx, ref OrgRef) (OrgID, error) { - col, val := ref.where() - q := sb.PostgreSQL.NewSelectBuilder() - - sql, args := q.Select("id").From("organizations"). - Where(q.Equal(col, val), q.IsNull("deleted_at")). - ForUpdate(). - Build() - - var id OrgID - if err := tx.QueryRow(ctx, sql, args...).Scan(&id); err != nil { - var zero OrgID - if errors.Is(err, pgx.ErrNoRows) { - return zero, ErrOrgNotFound - } - - return zero, err - } - - return id, nil -} - -// ValidateEmail checks that the value is a valid email address. -func ValidateEmail(value string) error { - if _, err := mail.ParseAddress(value); err != nil { - return ErrInvalidEmail - } - return nil -} - -// ValidateSlug checks format and returns the normalized (lowercased) slug. -func ValidateSlug(value string) (string, error) { - if len(value) < 2 || len(value) > 64 || !slugRe.MatchString(value) { - return "", ErrInvalidSlug - } - return strings.ToLower(value), nil -} - -// ValidateRole checks that the value is a valid role string. -// Derives valid roles from rolePermissions — single source of truth. -func ValidateRole(value string) error { - if _, ok := rolePermissions[value]; !ok { - return ErrInvalidRole - } - return nil -} diff --git a/cmd/mirum-server/id.go b/cmd/mirum-server/id.go deleted file mode 100644 --- a/cmd/mirum-server/id.go +++ /dev/null @@ -1,303 +0,0 @@ -// SPDX-FileCopyrightText: 2026 Nikolay Govorov -// SPDX-License-Identifier: AGPL-3.0-or-later - -package main - -import ( - "database/sql/driver" - "encoding/hex" - "errors" - "fmt" - "log/slog" - "strings" - - "github.com/google/uuid" -) - -var ( - ErrBadID = errors.New("database: bad id") - ErrBadIDPrefix = errors.New("database: wrong id prefix") -) - -type ( - OrgID = ID[OrgKind] - UserID = ID[UserKind] - WorkerID = ID[WorkerKind] -) - -// IDKind is a phantom-type tag that distinguishes otherwise-identical -// 16-byte IDs at the Go type level. Each tag is a zero-sized struct -// that carries only its 3-letter prefix. -type IDKind interface { - UserKind | OrgKind | WorkerKind - Prefix() string -} - -type ( - UserKind struct{} - OrgKind struct{} - WorkerKind struct{} -) - -func (OrgKind) Prefix() string { return "org" } -func (UserKind) Prefix() string { return "usr" } -func (WorkerKind) Prefix() string { return "wrk" } - -// ID[K] is a typed UUIDv7. Instantiations with different K are distinct -// types, so passing a UserID where an OrgID is expected is a compile -// error. Cross-kind conversion requires an explicit cast, visible in -// review. -type ID[K IDKind] uuid.UUID - -func NewID[K IDKind]() ID[K] { - return ID[K](uuid.Must(uuid.NewV7())) -} - -func IDFromBytes[K IDKind](b []byte) (ID[K], error) { - var zero ID[K] - if len(b) != 16 { - return zero, fmt.Errorf("%w: got %d bytes", ErrBadID, len(b)) - } - u := uuid.UUID(b) - if err := validateV7(u); err != nil { - return zero, err - } - return ID[K](u), nil -} - -func validateV7(u uuid.UUID) error { - if v := u.Variant(); v != uuid.RFC4122 { - return fmt.Errorf("%w: variant %s", ErrBadID, v) - } - if v := u.Version(); v != 7 { - return fmt.Errorf("%w: version %d, want 7", ErrBadID, v) - } - return nil -} - -func (id ID[K]) UUID() uuid.UUID { return uuid.UUID(id) } -func (id ID[K]) Bytes() []byte { return id[:] } - -func (id ID[K]) IsZero() bool { - var zero ID[K] - return id == zero -} - -// String returns the prefixed form for logs, JSON, errors and anywhere -// the entity type isn't obvious from context. For URL path segments -// where the route already names the type, use Bare(). -func (id ID[K]) String() string { - var k K - return k.Prefix() + "_" + encodeBase58(uuid.UUID(id)) -} - -// Bare returns the base58 form without a type prefix — ≤ 22 chars. -// Use this for URL path segments where the route already identifies -// the entity ("/org/:id"); use String() everywhere else. -func (id ID[K]) Bare() string { - return encodeBase58(uuid.UUID(id)) -} - -func (id ID[K]) LogValue() slog.Value { - return slog.StringValue(id.String()) -} - -func (id ID[K]) MarshalText() ([]byte, error) { - return []byte(id.String()), nil -} - -func (id *ID[K]) UnmarshalText(b []byte) error { - parsed, err := ParseID[K](string(b)) - if err != nil { - return err - } - *id = parsed - return nil -} - -func (id *ID[K]) Scan(src any) error { - var u uuid.UUID - if err := u.Scan(src); err != nil { - return err - } - *id = ID[K](u) - return nil -} - -func (id ID[K]) Value() (driver.Value, error) { - return uuid.UUID(id).Value() -} - -// ParseID accepts the prefixed base58 ("<prefix>_<b58>"), bare base58 -// (≤ 22 chars, as returned by Bare()), or a canonical UUID string. -// Bare forms stay supported so existing CLI flags, manual SQL lookups -// and URL path parameters keep working without a flag day. -func ParseID[K IDKind](s string) (ID[K], error) { - var k K - u, err := parseID(k.Prefix(), s) - return ID[K](u), err -} - -func MustParseID[K IDKind](s string) ID[K] { - id, err := ParseID[K](s) - if err != nil { - panic(err) - } - return id -} - -// ParseAnyID parses a prefixed, bare base58, or canonical UUID string -// into raw 16 bytes. Unlike ParseID[K], it does not require a known -// entity kind — any 3-letter prefix is stripped silently. Intended for -// CLI boundary code that dispatches on proto field names. -func ParseAnyID(s string) ([16]byte, error) { - if s == "" { - return [16]byte{}, fmt.Errorf("%w: empty", ErrBadID) - } - // Strip any typed prefix. - if len(s) > 4 && s[3] == '_' { - s = s[4:] - } - if len(s) <= 22 { - return decodeBase58(s) - } - u, err := uuid.Parse(s) - if err != nil { - return [16]byte{}, fmt.Errorf("%w: %w", ErrBadID, err) - } - return u, nil -} - -// FormatAnyID formats raw 16-byte ID as bare base58. Returns base64 for -// non-16-byte inputs as a fallback. -func FormatAnyID(b []byte) string { - if len(b) == 16 { - return encodeBase58(uuid.UUID(b)) - } - return hex.EncodeToString(b) -} - -func parseID(prefix, s string) (uuid.UUID, error) { - if s == "" { - return uuid.Nil, fmt.Errorf("%w: empty", ErrBadID) - } - - if rest, ok := strings.CutPrefix(s, prefix+"_"); ok { - return decodeBase58(rest) - } - - // Typed prefix with the wrong value — reject so a cross-type paste - // doesn't silently fall through to the bare path. - if len(s) > 4 && s[3] == '_' { - return uuid.Nil, fmt.Errorf("%w: want %q, got %q", ErrBadIDPrefix, prefix, s[:3]) - } - - // Bare form: base58 (≤ 22 chars, from Bare()) or canonical UUID - // (32–36 chars, from manual SQL / legacy CLI). - if len(s) <= 22 { - return decodeBase58(s) - } - - u, err := uuid.Parse(s) - if err != nil { - return uuid.Nil, fmt.Errorf("%w: %w", ErrBadID, err) - } - - return u, nil -} - -// --- base58 (Bitcoin alphabet) --- -// -// 16 bytes fit in ≤ 22 base58 chars: log₅₈(2¹²⁸) ≈ 21.86. -// UUIDv7 values in the post-1970 range always have a non-zero -// leading byte, so encoded length is effectively a constant 21-22. - -const b58Alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" - -var b58Index [256]byte - -func init() { - for i := range b58Index { - b58Index[i] = 0xff - } - for i := 0; i < len(b58Alphabet); i++ { - b58Index[b58Alphabet[i]] = byte(i) - } -} - -func encodeBase58(src uuid.UUID) string { - zeros := 0 - for zeros < 16 && src[zeros] == 0 { - zeros++ - } - - buf := src // array copy; long-division is in place - start := zeros - out := make([]byte, 0, 22) - - for start < 16 { - rem := 0 - for i := start; i < 16; i++ { - v := rem*256 + int(buf[i]) - buf[i] = byte(v / 58) - rem = v % 58 - } - out = append(out, b58Alphabet[rem]) - for start < 16 && buf[start] == 0 { - start++ - } - } - - for i := 0; i < zeros; i++ { - out = append(out, b58Alphabet[0]) - } - - // Reverse into big-endian order. - for i, j := 0, len(out)-1; i < j; i, j = i+1, j-1 { - out[i], out[j] = out[j], out[i] - } - - return string(out) -} - -// decodeBase58 is strict about length — any input that round-trips to a -// value of a different byte length is rejected; we never want a -// 15-byte or 17-byte payload masquerading as a UUID. -func decodeBase58(s string) (uuid.UUID, error) { - if s == "" { - return uuid.Nil, fmt.Errorf("%w: empty", ErrBadID) - } - zeros := 0 - for zeros < len(s) && s[zeros] == b58Alphabet[0] { - zeros++ - } - - var out uuid.UUID - for i := zeros; i < len(s); i++ { - v := b58Index[s[i]] - if v == 0xff { - return uuid.Nil, fmt.Errorf("%w: bad base58 char %q", ErrBadID, s[i]) - } - carry := int(v) - for j := 15; j >= 0; j-- { - acc := int(out[j])*58 + carry - out[j] = byte(acc) - carry = acc >> 8 - } - if carry != 0 { - return uuid.Nil, fmt.Errorf("%w: overflow", ErrBadID) - } - } - - // The '1' prefix count in the string must equal the leading-zero - // byte count in the result — any mismatch means the input decoded - // to a different byte length than a UUID. - actualZeros := 0 - for actualZeros < 16 && out[actualZeros] == 0 { - actualZeros++ - } - if actualZeros != zeros { - return uuid.Nil, fmt.Errorf("%w: length mismatch", ErrBadID) - } - return out, nil -} diff --git a/cmd/mirum-server/id_test.go b/cmd/mirum-server/id_test.go deleted file mode 100644 --- a/cmd/mirum-server/id_test.go +++ /dev/null @@ -1,76 +0,0 @@ -// SPDX-FileCopyrightText: 2026 Nikolay Govorov -// SPDX-License-Identifier: AGPL-3.0-or-later - -package main - -import ( - "testing" - - "github.com/google/uuid" -) - -func TestIDRoundTrip(t *testing.T) { - for range 50 { - id := NewID[UserKind]() - got, err := ParseID[UserKind](id.String()) - if err != nil { - t.Fatalf("parse prefixed: %v", err) - } - if got != id { - t.Fatalf("prefixed: got %v, want %v", got, id) - } - got, err = ParseID[UserKind](id.Bare()) - if err != nil { - t.Fatalf("parse bare: %v", err) - } - if got != id { - t.Fatalf("bare: got %v, want %v", got, id) - } - } -} - -func TestIDRoundTripCanonicalUUID(t *testing.T) { - id := NewID[OrgKind]() - got, err := ParseID[OrgKind](id.UUID().String()) - if err != nil { - t.Fatalf("parse canonical: %v", err) - } - if got != id { - t.Fatalf("canonical: got %v, want %v", got, id) - } -} - -func TestIDCrossPrefixRejected(t *testing.T) { - id := NewID[UserKind]() - _, err := ParseID[OrgKind](id.String()) - if err == nil { - t.Fatal("expected error parsing usr_ as org") - } -} - -func TestBase58EdgeCases(t *testing.T) { - cases := [][16]byte{ - {}, // all zeros - {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, // minimal - {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, // max - } - for _, raw := range cases { - enc := encodeBase58(uuid.UUID(raw)) - dec, err := decodeBase58(enc) - if err != nil { - t.Fatalf("decode(%q): %v", enc, err) - } - if dec != uuid.UUID(raw) { - t.Fatalf("roundtrip: got %x, want %x", dec, raw) - } - } -} - -func TestIDFromBytesRejectsV4(t *testing.T) { - v4 := uuid.Must(uuid.NewRandom()) // v4 - _, err := IDFromBytes[UserKind](v4[:]) - if err == nil { - t.Fatal("expected v4 rejection") - } -} diff --git a/cmd/mirum-server/licenses.go b/cmd/mirum-server/licenses.go deleted file mode 100644 --- a/cmd/mirum-server/licenses.go +++ /dev/null @@ -1,44 +0,0 @@ -// SPDX-FileCopyrightText: 2026 Nikolay Govorov -// SPDX-License-Identifier: AGPL-3.0-or-later - -package main - -import ( - "encoding/json" - "net/http" - "strings" - - mirum "dimidiumlabs/mirum" -) - -// primarySPDX is the SPDX identifier of mirum itself. -const primarySPDX = "AGPL-3.0-or-later" - -// licensesPageData mirrors tools/licensegen's Manifest with a primary -// license header prepended. The frontend consumes it verbatim. -type licensesPageData struct { - Primary struct { - Name string `json:"name"` - SPDX string `json:"spdx"` - Text string `json:"text"` - } `json:"primary"` - Manifest json.RawMessage `json:"manifest"` -} - -var licensesData = mustLoadLicenses() - -func mustLoadLicenses() *licensesPageData { - if !json.Valid(mirum.Licenses) { - panic("embedded licenses.json is not valid JSON") - } - d := &licensesPageData{Manifest: mirum.Licenses} - d.Primary.Name = "mirum" - d.Primary.SPDX = primarySPDX - d.Primary.Text = strings.TrimSpace(mirum.License) + "\n" - return d -} - -// licenses serves /about/licenses. Public: no auth, no CSRF. -func (h *webHandler) licenses(w http.ResponseWriter, _ *http.Request) { - h.assets.renderPage(w, "licenses", http.StatusOK, licensesData) -} diff --git a/cmd/mirum-server/main.go b/cmd/mirum-server/main.go deleted file mode 100644 --- a/cmd/mirum-server/main.go +++ /dev/null @@ -1,253 +0,0 @@ -// SPDX-FileCopyrightText: 2026 Nikolay Govorov -// SPDX-License-Identifier: AGPL-3.0-or-later - -package main - -import ( - "context" - "errors" - "fmt" - "log/slog" - "net" - "net/http" - "os" - - "dimidiumlabs/mirum/cmd/mirum-server/apipb/apipbconnect" - "dimidiumlabs/mirum/internal/config" - "dimidiumlabs/mirum/internal/forges" - "dimidiumlabs/mirum/internal/protocol/wirepb" - "dimidiumlabs/mirum/internal/supervisor" - - "github.com/spf13/cobra" -) - -func hardenServer(s *http.Server) *http.Server { - s.IdleTimeout = config.HTTPIdleTimeout - s.MaxHeaderBytes = config.HTTPMaxHeaderBytes - s.ReadHeaderTimeout = config.HTTPReadHeaderTimeout - return s -} - -func main() { - var socketPath string - - root := &cobra.Command{Use: "mirum-server", Short: "Mirum CI server"} - root.PersistentFlags().StringVar(&socketPath, "socket", "", "admin socket path (default from config or /run/mirum-server/admin.sock)") - root.AddGroup(&cobra.Group{ID: "main", Title: "Commands:"}) - - daemonCmd := &cobra.Command{ - Use: "daemon", - Short: "Start the server", - GroupID: "main", - SilenceUsage: true, - SilenceErrors: true, - RunE: func(cmd *cobra.Command, args []string) error { - configFile, _ := cmd.Flags().GetString("config") - return daemon(configFile, socketPath) - }, - } - daemonCmd.Flags().String("config", "", "path to config file") - _ = daemonCmd.MarkFlagRequired("config") - root.AddCommand(daemonCmd) - - // Console subcommands are generated from api.proto via reflection. - buildConsoleCLI(root, func() apipbconnect.ConsoleClient { return consoleClient(socketPath) }) - for _, c := range root.Commands() { - if c.GroupID == "" { - c.GroupID = "main" - } - } - - if err := root.Execute(); err != nil { - os.Exit(1) - } -} - -func daemon(configFile, socketFlag string) error { - cfg, err := getConfig(configFile) - if err != nil { - slog.Error("config parsing failed", "err", err) - return err - } - - if socketFlag != "" { - cfg.AdminSocket = socketFlag - } - - slog.Info("config loaded", "configfile", configFile) - - sup := supervisor.Detect() - ctx, cancel := context.WithCancel(sup.WaitForStop(context.Background())) - defer cancel() - - db, err := DatabaseOpen(ctx, cfg.DatabaseUri) - if err != nil { - slog.Error("couldn't open database", "err", err) - return err - } - - srv := &server{ - db: db, - cfg: cfg, - forge: &forges.GitHub{Secret: cfg.WebhookSecret, Token: cfg.GitHubToken}, - queue: make(chan *wirepb.Task, config.TaskQueueCapacity), - } - defer srv.Close() - - if err := db.Migrate(ctx); err != nil { - slog.Error("migration failed", "err", err) - return err - } - - slog.Info("database ready") - - go srv.PurgeSessions(ctx) - - consolePath, consoleHandler := NewConsoleHandler(srv) - - webSrv := hardenServer(NewWebServer(ctx, srv, consolePath, consoleHandler)) - grpcSrv := hardenServer(NewGrpcServer(ctx, srv)) - - adminMux := http.NewServeMux() - adminMux.Handle(consolePath, consoleHandler) - adminSrv := hardenServer(&http.Server{ - Handler: adminMux, - ConnContext: func(ctx context.Context, _ net.Conn) context.Context { - return context.WithValue(ctx, actorKey{}, OperatorActor()) - }, - BaseContext: func(_ net.Listener) context.Context { - return ctx - }, - }) - - grpcLn, webLn, adminLn, err := listeners(cfg, sup) - if err != nil { - slog.Error("listeners failed", "err", err) - return err - } - - slog.Info("listening", "grpc", grpcLn.Addr(), "web", webLn.Addr(), "admin", cfg.AdminSocket) - - errs := make(chan error, 3) - serve := func(name string, fn func() error) { - go func() { - err := fn() - if errors.Is(err, http.ErrServerClosed) { - err = nil - } - if err != nil { - err = fmt.Errorf("%s server: %w", name, err) - } - errs <- err - }() - } - serve("web", func() error { - if webSrv.TLSConfig != nil { - return webSrv.ServeTLS(webLn, "", "") - } - return webSrv.Serve(webLn) - }) - serve("grpc", func() error { return grpcSrv.ServeTLS(grpcLn, "", "") }) - serve("admin", func() error { return adminSrv.Serve(adminLn) }) - - sup.Ready() - go sup.StartWatchdog(ctx) - - var runErr error - select { - case <-ctx.Done(): - slog.Info("shutting down") - case err := <-errs: - runErr = err - // Propagate the crash to all handler contexts so Poll and - // other long-lived RPCs exit via ctx.Done(); Shutdown below - // then completes without waiting on them. - cancel() - if err != nil { - slog.Error("server exited, shutting down peers", "err", err) - } else { - slog.Warn("server exited unexpectedly, shutting down peers") - } - } - - sup.Stopping() - - shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), config.HTTPShutdownTimeout) - defer shutdownCancel() - - shutdown := func(name string, s *http.Server) { - if err := s.Shutdown(shutdownCtx); err != nil { - slog.Error("server shutdown", "name", name, "err", err) - } - } - - shutdown("web", webSrv) - shutdown("grpc", grpcSrv) - shutdown("admin", adminSrv) - - return runErr -} - -// listeners returns gRPC, web, and admin listeners. -// With systemd socket activation it expects two named fds: "grpc" and "web". -// Without socket activation it falls back to configured addresses. -func listeners(cfg *appConfig, sup supervisor.Supervisor) (grpcLn, webLn, adminLn net.Listener, err error) { - named, err := sup.ActivationListeners() - if err != nil { - return nil, nil, nil, fmt.Errorf("socket activation: %w", err) - } - - if lns := named["grpc"]; len(lns) > 0 { - grpcLn = lns[0] - } else if grpcLn, err = net.Listen("tcp", cfg.GrpcAddr); err != nil { - return nil, nil, nil, err - } - defer func() { - if err != nil && grpcLn != nil { - _ = grpcLn.Close() - } - }() - - if lns := named["web"]; len(lns) > 0 { - webLn = lns[0] - } else if webLn, err = net.Listen("tcp", cfg.WebAddr); err != nil { - return nil, nil, nil, err - } - defer func() { - if err != nil && webLn != nil { - _ = webLn.Close() - } - }() - - _ = os.Remove(cfg.AdminSocket) - if adminLn, err = net.Listen("unix", cfg.AdminSocket); err != nil { - return nil, nil, nil, err - } - defer func() { - if err != nil && adminLn != nil { - _ = adminLn.Close() - } - }() - - if err = os.Chmod(cfg.AdminSocket, 0o660); err != nil { - return nil, nil, nil, fmt.Errorf("chmod admin socket: %w", err) - } - - return grpcLn, webLn, adminLn, nil -} - -func consoleClient(socketPath string) apipbconnect.ConsoleClient { - if socketPath == "" { - socketPath = "/run/mirum-server/admin.sock" - } - return apipbconnect.NewConsoleClient( - &http.Client{ - Transport: &http.Transport{ - DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) { - return net.Dial("unix", socketPath) - }, - }, - }, - "http://localhost.unix", - ) -} diff --git a/cmd/mirum-server/proto/api.proto b/cmd/mirum-server/proto/api.proto deleted file mode 100644 --- a/cmd/mirum-server/proto/api.proto +++ /dev/null @@ -1,360 +0,0 @@ -// SPDX-FileCopyrightText: 2026 Nikolay Govorov -// SPDX-License-Identifier: AGPL-3.0-or-later - -syntax = "proto3"; - -package mirum.api; - -import "buf/validate/validate.proto"; -import "google/protobuf/timestamp.proto"; - -option go_package = "dimidiumlabs/mirum/cmd/mirum-server/apipb"; - -service Console { - rpc UserCreate(UserCreateRequest) returns (UserCreateResponse); - rpc UserGet(UserGetRequest) returns (UserGetResponse); - rpc UserList(UserListRequest) returns (UserListResponse); - rpc UserUpdate(UserUpdateRequest) returns (UserUpdateResponse); - rpc UserDelete(UserDeleteRequest) returns (UserDeleteResponse); - - rpc OrgCreate(OrgCreateRequest) returns (OrgCreateResponse); - rpc OrgGet(OrgGetRequest) returns (OrgGetResponse); - rpc OrgList(OrgListRequest) returns (OrgListResponse); - rpc OrgUpdate(OrgUpdateRequest) returns (OrgUpdateResponse); - rpc OrgDelete(OrgDeleteRequest) returns (OrgDeleteResponse); - - rpc OrgMemberAdd(OrgMemberAddRequest) returns (OrgMemberAddResponse); - rpc OrgMemberGet(OrgMemberGetRequest) returns (OrgMemberGetResponse); - rpc OrgMemberList(OrgMemberListRequest) returns (OrgMemberListResponse); - rpc OrgMemberUpdate(OrgMemberUpdateRequest) returns (OrgMemberUpdateResponse); - rpc OrgMemberRemove(OrgMemberRemoveRequest) returns (OrgMemberRemoveResponse); - - rpc WorkerCreate(WorkerCreateRequest) returns (WorkerCreateResponse); - rpc WorkerGet(WorkerGetRequest) returns (WorkerGetResponse); - rpc WorkerList(WorkerListRequest) returns (WorkerListResponse); - rpc WorkerDelete(WorkerDeleteRequest) returns (WorkerDeleteResponse); -} - -// Common types - -enum Role { - ROLE_NONE = 0; - ROLE_OWNER = 1; - ROLE_ADMIN = 2; - ROLE_MEMBER = 3; -} - -enum Perm { - PERM_NONE = 0; - - PERM_USER_READ = 1; - PERM_USER_WRITE = 2; - PERM_USER_DELETE = 3; - - PERM_ORG_READ = 4; - PERM_ORG_WRITE = 5; - PERM_ORG_DELETE = 6; - PERM_ORG_MEMBER_READ = 7; - PERM_ORG_MEMBER_WRITE = 8; - - PERM_WORKER_READ = 9; - PERM_WORKER_WRITE = 10; -} - -// ErrorInfo is attached as a ConnectError detail on every error. -// The ConnectError message field is always empty — the server never sends -// human-readable text. Clients switch on reason to select user-facing copy. -message ErrorInfo { - ErrorReason reason = 1; - - // Short stable identifiers for UI targeting (e.g. {"field": "slug"}). - // Never human-readable text. - map<string, string> metadata = 2; -} - -enum ErrorReason { - ERROR_REASON_UNSPECIFIED = 0; - ERROR_REASON_INTERNAL = 1; - - // Lookup failures - ERROR_REASON_USER_NOT_FOUND = 10; - ERROR_REASON_ORG_NOT_FOUND = 11; - ERROR_REASON_WORKER_NOT_FOUND = 12; - ERROR_REASON_MEMBER_NOT_FOUND = 13; - - // Conflicts - ERROR_REASON_EMAIL_TAKEN = 20; - ERROR_REASON_SLUG_TAKEN = 21; - ERROR_REASON_ALREADY_MEMBER = 22; - - // State preconditions - ERROR_REASON_LAST_OWNER = 30; - ERROR_REASON_SOLE_OWNER = 31; - - // Validation - ERROR_REASON_INVALID_SLUG = 40; - ERROR_REASON_INVALID_ROLE = 41; - ERROR_REASON_RESERVED_EMAIL = 42; - ERROR_REASON_INVALID_LOCALE = 43; - ERROR_REASON_INVALID_DATE_FORMAT = 44; - ERROR_REASON_INVALID_TIMEZONE = 45; - - // Auth - ERROR_REASON_UNAUTHENTICATED = 50; - ERROR_REASON_PERMISSION_DENIED = 51; - ERROR_REASON_INVALID_CREDENTIALS = 52; - ERROR_REASON_INVALID_CSRF = 53; - - // Infrastructure - ERROR_REASON_RATE_LIMITED = 60; - ERROR_REASON_UNAVAILABLE = 61; - ERROR_REASON_UNIMPLEMENTED = 62; -} - -message UserRef { - oneof ref { - option (buf.validate.oneof).required = true; - bytes id = 1 [(buf.validate.field).bytes = { - min_len: 16 - max_len: 16 - }]; - string email = 2 [(buf.validate.field).string.email = true]; - } -} - -message OrgRef { - oneof ref { - option (buf.validate.oneof).required = true; - bytes id = 1 [(buf.validate.field).bytes = { - min_len: 16 - max_len: 16 - }]; - string slug = 2 [(buf.validate.field).string = { - min_len: 2 - max_len: 64 - }]; - } -} - -message PageRequest { - bytes cursor = 1; - int32 page_size = 2; -} -message PageResponse { - bytes next_cursor = 1; - int32 total_count = 2; -} - -// User management - -message Locale { - optional string language = 1; - optional DateFormat date_format = 2; -} - -enum DateFormat { - DATE_FORMAT_UNSPECIFIED = 0; - DATE_FORMAT_DMY = 1; - DATE_FORMAT_MDY = 2; - DATE_FORMAT_YMD = 3; -} - -message User { - bytes id = 1; - string email = 2; - google.protobuf.Timestamp created_at = 3; - Locale locale = 4; - optional string timezone = 5; -} - -message UserCreateRequest { - string email = 1 [(buf.validate.field).string.email = true]; - string password = 2 [(buf.validate.field).string.min_len = 1]; -} -message UserCreateResponse { - bytes id = 1; -} - -message UserGetRequest { - UserRef user = 1 [(buf.validate.field).required = true]; -} -message UserGetResponse { - User user = 1; -} - -message UserListRequest { - optional PageRequest page = 1; - optional string filter = 2; -} -message UserListResponse { - PageResponse page = 1; - repeated User users = 2; -} - -message UserUpdateRequest { - UserRef user = 1 [(buf.validate.field).required = true]; - optional string email = 2 [(buf.validate.field).string.email = true]; - optional string password = 3 [(buf.validate.field).string.min_len = 1]; - optional Locale locale = 4; - optional string timezone = 5; -} -message UserUpdateResponse {} - -message UserDeleteRequest { - UserRef user = 1 [(buf.validate.field).required = true]; -} -message UserDeleteResponse {} - -// Organization management - -message Org { - bytes id = 1; - string name = 2; - string slug = 3; - bool public = 4; - google.protobuf.Timestamp created_at = 5; -} - -message OrgCreateRequest { - string name = 1 [(buf.validate.field).string.min_len = 1]; - string slug = 2 [(buf.validate.field).string = { - min_len: 2 - max_len: 64 - }]; - UserRef owner = 3 [(buf.validate.field).required = true]; - bool public = 4; -} -message OrgCreateResponse { - bytes id = 1; -} - -message OrgGetRequest { - OrgRef org = 1 [(buf.validate.field).required = true]; -} -message OrgGetResponse { - Org org = 1; -} - -message OrgListRequest { - optional PageRequest page = 1; - optional string filter = 2; -} -message OrgListResponse { - PageResponse page = 1; - repeated Org organizations = 2; -} - -message OrgUpdateRequest { - OrgRef org = 1 [(buf.validate.field).required = true]; - optional string name = 2 [(buf.validate.field).string.min_len = 1]; - optional string slug = 3 [(buf.validate.field).string = { - min_len: 2 - max_len: 64 - }]; - optional bool public = 4; -} -message OrgUpdateResponse {} - -message OrgDeleteRequest { - OrgRef org = 1 [(buf.validate.field).required = true]; -} -message OrgDeleteResponse {} - -// Organization members - -message OrgMemberInfo { - User user = 1; - Role role = 2; - google.protobuf.Timestamp joined_at = 3; -} - -message OrgMemberGetRequest { - OrgRef org = 1 [(buf.validate.field).required = true]; - UserRef user = 2 [(buf.validate.field).required = true]; -} -message OrgMemberGetResponse { - OrgMemberInfo member = 1; -} - -message OrgMemberAddRequest { - OrgRef org = 1 [(buf.validate.field).required = true]; - UserRef user = 2 [(buf.validate.field).required = true]; - Role role = 3 [(buf.validate.field).enum = { - defined_only: true - not_in: [0] - }]; -} -message OrgMemberAddResponse {} - -message OrgMemberListRequest { - optional PageRequest page = 1; - OrgRef org = 2 [(buf.validate.field).required = true]; - optional string filter = 3; -} -message OrgMemberListResponse { - PageResponse page = 1; - repeated OrgMemberInfo members = 2; -} - -message OrgMemberUpdateRequest { - OrgRef org = 1 [(buf.validate.field).required = true]; - UserRef user = 2 [(buf.validate.field).required = true]; - Role role = 3 [(buf.validate.field).enum = { - defined_only: true - not_in: [0] - }]; -} -message OrgMemberUpdateResponse {} - -message OrgMemberRemoveRequest { - OrgRef org = 1 [(buf.validate.field).required = true]; - UserRef user = 2 [(buf.validate.field).required = true]; -} -message OrgMemberRemoveResponse {} - -// Worker management - -message Worker { - bytes id = 1; - bytes public_key = 2; - optional bytes org_id = 3; - google.protobuf.Timestamp created_at = 4; -} - -message WorkerCreateRequest { - bytes public_key = 1 [(buf.validate.field).bytes = { - min_len: 32 - max_len: 32 - }]; - optional OrgRef org = 2; -} -message WorkerCreateResponse { - bytes id = 1; -} - -message WorkerGetRequest { - bytes id = 1 [(buf.validate.field).bytes = { - min_len: 16 - max_len: 16 - }]; -} -message WorkerGetResponse { - Worker worker = 1; -} - -message WorkerListRequest { - optional PageRequest page = 1; - optional string filter = 2; -} -message WorkerListResponse { - PageResponse page = 1; - repeated Worker workers = 2; -} - -message WorkerDeleteRequest { - bytes id = 1 [(buf.validate.field).bytes = { - min_len: 16 - max_len: 16 - }]; -} -message WorkerDeleteResponse {} diff --git a/cmd/mirum-server/proto/buf.gen.yaml b/cmd/mirum-server/proto/buf.gen.yaml deleted file mode 100644 --- a/cmd/mirum-server/proto/buf.gen.yaml +++ /dev/null @@ -1,16 +0,0 @@ -# SPDX-FileCopyrightText: 2026 Nikolay Govorov -# SPDX-License-Identifier: AGPL-3.0-or-later - -version: v2 -plugins: - - local: ["go", "tool", "google.golang.org/protobuf/cmd/protoc-gen-go"] - out: ../apipb - opt: paths=source_relative - - local: ["go", "tool", "connectrpc.com/connect/cmd/protoc-gen-connect-go"] - out: ../apipb - opt: paths=source_relative - - local: ["../web/node_modules/.bin/protoc-gen-es"] - out: ../web/gen - opt: - - target=ts - - import_extension=js diff --git a/cmd/mirum-server/server.go b/cmd/mirum-server/server.go deleted file mode 100644 --- a/cmd/mirum-server/server.go +++ /dev/null @@ -1,99 +0,0 @@ -// SPDX-FileCopyrightText: 2026 Nikolay Govorov -// SPDX-License-Identifier: AGPL-3.0-or-later - -package main - -import ( - "context" - "fmt" - "log/slog" - "sync" - "sync/atomic" - "time" - - "dimidiumlabs/mirum/internal/config" - "dimidiumlabs/mirum/internal/forges" - "dimidiumlabs/mirum/internal/protocol/wirepb" -) - -// server holds the shared application state. -type server struct { - cfg *appConfig - db *DB - forge forges.Forge - - queue chan *wirepb.Task - tasks sync.Map // task_id → *forges.PushEvent - taskCounter atomic.Int64 -} - -// Close releases resources owned by the server. Call exactly once, after -// all HTTP servers have finished Shutdown. -func (s *server) Close() { - s.db.Close() -} - -// PurgeSessions periodically deletes expired sessions until ctx is cancelled. -func (s *server) PurgeSessions(ctx context.Context) { - ticker := time.NewTicker(config.SessionPurgeInterval) - defer ticker.Stop() - - for { - select { - case <-ticker.C: - if err := s.db.UserSessionPurgeExpired(ctx); err != nil { - slog.Error("purge sessions", "err", err) - } - case <-ctx.Done(): - return - } - } -} - -func (s *server) enqueue(ctx context.Context, ev *forges.PushEvent) (string, error) { - id := fmt.Sprintf("task-%d", s.taskCounter.Add(1)) - - slog.Info("push", "repo", ev.Owner+"/"+ev.Repo, "branch", ev.Branch, "sha", ev.SHA[:8], "task", id) - - s.tasks.Store(id, ev) - _ = s.forge.SetStatus(ctx, ev, forges.StatusPending, "Queued") - - select { - case s.queue <- &wirepb.Task{ - Id: id, - CloneUrl: s.forge.AuthURL(ev.CloneURL), - Branch: ev.Branch, - Sha: ev.SHA, - RepoFullName: ev.Owner + "/" + ev.Repo, - }: - return id, nil - case <-ctx.Done(): - s.tasks.Delete(id) - return "", ctx.Err() - } -} - -func (s *server) complete(ctx context.Context, taskID string, success bool, errMsg string) error { - val, ok := s.tasks.LoadAndDelete(taskID) - if !ok { - return fmt.Errorf("unknown task: %s", taskID) - } - ev := val.(*forges.PushEvent) - - st := forges.StatusSuccess - desc := "Build passed" - if !success { - st = forges.StatusFailure - desc = "Build failed" - if errMsg != "" { - desc = errMsg - } - } - - if err := s.forge.SetStatus(ctx, ev, st, desc); err != nil { - slog.Error("set status", "task", taskID, "err", err) - } - - slog.Info("task complete", "id", taskID, "success", success) - return nil -} diff --git a/cmd/mirum-server/server_admin.go b/cmd/mirum-server/server_admin.go deleted file mode 100644 --- a/cmd/mirum-server/server_admin.go +++ /dev/null @@ -1,556 +0,0 @@ -// SPDX-FileCopyrightText: 2026 Nikolay Govorov -// SPDX-License-Identifier: AGPL-3.0-or-later - -package main - -import ( - "context" - "errors" - "log/slog" - "net/http" - - "connectrpc.com/connect" - "connectrpc.com/validate" - "google.golang.org/protobuf/types/known/timestamppb" - - "dimidiumlabs/mirum/cmd/mirum-server/apipb" - "dimidiumlabs/mirum/cmd/mirum-server/apipb/apipbconnect" -) - -// NewConsoleHandler creates the ConnectRPC handler with validation. -// Authorization is handled inside DB methods, not by an interceptor. -func NewConsoleHandler(srv *server) (string, http.Handler) { - as := &consoleService{srv: srv} - return apipbconnect.NewConsoleHandler(as, - connect.WithInterceptors(validate.NewInterceptor()), - ) -} - -type consoleService struct { - apipbconnect.UnimplementedConsoleHandler - srv *server -} - -// --- Error mapping --- - -// newAPIError builds a ConnectError with an empty message string and attaches -// an ErrorInfo detail carrying the domain reason. Clients switch on reason to -// pick user-facing text; the wire never carries human-readable strings. -func newAPIError(code connect.Code, reason apipb.ErrorReason, metadata map[string]string) error { - e := connect.NewError(code, nil) - if d, err := connect.NewErrorDetail(&apipb.ErrorInfo{Reason: reason, Metadata: metadata}); err == nil { - e.AddDetail(d) - } - return e -} - -var errSpecs = []struct { - err error - code connect.Code - reason apipb.ErrorReason -}{ - {ErrUserNotFound, connect.CodeNotFound, apipb.ErrorReason_ERROR_REASON_USER_NOT_FOUND}, - {ErrOrgNotFound, connect.CodeNotFound, apipb.ErrorReason_ERROR_REASON_ORG_NOT_FOUND}, - {ErrWorkerNotFound, connect.CodeNotFound, apipb.ErrorReason_ERROR_REASON_WORKER_NOT_FOUND}, - {ErrNotMember, connect.CodeNotFound, apipb.ErrorReason_ERROR_REASON_MEMBER_NOT_FOUND}, - {ErrEmailTaken, connect.CodeAlreadyExists, apipb.ErrorReason_ERROR_REASON_EMAIL_TAKEN}, - {ErrSlugTaken, connect.CodeAlreadyExists, apipb.ErrorReason_ERROR_REASON_SLUG_TAKEN}, - {ErrAlreadyMember, connect.CodeAlreadyExists, apipb.ErrorReason_ERROR_REASON_ALREADY_MEMBER}, - {ErrLastOwner, connect.CodeFailedPrecondition, apipb.ErrorReason_ERROR_REASON_LAST_OWNER}, - {ErrSoleOwner, connect.CodeFailedPrecondition, apipb.ErrorReason_ERROR_REASON_SOLE_OWNER}, - {ErrInvalidSlug, connect.CodeInvalidArgument, apipb.ErrorReason_ERROR_REASON_INVALID_SLUG}, - {ErrInvalidRole, connect.CodeInvalidArgument, apipb.ErrorReason_ERROR_REASON_INVALID_ROLE}, - {ErrInvalidDateFormat, connect.CodeInvalidArgument, apipb.ErrorReason_ERROR_REASON_INVALID_DATE_FORMAT}, - {ErrInvalidTimezone, connect.CodeInvalidArgument, apipb.ErrorReason_ERROR_REASON_INVALID_TIMEZONE}, - {ErrReservedEmail, connect.CodeInvalidArgument, apipb.ErrorReason_ERROR_REASON_RESERVED_EMAIL}, - {ErrPermissionDenied, connect.CodePermissionDenied, apipb.ErrorReason_ERROR_REASON_PERMISSION_DENIED}, - {ErrUnauthenticated, connect.CodeUnauthenticated, apipb.ErrorReason_ERROR_REASON_UNAUTHENTICATED}, - {ErrNotImplemented, connect.CodeUnimplemented, apipb.ErrorReason_ERROR_REASON_UNIMPLEMENTED}, -} - -func mapErr(err error) error { - if err == nil { - return nil - } - for _, s := range errSpecs { - if errors.Is(err, s.err) { - return newAPIError(s.code, s.reason, nil) - } - } - slog.Error("unmapped handler error", "err", err) - return newAPIError(connect.CodeInternal, apipb.ErrorReason_ERROR_REASON_INTERNAL, nil) -} - -// --- Ref converters --- - -func userRef(r *apipb.UserRef) (UserRef, error) { - switch v := r.GetRef().(type) { - case *apipb.UserRef_Id: - id, err := IDFromBytes[UserKind](v.Id) - if err != nil { - return UserRef{}, err - } - return UserByID(id), nil - case *apipb.UserRef_Email: - return UserByEmail(v.Email), nil - default: - return UserRef{}, nil - } -} - -func orgRef(r *apipb.OrgRef) (OrgRef, error) { - switch v := r.GetRef().(type) { - case *apipb.OrgRef_Id: - id, err := IDFromBytes[OrgKind](v.Id) - if err != nil { - return OrgRef{}, err - } - return OrgByID(id), nil - case *apipb.OrgRef_Slug: - return OrgBySlug(v.Slug), nil - default: - return OrgRef{}, nil - } -} - -// --- Role converters --- - -var roleToString = map[apipb.Role]string{ - apipb.Role_ROLE_OWNER: "owner", - apipb.Role_ROLE_ADMIN: "admin", - apipb.Role_ROLE_MEMBER: "member", -} - -var roleToProto = map[string]apipb.Role{ - "owner": apipb.Role_ROLE_OWNER, - "admin": apipb.Role_ROLE_ADMIN, - "member": apipb.Role_ROLE_MEMBER, -} - -// --- Page helpers --- - -const ( - defaultPageSize = 50 - maxPageSize = 200 -) - -func pageParams[K IDKind](p *apipb.PageRequest) (cursor ID[K], limit int, err error) { - limit = defaultPageSize - if p != nil { - if p.PageSize > 0 && int(p.PageSize) < maxPageSize { - limit = int(p.PageSize) - } else if int(p.PageSize) >= maxPageSize { - limit = maxPageSize - } - if len(p.Cursor) == 16 { - cursor, err = IDFromBytes[K](p.Cursor) - if err != nil { - return - } - } - } - return -} - -func pageResponse[K IDKind](items int, limit int, lastID ID[K], total int) *apipb.PageResponse { - resp := &apipb.PageResponse{TotalCount: int32(total)} - if items == limit { - resp.NextCursor = lastID.Bytes() - } - return resp -} - -// --- Proto converters --- - -func userToProto(u User) *apipb.User { - pb := &apipb.User{ - Id: u.ID.Bytes(), Email: u.Email, CreatedAt: timestamppb.New(u.CreatedAt), - Timezone: u.Timezone, - } - pb.Locale = &apipb.Locale{Language: u.Locale.Language} - if u.Locale.DateFormat != nil { - df := apipb.DateFormat(*u.Locale.DateFormat) - pb.Locale.DateFormat = &df - } - return pb -} - -func orgToProto(o Organization) *apipb.Org { - return &apipb.Org{ - Id: o.ID.Bytes(), Name: o.Name, Slug: o.Slug, - Public: o.Public, CreatedAt: timestamppb.New(o.CreatedAt), - } -} - -func memberToProto(m OrgMember) *apipb.OrgMemberInfo { - return &apipb.OrgMemberInfo{ - User: userToProto(m.User), Role: roleToProto[m.Role], - JoinedAt: timestamppb.New(m.JoinedAt), - } -} - -func workerToProto(w Worker) *apipb.Worker { - pw := &apipb.Worker{ - Id: w.ID.Bytes(), PublicKey: w.PublicKey, CreatedAt: timestamppb.New(w.CreatedAt), - } - if w.OrgID != nil { - pw.OrgId = w.OrgID.Bytes() - } - return pw -} - -// --- User handlers --- - -func (a *consoleService) UserCreate(ctx context.Context, req *connect.Request[apipb.UserCreateRequest]) (*connect.Response[apipb.UserCreateResponse], error) { - id, err := a.srv.db.UserCreate(ctx, ActorFromContext(ctx), req.Msg.Email, req.Msg.Password, []byte(a.srv.cfg.Pepper)) - if err != nil { - return nil, mapErr(err) - } - return connect.NewResponse(&apipb.UserCreateResponse{Id: id.Bytes()}), nil -} - -func (a *consoleService) UserGet(ctx context.Context, req *connect.Request[apipb.UserGetRequest]) (*connect.Response[apipb.UserGetResponse], error) { - ref, err := userRef(req.Msg.User) - if err != nil { - return nil, mapErr(err) - } - u, err := a.srv.db.UserGet(ctx, ActorFromContext(ctx), ref) - if err != nil { - return nil, mapErr(err) - } - return connect.NewResponse(&apipb.UserGetResponse{User: userToProto(*u)}), nil -} - -func (a *consoleService) UserList(ctx context.Context, req *connect.Request[apipb.UserListRequest]) (*connect.Response[apipb.UserListResponse], error) { - cursor, limit, err := pageParams[UserKind](req.Msg.Page) - if err != nil { - return nil, mapErr(err) - } - filter := "" - if req.Msg.Filter != nil { - filter = *req.Msg.Filter - } - - users, total, err := a.srv.db.UserList(ctx, ActorFromContext(ctx), cursor, limit, filter) - if err != nil { - return nil, mapErr(err) - } - - out := make([]*apipb.User, len(users)) - for i := range users { - out[i] = userToProto(users[i]) - } - - var lastID UserID - if len(users) > 0 { - lastID = users[len(users)-1].ID - } - - return connect.NewResponse(&apipb.UserListResponse{ - Page: pageResponse(len(users), limit, lastID, total), - Users: out, - }), nil -} - -func (a *consoleService) UserUpdate(ctx context.Context, req *connect.Request[apipb.UserUpdateRequest]) (*connect.Response[apipb.UserUpdateResponse], error) { - ref, err := userRef(req.Msg.User) - if err != nil { - return nil, mapErr(err) - } - p := UserUpdateParams{ - Email: req.Msg.Email, - Password: req.Msg.Password, - Timezone: req.Msg.Timezone, - } - if req.Msg.Locale != nil { - p.Locale = &Locale{Language: req.Msg.Locale.Language} - if req.Msg.Locale.DateFormat != nil { - df := DateFormat(*req.Msg.Locale.DateFormat) - p.Locale.DateFormat = &df - } - } - if err := a.srv.db.UserUpdate(ctx, ActorFromContext(ctx), ref, p, []byte(a.srv.cfg.Pepper)); err != nil { - return nil, mapErr(err) - } - return connect.NewResponse(&apipb.UserUpdateResponse{}), nil -} - -func (a *consoleService) UserDelete(ctx context.Context, req *connect.Request[apipb.UserDeleteRequest]) (*connect.Response[apipb.UserDeleteResponse], error) { - ref, err := userRef(req.Msg.User) - if err != nil { - return nil, mapErr(err) - } - if err := a.srv.db.UserDelete(ctx, ActorFromContext(ctx), ref); err != nil { - return nil, mapErr(err) - } - return connect.NewResponse(&apipb.UserDeleteResponse{}), nil -} - -// --- Org handlers --- - -func (a *consoleService) OrgCreate(ctx context.Context, req *connect.Request[apipb.OrgCreateRequest]) (*connect.Response[apipb.OrgCreateResponse], error) { - slug, err := ValidateSlug(req.Msg.Slug) - if err != nil { - return nil, mapErr(err) - } - owner, err := userRef(req.Msg.Owner) - if err != nil { - return nil, mapErr(err) - } - id, err := a.srv.db.OrgCreate(ctx, ActorFromContext(ctx), req.Msg.Name, slug, req.Msg.Public, owner) - if err != nil { - return nil, mapErr(err) - } - return connect.NewResponse(&apipb.OrgCreateResponse{Id: id.Bytes()}), nil -} - -func (a *consoleService) OrgGet(ctx context.Context, req *connect.Request[apipb.OrgGetRequest]) (*connect.Response[apipb.OrgGetResponse], error) { - ref, err := orgRef(req.Msg.Org) - if err != nil { - return nil, mapErr(err) - } - o, err := a.srv.db.OrgGet(ctx, ActorFromContext(ctx), ref) - if err != nil { - return nil, mapErr(err) - } - return connect.NewResponse(&apipb.OrgGetResponse{Org: orgToProto(*o)}), nil -} - -func (a *consoleService) OrgList(ctx context.Context, req *connect.Request[apipb.OrgListRequest]) (*connect.Response[apipb.OrgListResponse], error) { - cursor, limit, err := pageParams[OrgKind](req.Msg.Page) - if err != nil { - return nil, mapErr(err) - } - filter := "" - if req.Msg.Filter != nil { - filter = *req.Msg.Filter - } - - orgs, total, err := a.srv.db.OrgList(ctx, ActorFromContext(ctx), cursor, limit, filter) - if err != nil { - return nil, mapErr(err) - } - - out := make([]*apipb.Org, len(orgs)) - for i := range orgs { - out[i] = orgToProto(orgs[i]) - } - - var lastID OrgID - if len(orgs) > 0 { - lastID = orgs[len(orgs)-1].ID - } - - return connect.NewResponse(&apipb.OrgListResponse{ - Page: pageResponse(len(orgs), limit, lastID, total), - Organizations: out, - }), nil -} - -func (a *consoleService) OrgUpdate(ctx context.Context, req *connect.Request[apipb.OrgUpdateRequest]) (*connect.Response[apipb.OrgUpdateResponse], error) { - var slug *string - if req.Msg.Slug != nil { - s, err := ValidateSlug(*req.Msg.Slug) - if err != nil { - return nil, mapErr(err) - } - slug = &s - } - ref, err := orgRef(req.Msg.Org) - if err != nil { - return nil, mapErr(err) - } - if err := a.srv.db.OrgUpdate(ctx, ActorFromContext(ctx), ref, req.Msg.Name, slug, req.Msg.Public); err != nil { - return nil, mapErr(err) - } - return connect.NewResponse(&apipb.OrgUpdateResponse{}), nil -} - -func (a *consoleService) OrgDelete(ctx context.Context, req *connect.Request[apipb.OrgDeleteRequest]) (*connect.Response[apipb.OrgDeleteResponse], error) { - ref, err := orgRef(req.Msg.Org) - if err != nil { - return nil, mapErr(err) - } - if err := a.srv.db.OrgDelete(ctx, ActorFromContext(ctx), ref); err != nil { - return nil, mapErr(err) - } - return connect.NewResponse(&apipb.OrgDeleteResponse{}), nil -} - -// --- OrgMember handlers --- - -func (a *consoleService) OrgMemberAdd(ctx context.Context, req *connect.Request[apipb.OrgMemberAddRequest]) (*connect.Response[apipb.OrgMemberAddResponse], error) { - role, ok := roleToString[req.Msg.Role] - if !ok { - return nil, newAPIError(connect.CodeInvalidArgument, apipb.ErrorReason_ERROR_REASON_INVALID_ROLE, nil) - } - org, err := orgRef(req.Msg.Org) - if err != nil { - return nil, mapErr(err) - } - user, err := userRef(req.Msg.User) - if err != nil { - return nil, mapErr(err) - } - if err := a.srv.db.OrgMemberAdd(ctx, ActorFromContext(ctx), org, user, role); err != nil { - return nil, mapErr(err) - } - return connect.NewResponse(&apipb.OrgMemberAddResponse{}), nil -} - -func (a *consoleService) OrgMemberGet(ctx context.Context, req *connect.Request[apipb.OrgMemberGetRequest]) (*connect.Response[apipb.OrgMemberGetResponse], error) { - org, err := orgRef(req.Msg.Org) - if err != nil { - return nil, mapErr(err) - } - user, err := userRef(req.Msg.User) - if err != nil { - return nil, mapErr(err) - } - m, err := a.srv.db.OrgMemberGet(ctx, ActorFromContext(ctx), org, user) - if err != nil { - return nil, mapErr(err) - } - return connect.NewResponse(&apipb.OrgMemberGetResponse{Member: memberToProto(*m)}), nil -} - -func (a *consoleService) OrgMemberList(ctx context.Context, req *connect.Request[apipb.OrgMemberListRequest]) (*connect.Response[apipb.OrgMemberListResponse], error) { - cursor, limit, err := pageParams[UserKind](req.Msg.Page) - if err != nil { - return nil, mapErr(err) - } - filter := "" - if req.Msg.Filter != nil { - filter = *req.Msg.Filter - } - org, err := orgRef(req.Msg.Org) - if err != nil { - return nil, mapErr(err) - } - - members, total, err := a.srv.db.OrgMembersList(ctx, ActorFromContext(ctx), org, cursor, limit, filter) - if err != nil { - return nil, mapErr(err) - } - - out := make([]*apipb.OrgMemberInfo, len(members)) - for i := range members { - out[i] = memberToProto(members[i]) - } - - var lastID UserID - if len(members) > 0 { - lastID = members[len(members)-1].User.ID - } - - return connect.NewResponse(&apipb.OrgMemberListResponse{ - Page: pageResponse(len(members), limit, lastID, total), - Members: out, - }), nil -} - -func (a *consoleService) OrgMemberUpdate(ctx context.Context, req *connect.Request[apipb.OrgMemberUpdateRequest]) (*connect.Response[apipb.OrgMemberUpdateResponse], error) { - role, ok := roleToString[req.Msg.Role] - if !ok { - return nil, newAPIError(connect.CodeInvalidArgument, apipb.ErrorReason_ERROR_REASON_INVALID_ROLE, nil) - } - org, err := orgRef(req.Msg.Org) - if err != nil { - return nil, mapErr(err) - } - user, err := userRef(req.Msg.User) - if err != nil { - return nil, mapErr(err) - } - if err := a.srv.db.OrgMemberUpdateRole(ctx, ActorFromContext(ctx), org, user, role); err != nil { - return nil, mapErr(err) - } - return connect.NewResponse(&apipb.OrgMemberUpdateResponse{}), nil -} - -func (a *consoleService) OrgMemberRemove(ctx context.Context, req *connect.Request[apipb.OrgMemberRemoveRequest]) (*connect.Response[apipb.OrgMemberRemoveResponse], error) { - org, err := orgRef(req.Msg.Org) - if err != nil { - return nil, mapErr(err) - } - user, err := userRef(req.Msg.User) - if err != nil { - return nil, mapErr(err) - } - if err := a.srv.db.OrgMemberRemove(ctx, ActorFromContext(ctx), org, user); err != nil { - return nil, mapErr(err) - } - return connect.NewResponse(&apipb.OrgMemberRemoveResponse{}), nil -} - -// --- Worker handlers --- - -func (a *consoleService) WorkerCreate(ctx context.Context, req *connect.Request[apipb.WorkerCreateRequest]) (*connect.Response[apipb.WorkerCreateResponse], error) { - var org *OrgRef - if req.Msg.Org != nil { - r, err := orgRef(req.Msg.Org) - if err != nil { - return nil, mapErr(err) - } - org = &r - } - id, err := a.srv.db.WorkerCreate(ctx, ActorFromContext(ctx), req.Msg.PublicKey, org) - if err != nil { - return nil, mapErr(err) - } - return connect.NewResponse(&apipb.WorkerCreateResponse{Id: id.Bytes()}), nil -} - -func (a *consoleService) WorkerGet(ctx context.Context, req *connect.Request[apipb.WorkerGetRequest]) (*connect.Response[apipb.WorkerGetResponse], error) { - wid, err := IDFromBytes[WorkerKind](req.Msg.Id) - if err != nil { - return nil, mapErr(err) - } - w, err := a.srv.db.WorkerGet(ctx, ActorFromContext(ctx), wid) - if err != nil { - return nil, mapErr(err) - } - return connect.NewResponse(&apipb.WorkerGetResponse{Worker: workerToProto(*w)}), nil -} - -func (a *consoleService) WorkerList(ctx context.Context, req *connect.Request[apipb.WorkerListRequest]) (*connect.Response[apipb.WorkerListResponse], error) { - cursor, limit, err := pageParams[WorkerKind](req.Msg.Page) - if err != nil { - return nil, mapErr(err) - } - filter := "" - if req.Msg.Filter != nil { - filter = *req.Msg.Filter - } - - workers, total, err := a.srv.db.WorkerList(ctx, ActorFromContext(ctx), cursor, limit, filter) - if err != nil { - return nil, mapErr(err) - } - - out := make([]*apipb.Worker, len(workers)) - for i := range workers { - out[i] = workerToProto(workers[i]) - } - - var lastID WorkerID - if len(workers) > 0 { - lastID = workers[len(workers)-1].ID - } - - return connect.NewResponse(&apipb.WorkerListResponse{ - Page: pageResponse(len(workers), limit, lastID, total), - Workers: out, - }), nil -} - -func (a *consoleService) WorkerDelete(ctx context.Context, req *connect.Request[apipb.WorkerDeleteRequest]) (*connect.Response[apipb.WorkerDeleteResponse], error) { - wid, err := IDFromBytes[WorkerKind](req.Msg.Id) - if err != nil { - return nil, mapErr(err) - } - if err := a.srv.db.WorkerDelete(ctx, ActorFromContext(ctx), wid); err != nil { - return nil, mapErr(err) - } - return connect.NewResponse(&apipb.WorkerDeleteResponse{}), nil -} diff --git a/cmd/mirum-server/server_grpc.go b/cmd/mirum-server/server_grpc.go deleted file mode 100644 --- a/cmd/mirum-server/server_grpc.go +++ /dev/null @@ -1,123 +0,0 @@ -// SPDX-FileCopyrightText: 2026 Nikolay Govorov -// SPDX-License-Identifier: AGPL-3.0-or-later - -package main - -import ( - "context" - "crypto/ed25519" - "crypto/tls" - "crypto/x509" - "errors" - "fmt" - "log/slog" - "net" - "net/http" - "time" - - "connectrpc.com/connect" - "connectrpc.com/validate" - - "dimidiumlabs/mirum/internal/config" - "dimidiumlabs/mirum/internal/protocol" - "dimidiumlabs/mirum/internal/protocol/wirepb" - "dimidiumlabs/mirum/internal/protocol/wirepb/wirepbconnect" -) - -func NewGrpcServer(ctx context.Context, srv *server) *http.Server { - gsrv := &grpcService{srv: srv} - - path, handler := wirepbconnect.NewWorkerHandler(gsrv, - connect.WithInterceptors(validate.NewInterceptor()), - ) - - mux := http.NewServeMux() - mux.Handle(path, workerLog(handler)) - - certs := newCertReloader(srv.cfg.GrpcTls.Cert, srv.cfg.GrpcTls.Key) - - return &http.Server{ - Handler: mux, - BaseContext: func(_ net.Listener) context.Context { - return ctx - }, - TLSConfig: &tls.Config{ - NextProtos: []string{"h2"}, - MinVersion: tls.VersionTLS13, - ClientAuth: tls.RequireAnyClientCert, - GetCertificate: certs.GetCertificate, - VerifyPeerCertificate: func(rawCerts [][]byte, _ [][]*x509.Certificate) error { - if len(rawCerts) == 0 { - return errors.New("client certificate required") - } - - c, err := x509.ParseCertificate(rawCerts[0]) - if err != nil { - return fmt.Errorf("parse client cert: %w", err) - } - - pubKey, ok := c.PublicKey.(ed25519.PublicKey) - if !ok { - return errors.New("ed25519 certificate required") - } - - if _, err := srv.db.WorkerLookup(context.Background(), SystemActor(), pubKey); err != nil { - return fmt.Errorf("unknown worker: %w", err) - } - - // Clock skew: NotBefore is set to time.Now() when the cert was generated. - // Checked here (once per TLS handshake), not in the interceptor, - // because HTTP/2 reuses the connection and NotBefore would go stale. - if skew := time.Since(c.NotBefore).Abs(); skew > config.WorkerClockSkewLimit { - return fmt.Errorf("%w: %s", protocol.ErrClockSkew, skew.Truncate(time.Second)) - } - - return nil - }, - }, - } -} - -// grpcService is the ConnectRPC transport adapter over server. -type grpcService struct { - wirepbconnect.UnimplementedWorkerHandler - srv *server -} - -func (g *grpcService) Poll(ctx context.Context, req *connect.Request[wirepb.PollRequest]) (*connect.Response[wirepb.Task], error) { - select { - case task, ok := <-g.srv.queue: - if !ok { - return nil, connect.NewError(connect.CodeUnavailable, fmt.Errorf("server is shutting down")) - } - slog.Info("task dispatched", "id", task.Id, "repo", task.RepoFullName) - return connect.NewResponse(task), nil - case <-ctx.Done(): - return nil, ctx.Err() - } -} - -func (g *grpcService) Complete(ctx context.Context, req *connect.Request[wirepb.TaskResult]) (*connect.Response[wirepb.CompleteResponse], error) { - if err := g.srv.complete(ctx, req.Msg.TaskId, req.Msg.Success, req.Msg.Error); err != nil { - return nil, err - } - return connect.NewResponse(&wirepb.CompleteResponse{}), nil -} - -// workerLog logs worker metadata from the mTLS client certificate -// and sets the server version response header. -func workerLog(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.TLS != nil && len(r.TLS.PeerCertificates) > 0 { - if meta := protocol.ParseWorkerMeta(r.TLS.PeerCertificates[0]); meta != nil { - slog.Info("worker request", - "name", meta.Name, - "version", meta.Version, - "path", r.URL.Path, - ) - } - } - w.Header().Set("X-Server-Version", protocol.VersionString()) - next.ServeHTTP(w, r) - }) -} diff --git a/cmd/mirum-server/server_web.go b/cmd/mirum-server/server_web.go deleted file mode 100644 --- a/cmd/mirum-server/server_web.go +++ /dev/null @@ -1,396 +0,0 @@ -// SPDX-FileCopyrightText: 2026 Nikolay Govorov -// SPDX-License-Identifier: AGPL-3.0-or-later - -package main - -import ( - "context" - "crypto/rand" - "crypto/subtle" - "crypto/tls" - "encoding/base64" - "errors" - "io" - "log/slog" - "net" - "net/http" - "runtime/debug" - "strings" - - "github.com/go-chi/chi/v5" - "github.com/go-chi/chi/v5/middleware" - "github.com/go-chi/httprate" - - "dimidiumlabs/mirum/cmd/mirum-server/apipb" - "dimidiumlabs/mirum/internal/config" - "dimidiumlabs/mirum/internal/forges" -) - -// __Host- prefixed cookies can only be set with Secure, Path=/, and no -// Domain attribute. Browsers silently reject violations, so subdomain and -// network attackers cannot forge them. -const ( - sessionCookie = "__Host-session" - csrfCookie = "__Host-csrf" -) - -func NewWebServer(ctx context.Context, srv *server, consolePath string, consoleHandler http.Handler) *http.Server { - h := &webHandler{ - srv: srv, - assets: newAssetResolver(), - } - - r := chi.NewRouter() - - r.Use(middleware.CleanPath) - r.Use(middleware.StripSlashes) - r.Use(middleware.RequestID) - r.Use(middleware.Logger) - r.Use(h.recoverer) - r.Use(middleware.Compress(5)) - r.Use(middleware.Heartbeat("/ping")) - r.Use(middleware.Timeout(config.WebRequestTimeout)) - r.Use(middleware.RequestSize(config.WebMaxBodyBytes)) - r.Use(trustedProxyMiddleware(srv.cfg.TrustedProxies)) - - r.Use(func(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Security-Policy", csp) - w.Header().Set("X-Content-Type-Options", "nosniff") - w.Header().Set("Referrer-Policy", "no-referrer") - w.Header().Set("Cross-Origin-Opener-Policy", "same-origin") - w.Header().Set("Cross-Origin-Resource-Policy", "same-origin") - w.Header().Set("Permissions-Policy", "accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()") - - if srv.cfg.WebTls != nil { - w.Header().Set("Strict-Transport-Security", "max-age=63072000; includeSubDomains; preload") - } - - next.ServeHTTP(w, r) - }) - }) - - // The authorization session sets the user to ctx - r.Use(h.SessionMiddleware) - - r.With(middleware.SetHeader("Cache-Control", "public, max-age=31536000, immutable")). - Mount("/assets", assetsHandler()) - - r.Get("/", authonly(h.index)) - r.Get("/about/licenses", h.licenses) - r.Post("/webhook", h.webhook) - - r.Route("/auth", func(r chi.Router) { - r.Use(middleware.NoCache) - r.Use(httprate.LimitByIP(config.AuthRateLimit, config.AuthRateWindow)) - r.Use(middleware.RequestSize(config.AuthMaxBodyBytes)) - - r.Get("/login", h.loginPage) - r.Post("/login", h.login) - r.Post("/logout", h.logout) - }) - - r.With(middleware.NoCache, httprate.LimitByIP(config.APIRateLimit, config.APIRateWindow)). - Mount("/api/v1", http.StripPrefix("/api/v1", consoleHandler)) - - r.NotFound(func(w http.ResponseWriter, r *http.Request) { - h.renderError(w, r, http.StatusNotFound) - }) - r.MethodNotAllowed(func(w http.ResponseWriter, r *http.Request) { - h.renderError(w, r, http.StatusMethodNotAllowed) - }) - - var tlsCfg *tls.Config - if srv.cfg.WebTls != nil { - certs := newCertReloader(srv.cfg.WebTls.Cert, srv.cfg.WebTls.Key) - tlsCfg = &tls.Config{ - MinVersion: tls.VersionTLS13, - GetCertificate: certs.GetCertificate, - } - } - - return &http.Server{ - Handler: r, - TLSConfig: tlsCfg, - BaseContext: func(_ net.Listener) context.Context { - return ctx - }, - } -} - -type actorKey struct{} - -type webHandler struct { - srv *server - assets *assetResolver -} - -// ActorFromContext returns the authenticated actor, or AnonActor if none. -func ActorFromContext(ctx context.Context) Actor { - if v, ok := ctx.Value(actorKey{}).(Actor); ok { - return v - } - return AnonActor() -} - -// SessionMiddleware resolves the session cookie and puts the Actor in context. -func (h *webHandler) SessionMiddleware(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if c, err := r.Cookie(sessionCookie); err == nil { - if actor, err := h.srv.db.UserSessionGet(r.Context(), SystemActor(), c.Value); err == nil { - ctx := context.WithValue(r.Context(), actorKey{}, actor) - r = r.WithContext(ctx) - } - } - next.ServeHTTP(w, r) - }) -} - -type authedHandler func(w http.ResponseWriter, r *http.Request, actor Actor) - -func authonly(next authedHandler) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - actor := ActorFromContext(r.Context()) - if actor.Kind() == KindAnon { - http.Redirect(w, r, "/auth/login", http.StatusSeeOther) - return - } - next(w, r, actor) - } -} - -func (h *webHandler) index(w http.ResponseWriter, r *http.Request, actor Actor) { - h.assets.renderPage(w, "dashboard", http.StatusOK, map[string]any{ - "user": map[string]string{"email": actor.Email()}, - "csrf": csrfToken(w, r), - }) -} - -// renderError serves the error page with the given HTTP status. -func (h *webHandler) renderError(w http.ResponseWriter, r *http.Request, status int) { - h.assets.renderPage(w, "error", status, map[string]any{"status": status}) -} - -// recoverer catches panics, logs them, and renders the 500 page so the -// client sees something more useful than chi's plaintext default. The -// http.ErrAbortHandler sentinel is re-raised so net/http's server -// machinery can recognise an intentional handler abort. -func (h *webHandler) recoverer(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - defer func() { - rvr := recover() - if rvr == nil { - return - } - if rvr == http.ErrAbortHandler { - panic(rvr) - } - slog.Error("panic", - "err", rvr, - "path", r.URL.Path, - "stack", string(debug.Stack()), - ) - h.renderError(w, r, http.StatusInternalServerError) - }() - next.ServeHTTP(w, r) - }) -} - -func (h *webHandler) webhook(w http.ResponseWriter, r *http.Request) { - body, err := io.ReadAll(r.Body) - if err != nil { - http.Error(w, "read body", http.StatusBadRequest) - return - } - - ev, err := h.srv.forge.Webhook(r, body) - if errors.Is(err, forges.ErrInvalidSignature) { - http.Error(w, "invalid signature", http.StatusUnauthorized) - return - } - if err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - if ev == nil { - w.WriteHeader(http.StatusNoContent) - return - } - - if _, err := h.srv.enqueue(r.Context(), ev); err != nil { - http.Error(w, "server shutting down", http.StatusServiceUnavailable) - return - } - w.WriteHeader(http.StatusAccepted) -} - -func (h *webHandler) loginPage(w http.ResponseWriter, r *http.Request) { - h.renderLogin(w, r, http.StatusOK, apipb.ErrorReason_ERROR_REASON_UNSPECIFIED) -} - -// renderLogin is the single entry point for every login-flow outcome that -// lands back on the login page. Reason == UNSPECIFIED means no error banner. -// No caller writes error text itself — the client maps reason → copy. -func (h *webHandler) renderLogin(w http.ResponseWriter, r *http.Request, status int, reason apipb.ErrorReason) { - data := map[string]any{"csrf": csrfToken(w, r)} - if reason != apipb.ErrorReason_ERROR_REASON_UNSPECIFIED { - data["errorReason"] = int32(reason) - } - h.assets.renderPage(w, "login", status, data) -} - -func (h *webHandler) login(w http.ResponseWriter, r *http.Request) { - if !csrfOK(r) { - clearCookie(w, csrfCookie) - h.renderLogin(w, r, http.StatusForbidden, apipb.ErrorReason_ERROR_REASON_INVALID_CSRF) - return - } - - email := r.FormValue("email") - password := r.FormValue("password") - - userID, err := h.srv.db.UserVerifyPassword(r.Context(), SystemActor(), email, password, []byte(h.srv.cfg.Pepper)) - if err != nil { - h.renderLogin(w, r, http.StatusUnauthorized, apipb.ErrorReason_ERROR_REASON_INVALID_CREDENTIALS) - return - } - - token, err := h.srv.db.UserSessionCreate(r.Context(), SystemActor(), userID) - if err != nil { - slog.Error("create session failed", "err", err) - h.renderLogin(w, r, http.StatusInternalServerError, apipb.ErrorReason_ERROR_REASON_INTERNAL) - return - } - - http.SetCookie(w, &http.Cookie{ - Name: sessionCookie, - Value: token, - Path: "/", - HttpOnly: true, - Secure: true, - SameSite: http.SameSiteLaxMode, - MaxAge: int(config.SessionTTL.Seconds()), - }) - http.Redirect(w, r, "/", http.StatusSeeOther) -} - -func (h *webHandler) logout(w http.ResponseWriter, r *http.Request) { - if !csrfOK(r) { - // Forged logout attempt — ignore silently. Session stays valid, - // user ends up wherever / takes them. - http.Redirect(w, r, "/", http.StatusSeeOther) - return - } - if c, err := r.Cookie(sessionCookie); err == nil { - if err := h.srv.db.UserSessionDelete(r.Context(), SystemActor(), c.Value); err != nil { - slog.Warn("logout: failed to delete session", "err", err) - } - } - clearCookie(w, sessionCookie) - clearCookie(w, csrfCookie) - http.Redirect(w, r, "/auth/login", http.StatusSeeOther) -} - -// csrfToken returns the current CSRF token, setting a cookie if absent. -func csrfToken(w http.ResponseWriter, r *http.Request) string { - if c, err := r.Cookie(csrfCookie); err == nil && c.Value != "" { - return c.Value - } - b := make([]byte, 32) - if _, err := rand.Read(b); err != nil { - panic("crypto/rand failed: " + err.Error()) - } - token := base64.RawURLEncoding.EncodeToString(b) - http.SetCookie(w, &http.Cookie{ - Name: csrfCookie, - Value: token, - Path: "/", - HttpOnly: true, - Secure: true, - SameSite: http.SameSiteStrictMode, - MaxAge: int(config.SessionTTL.Seconds()), - }) - return token -} - -// csrfOK checks that the form field or X-CSRF-Token header matches the -// cookie (double-submit). Form posts use the hidden "csrf" field; API calls -// from the SPA pass the token via the X-CSRF-Token header. -func csrfOK(r *http.Request) bool { - cookie, err := r.Cookie(csrfCookie) - if err != nil || cookie.Value == "" { - return false - } - token := r.FormValue("csrf") - if token == "" { - token = r.Header.Get("X-CSRF-Token") - } - return subtle.ConstantTimeCompare([]byte(cookie.Value), []byte(token)) == 1 -} - -func clearCookie(w http.ResponseWriter, name string) { - http.SetCookie(w, &http.Cookie{ - Name: name, - Value: "", - Path: "/", - HttpOnly: true, - Secure: true, - MaxAge: -1, - }) -} - -// trustedProxyMiddleware resolves the real client IP from X-Forwarded-For, -// walking right-to-left and stopping at the first untrusted hop. -// Empty cidrs = trust RemoteAddr only (safe default). -func trustedProxyMiddleware(cidrs []string) func(http.Handler) http.Handler { - nets := make([]*net.IPNet, 0, len(cidrs)) - for _, c := range cidrs { - _, n, err := net.ParseCIDR(c) - if err != nil { - panic("invalid trusted_proxies CIDR: " + c) - } - nets = append(nets, n) - } - - isTrusted := func(ip net.IP) bool { - for _, n := range nets { - if n.Contains(ip) { - return true - } - } - return false - } - - return func(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if len(nets) == 0 { - next.ServeHTTP(w, r) - return - } - - host, _, _ := net.SplitHostPort(r.RemoteAddr) - ip := net.ParseIP(host) - if ip == nil || !isTrusted(ip) { - // RemoteAddr is not a trusted proxy — use as-is. - next.ServeHTTP(w, r) - return - } - - // Walk X-Forwarded-For right to left. - xff := strings.Split(r.Header.Get("X-Forwarded-For"), ",") - for i := len(xff) - 1; i >= 0; i-- { - candidate := strings.TrimSpace(xff[i]) - ip = net.ParseIP(candidate) - if ip == nil { - break // garbage — stop, don't trust anything further left - } - if !isTrusted(ip) { - r.RemoteAddr = candidate + ":0" - break - } - } - - next.ServeHTTP(w, r) - }) - } -} diff --git a/cmd/mirum-server/static.go b/cmd/mirum-server/static.go deleted file mode 100644 --- a/cmd/mirum-server/static.go +++ /dev/null @@ -1,235 +0,0 @@ -// SPDX-FileCopyrightText: 2026 Nikolay Govorov -// SPDX-License-Identifier: AGPL-3.0-or-later - -package main - -import ( - "crypto/sha512" - "embed" - "encoding/base64" - "encoding/json" - "fmt" - "html/template" - "io/fs" - "log/slog" - "net/http" - "path" - "strings" -) - -//go:embed web/*.html -var templateFS embed.FS - -var shellTmpl = template.Must(template.ParseFS(templateFS, "web/shell.html")) - -type assetRef struct { - Href string - Integrity string // "sha384-BASE64" or empty -} - -type pageAssets struct { - CSS []assetRef // <link rel="stylesheet"> - Scripts []assetRef // <script type="module"> - Preloads []assetRef // <link rel="modulepreload"> - Preamble string // inline <script type="module"> body, dev only - - // Full Content-Security-Policy header for this entry, with hash-based script-src. - // Empty in dev mode; renderPage falls back to the global csp in that case. - CSP string -} - -func buildCSP(scripts, preloads, css []assetRef) string { - var sb strings.Builder - sb.WriteString(cspBase) - - writeHashes := func(refs []assetRef) { - for _, r := range refs { - if r.Integrity == "" { - continue - } - sb.WriteString(" '") - sb.WriteString(r.Integrity) - sb.WriteString("'") - } - } - - sb.WriteString("; script-src") - writeHashes(scripts) - writeHashes(preloads) - - // style-src hash-sources for external stylesheets are inconsistently - // supported (Firefox blocks them even when the hash matches). Allow - // same-origin loading and rely on the integrity="" attribute rendered - // into the <link> tag for tamper detection. - sb.WriteString("; style-src 'self'") - writeHashes(css) - - return sb.String() -} - -type assetResolver struct { - assets map[string]pageAssets // keyed by entry name (e.g. "dashboard") -} - -func newAssetResolver() *assetResolver { - if viteDevURL != "" { - return &assetResolver{} - } - - if len(manifestJSON) == 0 { - slog.Warn("vite manifest not found, frontend assets unavailable") - return &assetResolver{assets: map[string]pageAssets{}} - } - - type viteManifestEntry struct { - Name string `json:"name"` - File string `json:"file"` - Src string `json:"src"` - CSS []string `json:"css"` - Imports []string `json:"imports"` - IsEntry bool `json:"isEntry"` - } - - var manifest map[string]viteManifestEntry - if err := json.Unmarshal(manifestJSON, &manifest); err != nil { - slog.Error("failed to parse vite manifest", "err", err) - return &assetResolver{assets: map[string]pageAssets{}} - } - - refOf := func(file string) assetRef { - integrity := "" - - data, err := assetsFS.ReadFile("static/" + file) - if err != nil { - slog.Error("asset not found for integrity hash", "file", file, "err", err) - } else { - sum := sha512.Sum384(data) - integrity = "sha384-" + base64.StdEncoding.EncodeToString(sum[:]) - } - - return assetRef{Href: "/" + file, Integrity: integrity} - } - - assets := make(map[string]pageAssets) - for key, entry := range manifest { - if !entry.IsEntry { - continue - } - - // Entry key is e.g. "entries/dashboard.tsx", derive "dashboard". - name := strings.TrimSuffix(path.Base(key), path.Ext(key)) - - seen := make(map[string]bool) - var scripts, preloads, css []assetRef - - var walk func(keys []string) - walk = func(keys []string) { - for _, k := range keys { - if seen[k] { - continue - } - seen[k] = true - chunk, ok := manifest[k] - if !ok { - continue - } - if chunk.IsEntry { - scripts = append(scripts, refOf(chunk.File)) - } else { - preloads = append(preloads, refOf(chunk.File)) - } - for _, c := range chunk.CSS { - if seen[c] { - continue - } - seen[c] = true - css = append(css, refOf(c)) - } - walk(chunk.Imports) - } - } - walk([]string{key}) - - assets[name] = pageAssets{ - Scripts: scripts, - Preloads: preloads, - CSS: css, - CSP: buildCSP(scripts, preloads, css), - } - } - - return &assetResolver{assets: assets} -} - -// resolve returns page assets for the given entry name. Integrity is omitted -// in dev mode because Vite's HMR mutates file contents between requests. -func (ar *assetResolver) resolve(name string) pageAssets { - if viteDevURL != "" { - return pageAssets{ - Scripts: []assetRef{ - {Href: viteDevURL + "/@vite/client"}, - {Href: viteDevURL + "/entries/" + name + ".tsx"}, - }, - // @vitejs/plugin-react requires this preamble to install the - // React Refresh hooks before any component module runs. Vite - // auto-injects it when it serves HTML, so we reproduce it here. - Preamble: `import RefreshRuntime from "` + viteDevURL + `/@react-refresh" -RefreshRuntime.injectIntoGlobalHook(window) -window.$RefreshReg$ = () => {} -window.$RefreshSig$ = () => (type) => type -window.__vite_plugin_react_preamble_installed__ = true`, - } - } - return ar.assets[name] -} - -func (ar *assetResolver) renderPage(w http.ResponseWriter, entry string, status int, data any) { - dataJSON, err := json.Marshal(data) - if err != nil { - http.Error(w, "internal error", http.StatusInternalServerError) - return - } - - assets := ar.resolve(entry) - - w.Header().Set("Content-Type", "text/html; charset=utf-8") - w.Header().Set("Cache-Control", "no-cache") - if assets.CSP != "" { - w.Header().Set("Content-Security-Policy", assets.CSP) - } - if status != 0 && status != http.StatusOK { - w.WriteHeader(status) - } - - if err := shellTmpl.Execute(w, struct { - DataJSON template.JS - Scripts []assetRef - Preloads []assetRef - CSS []assetRef - Preamble template.JS - }{ - DataJSON: template.JS(dataJSON), - Scripts: assets.Scripts, - Preloads: assets.Preloads, - CSS: assets.CSS, - Preamble: template.JS(assets.Preamble), - }); err != nil { - http.Error(w, "internal error", http.StatusInternalServerError) - } -} - -func assetsHandler() http.Handler { - sub, err := fs.Sub(assetsFS, "static") - if err != nil { - panic(fmt.Sprintf("static fs: %v", err)) - } - - fileServer := http.FileServer(http.FS(sub)) - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if strings.HasSuffix(r.URL.Path, "/") { - http.NotFound(w, r) - return - } - fileServer.ServeHTTP(w, r) - }) -} diff --git a/cmd/mirum-server/static_dev.go b/cmd/mirum-server/static_dev.go deleted file mode 100644 --- a/cmd/mirum-server/static_dev.go +++ /dev/null @@ -1,36 +0,0 @@ -// SPDX-FileCopyrightText: 2026 Nikolay Govorov -// SPDX-License-Identifier: AGPL-3.0-or-later - -//go:build dev - -package main - -import "embed" - -// viteDevURL points the HTML shell at a running Vite dev server when the -// binary is built with `go build -tags dev`. Hot module replacement and -// source modules are fetched from there instead of the embedded static/. -const viteDevWs = "ws://localhost:5173" -const viteDevURL = "http://localhost:5173" - -const cspBase = "default-src 'self'; " + - "base-uri 'none'; " + - "img-src 'self' data: " + viteDevURL + "; " + - "font-src 'self' " + viteDevURL + "; " + - "style-src-attr 'unsafe-inline'; " + - "object-src 'none'; " + - "connect-src 'self' " + viteDevURL + " " + viteDevWs + "; " + - "form-action 'self'; " + - "frame-ancestors 'none'" - -// csp applied globally to every response as a safety net. -// For HTML pages it is overridden per-entry in renderPage. -// 'unsafe-inline' is necessary for the @vitejs/plugin-react HMR preamble -// that renderPage injects inline in dev mode. -const csp = cspBase + - "; script-src 'self' 'unsafe-inline' " + viteDevURL + - "; style-src 'self' 'unsafe-inline' " + viteDevURL - -// Empty in dev builds: assets are served by the Vite dev server. -var manifestJSON []byte -var assetsFS embed.FS diff --git a/cmd/mirum-server/static_prod.go b/cmd/mirum-server/static_prod.go deleted file mode 100644 --- a/cmd/mirum-server/static_prod.go +++ /dev/null @@ -1,32 +0,0 @@ -// SPDX-FileCopyrightText: 2026 Nikolay Govorov -// SPDX-License-Identifier: AGPL-3.0-or-later - -//go:build !dev - -package main - -import "embed" - -// viteDevURL is empty in production builds: the HTML shell loads hashed -// assets from the embedded static/ directory via the Vite manifest. -const viteDevURL = "" - -const cspBase = "default-src 'self'; " + - "base-uri 'none'; " + - "img-src 'self' data:; " + - "font-src 'self'; " + - "style-src-attr 'unsafe-inline'; " + - "object-src 'none'; " + - "connect-src 'self'; " + - "form-action 'self'; " + - "frame-ancestors 'none'" - -// csp applied globally to every response as a safety net. -// For HTML pages it is overridden per-entry in renderPage. -const csp = cspBase + "; script-src 'self'; style-src 'self'" - -//go:embed static/.vite/manifest.json -var manifestJSON []byte - -//go:embed static/assets -var assetsFS embed.FS diff --git a/cmd/mirum-server/web/.prettierignore b/cmd/mirum-server/web/.prettierignore deleted file mode 100644 --- a/cmd/mirum-server/web/.prettierignore +++ /dev/null @@ -1,8 +0,0 @@ -# SPDX-FileCopyrightText: 2026 Nikolay Govorov -# SPDX-License-Identifier: AGPL-3.0-or-later - -/node_modules -/gen - -# it's a go template, not just a html -shell.html diff --git a/cmd/mirum-server/web/api/client.ts b/cmd/mirum-server/web/api/client.ts deleted file mode 100644 --- a/cmd/mirum-server/web/api/client.ts +++ /dev/null @@ -1,36 +0,0 @@ -// SPDX-FileCopyrightText: 2026 Nikolay Govorov -// SPDX-License-Identifier: AGPL-3.0-or-later - -import { createClient, type Interceptor } from "@connectrpc/connect"; -import { createConnectTransport } from "@connectrpc/connect-web"; -import { Console, ErrorReason } from "@/gen/api_pb"; -import { errorReason } from "@/lib/errors"; - -const csrfInterceptor = - (csrfToken: string): Interceptor => - (next) => - async (req) => { - req.header.set("X-CSRF-Token", csrfToken); - return next(req); - }; - -// authInterceptor redirects to /auth/login on Unauthenticated so individual -// callers don't need to handle session expiry explicitly. -const authInterceptor: Interceptor = (next) => async (req) => { - try { - return await next(req); - } catch (err) { - if (errorReason(err) === ErrorReason.UNAUTHENTICATED) { - window.location.assign("/auth/login"); - } - throw err; - } -}; - -export function createConsoleClient(csrfToken: string) { - const transport = createConnectTransport({ - baseUrl: "/api/v1", - interceptors: [csrfInterceptor(csrfToken), authInterceptor], - }); - return createClient(Console, transport); -} diff --git a/cmd/mirum-server/web/components.json b/cmd/mirum-server/web/components.json deleted file mode 100644 --- a/cmd/mirum-server/web/components.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "$schema": "https://ui.shadcn.com/schema.json", - "style": "radix-vega", - "tsx": true, - "rtl": true, - "rsc": false, - "tailwind": { - "config": "", - "css": "index.css", - "baseColor": "neutral", - "cssVariables": true, - "prefix": "" - }, - "registries": {}, - "menuColor": "default", - "menuAccent": "subtle", - "iconLibrary": "lucide", - "aliases": { - "components": "@/components", - "utils": "@/lib/utils", - "ui": "@/components/ui", - "lib": "@/lib", - "hooks": "@/hooks" - } -} diff --git a/cmd/mirum-server/web/components/pages/dashboard.tsx b/cmd/mirum-server/web/components/pages/dashboard.tsx deleted file mode 100644 --- a/cmd/mirum-server/web/components/pages/dashboard.tsx +++ /dev/null @@ -1,74 +0,0 @@ -// SPDX-FileCopyrightText: 2026 Nikolay Govorov -// SPDX-License-Identifier: AGPL-3.0-or-later - -import { useEffect, useMemo, useState } from "react"; -import { createConsoleClient } from "@/api/client"; -import { formatError } from "@/lib/errors"; -import type { Org } from "@/gen/api_pb"; -import { Button } from "@/components/ui/button"; -import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; - -export interface DashboardProps { - user: { email: string }; - csrf: string; -} - -export function Page({ user, csrf }: DashboardProps) { - const client = useMemo(() => createConsoleClient(csrf), [csrf]); - const [orgs, setOrgs] = useState<Org[] | null>(null); - const [error, setError] = useState<string | null>(null); - - useEffect(() => { - const ac = new AbortController(); - client - .orgList({}, { signal: ac.signal }) - .then((res) => setOrgs(res.organizations)) - .catch((err: unknown) => { - if (ac.signal.aborted) return; - setError(formatError(err)); - }); - return () => ac.abort(); - }, [client]); - - return ( - <div className="mx-auto max-w-3xl p-8 space-y-6"> - <header className="flex items-center justify-between"> - <h1 className="text-2xl font-semibold">Mirum</h1> - <div className="flex items-center gap-3"> - <span className="text-sm text-muted-foreground">{user.email}</span> - <form method="POST" action="/auth/logout"> - <input type="hidden" name="csrf" value={csrf} /> - <Button type="submit" variant="outline" size="sm"> - Sign out - </Button> - </form> - </div> - </header> - - <Card> - <CardHeader> - <CardTitle>Organizations</CardTitle> - </CardHeader> - <CardContent> - {error && <p className="text-destructive">{error}</p>} - {orgs === null && !error && ( - <p className="text-muted-foreground">Loading…</p> - )} - {orgs?.length === 0 && ( - <p className="text-muted-foreground">No organizations yet.</p> - )} - {orgs && orgs.length > 0 && ( - <ul className="divide-y"> - {orgs.map((org) => ( - <li key={org.slug} className="py-2"> - <span className="font-medium">{org.name}</span>{" "} - <span className="text-muted-foreground">({org.slug})</span> - </li> - ))} - </ul> - )} - </CardContent> - </Card> - </div> - ); -} diff --git a/cmd/mirum-server/web/components/pages/error.tsx b/cmd/mirum-server/web/components/pages/error.tsx deleted file mode 100644 --- a/cmd/mirum-server/web/components/pages/error.tsx +++ /dev/null @@ -1,63 +0,0 @@ -// SPDX-FileCopyrightText: 2026 Nikolay Govorov -// SPDX-License-Identifier: AGPL-3.0-or-later - -import { Button } from "@/components/ui/button"; - -export interface ErrorPageProps { - status: number; -} - -const copy: Record<number, { title: string; body: string }> = { - 400: { title: "Bad request", body: "The request couldn't be processed." }, - 401: { title: "Not signed in", body: "Please sign in to continue." }, - 403: { - title: "Forbidden", - body: "You don't have permission to access this page.", - }, - 404: { - title: "Not found", - body: "The page you were looking for doesn't exist.", - }, - 405: { - title: "Method not allowed", - body: "That action isn't supported here.", - }, - 500: { - title: "Something went wrong", - body: "An internal error occurred. Please try again.", - }, - 503: { - title: "Unavailable", - body: "The server is temporarily unable to handle the request.", - }, -}; - -export function Page({ status }: ErrorPageProps) { - const text = copy[status] ?? { - title: `Error ${status}`, - body: "Something went wrong.", - }; - - return ( - <main className="relative flex min-h-svh items-center justify-center overflow-hidden p-6"> - <p - aria-hidden - className="pointer-events-none absolute inset-x-0 top-1/2 hidden -translate-y-1/2 select-none text-center font-bold leading-none tracking-tighter text-foreground/[0.04] text-[clamp(12rem,32vw,24rem)] md:block" - > - {status} - </p> - <div className="relative w-full max-w-sm"> - <p className="text-xs font-medium uppercase tracking-wider text-foreground/60"> - Error {status} - </p> - <h1 className="mt-2 text-2xl font-semibold tracking-tight"> - {text.title} - </h1> - <p className="mt-3 text-sm text-foreground/80">{text.body}</p> - <Button asChild variant="outline" size="sm" className="mt-6"> - <a href="/">Back to home</a> - </Button> - </div> - </main> - ); -} diff --git a/cmd/mirum-server/web/components/pages/licenses.tsx b/cmd/mirum-server/web/components/pages/licenses.tsx deleted file mode 100644 --- a/cmd/mirum-server/web/components/pages/licenses.tsx +++ /dev/null @@ -1,179 +0,0 @@ -// SPDX-FileCopyrightText: 2026 Nikolay Govorov -// SPDX-License-Identifier: AGPL-3.0-or-later - -import { - Accordion, - AccordionContent, - AccordionItem, - AccordionTrigger, -} from "@/components/ui/accordion"; - -interface Dep { - name: string; - version?: string; - spdx: string; - url?: string; - count?: number; // >0 means a collapsed scope entry covering N sub-packages -} - -function depLabel(d: Dep): string { - if (d.count && d.count > 0) { - return `${d.name} (via ${d.count} npm packages)`; - } - return d.version ? `${d.name}@${d.version}` : d.name; -} - -interface Variant { - text: string; - deps: Dep[]; -} - -interface Group { - spdx: string; - total: number; - variants: Variant[]; -} - -interface Ecosystem { - total: number; - groups: Group[]; -} - -export interface LicensesPageProps { - primary: { name: string; spdx: string; text: string }; - manifest: { - generated_at: string; - go: Ecosystem; - npm: Ecosystem; - }; -} - -function slug(s: string): string { - return s.replace(/[^A-Za-z0-9.-]/g, "_"); -} - -/** License renders one variant as an accordion item: the trigger shows the - * comma-separated list of packages sharing this exact LICENSE text; the - * text itself is hidden inside the collapsed content. */ -function License({ - value, - spdx, - variant, -}: { - value: string; - spdx: string; - variant: Variant; -}) { - return ( - <AccordionItem value={value}> - <AccordionTrigger> - <span className="text-start"> - {variant.deps.map((d, i) => { - const outerExpr = d.spdx.replace(/^\((.*)\)$/, "$1"); - return ( - <span key={`${d.name}@${d.version ?? ""}`}> - {i > 0 && ", "} - {depLabel(d)} - {outerExpr !== spdx && ` (${outerExpr})`} - </span> - ); - })} - </span> - </AccordionTrigger> - <AccordionContent> - <pre className="max-h-[280px] overflow-y-scroll border-s-4 border-neutral-800 bg-amber-50 px-4 py-3 text-sm whitespace-pre-wrap dark:border-neutral-500 dark:bg-neutral-800"> - {variant.text} - </pre> - </AccordionContent> - </AccordionItem> - ); -} - -function EcosystemSection({ - title, - data, - prefix, -}: { - title: string; - data: Ecosystem; - prefix: string; -}) { - return ( - <> - <h2 className="mt-6 mb-2 text-2xl font-bold leading-[1.2] text-balance"> - {title} ({data.total}) - </h2> - <p className="my-4 text-pretty"> - {data.total} runtime {title.toLowerCase()} packages across{" "} - {data.groups.length} licenses: - </p> - <ul className="my-4 list-disc ps-6"> - {data.groups.map((g) => ( - <li key={g.spdx} className="my-1"> - <a - href={`#${prefix}-${slug(g.spdx)}`} - className="text-sky-700 hover:underline dark:text-sky-400" - > - {g.spdx} ({g.total}) - </a> - </li> - ))} - </ul> - - {data.groups.map((g) => ( - <div key={g.spdx}> - <h3 - id={`${prefix}-${slug(g.spdx)}`} - className="mt-6 mb-2 text-[1.15rem] font-bold leading-[1.2] text-balance" - > - <a - href={`#${prefix}-${slug(g.spdx)}`} - className="text-inherit hover:underline" - > - {g.spdx} - </a> - </h3> - <Accordion type="multiple"> - {g.variants.map((v, i) => ( - <License - key={`${prefix}-${slug(g.spdx)}-${i}`} - value={`${prefix}-${slug(g.spdx)}-${i}`} - spdx={g.spdx} - variant={v} - /> - ))} - </Accordion> - </div> - ))} - </> - ); -} - -export function Page({ primary, manifest }: LicensesPageProps) { - return ( - <article className="mx-auto max-w-[80ch] px-8 py-6 font-mono leading-[1.4]"> - <h1 className="text-4xl font-bold leading-[1.1] text-balance"> - Licenses - </h1> - - <h2 className="mt-6 mb-2 text-2xl font-bold leading-[1.2] text-balance"> - Mirum - </h2> - - <p className="my-4 text-pretty"> - Mirum is licensed under{" "} - <code className="bg-neutral-200/40 px-[0.4em] py-[0.2em] dark:bg-neutral-700/40"> - {primary.spdx} - </code> - . The full license text is included below. - </p> - - <pre className="my-4 max-h-[280px] overflow-y-scroll border-s-4 border-neutral-800 bg-amber-50 px-4 py-3 text-sm whitespace-pre-wrap dark:border-neutral-500 dark:bg-neutral-800"> - {primary.text} - </pre> - - <EcosystemSection title="Go" data={manifest.go} prefix="go" /> - <EcosystemSection title="NPM" data={manifest.npm} prefix="npm" /> - </article> - ); -} diff --git a/cmd/mirum-server/web/components/pages/login.tsx b/cmd/mirum-server/web/components/pages/login.tsx deleted file mode 100644 --- a/cmd/mirum-server/web/components/pages/login.tsx +++ /dev/null @@ -1,107 +0,0 @@ -// SPDX-FileCopyrightText: 2026 Nikolay Govorov -// SPDX-License-Identifier: AGPL-3.0-or-later - -import { useState } from "react"; -import { GalleryVerticalEnd, AlertCircle } from "lucide-react"; -import { Button } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; -import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; -import { - Field, - FieldDescription, - FieldGroup, - FieldLabel, -} from "@/components/ui/field"; -import { ErrorReason } from "@/gen/api_pb"; -import { textForReason } from "@/lib/errors"; - -export interface LoginFormProps { - csrf: string; - errorReason?: ErrorReason; -} - -export function Page({ csrf, errorReason }: LoginFormProps) { - const [error, setError] = useState( - errorReason !== undefined ? textForReason(errorReason) : undefined, - ); - const dismissError = () => setError(undefined); - - return ( - <div className="flex min-h-svh flex-col items-center justify-center gap-6 bg-muted p-6 md:p-10"> - <div className="flex w-full max-w-sm flex-col gap-6"> - <div className="flex items-center gap-2 self-center font-medium"> - <div className="flex size-6 items-center justify-center rounded-md bg-primary text-primary-foreground"> - <GalleryVerticalEnd className="size-4" /> - </div> - Dimidium Labs Limited - </div> - - <div className={"flex flex-col gap-6"}> - <Card> - <CardHeader className="text-center"> - <CardTitle className="text-xl">Sign In to Mirum</CardTitle> - </CardHeader> - - <CardContent> - <form method="POST" action="/auth/login"> - <input type="hidden" name="csrf" value={csrf} /> - - <FieldGroup> - {error && ( - <Field> - <div - role="alert" - className="flex items-center gap-2 rounded-md border border-destructive/40 bg-destructive/10 px-3 py-2 text-sm text-destructive" - > - <AlertCircle className="size-4 shrink-0" aria-hidden /> - <span>{error}</span> - </div> - </Field> - )} - - <Field> - <FieldLabel htmlFor="email">Email</FieldLabel> - <Input - id="email" - name="email" - type="email" - placeholder="hey@mirum.dev" - required - autoFocus - onInput={dismissError} - /> - </Field> - - <Field> - <FieldLabel htmlFor="password">Password</FieldLabel> - <Input - id="password" - name="password" - type="password" - autoComplete="current-password" - required - onInput={dismissError} - /> - </Field> - - <Field> - <Button type="submit">Login</Button> - - <FieldDescription className="text-center"> - Contact your administrator to sign up. - </FieldDescription> - </Field> - </FieldGroup> - </form> - </CardContent> - </Card> - - <FieldDescription className="px-6 text-center"> - By clicking continue, you agree to our{" "} - <a href="#">Terms of Service</a> and <a href="#">Privacy Policy</a>. - </FieldDescription> - </div> - </div> - </div> - ); -} diff --git a/cmd/mirum-server/web/components/ui/accordion.tsx b/cmd/mirum-server/web/components/ui/accordion.tsx deleted file mode 100644 --- a/cmd/mirum-server/web/components/ui/accordion.tsx +++ /dev/null @@ -1,88 +0,0 @@ -// SPDX-FileCopyrightText: 2023 shadcn -// SPDX-License-Identifier: MIT - -import * as React from "react"; -import { Accordion as AccordionPrimitive } from "radix-ui"; - -import { cn } from "@/lib/utils"; -import { ChevronDownIcon, ChevronUpIcon } from "lucide-react"; - -function Accordion({ - className, - ...props -}: React.ComponentProps<typeof AccordionPrimitive.Root>) { - return ( - <AccordionPrimitive.Root - data-slot="accordion" - className={cn("flex w-full flex-col", className)} - {...props} - /> - ); -} - -function AccordionItem({ - className, - ...props -}: React.ComponentProps<typeof AccordionPrimitive.Item>) { - return ( - <AccordionPrimitive.Item - data-slot="accordion-item" - className={cn("not-last:border-b", className)} - {...props} - /> - ); -} - -function AccordionTrigger({ - className, - children, - ...props -}: React.ComponentProps<typeof AccordionPrimitive.Trigger>) { - return ( - <AccordionPrimitive.Header className="flex"> - <AccordionPrimitive.Trigger - data-slot="accordion-trigger" - className={cn( - "group/accordion-trigger relative flex flex-1 items-start justify-between rounded-md border border-transparent py-4 text-start text-sm font-medium transition-all outline-none hover:underline focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 focus-visible:after:border-ring disabled:pointer-events-none disabled:opacity-50 **:data-[slot=accordion-trigger-icon]:ms-auto **:data-[slot=accordion-trigger-icon]:size-4 **:data-[slot=accordion-trigger-icon]:text-muted-foreground", - className, - )} - {...props} - > - {children} - <ChevronDownIcon - data-slot="accordion-trigger-icon" - className="pointer-events-none shrink-0 group-aria-expanded/accordion-trigger:hidden" - /> - <ChevronUpIcon - data-slot="accordion-trigger-icon" - className="pointer-events-none hidden shrink-0 group-aria-expanded/accordion-trigger:inline" - /> - </AccordionPrimitive.Trigger> - </AccordionPrimitive.Header> - ); -} - -function AccordionContent({ - className, - children, - ...props -}: React.ComponentProps<typeof AccordionPrimitive.Content>) { - return ( - <AccordionPrimitive.Content - data-slot="accordion-content" - className="overflow-hidden text-sm data-open:animate-accordion-down data-closed:animate-accordion-up" - {...props} - > - <div - className={cn( - "h-(--radix-accordion-content-height) pt-0 pb-4 [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground [&_p:not(:last-child)]:mb-4", - className, - )} - > - {children} - </div> - </AccordionPrimitive.Content> - ); -} - -export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }; diff --git a/cmd/mirum-server/web/components/ui/button.tsx b/cmd/mirum-server/web/components/ui/button.tsx deleted file mode 100644 --- a/cmd/mirum-server/web/components/ui/button.tsx +++ /dev/null @@ -1,70 +0,0 @@ -// SPDX-FileCopyrightText: 2023 shadcn -// SPDX-License-Identifier: MIT - -import * as React from "react"; -import { cva, type VariantProps } from "class-variance-authority"; -import { Slot } from "radix-ui"; - -import { cn } from "@/lib/utils"; - -const buttonVariants = cva( - "group/button inline-flex shrink-0 items-center justify-center rounded-md border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", - { - variants: { - variant: { - default: "bg-primary text-primary-foreground hover:bg-primary/80", - outline: - "border-border bg-background shadow-xs hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50", - secondary: - "bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground", - ghost: - "hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50", - destructive: - "bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:hover:bg-destructive/30 dark:focus-visible:ring-destructive/40", - link: "text-primary underline-offset-4 hover:underline", - }, - size: { - default: - "h-9 gap-1.5 px-2.5 in-data-[slot=button-group]:rounded-md has-data-[icon=inline-end]:pe-2 has-data-[icon=inline-start]:ps-2", - xs: "h-6 gap-1 rounded-[min(var(--radius-md),8px)] px-2 text-xs in-data-[slot=button-group]:rounded-md has-data-[icon=inline-end]:pe-1.5 has-data-[icon=inline-start]:ps-1.5 [&_svg:not([class*='size-'])]:size-3", - sm: "h-8 gap-1 rounded-[min(var(--radius-md),10px)] px-2.5 in-data-[slot=button-group]:rounded-md has-data-[icon=inline-end]:pe-1.5 has-data-[icon=inline-start]:ps-1.5", - lg: "h-10 gap-1.5 px-2.5 has-data-[icon=inline-end]:pe-2 has-data-[icon=inline-start]:ps-2", - icon: "size-9", - "icon-xs": - "size-6 rounded-[min(var(--radius-md),8px)] in-data-[slot=button-group]:rounded-md [&_svg:not([class*='size-'])]:size-3", - "icon-sm": - "size-8 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-md", - "icon-lg": "size-10", - }, - }, - defaultVariants: { - variant: "default", - size: "default", - }, - }, -); - -function Button({ - className, - variant = "default", - size = "default", - asChild = false, - ...props -}: React.ComponentProps<"button"> & - VariantProps<typeof buttonVariants> & { - asChild?: boolean; - }) { - const Comp = asChild ? Slot.Root : "button"; - - return ( - <Comp - data-slot="button" - data-variant={variant} - data-size={size} - className={cn(buttonVariants({ variant, size, className }))} - {...props} - /> - ); -} - -export { Button, buttonVariants }; diff --git a/cmd/mirum-server/web/components/ui/card.tsx b/cmd/mirum-server/web/components/ui/card.tsx deleted file mode 100644 --- a/cmd/mirum-server/web/components/ui/card.tsx +++ /dev/null @@ -1,106 +0,0 @@ -// SPDX-FileCopyrightText: 2023 shadcn -// SPDX-License-Identifier: MIT - -import * as React from "react"; - -import { cn } from "@/lib/utils"; - -function Card({ - className, - size = "default", - ...props -}: React.ComponentProps<"div"> & { size?: "default" | "sm" }) { - return ( - <div - data-slot="card" - data-size={size} - className={cn( - "group/card flex flex-col gap-6 overflow-hidden rounded-xl bg-card py-6 text-sm text-card-foreground shadow-xs ring-1 ring-foreground/10 has-[>img:first-child]:pt-0 data-[size=sm]:gap-4 data-[size=sm]:py-4 *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl", - className, - )} - {...props} - /> - ); -} - -function CardHeader({ className, ...props }: React.ComponentProps<"div">) { - return ( - <div - data-slot="card-header" - className={cn( - "group/card-header @container/card-header grid auto-rows-min items-start gap-1 rounded-t-xl px-6 group-data-[size=sm]/card:px-4 has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] [.border-b]:pb-6 group-data-[size=sm]/card:[.border-b]:pb-4", - className, - )} - {...props} - /> - ); -} - -function CardTitle({ className, ...props }: React.ComponentProps<"div">) { - return ( - <div - data-slot="card-title" - className={cn( - "font-heading text-base leading-normal font-medium group-data-[size=sm]/card:text-sm", - className, - )} - {...props} - /> - ); -} - -function CardDescription({ className, ...props }: React.ComponentProps<"div">) { - return ( - <div - data-slot="card-description" - className={cn("text-sm text-muted-foreground", className)} - {...props} - /> - ); -} - -function CardAction({ className, ...props }: React.ComponentProps<"div">) { - return ( - <div - data-slot="card-action" - className={cn( - "col-start-2 row-span-2 row-start-1 self-start justify-self-end", - className, - )} - {...props} - /> - ); -} - -function CardContent({ className, ...props }: React.ComponentProps<"div">) { - return ( - <div - data-slot="card-content" - className={cn("px-6 group-data-[size=sm]/card:px-4", className)} - {...props} - /> - ); -} - -function CardFooter({ className, ...props }: React.ComponentProps<"div">) { - return ( - <div - data-slot="card-footer" - className={cn( - "flex items-center rounded-b-xl px-6 group-data-[size=sm]/card:px-4 [.border-t]:pt-6 group-data-[size=sm]/card:[.border-t]:pt-4", - className, - )} - {...props} - /> - ); -} - -export { - Card, - CardHeader, - CardFooter, - CardTitle, - CardAction, - CardDescription, - CardContent, -}; diff --git a/cmd/mirum-server/web/components/ui/field.tsx b/cmd/mirum-server/web/components/ui/field.tsx deleted file mode 100644 --- a/cmd/mirum-server/web/components/ui/field.tsx +++ /dev/null @@ -1,239 +0,0 @@ -// SPDX-FileCopyrightText: 2023 shadcn -// SPDX-License-Identifier: MIT - -import { useMemo } from "react"; -import { cva, type VariantProps } from "class-variance-authority"; - -import { cn } from "@/lib/utils"; -import { Label } from "@/components/ui/label"; -import { Separator } from "@/components/ui/separator"; - -function FieldSet({ className, ...props }: React.ComponentProps<"fieldset">) { - return ( - <fieldset - data-slot="field-set" - className={cn( - "flex flex-col gap-6 has-[>[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3", - className, - )} - {...props} - /> - ); -} - -function FieldLegend({ - className, - variant = "legend", - ...props -}: React.ComponentProps<"legend"> & { variant?: "legend" | "label" }) { - return ( - <legend - data-slot="field-legend" - data-variant={variant} - className={cn( - "mb-3 font-medium data-[variant=label]:text-sm data-[variant=legend]:text-base", - className, - )} - {...props} - /> - ); -} - -function FieldGroup({ className, ...props }: React.ComponentProps<"div">) { - return ( - <div - data-slot="field-group" - className={cn( - "group/field-group @container/field-group flex w-full flex-col gap-7 data-[slot=checkbox-group]:gap-3 *:data-[slot=field-group]:gap-4", - className, - )} - {...props} - /> - ); -} - -const fieldVariants = cva( - "group/field flex w-full gap-3 data-[invalid=true]:text-destructive", - { - variants: { - orientation: { - vertical: "flex-col *:w-full [&>.sr-only]:w-auto", - horizontal: - "flex-row items-center has-[>[data-slot=field-content]]:items-start *:data-[slot=field-label]:flex-auto has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px", - responsive: - "flex-col *:w-full @md/field-group:flex-row @md/field-group:items-center @md/field-group:*:w-auto @md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:*:data-[slot=field-label]:flex-auto [&>.sr-only]:w-auto @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px", - }, - }, - defaultVariants: { - orientation: "vertical", - }, - }, -); - -function Field({ - className, - orientation = "vertical", - ...props -}: React.ComponentProps<"div"> & VariantProps<typeof fieldVariants>) { - return ( - <div - role="group" - data-slot="field" - data-orientation={orientation} - className={cn(fieldVariants({ orientation }), className)} - {...props} - /> - ); -} - -function FieldContent({ className, ...props }: React.ComponentProps<"div">) { - return ( - <div - data-slot="field-content" - className={cn( - "group/field-content flex flex-1 flex-col gap-1 leading-snug", - className, - )} - {...props} - /> - ); -} - -function FieldLabel({ - className, - ...props -}: React.ComponentProps<typeof Label>) { - return ( - <Label - data-slot="field-label" - className={cn( - "group/field-label peer/field-label flex w-fit gap-2 leading-snug group-data-[disabled=true]/field:opacity-50 has-data-checked:border-primary/30 has-data-checked:bg-primary/5 has-[>[data-slot=field]]:rounded-md has-[>[data-slot=field]]:border *:data-[slot=field]:p-3 dark:has-data-checked:border-primary/20 dark:has-data-checked:bg-primary/10", - "has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col", - className, - )} - {...props} - /> - ); -} - -function FieldTitle({ className, ...props }: React.ComponentProps<"div">) { - return ( - <div - data-slot="field-label" - className={cn( - "flex w-fit items-center gap-2 text-sm leading-snug font-medium group-data-[disabled=true]/field:opacity-50", - className, - )} - {...props} - /> - ); -} - -function FieldDescription({ className, ...props }: React.ComponentProps<"p">) { - return ( - <p - data-slot="field-description" - className={cn( - "text-start text-sm leading-normal font-normal text-muted-foreground group-has-data-horizontal/field:text-balance [[data-variant=legend]+&]:-mt-1.5", - "last:mt-0 nth-last-2:-mt-1", - "[&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary", - className, - )} - {...props} - /> - ); -} - -function FieldSeparator({ - children, - className, - ...props -}: React.ComponentProps<"div"> & { - children?: React.ReactNode; -}) { - return ( - <div - data-slot="field-separator" - data-content={!!children} - className={cn( - "relative -my-2 h-5 text-sm group-data-[variant=outline]/field-group:-mb-2", - className, - )} - {...props} - > - <Separator className="absolute inset-0 top-1/2" /> - {children && ( - <span - className="relative mx-auto block w-fit bg-background px-2 text-muted-foreground" - data-slot="field-separator-content" - > - {children} - </span> - )} - </div> - ); -} - -function FieldError({ - className, - children, - errors, - ...props -}: React.ComponentProps<"div"> & { - errors?: ({ message?: string } | undefined)[]; -}) { - const content = useMemo(() => { - if (children) { - return children; - } - - if (!errors?.length) { - return null; - } - - const uniqueErrors = [ - ...new Map(errors.map((error) => [error?.message, error])).values(), - ]; - - if (uniqueErrors?.length == 1) { - return uniqueErrors[0]?.message; - } - - return ( - <ul className="ms-4 flex list-disc flex-col gap-1"> - {uniqueErrors.map( - (error, index) => - error?.message && <li key={index}>{error.message}</li>, - )} - </ul> - ); - }, [children, errors]); - - if (!content) { - return null; - } - - return ( - <div - role="alert" - data-slot="field-error" - className={cn("text-sm font-normal text-destructive", className)} - {...props} - > - {content} - </div> - ); -} - -export { - Field, - FieldLabel, - FieldDescription, - FieldError, - FieldGroup, - FieldLegend, - FieldSeparator, - FieldSet, - FieldContent, - FieldTitle, -}; diff --git a/cmd/mirum-server/web/components/ui/input.tsx b/cmd/mirum-server/web/components/ui/input.tsx deleted file mode 100644 --- a/cmd/mirum-server/web/components/ui/input.tsx +++ /dev/null @@ -1,22 +0,0 @@ -// SPDX-FileCopyrightText: 2023 shadcn -// SPDX-License-Identifier: MIT - -import * as React from "react"; - -import { cn } from "@/lib/utils"; - -function Input({ className, type, ...props }: React.ComponentProps<"input">) { - return ( - <input - type={type} - data-slot="input" - className={cn( - "h-9 w-full min-w-0 rounded-md border border-input bg-transparent px-2.5 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40", - className, - )} - {...props} - /> - ); -} - -export { Input }; diff --git a/cmd/mirum-server/web/components/ui/label.tsx b/cmd/mirum-server/web/components/ui/label.tsx deleted file mode 100644 --- a/cmd/mirum-server/web/components/ui/label.tsx +++ /dev/null @@ -1,25 +0,0 @@ -// SPDX-FileCopyrightText: 2023 shadcn -// SPDX-License-Identifier: MIT - -import * as React from "react"; -import { Label as LabelPrimitive } from "radix-ui"; - -import { cn } from "@/lib/utils"; - -function Label({ - className, - ...props -}: React.ComponentProps<typeof LabelPrimitive.Root>) { - return ( - <LabelPrimitive.Root - data-slot="label" - className={cn( - "flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50", - className, - )} - {...props} - /> - ); -} - -export { Label }; diff --git a/cmd/mirum-server/web/components/ui/separator.tsx b/cmd/mirum-server/web/components/ui/separator.tsx deleted file mode 100644 --- a/cmd/mirum-server/web/components/ui/separator.tsx +++ /dev/null @@ -1,29 +0,0 @@ -// SPDX-FileCopyrightText: 2023 shadcn -// SPDX-License-Identifier: MIT - -import * as React from "react"; -import { Separator as SeparatorPrimitive } from "radix-ui"; - -import { cn } from "@/lib/utils"; - -function Separator({ - className, - orientation = "horizontal", - decorative = true, - ...props -}: React.ComponentProps<typeof SeparatorPrimitive.Root>) { - return ( - <SeparatorPrimitive.Root - data-slot="separator" - decorative={decorative} - orientation={orientation} - className={cn( - "shrink-0 bg-border data-horizontal:h-px data-horizontal:w-full data-vertical:w-px data-vertical:self-stretch", - className, - )} - {...props} - /> - ); -} - -export { Separator }; diff --git a/cmd/mirum-server/web/entries/dashboard.tsx b/cmd/mirum-server/web/entries/dashboard.tsx deleted file mode 100644 --- a/cmd/mirum-server/web/entries/dashboard.tsx +++ /dev/null @@ -1,8 +0,0 @@ -// SPDX-FileCopyrightText: 2026 Nikolay Govorov -// SPDX-License-Identifier: AGPL-3.0-or-later - -import "@/index.css"; -import { mountPage } from "@/lib/mount"; -import { Page } from "@/components/pages/dashboard"; - -mountPage(Page); diff --git a/cmd/mirum-server/web/entries/error.tsx b/cmd/mirum-server/web/entries/error.tsx deleted file mode 100644 --- a/cmd/mirum-server/web/entries/error.tsx +++ /dev/null @@ -1,8 +0,0 @@ -// SPDX-FileCopyrightText: 2026 Nikolay Govorov -// SPDX-License-Identifier: AGPL-3.0-or-later - -import "@/index.css"; -import { mountPage } from "@/lib/mount"; -import { Page } from "@/components/pages/error"; - -mountPage(Page); diff --git a/cmd/mirum-server/web/entries/licenses.tsx b/cmd/mirum-server/web/entries/licenses.tsx deleted file mode 100644 --- a/cmd/mirum-server/web/entries/licenses.tsx +++ /dev/null @@ -1,8 +0,0 @@ -// SPDX-FileCopyrightText: 2026 Nikolay Govorov -// SPDX-License-Identifier: AGPL-3.0-or-later - -import "@/index.css"; -import { mountPage } from "@/lib/mount"; -import { Page } from "@/components/pages/licenses"; - -mountPage(Page); diff --git a/cmd/mirum-server/web/entries/login.tsx b/cmd/mirum-server/web/entries/login.tsx deleted file mode 100644 --- a/cmd/mirum-server/web/entries/login.tsx +++ /dev/null @@ -1,8 +0,0 @@ -// SPDX-FileCopyrightText: 2026 Nikolay Govorov -// SPDX-License-Identifier: AGPL-3.0-or-later - -import "@/index.css"; -import { mountPage } from "@/lib/mount"; -import { Page } from "@/components/pages/login"; - -mountPage(Page); diff --git a/cmd/mirum-server/web/eslint.config.js b/cmd/mirum-server/web/eslint.config.js deleted file mode 100644 --- a/cmd/mirum-server/web/eslint.config.js +++ /dev/null @@ -1,15 +0,0 @@ -// SPDX-FileCopyrightText: 2026 Nikolay Govorov -// SPDX-License-Identifier: AGPL-3.0-or-later - -import eslint from "@eslint/js"; -import tseslint from "typescript-eslint"; -import prettier from "eslint-config-prettier"; -import { defineConfig } from "eslint/config"; - -export default defineConfig([ - { ignores: ["gen/"] }, - eslint.configs.recommended, - tseslint.configs.strict, - tseslint.configs.stylistic, - prettier, -]); diff --git a/cmd/mirum-server/web/index.css b/cmd/mirum-server/web/index.css deleted file mode 100644 --- a/cmd/mirum-server/web/index.css +++ /dev/null @@ -1,133 +0,0 @@ -/* SPDX-FileCopyrightText: 2026 Nikolay Govorov - * SPDX-License-Identifier: AGPL-3.0-or-later */ - -@import "tailwindcss"; -@import "tw-animate-css"; -@import "./shadcn.css"; -@import "@fontsource-variable/geist"; - -@custom-variant dark (&:is(.dark *)); - -@theme inline { - --font-heading: var(--font-sans); - --font-sans: "Geist Variable", sans-serif; - --color-sidebar-ring: var(--sidebar-ring); - --color-sidebar-border: var(--sidebar-border); - --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); - --color-sidebar-accent: var(--sidebar-accent); - --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); - --color-sidebar-primary: var(--sidebar-primary); - --color-sidebar-foreground: var(--sidebar-foreground); - --color-sidebar: var(--sidebar); - --color-chart-5: var(--chart-5); - --color-chart-4: var(--chart-4); - --color-chart-3: var(--chart-3); - --color-chart-2: var(--chart-2); - --color-chart-1: var(--chart-1); - --color-ring: var(--ring); - --color-input: var(--input); - --color-border: var(--border); - --color-destructive: var(--destructive); - --color-accent-foreground: var(--accent-foreground); - --color-accent: var(--accent); - --color-muted-foreground: var(--muted-foreground); - --color-muted: var(--muted); - --color-secondary-foreground: var(--secondary-foreground); - --color-secondary: var(--secondary); - --color-primary-foreground: var(--primary-foreground); - --color-primary: var(--primary); - --color-popover-foreground: var(--popover-foreground); - --color-popover: var(--popover); - --color-card-foreground: var(--card-foreground); - --color-card: var(--card); - --color-foreground: var(--foreground); - --color-background: var(--background); - --radius-sm: calc(var(--radius) * 0.6); - --radius-md: calc(var(--radius) * 0.8); - --radius-lg: var(--radius); - --radius-xl: calc(var(--radius) * 1.4); - --radius-2xl: calc(var(--radius) * 1.8); - --radius-3xl: calc(var(--radius) * 2.2); - --radius-4xl: calc(var(--radius) * 2.6); -} - -:root { - --background: oklch(1 0 0); - --foreground: oklch(0.145 0 0); - --card: oklch(1 0 0); - --card-foreground: oklch(0.145 0 0); - --popover: oklch(1 0 0); - --popover-foreground: oklch(0.145 0 0); - --primary: oklch(0.205 0 0); - --primary-foreground: oklch(0.985 0 0); - --secondary: oklch(0.97 0 0); - --secondary-foreground: oklch(0.205 0 0); - --muted: oklch(0.97 0 0); - --muted-foreground: oklch(0.556 0 0); - --accent: oklch(0.97 0 0); - --accent-foreground: oklch(0.205 0 0); - --destructive: oklch(0.577 0.245 27.325); - --border: oklch(0.922 0 0); - --input: oklch(0.922 0 0); - --ring: oklch(0.708 0 0); - --chart-1: oklch(0.905 0.182 98.111); - --chart-2: oklch(0.795 0.184 86.047); - --chart-3: oklch(0.681 0.162 75.834); - --chart-4: oklch(0.554 0.135 66.442); - --chart-5: oklch(0.476 0.114 61.907); - --radius: 0.45rem; - --sidebar: oklch(0.985 0 0); - --sidebar-foreground: oklch(0.145 0 0); - --sidebar-primary: oklch(0.205 0 0); - --sidebar-primary-foreground: oklch(0.985 0 0); - --sidebar-accent: oklch(0.97 0 0); - --sidebar-accent-foreground: oklch(0.205 0 0); - --sidebar-border: oklch(0.922 0 0); - --sidebar-ring: oklch(0.708 0 0); -} - -.dark { - --background: oklch(0.145 0 0); - --foreground: oklch(0.985 0 0); - --card: oklch(0.205 0 0); - --card-foreground: oklch(0.985 0 0); - --popover: oklch(0.205 0 0); - --popover-foreground: oklch(0.985 0 0); - --primary: oklch(0.922 0 0); - --primary-foreground: oklch(0.205 0 0); - --secondary: oklch(0.269 0 0); - --secondary-foreground: oklch(0.985 0 0); - --muted: oklch(0.269 0 0); - --muted-foreground: oklch(0.708 0 0); - --accent: oklch(0.269 0 0); - --accent-foreground: oklch(0.985 0 0); - --destructive: oklch(0.704 0.191 22.216); - --border: oklch(1 0 0 / 10%); - --input: oklch(1 0 0 / 15%); - --ring: oklch(0.556 0 0); - --chart-1: oklch(0.905 0.182 98.111); - --chart-2: oklch(0.795 0.184 86.047); - --chart-3: oklch(0.681 0.162 75.834); - --chart-4: oklch(0.554 0.135 66.442); - --chart-5: oklch(0.476 0.114 61.907); - --sidebar: oklch(0.205 0 0); - --sidebar-foreground: oklch(0.985 0 0); - --sidebar-primary: oklch(0.488 0.243 264.376); - --sidebar-primary-foreground: oklch(0.985 0 0); - --sidebar-accent: oklch(0.269 0 0); - --sidebar-accent-foreground: oklch(0.985 0 0); - --sidebar-border: oklch(1 0 0 / 10%); - --sidebar-ring: oklch(0.556 0 0); -} - -@layer base { - * { - @apply border-border outline-ring/50; - } - body { - @apply bg-background text-foreground; - } - html { - @apply font-sans; - } -} diff --git a/cmd/mirum-server/web/lib/errors.ts b/cmd/mirum-server/web/lib/errors.ts deleted file mode 100644 --- a/cmd/mirum-server/web/lib/errors.ts +++ /dev/null @@ -1,88 +0,0 @@ -// SPDX-FileCopyrightText: 2026 Nikolay Govorov -// SPDX-License-Identifier: AGPL-3.0-or-later - -import { Code, ConnectError } from "@connectrpc/connect"; -import { ErrorInfoSchema, ErrorReason } from "@/gen/api_pb"; - -// errorReason extracts the ErrorInfo.reason attached by the server. -// Returns null for transport failures or non-mirum responses. -export function errorReason(err: unknown): ErrorReason | null { - const info = ConnectError.from(err).findDetails(ErrorInfoSchema)[0]; - return info?.reason ?? null; -} - -// formatError maps any thrown value to user-facing text. -// All .catch() callers should route errors through this function. -export function formatError(err: unknown): string { - const e = ConnectError.from(err); - const info = e.findDetails(ErrorInfoSchema)[0]; - if (info) { - return textForReason(info.reason); - } - console.error("api error without ErrorInfo:", e); - return textForCode(e.code); -} - -export function textForReason(reason: ErrorReason): string { - switch (reason) { - case ErrorReason.USER_NOT_FOUND: - return "User not found."; - case ErrorReason.ORG_NOT_FOUND: - return "Organization not found."; - case ErrorReason.WORKER_NOT_FOUND: - return "Worker not found."; - case ErrorReason.MEMBER_NOT_FOUND: - return "Member not found."; - case ErrorReason.EMAIL_TAKEN: - return "This email is already in use."; - case ErrorReason.SLUG_TAKEN: - return "This slug is already taken."; - case ErrorReason.ALREADY_MEMBER: - return "Already a member of this organization."; - case ErrorReason.LAST_OWNER: - return "An organization must have at least one owner."; - case ErrorReason.SOLE_OWNER: - return "This user is the sole owner of an organization. Transfer ownership first."; - case ErrorReason.INVALID_SLUG: - return "Invalid slug. Use lowercase letters, digits, and hyphens."; - case ErrorReason.INVALID_ROLE: - return "Invalid role."; - case ErrorReason.INVALID_DATE_FORMAT: - return "Invalid date format."; - case ErrorReason.INVALID_TIMEZONE: - return "Invalid timezone."; - case ErrorReason.RESERVED_EMAIL: - return "This email domain is reserved. Please use a different address."; - case ErrorReason.UNAUTHENTICATED: - return "Your session has expired. Please sign in again."; - case ErrorReason.PERMISSION_DENIED: - return "You don't have permission to do that."; - case ErrorReason.INVALID_CREDENTIALS: - return "Wrong email or password. Please try again."; - case ErrorReason.INVALID_CSRF: - return "This form expired. Please try again."; - case ErrorReason.RATE_LIMITED: - return "Too many requests. Please slow down."; - case ErrorReason.UNAVAILABLE: - return "Service is temporarily unavailable. Please retry."; - case ErrorReason.UNIMPLEMENTED: - return "This operation is not supported."; - case ErrorReason.INTERNAL: - case ErrorReason.UNSPECIFIED: - default: - return "Something went wrong. Please try again."; - } -} - -function textForCode(code: Code): string { - switch (code) { - case Code.Canceled: - return "Request canceled."; - case Code.DeadlineExceeded: - return "Request timed out."; - case Code.Unavailable: - return "Service is temporarily unavailable. Please retry."; - default: - return "Connection failed. Please retry."; - } -} diff --git a/cmd/mirum-server/web/lib/mount.tsx b/cmd/mirum-server/web/lib/mount.tsx deleted file mode 100644 --- a/cmd/mirum-server/web/lib/mount.tsx +++ /dev/null @@ -1,29 +0,0 @@ -// SPDX-FileCopyrightText: 2026 Nikolay Govorov -// SPDX-License-Identifier: AGPL-3.0-or-later - -import { StrictMode, type ComponentType } from "react"; -import { createRoot } from "react-dom/client"; - -export function getInitialData<T>(): T { - const el = document.getElementById("__DATA__"); - if (!el?.textContent) { - throw new Error("missing __DATA__ script tag"); - } - - return JSON.parse(el.textContent) as T; -} - -export function mountPage<P extends object>(Component: ComponentType<P>) { - const props = getInitialData<P>(); - - const root = document.getElementById("app"); - if (!root) { - throw new Error("missing #app element"); - } - - createRoot(root).render( - <StrictMode> - <Component {...props} /> - </StrictMode>, - ); -} diff --git a/cmd/mirum-server/web/lib/utils.ts b/cmd/mirum-server/web/lib/utils.ts deleted file mode 100644 --- a/cmd/mirum-server/web/lib/utils.ts +++ /dev/null @@ -1,39 +0,0 @@ -// SPDX-FileCopyrightText: 2026 Nikolay Govorov -// SPDX-License-Identifier: AGPL-3.0-or-later - -import { twMerge } from "tailwind-merge"; - -export type ClassValue = - | ClassArray - | Record<string, unknown> - | string - | number - | bigint - | null - | boolean - | undefined; -export type ClassArray = ClassValue[]; - -// avoid dependency for 10 lines and this version is stricter in TS -function classList(value: ClassValue): string { - if ( - typeof value === "string" || - typeof value === "number" || - typeof value === "bigint" - ) { - return String(value); - } - if (Array.isArray(value)) { - return value.map(classList).filter(Boolean).join(" "); - } - if (value !== null && typeof value === "object") { - return Object.keys(value) - .filter((key) => value[key]) - .join(" "); - } - return ""; -} - -export function cn(...inputs: ClassValue[]): string { - return twMerge(classList(inputs)); -} diff --git a/cmd/mirum-server/web/package-lock.json b/cmd/mirum-server/web/package-lock.json deleted file mode 100644 --- a/cmd/mirum-server/web/package-lock.json +++ /dev/null @@ -1,8971 +0,0 @@ -{ - "name": "@mirum/web", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "@mirum/web", - "dependencies": { - "@bufbuild/protobuf": "^2.11.0", - "@connectrpc/connect": "^2.1.1", - "@connectrpc/connect-web": "^2.1.1", - "@fontsource-variable/geist": "^5.2.8", - "class-variance-authority": "^0.7.1", - "lucide-react": "^1.7.0", - "radix-ui": "^1.4.3", - "react": "^19.2.4", - "react-dom": "^19.2.4", - "tailwind-merge": "^3.5.0", - "tw-animate-css": "^1.4.0" - }, - "devDependencies": { - "@bufbuild/protoc-gen-es": "^2.11.0", - "@eslint/js": "^10.0.1", - "@size-limit/file": "^12.0.1", - "@tailwindcss/vite": "^4.2.2", - "@types/node": "^24.12.0", - "@types/react": "^19.2.14", - "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^6.0.1", - "eslint": "^10.2.0", - "eslint-config-prettier": "^10.1.8", - "prettier": "^3.8.2", - "prettier-plugin-tailwindcss": "^0.7.2", - "shadcn": "^4.1.2", - "size-limit": "^12.0.1", - "tailwindcss": "^4.2.2", - "typescript": "~5.9.3", - "typescript-eslint": "^8.58.1", - "vite": "^8.0.1" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", - "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-validator-identifier": "^7.28.5", - "js-tokens": "^4.0.0", - "picocolors": "^1.1.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/compat-data": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", - "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", - "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-compilation-targets": "^7.28.6", - "@babel/helper-module-transforms": "^7.28.6", - "@babel/helpers": "^7.28.6", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/traverse": "^7.29.0", - "@babel/types": "^7.29.0", - "@jridgewell/remapping": "^2.3.5", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/generator": { - "version": "7.29.1", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", - "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.29.0", - "@babel/types": "^7.29.0", - "@jridgewell/gen-mapping": "^0.3.12", - "@jridgewell/trace-mapping": "^0.3.28", - "jsesc": "^3.0.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.27.3", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", - "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.27.3" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", - "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.28.6", - "@babel/helper-validator-option": "^7.27.1", - "browserslist": "^4.24.0", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.6.tgz", - "integrity": "sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.3", - "@babel/helper-member-expression-to-functions": "^7.28.5", - "@babel/helper-optimise-call-expression": "^7.27.1", - "@babel/helper-replace-supers": "^7.28.6", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", - "@babel/traverse": "^7.28.6", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-globals": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", - "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.28.5.tgz", - "integrity": "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.28.5", - "@babel/types": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", - "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.28.6", - "@babel/types": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", - "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.28.6", - "@babel/helper-validator-identifier": "^7.28.5", - "@babel/traverse": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz", - "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", - "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-replace-supers": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.28.6.tgz", - "integrity": "sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-member-expression-to-functions": "^7.28.5", - "@babel/helper-optimise-call-expression": "^7.27.1", - "@babel/traverse": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz", - "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-option": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", - "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.29.2", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", - "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/template": "^7.28.6", - "@babel/types": "^7.29.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.29.2", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", - "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.29.0" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz", - "integrity": "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz", - "integrity": "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.28.6.tgz", - "integrity": "sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-transforms": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-typescript": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.6.tgz", - "integrity": "sha512-0YWL2RFxOqEm9Efk5PvreamxPME8OyY0wM5wh5lHjF+VtVhdneCWGzZeSqzOfiobVqQaNCd2z0tQvnI9DaPWPw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.3", - "@babel/helper-create-class-features-plugin": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", - "@babel/plugin-syntax-typescript": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-typescript": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.28.5.tgz", - "integrity": "sha512-+bQy5WOI2V6LJZpPVxY+yp66XdZ2yifu0Mc1aP5CQKgjn4QM5IN2i5fAZ4xKop47pr8rpVhiAeu+nDQa12C8+g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-validator-option": "^7.27.1", - "@babel/plugin-syntax-jsx": "^7.27.1", - "@babel/plugin-transform-modules-commonjs": "^7.27.1", - "@babel/plugin-transform-typescript": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/template": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", - "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.28.6", - "@babel/parser": "^7.28.6", - "@babel/types": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", - "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/types": "^7.29.0", - "debug": "^4.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/types": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", - "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@bufbuild/protobuf": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.11.0.tgz", - "integrity": "sha512-sBXGT13cpmPR5BMgHE6UEEfEaShh5Ror6rfN3yEK5si7QVrtZg8LEPQb0VVhiLRUslD2yLnXtnRzG035J/mZXQ==", - "license": "(Apache-2.0 AND BSD-3-Clause)" - }, - "node_modules/@bufbuild/protoc-gen-es": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/@bufbuild/protoc-gen-es/-/protoc-gen-es-2.11.0.tgz", - "integrity": "sha512-VzQuwEQDXipbZ1soWUuAWm1Z0C3B/IDWGeysnbX6ogJ6As91C2mdvAND/ekQ4YIWgen4d5nqLfIBOWLqCCjYUA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@bufbuild/protobuf": "2.11.0", - "@bufbuild/protoplugin": "2.11.0" - }, - "bin": { - "protoc-gen-es": "bin/protoc-gen-es" - }, - "engines": { - "node": ">=20" - }, - "peerDependencies": { - "@bufbuild/protobuf": "2.11.0" - }, - "peerDependenciesMeta": { - "@bufbuild/protobuf": { - "optional": true - } - } - }, - "node_modules/@bufbuild/protoplugin": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/@bufbuild/protoplugin/-/protoplugin-2.11.0.tgz", - "integrity": "sha512-lyZVNFUHArIOt4W0+dwYBe5GBwbKzbOy8ObaloEqsw9Mmiwv2O48TwddDoHN4itylC+BaEGqFdI1W8WQt2vWJQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@bufbuild/protobuf": "2.11.0", - "@typescript/vfs": "^1.6.2", - "typescript": "5.4.5" - } - }, - "node_modules/@bufbuild/protoplugin/node_modules/typescript": { - "version": "5.4.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", - "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/@connectrpc/connect": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@connectrpc/connect/-/connect-2.1.1.tgz", - "integrity": "sha512-JzhkaTvM73m2K1URT6tv53k2RwngSmCXLZJgK580qNQOXRzZRR/BCMfZw3h+90JpnG6XksP5bYT+cz0rpUzUWQ==", - "license": "Apache-2.0", - "peerDependencies": { - "@bufbuild/protobuf": "^2.7.0" - } - }, - "node_modules/@connectrpc/connect-web": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@connectrpc/connect-web/-/connect-web-2.1.1.tgz", - "integrity": "sha512-J8317Q2MaFRCT1jzVR1o06bZhDIBmU0UAzWx6xOIXzOq8+k71/+k7MUF7AwcBUX+34WIvbm5syRgC5HXQA8fOg==", - "license": "Apache-2.0", - "peerDependencies": { - "@bufbuild/protobuf": "^2.7.0", - "@connectrpc/connect": "2.1.1" - } - }, - "node_modules/@dotenvx/dotenvx": { - "version": "1.61.0", - "resolved": "https://registry.npmjs.org/@dotenvx/dotenvx/-/dotenvx-1.61.0.tgz", - "integrity": "sha512-utL3cpZoFzflyqUkjYbxYujI6STBTmO5LFn4bbin/NZnRWN6wQ7eErhr3/Vpa5h/jicPFC6kTa42r940mQftJQ==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "commander": "^11.1.0", - "dotenv": "^17.2.1", - "eciesjs": "^0.4.10", - "execa": "^5.1.1", - "fdir": "^6.2.0", - "ignore": "^5.3.0", - "object-treeify": "1.1.33", - "picomatch": "^4.0.2", - "which": "^4.0.0", - "yocto-spinner": "^1.1.0" - }, - "bin": { - "dotenvx": "src/cli/dotenvx.js" - }, - "funding": { - "url": "https://dotenvx.com" - } - }, - "node_modules/@dotenvx/dotenvx/node_modules/commander": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", - "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=16" - } - }, - "node_modules/@dotenvx/dotenvx/node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, - "license": "MIT", - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/@dotenvx/dotenvx/node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@dotenvx/dotenvx/node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=10.17.0" - } - }, - "node_modules/@dotenvx/dotenvx/node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@dotenvx/dotenvx/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/@dotenvx/dotenvx/node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@dotenvx/dotenvx/node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@dotenvx/dotenvx/node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/@dotenvx/dotenvx/node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/@dotenvx/dotenvx/node_modules/which": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", - "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^3.1.1" - }, - "bin": { - "node-which": "bin/which.js" - }, - "engines": { - "node": "^16.13.0 || >=18.0.0" - } - }, - "node_modules/@ecies/ciphers": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/@ecies/ciphers/-/ciphers-0.2.6.tgz", - "integrity": "sha512-patgsRPKGkhhoBjETV4XxD0En4ui5fbX0hzayqI3M8tvNMGUoUvmyYAIWwlxBc1KX5cturfqByYdj5bYGRpN9g==", - "dev": true, - "license": "MIT", - "engines": { - "bun": ">=1", - "deno": ">=2.7.10", - "node": ">=16" - }, - "peerDependencies": { - "@noble/ciphers": "^1.0.0" - } - }, - "node_modules/@emnapi/core": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.2.tgz", - "integrity": "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.2.1", - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/runtime": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.2.tgz", - "integrity": "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/wasi-threads": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", - "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", - "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "eslint-visitor-keys": "^3.4.3" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" - } - }, - "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint-community/regexpp": { - "version": "4.12.2", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", - "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" - } - }, - "node_modules/@eslint/config-array": { - "version": "0.23.5", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.5.tgz", - "integrity": "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/object-schema": "^3.0.5", - "debug": "^4.3.1", - "minimatch": "^10.2.4" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - } - }, - "node_modules/@eslint/config-helpers": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.5.5.tgz", - "integrity": "sha512-eIJYKTCECbP/nsKaaruF6LW967mtbQbsw4JTtSVkUQc9MneSkbrgPJAbKl9nWr0ZeowV8BfsarBmPpBzGelA2w==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/core": "^1.2.1" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - } - }, - "node_modules/@eslint/core": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.2.1.tgz", - "integrity": "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@types/json-schema": "^7.0.15" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - } - }, - "node_modules/@eslint/js": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-10.0.1.tgz", - "integrity": "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://eslint.org/donate" - }, - "peerDependencies": { - "eslint": "^10.0.0" - }, - "peerDependenciesMeta": { - "eslint": { - "optional": true - } - } - }, - "node_modules/@eslint/object-schema": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.5.tgz", - "integrity": "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - } - }, - "node_modules/@eslint/plugin-kit": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.1.tgz", - "integrity": "sha512-rZAP3aVgB9ds9KOeUSL+zZ21hPmo8dh6fnIFwRQj5EAZl9gzR7wxYbYXYysAM8CTqGmUGyp2S4kUdV17MnGuWQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/core": "^1.2.1", - "levn": "^0.4.1" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - } - }, - "node_modules/@floating-ui/core": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.5.tgz", - "integrity": "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==", - "license": "MIT", - "dependencies": { - "@floating-ui/utils": "^0.2.11" - } - }, - "node_modules/@floating-ui/dom": { - "version": "1.7.6", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.6.tgz", - "integrity": "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==", - "license": "MIT", - "dependencies": { - "@floating-ui/core": "^1.7.5", - "@floating-ui/utils": "^0.2.11" - } - }, - "node_modules/@floating-ui/react-dom": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.8.tgz", - "integrity": "sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A==", - "license": "MIT", - "dependencies": { - "@floating-ui/dom": "^1.7.6" - }, - "peerDependencies": { - "react": ">=16.8.0", - "react-dom": ">=16.8.0" - } - }, - "node_modules/@floating-ui/utils": { - "version": "0.2.11", - "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.11.tgz", - "integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==", - "license": "MIT" - }, - "node_modules/@fontsource-variable/geist": { - "version": "5.2.8", - "resolved": "https://registry.npmjs.org/@fontsource-variable/geist/-/geist-5.2.8.tgz", - "integrity": "sha512-cJ6m9e+8MQ5dCYJsLylfZrgBh6KkG4bOLckB35Tr9J/EqdkEM6QllH5PxqP1dhTvFup+HtMRPuz9xOjxXJggxw==", - "license": "OFL-1.1", - "funding": { - "url": "https://github.com/sponsors/ayuhito" - } - }, - "node_modules/@hono/node-server": { - "version": "1.19.13", - "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.13.tgz", - "integrity": "sha512-TsQLe4i2gvoTtrHje625ngThGBySOgSK3Xo2XRYOdqGN1teR8+I7vchQC46uLJi8OF62YTYA3AhSpumtkhsaKQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.14.1" - }, - "peerDependencies": { - "hono": "^4" - } - }, - "node_modules/@humanfs/core": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", - "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanfs/node": { - "version": "0.16.7", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", - "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@humanfs/core": "^0.19.1", - "@humanwhocodes/retry": "^0.4.0" - }, - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/retry": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", - "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@inquirer/ansi": { - "version": "1.0.2", - "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/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/figures": { - "version": "1.0.15", - "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.15.tgz", - "integrity": "sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "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/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/remapping": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", - "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@modelcontextprotocol/sdk": { - "version": "1.29.0", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.29.0.tgz", - "integrity": "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@hono/node-server": "^1.19.9", - "ajv": "^8.17.1", - "ajv-formats": "^3.0.1", - "content-type": "^1.0.5", - "cors": "^2.8.5", - "cross-spawn": "^7.0.5", - "eventsource": "^3.0.2", - "eventsource-parser": "^3.0.0", - "express": "^5.2.1", - "express-rate-limit": "^8.2.1", - "hono": "^4.11.4", - "jose": "^6.1.3", - "json-schema-typed": "^8.0.2", - "pkce-challenge": "^5.0.0", - "raw-body": "^3.0.0", - "zod": "^3.25 || ^4.0", - "zod-to-json-schema": "^3.25.1" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@cfworker/json-schema": "^4.1.1", - "zod": "^3.25 || ^4.0" - }, - "peerDependenciesMeta": { - "@cfworker/json-schema": { - "optional": true - }, - "zod": { - "optional": false - } - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/ajv": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", - "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true, - "license": "MIT" - }, - "node_modules/@mswjs/interceptors": { - "version": "0.41.3", - "resolved": "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.41.3.tgz", - "integrity": "sha512-cXu86tF4VQVfwz8W1SPbhoRyHJkti6mjH/XJIxp40jhO4j2k1m4KYrEykxqWPkFF3vrK4rgQppBh//AwyGSXPA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@open-draft/deferred-promise": "^2.2.0", - "@open-draft/logger": "^0.3.0", - "@open-draft/until": "^2.0.0", - "is-node-process": "^1.2.0", - "outvariant": "^1.4.3", - "strict-event-emitter": "^0.5.1" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.3.tgz", - "integrity": "sha512-xK9sGVbJWYb08+mTJt3/YV24WxvxpXcXtP6B172paPZ+Ts69Re9dAr7lKwJoeIx8OoeuimEiRZ7umkiUVClmmQ==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@tybys/wasm-util": "^0.10.1" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Brooooooklyn" - }, - "peerDependencies": { - "@emnapi/core": "^1.7.1", - "@emnapi/runtime": "^1.7.1" - } - }, - "node_modules/@noble/ciphers": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.3.0.tgz", - "integrity": "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^14.21.3 || >=16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@noble/curves": { - "version": "1.9.7", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.7.tgz", - "integrity": "sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@noble/hashes": "1.8.0" - }, - "engines": { - "node": "^14.21.3 || >=16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "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==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^14.21.3 || >=16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@open-draft/deferred-promise": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@open-draft/deferred-promise/-/deferred-promise-2.2.0.tgz", - "integrity": "sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@open-draft/logger": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@open-draft/logger/-/logger-0.3.0.tgz", - "integrity": "sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-node-process": "^1.2.0", - "outvariant": "^1.4.0" - } - }, - "node_modules/@open-draft/until": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@open-draft/until/-/until-2.1.0.tgz", - "integrity": "sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@oxc-project/types": { - "version": "0.124.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.124.0.tgz", - "integrity": "sha512-VBFWMTBvHxS11Z5Lvlr3IWgrwhMTXV+Md+EQF0Xf60+wAdsGFTBx7X7K/hP4pi8N7dcm1RvcHwDxZ16Qx8keUg==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/Boshen" - } - }, - "node_modules/@radix-ui/number": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.1.tgz", - "integrity": "sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==", - "license": "MIT" - }, - "node_modules/@radix-ui/primitive": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz", - "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==", - "license": "MIT" - }, - "node_modules/@radix-ui/react-accessible-icon": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/@radix-ui/react-accessible-icon/-/react-accessible-icon-1.1.7.tgz", - "integrity": "sha512-XM+E4WXl0OqUJFovy6GjmxxFyx9opfCAIUku4dlKRd5YEPqt4kALOkQOp0Of6reHuUkJuiPBEc5k0o4z4lTC8A==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-visually-hidden": "1.2.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-accordion": { - "version": "1.2.12", - "resolved": "https://registry.npmjs.org/@radix-ui/react-accordion/-/react-accordion-1.2.12.tgz", - "integrity": "sha512-T4nygeh9YE9dLRPhAHSeOZi7HBXo+0kYIPJXayZfvWOWA0+n3dESrZbjfDPUABkUNym6Hd+f2IR113To8D2GPA==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-collapsible": "1.1.12", - "@radix-ui/react-collection": "1.1.7", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-direction": "1.1.1", - "@radix-ui/react-id": "1.1.1", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-controllable-state": "1.2.2" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-alert-dialog": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/@radix-ui/react-alert-dialog/-/react-alert-dialog-1.1.15.tgz", - "integrity": "sha512-oTVLkEw5GpdRe29BqJ0LSDFWI3qu0vR1M0mUkOQWDIUnY/QIkLpgDMWuKxP94c2NAC2LGcgVhG1ImF3jkZ5wXw==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-dialog": "1.1.15", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-slot": "1.2.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-arrow": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.7.tgz", - "integrity": "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-primitive": "2.1.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-aspect-ratio": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/@radix-ui/react-aspect-ratio/-/react-aspect-ratio-1.1.7.tgz", - "integrity": "sha512-Yq6lvO9HQyPwev1onK1daHCHqXVLzPhSVjmsNjCa2Zcxy2f7uJD2itDtxknv6FzAKCwD1qQkeVDmX/cev13n/g==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-primitive": "2.1.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-avatar": { - "version": "1.1.10", - "resolved": "https://registry.npmjs.org/@radix-ui/react-avatar/-/react-avatar-1.1.10.tgz", - "integrity": "sha512-V8piFfWapM5OmNCXTzVQY+E1rDa53zY+MQ4Y7356v4fFz6vqCyUtIz2rUD44ZEdwg78/jKmMJHj07+C/Z/rcog==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-callback-ref": "1.1.1", - "@radix-ui/react-use-is-hydrated": "0.1.0", - "@radix-ui/react-use-layout-effect": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-checkbox": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.3.3.tgz", - "integrity": "sha512-wBbpv+NQftHDdG86Qc0pIyXk5IR3tM8Vd0nWLKDcX8nNn4nXFOFwsKuqw2okA/1D/mpaAkmuyndrPJTYDNZtFw==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-presence": "1.1.5", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-controllable-state": "1.2.2", - "@radix-ui/react-use-previous": "1.1.1", - "@radix-ui/react-use-size": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-collapsible": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/@radix-ui/react-collapsible/-/react-collapsible-1.1.12.tgz", - "integrity": "sha512-Uu+mSh4agx2ib1uIGPP4/CKNULyajb3p92LsVXmH2EHVMTfZWpll88XJ0j4W0z3f8NK1eYl1+Mf/szHPmcHzyA==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-id": "1.1.1", - "@radix-ui/react-presence": "1.1.5", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-controllable-state": "1.2.2", - "@radix-ui/react-use-layout-effect": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-collection": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz", - "integrity": "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-slot": "1.2.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-compose-refs": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", - "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-context": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", - "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-context-menu": { - "version": "2.2.16", - "resolved": "https://registry.npmjs.org/@radix-ui/react-context-menu/-/react-context-menu-2.2.16.tgz", - "integrity": "sha512-O8morBEW+HsVG28gYDZPTrT9UUovQUlJue5YO836tiTJhuIWBm/zQHc7j388sHWtdH/xUZurK9olD2+pcqx5ww==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-menu": "2.1.16", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-callback-ref": "1.1.1", - "@radix-ui/react-use-controllable-state": "1.2.2" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-dialog": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.15.tgz", - "integrity": "sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-dismissable-layer": "1.1.11", - "@radix-ui/react-focus-guards": "1.1.3", - "@radix-ui/react-focus-scope": "1.1.7", - "@radix-ui/react-id": "1.1.1", - "@radix-ui/react-portal": "1.1.9", - "@radix-ui/react-presence": "1.1.5", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-slot": "1.2.3", - "@radix-ui/react-use-controllable-state": "1.2.2", - "aria-hidden": "^1.2.4", - "react-remove-scroll": "^2.6.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-direction": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz", - "integrity": "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-dismissable-layer": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.11.tgz", - "integrity": "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-callback-ref": "1.1.1", - "@radix-ui/react-use-escape-keydown": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-dropdown-menu": { - "version": "2.1.16", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.16.tgz", - "integrity": "sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-id": "1.1.1", - "@radix-ui/react-menu": "2.1.16", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-controllable-state": "1.2.2" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-focus-guards": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.3.tgz", - "integrity": "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-focus-scope": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.7.tgz", - "integrity": "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-callback-ref": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-form": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/@radix-ui/react-form/-/react-form-0.1.8.tgz", - "integrity": "sha512-QM70k4Zwjttifr5a4sZFts9fn8FzHYvQ5PiB19O2HsYibaHSVt9fH9rzB0XZo/YcM+b7t/p7lYCT/F5eOeF5yQ==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-id": "1.1.1", - "@radix-ui/react-label": "2.1.7", - "@radix-ui/react-primitive": "2.1.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-hover-card": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/@radix-ui/react-hover-card/-/react-hover-card-1.1.15.tgz", - "integrity": "sha512-qgTkjNT1CfKMoP0rcasmlH2r1DAiYicWsDsufxl940sT2wHNEWWv6FMWIQXWhVdmC1d/HYfbhQx60KYyAtKxjg==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-dismissable-layer": "1.1.11", - "@radix-ui/react-popper": "1.2.8", - "@radix-ui/react-portal": "1.1.9", - "@radix-ui/react-presence": "1.1.5", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-controllable-state": "1.2.2" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-id": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz", - "integrity": "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-use-layout-effect": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-label": { - "version": "2.1.7", - "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.7.tgz", - "integrity": "sha512-YT1GqPSL8kJn20djelMX7/cTRp/Y9w5IZHvfxQTVHrOqa2yMl7i/UfMqKRU5V7mEyKTrUVgJXhNQPVCG8PBLoQ==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-primitive": "2.1.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-menu": { - "version": "2.1.16", - "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.16.tgz", - "integrity": "sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-collection": "1.1.7", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-direction": "1.1.1", - "@radix-ui/react-dismissable-layer": "1.1.11", - "@radix-ui/react-focus-guards": "1.1.3", - "@radix-ui/react-focus-scope": "1.1.7", - "@radix-ui/react-id": "1.1.1", - "@radix-ui/react-popper": "1.2.8", - "@radix-ui/react-portal": "1.1.9", - "@radix-ui/react-presence": "1.1.5", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-roving-focus": "1.1.11", - "@radix-ui/react-slot": "1.2.3", - "@radix-ui/react-use-callback-ref": "1.1.1", - "aria-hidden": "^1.2.4", - "react-remove-scroll": "^2.6.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-menubar": { - "version": "1.1.16", - "resolved": "https://registry.npmjs.org/@radix-ui/react-menubar/-/react-menubar-1.1.16.tgz", - "integrity": "sha512-EB1FktTz5xRRi2Er974AUQZWg2yVBb1yjip38/lgwtCVRd3a+maUoGHN/xs9Yv8SY8QwbSEb+YrxGadVWbEutA==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-collection": "1.1.7", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-direction": "1.1.1", - "@radix-ui/react-id": "1.1.1", - "@radix-ui/react-menu": "2.1.16", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-roving-focus": "1.1.11", - "@radix-ui/react-use-controllable-state": "1.2.2" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-navigation-menu": { - "version": "1.2.14", - "resolved": "https://registry.npmjs.org/@radix-ui/react-navigation-menu/-/react-navigation-menu-1.2.14.tgz", - "integrity": "sha512-YB9mTFQvCOAQMHU+C/jVl96WmuWeltyUEpRJJky51huhds5W2FQr1J8D/16sQlf0ozxkPK8uF3niQMdUwZPv5w==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-collection": "1.1.7", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-direction": "1.1.1", - "@radix-ui/react-dismissable-layer": "1.1.11", - "@radix-ui/react-id": "1.1.1", - "@radix-ui/react-presence": "1.1.5", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-callback-ref": "1.1.1", - "@radix-ui/react-use-controllable-state": "1.2.2", - "@radix-ui/react-use-layout-effect": "1.1.1", - "@radix-ui/react-use-previous": "1.1.1", - "@radix-ui/react-visually-hidden": "1.2.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-one-time-password-field": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/@radix-ui/react-one-time-password-field/-/react-one-time-password-field-0.1.8.tgz", - "integrity": "sha512-ycS4rbwURavDPVjCb5iS3aG4lURFDILi6sKI/WITUMZ13gMmn/xGjpLoqBAalhJaDk8I3UbCM5GzKHrnzwHbvg==", - "license": "MIT", - "dependencies": { - "@radix-ui/number": "1.1.1", - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-collection": "1.1.7", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-direction": "1.1.1", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-roving-focus": "1.1.11", - "@radix-ui/react-use-controllable-state": "1.2.2", - "@radix-ui/react-use-effect-event": "0.0.2", - "@radix-ui/react-use-is-hydrated": "0.1.0", - "@radix-ui/react-use-layout-effect": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-password-toggle-field": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-password-toggle-field/-/react-password-toggle-field-0.1.3.tgz", - "integrity": "sha512-/UuCrDBWravcaMix4TdT+qlNdVwOM1Nck9kWx/vafXsdfj1ChfhOdfi3cy9SGBpWgTXwYCuboT/oYpJy3clqfw==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-id": "1.1.1", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-controllable-state": "1.2.2", - "@radix-ui/react-use-effect-event": "0.0.2", - "@radix-ui/react-use-is-hydrated": "0.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-popover": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.15.tgz", - "integrity": "sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-dismissable-layer": "1.1.11", - "@radix-ui/react-focus-guards": "1.1.3", - "@radix-ui/react-focus-scope": "1.1.7", - "@radix-ui/react-id": "1.1.1", - "@radix-ui/react-popper": "1.2.8", - "@radix-ui/react-portal": "1.1.9", - "@radix-ui/react-presence": "1.1.5", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-slot": "1.2.3", - "@radix-ui/react-use-controllable-state": "1.2.2", - "aria-hidden": "^1.2.4", - "react-remove-scroll": "^2.6.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-popper": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.8.tgz", - "integrity": "sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==", - "license": "MIT", - "dependencies": { - "@floating-ui/react-dom": "^2.0.0", - "@radix-ui/react-arrow": "1.1.7", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-callback-ref": "1.1.1", - "@radix-ui/react-use-layout-effect": "1.1.1", - "@radix-ui/react-use-rect": "1.1.1", - "@radix-ui/react-use-size": "1.1.1", - "@radix-ui/rect": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-portal": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.9.tgz", - "integrity": "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-layout-effect": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-presence": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz", - "integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-use-layout-effect": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-primitive": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", - "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-slot": "1.2.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-progress": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/@radix-ui/react-progress/-/react-progress-1.1.7.tgz", - "integrity": "sha512-vPdg/tF6YC/ynuBIJlk1mm7Le0VgW6ub6J2UWnTQ7/D23KXcPI1qy+0vBkgKgd38RCMJavBXpB83HPNFMTb0Fg==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-primitive": "2.1.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-radio-group": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/@radix-ui/react-radio-group/-/react-radio-group-1.3.8.tgz", - "integrity": "sha512-VBKYIYImA5zsxACdisNQ3BjCBfmbGH3kQlnFVqlWU4tXwjy7cGX8ta80BcrO+WJXIn5iBylEH3K6ZTlee//lgQ==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-direction": "1.1.1", - "@radix-ui/react-presence": "1.1.5", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-roving-focus": "1.1.11", - "@radix-ui/react-use-controllable-state": "1.2.2", - "@radix-ui/react-use-previous": "1.1.1", - "@radix-ui/react-use-size": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-roving-focus": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.11.tgz", - "integrity": "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-collection": "1.1.7", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-direction": "1.1.1", - "@radix-ui/react-id": "1.1.1", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-callback-ref": "1.1.1", - "@radix-ui/react-use-controllable-state": "1.2.2" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-scroll-area": { - "version": "1.2.10", - "resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.2.10.tgz", - "integrity": "sha512-tAXIa1g3sM5CGpVT0uIbUx/U3Gs5N8T52IICuCtObaos1S8fzsrPXG5WObkQN3S6NVl6wKgPhAIiBGbWnvc97A==", - "license": "MIT", - "dependencies": { - "@radix-ui/number": "1.1.1", - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-direction": "1.1.1", - "@radix-ui/react-presence": "1.1.5", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-callback-ref": "1.1.1", - "@radix-ui/react-use-layout-effect": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-select": { - "version": "2.2.6", - "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.2.6.tgz", - "integrity": "sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ==", - "license": "MIT", - "dependencies": { - "@radix-ui/number": "1.1.1", - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-collection": "1.1.7", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-direction": "1.1.1", - "@radix-ui/react-dismissable-layer": "1.1.11", - "@radix-ui/react-focus-guards": "1.1.3", - "@radix-ui/react-focus-scope": "1.1.7", - "@radix-ui/react-id": "1.1.1", - "@radix-ui/react-popper": "1.2.8", - "@radix-ui/react-portal": "1.1.9", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-slot": "1.2.3", - "@radix-ui/react-use-callback-ref": "1.1.1", - "@radix-ui/react-use-controllable-state": "1.2.2", - "@radix-ui/react-use-layout-effect": "1.1.1", - "@radix-ui/react-use-previous": "1.1.1", - "@radix-ui/react-visually-hidden": "1.2.3", - "aria-hidden": "^1.2.4", - "react-remove-scroll": "^2.6.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-separator": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.7.tgz", - "integrity": "sha512-0HEb8R9E8A+jZjvmFCy/J4xhbXy3TV+9XSnGJ3KvTtjlIUy/YQ/p6UYZvi7YbeoeXdyU9+Y3scizK6hkY37baA==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-primitive": "2.1.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-slider": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slider/-/react-slider-1.3.6.tgz", - "integrity": "sha512-JPYb1GuM1bxfjMRlNLE+BcmBC8onfCi60Blk7OBqi2MLTFdS+8401U4uFjnwkOr49BLmXxLC6JHkvAsx5OJvHw==", - "license": "MIT", - "dependencies": { - "@radix-ui/number": "1.1.1", - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-collection": "1.1.7", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-direction": "1.1.1", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-controllable-state": "1.2.2", - "@radix-ui/react-use-layout-effect": "1.1.1", - "@radix-ui/react-use-previous": "1.1.1", - "@radix-ui/react-use-size": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-slot": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", - "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.2" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-switch": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.2.6.tgz", - "integrity": "sha512-bByzr1+ep1zk4VubeEVViV592vu2lHE2BZY5OnzehZqOOgogN80+mNtCqPkhn2gklJqOpxWgPoYTSnhBCqpOXQ==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-controllable-state": "1.2.2", - "@radix-ui/react-use-previous": "1.1.1", - "@radix-ui/react-use-size": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-tabs": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.13.tgz", - "integrity": "sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-direction": "1.1.1", - "@radix-ui/react-id": "1.1.1", - "@radix-ui/react-presence": "1.1.5", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-roving-focus": "1.1.11", - "@radix-ui/react-use-controllable-state": "1.2.2" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-toast": { - "version": "1.2.15", - "resolved": "https://registry.npmjs.org/@radix-ui/react-toast/-/react-toast-1.2.15.tgz", - "integrity": "sha512-3OSz3TacUWy4WtOXV38DggwxoqJK4+eDkNMl5Z/MJZaoUPaP4/9lf81xXMe1I2ReTAptverZUpbPY4wWwWyL5g==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-collection": "1.1.7", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-dismissable-layer": "1.1.11", - "@radix-ui/react-portal": "1.1.9", - "@radix-ui/react-presence": "1.1.5", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-callback-ref": "1.1.1", - "@radix-ui/react-use-controllable-state": "1.2.2", - "@radix-ui/react-use-layout-effect": "1.1.1", - "@radix-ui/react-visually-hidden": "1.2.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-toggle": { - "version": "1.1.10", - "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle/-/react-toggle-1.1.10.tgz", - "integrity": "sha512-lS1odchhFTeZv3xwHH31YPObmJn8gOg7Lq12inrr0+BH/l3Tsq32VfjqH1oh80ARM3mlkfMic15n0kg4sD1poQ==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-controllable-state": "1.2.2" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-toggle-group": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle-group/-/react-toggle-group-1.1.11.tgz", - "integrity": "sha512-5umnS0T8JQzQT6HbPyO7Hh9dgd82NmS36DQr+X/YJ9ctFNCiiQd6IJAYYZ33LUwm8M+taCz5t2ui29fHZc4Y6Q==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-direction": "1.1.1", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-roving-focus": "1.1.11", - "@radix-ui/react-toggle": "1.1.10", - "@radix-ui/react-use-controllable-state": "1.2.2" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-toolbar": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/@radix-ui/react-toolbar/-/react-toolbar-1.1.11.tgz", - "integrity": "sha512-4ol06/1bLoFu1nwUqzdD4Y5RZ9oDdKeiHIsntug54Hcr1pgaHiPqHFEaXI1IFP/EsOfROQZ8Mig9VTIRza6Tjg==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-direction": "1.1.1", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-roving-focus": "1.1.11", - "@radix-ui/react-separator": "1.1.7", - "@radix-ui/react-toggle-group": "1.1.11" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-tooltip": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.2.8.tgz", - "integrity": "sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-dismissable-layer": "1.1.11", - "@radix-ui/react-id": "1.1.1", - "@radix-ui/react-popper": "1.2.8", - "@radix-ui/react-portal": "1.1.9", - "@radix-ui/react-presence": "1.1.5", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-slot": "1.2.3", - "@radix-ui/react-use-controllable-state": "1.2.2", - "@radix-ui/react-visually-hidden": "1.2.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-callback-ref": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz", - "integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-controllable-state": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz", - "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-use-effect-event": "0.0.2", - "@radix-ui/react-use-layout-effect": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-effect-event": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz", - "integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-use-layout-effect": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-escape-keydown": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.1.tgz", - "integrity": "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-use-callback-ref": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-is-hydrated": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-is-hydrated/-/react-use-is-hydrated-0.1.0.tgz", - "integrity": "sha512-U+UORVEq+cTnRIaostJv9AGdV3G6Y+zbVd+12e18jQ5A3c0xL03IhnHuiU4UV69wolOQp5GfR58NW/EgdQhwOA==", - "license": "MIT", - "dependencies": { - "use-sync-external-store": "^1.5.0" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-layout-effect": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz", - "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-previous": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.1.tgz", - "integrity": "sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-rect": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.1.tgz", - "integrity": "sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==", - "license": "MIT", - "dependencies": { - "@radix-ui/rect": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-size": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.1.tgz", - "integrity": "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-use-layout-effect": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-visually-hidden": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.3.tgz", - "integrity": "sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-primitive": "2.1.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/rect": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.1.tgz", - "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==", - "license": "MIT" - }, - "node_modules/@rolldown/binding-android-arm64": { - "version": "1.0.0-rc.15", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.15.tgz", - "integrity": "sha512-YYe6aWruPZDtHNpwu7+qAHEMbQ/yRl6atqb/AhznLTnD3UY99Q1jE7ihLSahNWkF4EqRPVC4SiR4O0UkLK02tA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.0.0-rc.15", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.15.tgz", - "integrity": "sha512-oArR/ig8wNTPYsXL+Mzhs0oxhxfuHRfG7Ikw7jXsw8mYOtk71W0OkF2VEVh699pdmzjPQsTjlD1JIOoHkLP1Fg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.0.0-rc.15", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.15.tgz", - "integrity": "sha512-YzeVqOqjPYvUbJSWJ4EDL8ahbmsIXQpgL3JVipmN+MX0XnXMeWomLN3Fb+nwCmP/jfyqte5I3XRSm7OfQrbyxw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.0.0-rc.15", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.15.tgz", - "integrity": "sha512-9Erhx956jeQ0nNTyif1+QWAXDRD38ZNjr//bSHrt6wDwB+QkAfl2q6Mn1k6OBPerznjRmbM10lgRb1Pli4xZPw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.0.0-rc.15", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.15.tgz", - "integrity": "sha512-cVwk0w8QbZJGTnP/AHQBs5yNwmpgGYStL88t4UIaqcvYJWBfS0s3oqVLZPwsPU6M0zlW4GqjP0Zq5MnAGwFeGA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.0.0-rc.15", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.15.tgz", - "integrity": "sha512-eBZ/u8iAK9SoHGanqe/jrPnY0JvBN6iXbVOsbO38mbz+ZJsaobExAm1Iu+rxa4S1l2FjG0qEZn4Rc6X8n+9M+w==", - "cpu": [ - "arm64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.0.0-rc.15", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.15.tgz", - "integrity": "sha512-ZvRYMGrAklV9PEkgt4LQM6MjQX2P58HPAuecwYObY2DhS2t35R0I810bKi0wmaYORt6m/2Sm+Z+nFgb0WhXNcQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "libc": [ - "musl" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.0.0-rc.15", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.15.tgz", - "integrity": "sha512-VDpgGBzgfg5hLg+uBpCLoFG5kVvEyafmfxGUV0UHLcL5irxAK7PKNeC2MwClgk6ZAiNhmo9FLhRYgvMmedLtnQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.0.0-rc.15", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.15.tgz", - "integrity": "sha512-y1uXY3qQWCzcPgRJATPSOUP4tCemh4uBdY7e3EZbVwCJTY3gLJWnQABgeUetvED+bt1FQ01OeZwvhLS2bpNrAQ==", - "cpu": [ - "s390x" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.0.0-rc.15", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.15.tgz", - "integrity": "sha512-023bTPBod7J3Y/4fzAN6QtpkSABR0rigtrwaP+qSEabUh5zf6ELr9Nc7GujaROuPY3uwdSIXWrvhn1KxOvurWA==", - "cpu": [ - "x64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.0.0-rc.15", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.15.tgz", - "integrity": "sha512-witB2O0/hU4CgfOOKUoeFgQ4GktPi1eEbAhaLAIpgD6+ZnhcPkUtPsoKKHRzmOoWPZue46IThdSgdo4XneOLYw==", - "cpu": [ - "x64" - ], - "dev": true, - "libc": [ - "musl" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.0.0-rc.15", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.15.tgz", - "integrity": "sha512-UCL68NJ0Ud5zRipXZE9dF5PmirzJE4E4BCIOOssEnM7wLDsxjc6Qb0sGDxTNRTP53I6MZpygyCpY8Aa8sPfKPg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.0.0-rc.15", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.15.tgz", - "integrity": "sha512-ApLruZq/ig+nhaE7OJm4lDjayUnOHVUa77zGeqnqZ9pn0ovdVbbNPerVibLXDmWeUZXjIYIT8V3xkT58Rm9u5Q==", - "cpu": [ - "wasm32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "1.9.2", - "@emnapi/runtime": "1.9.2", - "@napi-rs/wasm-runtime": "^1.1.3" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.0.0-rc.15", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.15.tgz", - "integrity": "sha512-KmoUoU7HnN+Si5YWJigfTws1jz1bKBYDQKdbLspz0UaqjjFkddHsqorgiW1mxcAj88lYUE6NC/zJNwT+SloqtA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.0.0-rc.15", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.15.tgz", - "integrity": "sha512-3P2A8L+x75qavWLe/Dll3EYBJLQmtkJN8rfh+U/eR3MqMgL/h98PhYI+JFfXuDPgPeCB7iZAKiqii5vqOvnA0g==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/pluginutils": { - "version": "1.0.0-rc.7", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.7.tgz", - "integrity": "sha512-qujRfC8sFVInYSPPMLQByRh7zhwkGFS4+tyMQ83srV1qrxL4g8E2tyxVVyxd0+8QeBM1mIk9KbWxkegRr76XzA==", - "dev": true, - "license": "MIT" - }, - "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==", - "dev": true, - "license": "MIT" - }, - "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==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@size-limit/file": { - "version": "12.0.1", - "resolved": "https://registry.npmjs.org/@size-limit/file/-/file-12.0.1.tgz", - "integrity": "sha512-Kvbnz46iV7WeHaANf1HmWjXBVMU2KkCU+0xJ78FzIjZwlVKKEqy+QCZprdBMfIWrzrvYeqP4cfuzKG8z6xVivg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "peerDependencies": { - "size-limit": "12.0.1" - } - }, - "node_modules/@tailwindcss/node": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.2.tgz", - "integrity": "sha512-pXS+wJ2gZpVXqFaUEjojq7jzMpTGf8rU6ipJz5ovJV6PUGmlJ+jvIwGrzdHdQ80Sg+wmQxUFuoW1UAAwHNEdFA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/remapping": "^2.3.5", - "enhanced-resolve": "^5.19.0", - "jiti": "^2.6.1", - "lightningcss": "1.32.0", - "magic-string": "^0.30.21", - "source-map-js": "^1.2.1", - "tailwindcss": "4.2.2" - } - }, - "node_modules/@tailwindcss/oxide": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.2.2.tgz", - "integrity": "sha512-qEUA07+E5kehxYp9BVMpq9E8vnJuBHfJEC0vPC5e7iL/hw7HR61aDKoVoKzrG+QKp56vhNZe4qwkRmMC0zDLvg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 20" - }, - "optionalDependencies": { - "@tailwindcss/oxide-android-arm64": "4.2.2", - "@tailwindcss/oxide-darwin-arm64": "4.2.2", - "@tailwindcss/oxide-darwin-x64": "4.2.2", - "@tailwindcss/oxide-freebsd-x64": "4.2.2", - "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.2", - "@tailwindcss/oxide-linux-arm64-gnu": "4.2.2", - "@tailwindcss/oxide-linux-arm64-musl": "4.2.2", - "@tailwindcss/oxide-linux-x64-gnu": "4.2.2", - "@tailwindcss/oxide-linux-x64-musl": "4.2.2", - "@tailwindcss/oxide-wasm32-wasi": "4.2.2", - "@tailwindcss/oxide-win32-arm64-msvc": "4.2.2", - "@tailwindcss/oxide-win32-x64-msvc": "4.2.2" - } - }, - "node_modules/@tailwindcss/oxide-android-arm64": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.2.2.tgz", - "integrity": "sha512-dXGR1n+P3B6748jZO/SvHZq7qBOqqzQ+yFrXpoOWWALWndF9MoSKAT3Q0fYgAzYzGhxNYOoysRvYlpixRBBoDg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/oxide-darwin-arm64": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.2.2.tgz", - "integrity": "sha512-iq9Qjr6knfMpZHj55/37ouZeykwbDqF21gPFtfnhCCKGDcPI/21FKC9XdMO/XyBM7qKORx6UIhGgg6jLl7BZlg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/oxide-darwin-x64": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.2.2.tgz", - "integrity": "sha512-BlR+2c3nzc8f2G639LpL89YY4bdcIdUmiOOkv2GQv4/4M0vJlpXEa0JXNHhCHU7VWOKWT/CjqHdTP8aUuDJkuw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/oxide-freebsd-x64": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.2.2.tgz", - "integrity": "sha512-YUqUgrGMSu2CDO82hzlQ5qSb5xmx3RUrke/QgnoEx7KvmRJHQuZHZmZTLSuuHwFf0DJPybFMXMYf+WJdxHy/nQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.2.2.tgz", - "integrity": "sha512-FPdhvsW6g06T9BWT0qTwiVZYE2WIFo2dY5aCSpjG/S/u1tby+wXoslXS0kl3/KXnULlLr1E3NPRRw0g7t2kgaQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.2.2.tgz", - "integrity": "sha512-4og1V+ftEPXGttOO7eCmW7VICmzzJWgMx+QXAJRAhjrSjumCwWqMfkDrNu1LXEQzNAwz28NCUpucgQPrR4S2yw==", - "cpu": [ - "arm64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm64-musl": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.2.2.tgz", - "integrity": "sha512-oCfG/mS+/+XRlwNjnsNLVwnMWYH7tn/kYPsNPh+JSOMlnt93mYNCKHYzylRhI51X+TbR+ufNhhKKzm6QkqX8ag==", - "cpu": [ - "arm64" - ], - "dev": true, - "libc": [ - "musl" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/oxide-linux-x64-gnu": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.2.2.tgz", - "integrity": "sha512-rTAGAkDgqbXHNp/xW0iugLVmX62wOp2PoE39BTCGKjv3Iocf6AFbRP/wZT/kuCxC9QBh9Pu8XPkv/zCZB2mcMg==", - "cpu": [ - "x64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/oxide-linux-x64-musl": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.2.2.tgz", - "integrity": "sha512-XW3t3qwbIwiSyRCggeO2zxe3KWaEbM0/kW9e8+0XpBgyKU4ATYzcVSMKteZJ1iukJ3HgHBjbg9P5YPRCVUxlnQ==", - "cpu": [ - "x64" - ], - "dev": true, - "libc": [ - "musl" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.2.2.tgz", - "integrity": "sha512-eKSztKsmEsn1O5lJ4ZAfyn41NfG7vzCg496YiGtMDV86jz1q/irhms5O0VrY6ZwTUkFy/EKG3RfWgxSI3VbZ8Q==", - "bundleDependencies": [ - "@napi-rs/wasm-runtime", - "@emnapi/core", - "@emnapi/runtime", - "@tybys/wasm-util", - "@emnapi/wasi-threads", - "tslib" - ], - "cpu": [ - "wasm32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "^1.8.1", - "@emnapi/runtime": "^1.8.1", - "@emnapi/wasi-threads": "^1.1.0", - "@napi-rs/wasm-runtime": "^1.1.1", - "@tybys/wasm-util": "^0.10.1", - "tslib": "^2.8.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.2.2.tgz", - "integrity": "sha512-qPmaQM4iKu5mxpsrWZMOZRgZv1tOZpUm+zdhhQP0VhJfyGGO3aUKdbh3gDZc/dPLQwW4eSqWGrrcWNBZWUWaXQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/oxide-win32-x64-msvc": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.2.2.tgz", - "integrity": "sha512-1T/37VvI7WyH66b+vqHj/cLwnCxt7Qt3WFu5Q8hk65aOvlwAhs7rAp1VkulBJw/N4tMirXjVnylTR72uI0HGcA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/vite": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.2.2.tgz", - "integrity": "sha512-mEiF5HO1QqCLXoNEfXVA1Tzo+cYsrqV7w9Juj2wdUFyW07JRenqMG225MvPwr3ZD9N1bFQj46X7r33iHxLUW0w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@tailwindcss/node": "4.2.2", - "@tailwindcss/oxide": "4.2.2", - "tailwindcss": "4.2.2" - }, - "peerDependencies": { - "vite": "^5.2.0 || ^6 || ^7 || ^8" - } - }, - "node_modules/@ts-morph/common": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@ts-morph/common/-/common-0.27.0.tgz", - "integrity": "sha512-Wf29UqxWDpc+i61k3oIOzcUfQt79PIT9y/MWfAGlrkjg6lBC1hwDECLXPVJAhWjiGbfBCxZd65F/LIZF3+jeJQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-glob": "^3.3.3", - "minimatch": "^10.0.1", - "path-browserify": "^1.0.1" - } - }, - "node_modules/@tybys/wasm-util": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", - "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@types/esrecurse": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", - "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "24.12.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.2.tgz", - "integrity": "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~7.16.0" - } - }, - "node_modules/@types/react": { - "version": "19.2.14", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", - "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "csstype": "^3.2.2" - } - }, - "node_modules/@types/react-dom": { - "version": "19.2.3", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", - "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", - "devOptional": true, - "license": "MIT", - "peerDependencies": { - "@types/react": "^19.2.0" - } - }, - "node_modules/@types/statuses": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@types/statuses/-/statuses-2.0.6.tgz", - "integrity": "sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/validate-npm-package-name": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/validate-npm-package-name/-/validate-npm-package-name-4.0.2.tgz", - "integrity": "sha512-lrpDziQipxCEeK5kWxvljWYhUvOiB2A9izZd9B2AFarYAkqZshb4lPbRs7zKEic6eGtH8V/2qJW+dPp9OtF6bw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.58.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.58.1.tgz", - "integrity": "sha512-eSkwoemjo76bdXl2MYqtxg51HNwUSkWfODUOQ3PaTLZGh9uIWWFZIjyjaJnex7wXDu+TRx+ATsnSxdN9YWfRTQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.58.1", - "@typescript-eslint/type-utils": "8.58.1", - "@typescript-eslint/utils": "8.58.1", - "@typescript-eslint/visitor-keys": "8.58.1", - "ignore": "^7.0.5", - "natural-compare": "^1.4.0", - "ts-api-utils": "^2.5.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^8.58.1", - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", - "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/@typescript-eslint/parser": { - "version": "8.58.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.58.1.tgz", - "integrity": "sha512-gGkiNMPqerb2cJSVcruigx9eHBlLG14fSdPdqMoOcBfh+vvn4iCq2C8MzUB89PrxOXk0y3GZ1yIWb9aOzL93bw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/scope-manager": "8.58.1", - "@typescript-eslint/types": "8.58.1", - "@typescript-eslint/typescript-estree": "8.58.1", - "@typescript-eslint/visitor-keys": "8.58.1", - "debug": "^4.4.3" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/project-service": { - "version": "8.58.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.58.1.tgz", - "integrity": "sha512-gfQ8fk6cxhtptek+/8ZIqw8YrRW5048Gug8Ts5IYcMLCw18iUgrZAEY/D7s4hkI0FxEfGakKuPK/XUMPzPxi5g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.58.1", - "@typescript-eslint/types": "^8.58.1", - "debug": "^4.4.3" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "8.58.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.58.1.tgz", - "integrity": "sha512-TPYUEqJK6avLcEjumWsIuTpuYODTTDAtoMdt8ZZa93uWMTX13Nb8L5leSje1NluammvU+oI3QRr5lLXPgihX3w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.58.1", - "@typescript-eslint/visitor-keys": "8.58.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.58.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.58.1.tgz", - "integrity": "sha512-JAr2hOIct2Q+qk3G+8YFfqkqi7sC86uNryT+2i5HzMa2MPjw4qNFvtjnw1IiA1rP7QhNKVe21mSSLaSjwA1Olw==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/type-utils": { - "version": "8.58.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.58.1.tgz", - "integrity": "sha512-HUFxvTJVroT+0rXVJC7eD5zol6ID+Sn5npVPWoFuHGg9Ncq5Q4EYstqR+UOqaNRFXi5TYkpXXkLhoCHe3G0+7w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.58.1", - "@typescript-eslint/typescript-estree": "8.58.1", - "@typescript-eslint/utils": "8.58.1", - "debug": "^4.4.3", - "ts-api-utils": "^2.5.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/types": { - "version": "8.58.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.58.1.tgz", - "integrity": "sha512-io/dV5Aw5ezwzfPBBWLoT+5QfVtP8O7q4Kftjn5azJ88bYyp/ZMCsyW1lpKK46EXJcaYMZ1JtYj+s/7TdzmQMw==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.58.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.58.1.tgz", - "integrity": "sha512-w4w7WR7GHOjqqPnvAYbazq+Y5oS68b9CzasGtnd6jIeOIeKUzYzupGTB2T4LTPSv4d+WPeccbxuneTFHYgAAWg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/project-service": "8.58.1", - "@typescript-eslint/tsconfig-utils": "8.58.1", - "@typescript-eslint/types": "8.58.1", - "@typescript-eslint/visitor-keys": "8.58.1", - "debug": "^4.4.3", - "minimatch": "^10.2.2", - "semver": "^7.7.3", - "tinyglobby": "^0.2.15", - "ts-api-utils": "^2.5.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@typescript-eslint/utils": { - "version": "8.58.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.58.1.tgz", - "integrity": "sha512-Ln8R0tmWC7pTtLOzgJzYTXSCjJ9rDNHAqTaVONF4FEi2qwce8mD9iSOxOpLFFvWp/wBFlew0mjM1L1ihYWfBdQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.58.1", - "@typescript-eslint/types": "8.58.1", - "@typescript-eslint/typescript-estree": "8.58.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.58.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.58.1.tgz", - "integrity": "sha512-y+vH7QE8ycjoa0bWciFg7OpFcipUuem1ujhrdLtq1gByKwfbC7bPeKsiny9e0urg93DqwGcHey+bGRKCnF1nZQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.58.1", - "eslint-visitor-keys": "^5.0.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript/vfs": { - "version": "1.6.4", - "resolved": "https://registry.npmjs.org/@typescript/vfs/-/vfs-1.6.4.tgz", - "integrity": "sha512-PJFXFS4ZJKiJ9Qiuix6Dz/OwEIqHD7Dme1UwZhTK11vR+5dqW2ACbdndWQexBzCx+CPuMe5WBYQWCsFyGlQLlQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.4.3" - }, - "peerDependencies": { - "typescript": "*" - } - }, - "node_modules/@vitejs/plugin-react": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.1.tgz", - "integrity": "sha512-l9X/E3cDb+xY3SWzlG1MOGt2usfEHGMNIaegaUGFsLkb3RCn/k8/TOXBcab+OndDI4TBtktT8/9BwwW8Vi9KUQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@rolldown/pluginutils": "1.0.0-rc.7" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "peerDependencies": { - "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", - "babel-plugin-react-compiler": "^1.0.0", - "vite": "^8.0.0" - }, - "peerDependenciesMeta": { - "@rolldown/plugin-babel": { - "optional": true - }, - "babel-plugin-react-compiler": { - "optional": true - } - } - }, - "node_modules/accepts": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", - "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", - "dev": true, - "license": "MIT", - "dependencies": { - "mime-types": "^3.0.0", - "negotiator": "^1.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "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==", - "dev": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/agent-base": { - "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/ajv": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", - "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ajv-formats": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", - "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } - } - }, - "node_modules/ajv-formats/node_modules/ajv": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", - "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ajv-formats/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true, - "license": "MIT" - }, - "node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/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/aria-hidden": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz", - "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==", - "license": "MIT", - "dependencies": { - "tslib": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/ast-types": { - "version": "0.16.1", - "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.16.1.tgz", - "integrity": "sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==", - "dev": true, - "license": "MIT", - "dependencies": { - "tslib": "^2.0.1" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/baseline-browser-mapping": { - "version": "2.10.18", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.18.tgz", - "integrity": "sha512-VSnGQAOLtP5mib/DPyg2/t+Tlv65NTBz83BJBJvmLVHHuKJVaDOBvJJykiT5TR++em5nfAySPccDZDa4oSrn8A==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "baseline-browser-mapping": "dist/cli.cjs" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/body-parser": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", - "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", - "dev": true, - "license": "MIT", - "dependencies": { - "bytes": "^3.1.2", - "content-type": "^1.0.5", - "debug": "^4.4.3", - "http-errors": "^2.0.0", - "iconv-lite": "^0.7.0", - "on-finished": "^2.4.1", - "qs": "^6.14.1", - "raw-body": "^3.0.1", - "type-is": "^2.0.1" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "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/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/browserslist": { - "version": "4.28.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", - "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "baseline-browser-mapping": "^2.10.12", - "caniuse-lite": "^1.0.30001782", - "electron-to-chromium": "^1.5.328", - "node-releases": "^2.0.36", - "update-browserslist-db": "^1.2.3" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/bundle-name": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", - "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "run-applescript": "^7.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/bytes-iec": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/bytes-iec/-/bytes-iec-3.1.1.tgz", - "integrity": "sha512-fey6+4jDK7TFtFg/klGSvNKJctyU7n2aQdnM+CO0ruLPbqqMOM8Tio0Pc+deqUeVKX1tL5DQep1zQ7+37aTAsA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/call-bound": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001787", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001787.tgz", - "integrity": "sha512-mNcrMN9KeI68u7muanUpEejSLghOKlVhRqS/Za2IeyGllJ9I9otGpR9g3nsw7n4W378TE/LyIteA0+/FOZm4Kg==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/chalk": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", - "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/class-variance-authority": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", - "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==", - "license": "Apache-2.0", - "dependencies": { - "clsx": "^2.1.1" - }, - "funding": { - "url": "https://polar.sh/cva" - } - }, - "node_modules/cli-cursor": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", - "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", - "dev": true, - "license": "MIT", - "dependencies": { - "restore-cursor": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cli-spinners": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", - "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cli-width": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", - "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">= 12" - } - }, - "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/cliui/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/cliui/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/clsx": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", - "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/code-block-writer": { - "version": "13.0.3", - "resolved": "https://registry.npmjs.org/code-block-writer/-/code-block-writer-13.0.3.tgz", - "integrity": "sha512-Oofo0pq3IKnsFtuHqSF7TqBfr71aeyZDVJ0HpmqB7FBM2qEigL0iPONSCZSO9pE9dZTAxANe5XHG9Uy0YMv8cg==", - "dev": true, - "license": "MIT" - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/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", - "engines": { - "node": ">=20" - } - }, - "node_modules/content-disposition": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", - "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, - "license": "MIT" - }, - "node_modules/cookie": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-signature": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", - "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.6.0" - } - }, - "node_modules/cors": { - "version": "2.8.6", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", - "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", - "dev": true, - "license": "MIT", - "dependencies": { - "object-assign": "^4", - "vary": "^1" - }, - "engines": { - "node": ">= 0.10" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/cosmiconfig": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.1.tgz", - "integrity": "sha512-hr4ihw+DBqcvrsEDioRO31Z17x71pUYoNe/4h6Z0wB72p7MU7/9gH8Q3s12NFhHPfYBBOV3qyfUxmr/Yn3shnQ==", - "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/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/cssesc": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", - "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", - "dev": true, - "license": "MIT", - "bin": { - "cssesc": "bin/cssesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/csstype": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", - "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "devOptional": true, - "license": "MIT" - }, - "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", - "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 12" - } - }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/dedent": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.2.tgz", - "integrity": "sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "babel-plugin-macros": "^3.1.0" - }, - "peerDependenciesMeta": { - "babel-plugin-macros": { - "optional": true - } - } - }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/deepmerge": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/default-browser": { - "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": { - "bundle-name": "^4.1.0", - "default-browser-id": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/default-browser-id": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz", - "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/define-lazy-prop": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", - "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/detect-libc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", - "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=8" - } - }, - "node_modules/detect-node-es": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", - "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", - "license": "MIT" - }, - "node_modules/diff": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.4.tgz", - "integrity": "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/dotenv": { - "version": "17.4.1", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.1.tgz", - "integrity": "sha512-k8DaKGP6r1G30Lx8V4+pCsLzKr8vLmV2paqEj1Y55GdAgJuIqpRp5FfajGF8KtwMxCz9qJc6wUIJnm053d/WCw==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://dotenvx.com" - } - }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/eciesjs": { - "version": "0.4.18", - "resolved": "https://registry.npmjs.org/eciesjs/-/eciesjs-0.4.18.tgz", - "integrity": "sha512-wG99Zcfcys9fZux7Cft8BAX/YrOJLJSZ3jyYPfhZHqN2E+Ffx+QXBDsv3gubEgPtV6dTzJMSQUwk1H98/t/0wQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@ecies/ciphers": "^0.2.5", - "@noble/ciphers": "^1.3.0", - "@noble/curves": "^1.9.7", - "@noble/hashes": "^1.8.0" - }, - "engines": { - "bun": ">=1", - "deno": ">=2", - "node": ">=16" - } - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "dev": true, - "license": "MIT" - }, - "node_modules/electron-to-chromium": { - "version": "1.5.335", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.335.tgz", - "integrity": "sha512-q9n5T4BR4Xwa2cwbrwcsDJtHD/enpQ5S1xF1IAtdqf5AAgqDFmR/aakqH3ChFdqd/QXJhS3rnnXFtexU7rax6Q==", - "dev": true, - "license": "ISC" - }, - "node_modules/emoji-regex": { - "version": "10.6.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", - "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", - "dev": true, - "license": "MIT" - }, - "node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/enhanced-resolve": { - "version": "5.20.1", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.20.1.tgz", - "integrity": "sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.3.0" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/env-paths": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", - "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/error-ex": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", - "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "dev": true, - "license": "MIT" - }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.2.0.tgz", - "integrity": "sha512-+L0vBFYGIpSNIt/KWTpFonPrqYvgKw1eUI5Vn7mEogrQcWtWYtNQ7dNqC+px/J0idT3BAkiWrhfS7k+Tum8TUA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.8.0", - "@eslint-community/regexpp": "^4.12.2", - "@eslint/config-array": "^0.23.4", - "@eslint/config-helpers": "^0.5.4", - "@eslint/core": "^1.2.0", - "@eslint/plugin-kit": "^0.7.0", - "@humanfs/node": "^0.16.6", - "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.4.2", - "@types/estree": "^1.0.6", - "ajv": "^6.14.0", - "cross-spawn": "^7.0.6", - "debug": "^4.3.2", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^9.1.2", - "eslint-visitor-keys": "^5.0.1", - "espree": "^11.2.0", - "esquery": "^1.7.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^8.0.0", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "minimatch": "^10.2.4", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://eslint.org/donate" - }, - "peerDependencies": { - "jiti": "*" - }, - "peerDependenciesMeta": { - "jiti": { - "optional": true - } - } - }, - "node_modules/eslint-config-prettier": { - "version": "10.1.8", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz", - "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", - "dev": true, - "license": "MIT", - "bin": { - "eslint-config-prettier": "bin/cli.js" - }, - "funding": { - "url": "https://opencollective.com/eslint-config-prettier" - }, - "peerDependencies": { - "eslint": ">=7.0.0" - } - }, - "node_modules/eslint-scope": { - "version": "9.1.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz", - "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "@types/esrecurse": "^4.3.1", - "@types/estree": "^1.0.8", - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", - "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/espree": { - "version": "11.2.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz", - "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "acorn": "^8.16.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^5.0.1" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, - "license": "BSD-2-Clause", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/esquery": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", - "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/eventsource": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", - "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", - "dev": true, - "license": "MIT", - "dependencies": { - "eventsource-parser": "^3.0.1" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/eventsource-parser": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz", - "integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/execa": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-9.6.1.tgz", - "integrity": "sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==", - "dev": true, - "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" - } - }, - "node_modules/express": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", - "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", - "dev": true, - "license": "MIT", - "dependencies": { - "accepts": "^2.0.0", - "body-parser": "^2.2.1", - "content-disposition": "^1.0.0", - "content-type": "^1.0.5", - "cookie": "^0.7.1", - "cookie-signature": "^1.2.1", - "debug": "^4.4.0", - "depd": "^2.0.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "finalhandler": "^2.1.0", - "fresh": "^2.0.0", - "http-errors": "^2.0.0", - "merge-descriptors": "^2.0.0", - "mime-types": "^3.0.0", - "on-finished": "^2.4.1", - "once": "^1.4.0", - "parseurl": "^1.3.3", - "proxy-addr": "^2.0.7", - "qs": "^6.14.0", - "range-parser": "^1.2.1", - "router": "^2.2.0", - "send": "^1.1.0", - "serve-static": "^2.2.0", - "statuses": "^2.0.1", - "type-is": "^2.0.1", - "vary": "^1.1.2" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/express-rate-limit": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.3.2.tgz", - "integrity": "sha512-77VmFeJkO0/rvimEDuUC5H30oqUC4EyOhyGccfqoLebB0oiEYfM7nwPrsDsBL1gsTpwfzX8SFy2MT3TDyRq+bg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ip-address": "10.1.0" - }, - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://github.com/sponsors/express-rate-limit" - }, - "peerDependencies": { - "express": ">= 4.11" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-glob": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", - "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.8" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-glob/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", - "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "BSD-3-Clause" - }, - "node_modules/fastq": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", - "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", - "dev": true, - "license": "ISC", - "dependencies": { - "reusify": "^1.0.4" - } - }, - "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/fetch-blob": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", - "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "paypal", - "url": "https://paypal.me/jimmywarting" - } - ], - "license": "MIT", - "dependencies": { - "node-domexception": "^1.0.0", - "web-streams-polyfill": "^3.0.3" - }, - "engines": { - "node": "^12.20 || >= 14.13" - } - }, - "node_modules/figures": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz", - "integrity": "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-unicode-supported": "^2.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/file-entry-cache": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", - "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "flat-cache": "^4.0.0" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/finalhandler": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", - "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.4.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "on-finished": "^2.4.1", - "parseurl": "^1.3.3", - "statuses": "^2.0.1" - }, - "engines": { - "node": ">= 18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/flat-cache": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", - "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", - "dev": true, - "license": "MIT", - "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.4" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/flatted": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", - "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", - "dev": true, - "license": "ISC" - }, - "node_modules/formdata-polyfill": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", - "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "fetch-blob": "^3.1.2" - }, - "engines": { - "node": ">=12.20.0" - } - }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fresh": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", - "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/fs-extra": { - "version": "11.3.4", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.4.tgz", - "integrity": "sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=14.14" - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/fuzzysort": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/fuzzysort/-/fuzzysort-3.1.0.tgz", - "integrity": "sha512-sR9BNCjBg6LNgwvxlBd0sBABvQitkLzoVY9MYYROQVX/FvfJ4Mai9LsGhDgd8qYdds0bY77VzYd5iuB+v5rwQQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, - "license": "ISC", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-east-asian-width": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.5.0.tgz", - "integrity": "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-nonce": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", - "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/get-own-enumerable-keys": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/get-own-enumerable-keys/-/get-own-enumerable-keys-1.0.0.tgz", - "integrity": "sha512-PKsK2FSrQCyxcGHsGrLDcK0lx+0Ke+6e8KFFozA9/fIQLhQzPaRvJFdcz7+Axg3jUH/Mq+NI4xa5u/UT2tQskA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.16" - }, - "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", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "dev": true, - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/get-stream": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", - "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", - "dev": true, - "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" - } - }, - "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/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/graphql": { - "version": "16.13.2", - "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.13.2.tgz", - "integrity": "sha512-5bJ+nf/UCpAjHM8i06fl7eLyVC9iuNAjm9qzkiu2ZGhM0VscSvS6WDPfAwkdkBuoXGM9FJSbKl6wylMwP9Ktig==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" - } - }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/headers-polyfill": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/headers-polyfill/-/headers-polyfill-4.0.3.tgz", - "integrity": "sha512-IScLbePpkvO846sIwOtOTDjutRMWdXdJmXdMvk6gCBHxFO8d+QKOQedyZSxFTTFYRSmlgSTDtXqqq4pcenBXLQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/hono": { - "version": "4.12.12", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.12.tgz", - "integrity": "sha512-p1JfQMKaceuCbpJKAPKVqyqviZdS0eUxH9v82oWo1kb9xjQ5wA6iP3FNVAPDFlz5/p7d45lO+BpSk1tuSZMF4Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=16.9.0" - } - }, - "node_modules/http-errors": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", - "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "depd": "~2.0.0", - "inherits": "~2.0.4", - "setprototypeof": "~1.2.0", - "statuses": "~2.0.2", - "toidentifier": "~1.0.1" - }, - "engines": { - "node": ">= 0.8" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "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==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/human-signals": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.1.tgz", - "integrity": "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18.0" - } - }, - "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", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/import-fresh": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", - "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/ip-address": { - "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", - "engines": { - "node": ">= 12" - } - }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true, - "license": "MIT" - }, - "node_modules/is-docker": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", - "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", - "dev": true, - "license": "MIT", - "bin": { - "is-docker": "cli.js" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "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", - "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-docker": "^3.0.0" - }, - "bin": { - "is-inside-container": "cli.js" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-interactive": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", - "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-node-process": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/is-node-process/-/is-node-process-1.2.0.tgz", - "integrity": "sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==", - "dev": true, - "license": "MIT" - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-obj": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-3.0.0.tgz", - "integrity": "sha512-IlsXEHOjtKhpN8r/tRFj2nDyTmHvcfNeu/nrRIcXE17ROeatXchkojffa1SpdqW4cr/Fj6QkEf/Gn4zf6KKvEQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-promise": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", - "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/is-regexp": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-3.1.0.tgz", - "integrity": "sha512-rbku49cWloU5bSMI+zaRaXdQHXnthP6DZ/vLnfdSKyL4zUzuWnomtOEiZZOd+ioQ+avFo/qau3KPTc7Fjy1uPA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-unicode-supported": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", - "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC" - }, - "node_modules/jiti": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", - "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", - "dev": true, - "license": "MIT", - "bin": { - "jiti": "lib/jiti-cli.mjs" - } - }, - "node_modules/jose": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.2.tgz", - "integrity": "sha512-d7kPDd34KO/YnzaDOlikGpOurfF0ByC2sEV4cANCtdqLlTfBlw2p14O/5d/zv40gJPbIQxfES3nSx1/oYNyuZQ==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/panva" - } - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/js-yaml": { - "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": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/jsesc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", - "dev": true, - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-schema-typed": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz", - "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", - "dev": true, - "license": "BSD-2-Clause" - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, - "license": "MIT", - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/jsonfile": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", - "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", - "dev": true, - "license": "MIT", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "dev": true, - "license": "MIT", - "dependencies": { - "json-buffer": "3.0.1" - } - }, - "node_modules/kleur": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", - "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/lightningcss": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", - "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", - "dev": true, - "license": "MPL-2.0", - "dependencies": { - "detect-libc": "^2.0.3" - }, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "lightningcss-android-arm64": "1.32.0", - "lightningcss-darwin-arm64": "1.32.0", - "lightningcss-darwin-x64": "1.32.0", - "lightningcss-freebsd-x64": "1.32.0", - "lightningcss-linux-arm-gnueabihf": "1.32.0", - "lightningcss-linux-arm64-gnu": "1.32.0", - "lightningcss-linux-arm64-musl": "1.32.0", - "lightningcss-linux-x64-gnu": "1.32.0", - "lightningcss-linux-x64-musl": "1.32.0", - "lightningcss-win32-arm64-msvc": "1.32.0", - "lightningcss-win32-x64-msvc": "1.32.0" - } - }, - "node_modules/lightningcss-android-arm64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", - "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-arm64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", - "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", - "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-freebsd-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", - "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", - "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", - "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", - "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", - "cpu": [ - "arm64" - ], - "dev": true, - "libc": [ - "musl" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", - "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", - "cpu": [ - "x64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", - "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", - "cpu": [ - "x64" - ], - "dev": true, - "libc": [ - "musl" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", - "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", - "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lilconfig": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", - "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/antonk52" - } - }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true, - "license": "MIT" - }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/log-symbols": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-6.0.0.tgz", - "integrity": "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^5.3.0", - "is-unicode-supported": "^1.3.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/log-symbols/node_modules/is-unicode-supported": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", - "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/lucide-react": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.8.0.tgz", - "integrity": "sha512-WuvlsjngSk7TnTBJ1hsCy3ql9V9VOdcPkd3PKcSmM34vJD8KG6molxz7m7zbYFgICwsanQWmJ13JlYs4Zp7Arw==", - "license": "ISC", - "peerDependencies": { - "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" - } - }, - "node_modules/magic-string": { - "version": "0.30.21", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", - "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.5" - } - }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/media-typer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", - "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/merge-descriptors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", - "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true, - "license": "MIT" - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, - "license": "MIT", - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/micromatch/node_modules/picomatch": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", - "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/mime-db": { - "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", - "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "mime-db": "^1.54.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/mimic-function": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", - "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/minimatch": { - "version": "10.2.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", - "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "brace-expansion": "^5.0.5" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/msw": { - "version": "2.13.2", - "resolved": "https://registry.npmjs.org/msw/-/msw-2.13.2.tgz", - "integrity": "sha512-go2H1TIERKkC48pXiwec5l6sbNqYuvqOk3/vHGo1Zd+pq/H63oFawDQerH+WQdUw/flJFHDG7F+QdWMwhntA/A==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "@inquirer/confirm": "^5.0.0", - "@mswjs/interceptors": "^0.41.2", - "@open-draft/deferred-promise": "^2.2.0", - "@types/statuses": "^2.0.6", - "cookie": "^1.0.2", - "graphql": "^16.12.0", - "headers-polyfill": "^4.0.2", - "is-node-process": "^1.2.0", - "outvariant": "^1.4.3", - "path-to-regexp": "^6.3.0", - "picocolors": "^1.1.1", - "rettime": "^0.10.1", - "statuses": "^2.0.2", - "strict-event-emitter": "^0.5.1", - "tough-cookie": "^6.0.0", - "type-fest": "^5.2.0", - "until-async": "^3.0.2", - "yargs": "^17.7.2" - }, - "bin": { - "msw": "cli/index.js" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/mswjs" - }, - "peerDependencies": { - "typescript": ">= 4.8.x" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/msw/node_modules/cookie": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", - "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "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/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "dev": true, - "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" - } - }, - "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/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true, - "license": "MIT" - }, - "node_modules/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/node-domexception": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", - "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", - "deprecated": "Use your platform's native DOMException instead", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "github", - "url": "https://paypal.me/jimmywarting" - } - ], - "license": "MIT", - "engines": { - "node": ">=10.5.0" - } - }, - "node_modules/node-fetch": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", - "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", - "dev": true, - "license": "MIT", - "dependencies": { - "data-uri-to-buffer": "^4.0.0", - "fetch-blob": "^3.1.4", - "formdata-polyfill": "^4.0.10" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/node-fetch" - } - }, - "node_modules/node-releases": { - "version": "2.0.37", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.37.tgz", - "integrity": "sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg==", - "dev": true, - "license": "MIT" - }, - "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==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^4.0.0", - "unicorn-magic": "^0.3.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/npm-run-path/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==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-inspect": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", - "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object-treeify": { - "version": "1.1.33", - "resolved": "https://registry.npmjs.org/object-treeify/-/object-treeify-1.1.33.tgz", - "integrity": "sha512-EFVjAYfzWqWsBMRHPMAXLCDIJnpMhdWAqR7xG6M6a2cs6PMFpl/+Z20w9zDW4vkxOFfddegBKq9Rehd0bxWE7A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10" - } - }, - "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/onetime": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", - "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "mimic-function": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/open": { - "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.4.0", - "define-lazy-prop": "^3.0.0", - "is-in-ssh": "^1.0.0", - "is-inside-container": "^1.0.0", - "powershell-utils": "^0.1.0", - "wsl-utils": "^0.3.0" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/optionator": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", - "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", - "dev": true, - "license": "MIT", - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.5" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/ora": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/ora/-/ora-8.2.0.tgz", - "integrity": "sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^5.3.0", - "cli-cursor": "^5.0.0", - "cli-spinners": "^2.9.2", - "is-interactive": "^2.0.0", - "is-unicode-supported": "^2.0.0", - "log-symbols": "^6.0.0", - "stdin-discarder": "^0.2.2", - "string-width": "^7.2.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/outvariant": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/outvariant/-/outvariant-1.4.3.tgz", - "integrity": "sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==", - "dev": true, - "license": "MIT" - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "license": "MIT", - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/parse-ms": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-4.0.0.tgz", - "integrity": "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/path-browserify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", - "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", - "dev": true, - "license": "MIT" - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-to-regexp": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", - "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, - "license": "ISC" - }, - "node_modules/picomatch": { - "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": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pkce-challenge": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz", - "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=16.20.0" - } - }, - "node_modules/postcss": { - "version": "8.5.9", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.9.tgz", - "integrity": "sha512-7a70Nsot+EMX9fFU3064K/kdHWZqGVY+BADLyXc8Dfv+mTLLVl6JzJpPaCZ2kQL9gIJvKXSLMHhqdRRjwQeFtw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/postcss-selector-parser": { - "version": "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/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/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/prettier": { - "version": "3.8.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.2.tgz", - "integrity": "sha512-8c3mgTe0ASwWAJK+78dpviD+A8EqhndQPUBpNUIPt6+xWlIigCwfN01lWr9MAede4uqXGTEKeQWTvzb3vjia0Q==", - "dev": true, - "license": "MIT", - "bin": { - "prettier": "bin/prettier.cjs" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, - "node_modules/prettier-plugin-tailwindcss": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.7.2.tgz", - "integrity": "sha512-LkphyK3Fw+q2HdMOoiEHWf93fNtYJwfamoKPl7UwtjFQdei/iIBoX11G6j706FzN3ymX9mPVi97qIY8328vdnA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=20.19" - }, - "peerDependencies": { - "@ianvs/prettier-plugin-sort-imports": "*", - "@prettier/plugin-hermes": "*", - "@prettier/plugin-oxc": "*", - "@prettier/plugin-pug": "*", - "@shopify/prettier-plugin-liquid": "*", - "@trivago/prettier-plugin-sort-imports": "*", - "@zackad/prettier-plugin-twig": "*", - "prettier": "^3.0", - "prettier-plugin-astro": "*", - "prettier-plugin-css-order": "*", - "prettier-plugin-jsdoc": "*", - "prettier-plugin-marko": "*", - "prettier-plugin-multiline-arrays": "*", - "prettier-plugin-organize-attributes": "*", - "prettier-plugin-organize-imports": "*", - "prettier-plugin-sort-imports": "*", - "prettier-plugin-svelte": "*" - }, - "peerDependenciesMeta": { - "@ianvs/prettier-plugin-sort-imports": { - "optional": true - }, - "@prettier/plugin-hermes": { - "optional": true - }, - "@prettier/plugin-oxc": { - "optional": true - }, - "@prettier/plugin-pug": { - "optional": true - }, - "@shopify/prettier-plugin-liquid": { - "optional": true - }, - "@trivago/prettier-plugin-sort-imports": { - "optional": true - }, - "@zackad/prettier-plugin-twig": { - "optional": true - }, - "prettier-plugin-astro": { - "optional": true - }, - "prettier-plugin-css-order": { - "optional": true - }, - "prettier-plugin-jsdoc": { - "optional": true - }, - "prettier-plugin-marko": { - "optional": true - }, - "prettier-plugin-multiline-arrays": { - "optional": true - }, - "prettier-plugin-organize-attributes": { - "optional": true - }, - "prettier-plugin-organize-imports": { - "optional": true - }, - "prettier-plugin-sort-imports": { - "optional": true - }, - "prettier-plugin-svelte": { - "optional": true - } - } - }, - "node_modules/pretty-ms": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.3.0.tgz", - "integrity": "sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "parse-ms": "^4.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/prompts": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", - "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/prompts/node_modules/kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "dev": true, - "license": "MIT", - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/qs": { - "version": "6.15.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.1.tgz", - "integrity": "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/radix-ui": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/radix-ui/-/radix-ui-1.4.3.tgz", - "integrity": "sha512-aWizCQiyeAenIdUbqEpXgRA1ya65P13NKn/W8rWkcN0OPkRDxdBVLWnIEDsS2RpwCK2nobI7oMUSmexzTDyAmA==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-accessible-icon": "1.1.7", - "@radix-ui/react-accordion": "1.2.12", - "@radix-ui/react-alert-dialog": "1.1.15", - "@radix-ui/react-arrow": "1.1.7", - "@radix-ui/react-aspect-ratio": "1.1.7", - "@radix-ui/react-avatar": "1.1.10", - "@radix-ui/react-checkbox": "1.3.3", - "@radix-ui/react-collapsible": "1.1.12", - "@radix-ui/react-collection": "1.1.7", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-context-menu": "2.2.16", - "@radix-ui/react-dialog": "1.1.15", - "@radix-ui/react-direction": "1.1.1", - "@radix-ui/react-dismissable-layer": "1.1.11", - "@radix-ui/react-dropdown-menu": "2.1.16", - "@radix-ui/react-focus-guards": "1.1.3", - "@radix-ui/react-focus-scope": "1.1.7", - "@radix-ui/react-form": "0.1.8", - "@radix-ui/react-hover-card": "1.1.15", - "@radix-ui/react-label": "2.1.7", - "@radix-ui/react-menu": "2.1.16", - "@radix-ui/react-menubar": "1.1.16", - "@radix-ui/react-navigation-menu": "1.2.14", - "@radix-ui/react-one-time-password-field": "0.1.8", - "@radix-ui/react-password-toggle-field": "0.1.3", - "@radix-ui/react-popover": "1.1.15", - "@radix-ui/react-popper": "1.2.8", - "@radix-ui/react-portal": "1.1.9", - "@radix-ui/react-presence": "1.1.5", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-progress": "1.1.7", - "@radix-ui/react-radio-group": "1.3.8", - "@radix-ui/react-roving-focus": "1.1.11", - "@radix-ui/react-scroll-area": "1.2.10", - "@radix-ui/react-select": "2.2.6", - "@radix-ui/react-separator": "1.1.7", - "@radix-ui/react-slider": "1.3.6", - "@radix-ui/react-slot": "1.2.3", - "@radix-ui/react-switch": "1.2.6", - "@radix-ui/react-tabs": "1.1.13", - "@radix-ui/react-toast": "1.2.15", - "@radix-ui/react-toggle": "1.1.10", - "@radix-ui/react-toggle-group": "1.1.11", - "@radix-ui/react-toolbar": "1.1.11", - "@radix-ui/react-tooltip": "1.2.8", - "@radix-ui/react-use-callback-ref": "1.1.1", - "@radix-ui/react-use-controllable-state": "1.2.2", - "@radix-ui/react-use-effect-event": "0.0.2", - "@radix-ui/react-use-escape-keydown": "1.1.1", - "@radix-ui/react-use-is-hydrated": "0.1.0", - "@radix-ui/react-use-layout-effect": "1.1.1", - "@radix-ui/react-use-size": "1.1.1", - "@radix-ui/react-visually-hidden": "1.2.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/raw-body": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", - "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", - "dev": true, - "license": "MIT", - "dependencies": { - "bytes": "~3.1.2", - "http-errors": "~2.0.1", - "iconv-lite": "~0.7.0", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/react": { - "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" - } - }, - "node_modules/react-dom": { - "version": "19.2.5", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.5.tgz", - "integrity": "sha512-J5bAZz+DXMMwW/wV3xzKke59Af6CHY7G4uYLN1OvBcKEsWOs4pQExj86BBKamxl/Ik5bx9whOrvBlSDfWzgSag==", - "license": "MIT", - "dependencies": { - "scheduler": "^0.27.0" - }, - "peerDependencies": { - "react": "^19.2.5" - } - }, - "node_modules/react-remove-scroll": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz", - "integrity": "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==", - "license": "MIT", - "dependencies": { - "react-remove-scroll-bar": "^2.3.7", - "react-style-singleton": "^2.2.3", - "tslib": "^2.1.0", - "use-callback-ref": "^1.3.3", - "use-sidecar": "^1.1.3" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/react-remove-scroll-bar": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz", - "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==", - "license": "MIT", - "dependencies": { - "react-style-singleton": "^2.2.2", - "tslib": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/react-style-singleton": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", - "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==", - "license": "MIT", - "dependencies": { - "get-nonce": "^1.0.0", - "tslib": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/recast": { - "version": "0.23.11", - "resolved": "https://registry.npmjs.org/recast/-/recast-0.23.11.tgz", - "integrity": "sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ast-types": "^0.16.1", - "esprima": "~4.0.0", - "source-map": "~0.6.1", - "tiny-invariant": "^1.3.3", - "tslib": "^2.0.1" - }, - "engines": { - "node": ">= 4" - } - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/restore-cursor": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", - "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", - "dev": true, - "license": "MIT", - "dependencies": { - "onetime": "^7.0.0", - "signal-exit": "^4.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/rettime": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/rettime/-/rettime-0.10.1.tgz", - "integrity": "sha512-uyDrIlUEH37cinabq0AX4QbgV4HbFZ/gqoiunWQ1UqBtRvTTytwhNYjE++pO/MjPTZL5KQCf2bEoJ/BJNVQ5Kw==", - "dev": true, - "license": "MIT" - }, - "node_modules/reusify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", - "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", - "dev": true, - "license": "MIT", - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rolldown": { - "version": "1.0.0-rc.15", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.15.tgz", - "integrity": "sha512-Ff31guA5zT6WjnGp0SXw76X6hzGRk/OQq2hE+1lcDe+lJdHSgnSX6nK3erbONHyCbpSj9a9E+uX/OvytZoWp2g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@oxc-project/types": "=0.124.0", - "@rolldown/pluginutils": "1.0.0-rc.15" - }, - "bin": { - "rolldown": "bin/cli.mjs" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.0.0-rc.15", - "@rolldown/binding-darwin-arm64": "1.0.0-rc.15", - "@rolldown/binding-darwin-x64": "1.0.0-rc.15", - "@rolldown/binding-freebsd-x64": "1.0.0-rc.15", - "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.15", - "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.15", - "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.15", - "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.15", - "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.15", - "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.15", - "@rolldown/binding-linux-x64-musl": "1.0.0-rc.15", - "@rolldown/binding-openharmony-arm64": "1.0.0-rc.15", - "@rolldown/binding-wasm32-wasi": "1.0.0-rc.15", - "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.15", - "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.15" - } - }, - "node_modules/rolldown/node_modules/@rolldown/pluginutils": { - "version": "1.0.0-rc.15", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.15.tgz", - "integrity": "sha512-UromN0peaE53IaBRe9W7CjrZgXl90fqGpK+mIZbA3qSTeYqg3pqpROBdIPvOG3F5ereDHNwoHBI2e50n1BDr1g==", - "dev": true, - "license": "MIT" - }, - "node_modules/router": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", - "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.4.0", - "depd": "^2.0.0", - "is-promise": "^4.0.0", - "parseurl": "^1.3.3", - "path-to-regexp": "^8.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/router/node_modules/path-to-regexp": { - "version": "8.4.2", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", - "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", - "dev": true, - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/run-applescript": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", - "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true, - "license": "MIT" - }, - "node_modules/scheduler": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", - "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", - "license": "MIT" - }, - "node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/send": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", - "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.4.3", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "fresh": "^2.0.0", - "http-errors": "^2.0.1", - "mime-types": "^3.0.2", - "ms": "^2.1.3", - "on-finished": "^2.4.1", - "range-parser": "^1.2.1", - "statuses": "^2.0.2" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/serve-static": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", - "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", - "dev": true, - "license": "MIT", - "dependencies": { - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "parseurl": "^1.3.3", - "send": "^1.2.0" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "dev": true, - "license": "ISC" - }, - "node_modules/shadcn": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/shadcn/-/shadcn-4.2.0.tgz", - "integrity": "sha512-ZDuV340itidaUd4Gi1BxQX+Y7Ush6BHp6URZBM2RyxUUBZ6yFtOWIr4nVY+Ro+YRSpo82v7JrsmtcU5xoBCMJQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.28.0", - "@babel/parser": "^7.28.0", - "@babel/plugin-transform-typescript": "^7.28.0", - "@babel/preset-typescript": "^7.27.1", - "@dotenvx/dotenvx": "^1.48.4", - "@modelcontextprotocol/sdk": "^1.26.0", - "@types/validate-npm-package-name": "^4.0.2", - "browserslist": "^4.26.2", - "commander": "^14.0.0", - "cosmiconfig": "^9.0.0", - "dedent": "^1.6.0", - "deepmerge": "^4.3.1", - "diff": "^8.0.2", - "execa": "^9.6.0", - "fast-glob": "^3.3.3", - "fs-extra": "^11.3.1", - "fuzzysort": "^3.1.0", - "https-proxy-agent": "^7.0.6", - "kleur": "^4.1.5", - "msw": "^2.10.4", - "node-fetch": "^3.3.2", - "open": "^11.0.0", - "ora": "^8.2.0", - "postcss": "^8.5.6", - "postcss-selector-parser": "^7.1.0", - "prompts": "^2.4.2", - "recast": "^0.23.11", - "stringify-object": "^5.0.0", - "tailwind-merge": "^3.0.1", - "ts-morph": "^26.0.0", - "tsconfig-paths": "^4.2.0", - "validate-npm-package-name": "^7.0.1", - "zod": "^3.24.1", - "zod-to-json-schema": "^3.24.6" - }, - "bin": { - "shadcn": "dist/index.js" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/side-channel": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", - "side-channel-map": "^1.0.1", - "side-channel-weakmap": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-list": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", - "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-map": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", - "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-weakmap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", - "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3", - "side-channel-map": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "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/sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "dev": true, - "license": "MIT" - }, - "node_modules/size-limit": { - "version": "12.0.1", - "resolved": "https://registry.npmjs.org/size-limit/-/size-limit-12.0.1.tgz", - "integrity": "sha512-vuFj+6lDOoBJQu6OLhcMQv7jnbXjuoEn4WsQHlSLOV/8EFfOka/tfjtLQ/rZig5Gagi3R0GnU/0kd4EY/y2etg==", - "dev": true, - "license": "MIT", - "dependencies": { - "bytes-iec": "^3.1.1", - "lilconfig": "^3.1.3", - "nanospinner": "^1.2.2", - "picocolors": "^1.1.1", - "tinyglobby": "^0.2.15" - }, - "bin": { - "size-limit": "bin.js" - }, - "engines": { - "node": "^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "peerDependencies": { - "jiti": "^2.0.0" - }, - "peerDependenciesMeta": { - "jiti": { - "optional": true - } - } - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/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/statuses": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", - "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/stdin-discarder": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.2.2.tgz", - "integrity": "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/strict-event-emitter": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.5.1.tgz", - "integrity": "sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/string-width": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", - "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^10.3.0", - "get-east-asian-width": "^1.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/stringify-object": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-5.0.0.tgz", - "integrity": "sha512-zaJYxz2FtcMb4f+g60KsRNFOpVMUyuJgA51Zi5Z1DOTC3S59+OQiVOzE9GZt0x72uBGWKsQIuBKeF9iusmKFsg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "get-own-enumerable-keys": "^1.0.0", - "is-obj": "^3.0.0", - "is-regexp": "^3.1.0" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/yeoman/stringify-object?sponsor=1" - } - }, - "node_modules/strip-ansi": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", - "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.2.2" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "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==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/tagged-tag": { - "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" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/tailwind-merge": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.5.0.tgz", - "integrity": "sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/dcastil" - } - }, - "node_modules/tailwindcss": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.2.tgz", - "integrity": "sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/tapable": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.2.tgz", - "integrity": "sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/tiny-invariant": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", - "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", - "dev": true, - "license": "MIT" - }, - "node_modules/tinyglobby": { - "version": "0.2.16", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", - "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", - "dev": true, - "license": "MIT", - "dependencies": { - "fdir": "^6.5.0", - "picomatch": "^4.0.4" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/tldts": { - "version": "7.0.28", - "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.28.tgz", - "integrity": "sha512-+Zg3vWhRUv8B1maGSTFdev9mjoo8Etn2Ayfs4cnjlD3CsGkxXX4QyW3j2WJ0wdjYcYmy7Lx2RDsZMhgCWafKIw==", - "dev": true, - "license": "MIT", - "dependencies": { - "tldts-core": "^7.0.28" - }, - "bin": { - "tldts": "bin/cli.js" - } - }, - "node_modules/tldts-core": { - "version": "7.0.28", - "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.28.tgz", - "integrity": "sha512-7W5Efjhsc3chVdFhqtaU0KtK32J37Zcr9RKtID54nG+tIpcY79CQK/veYPODxtD/LJ4Lue66jvrQzIX2Z2/pUQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/tough-cookie": { - "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": { - "tldts": "^7.0.5" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/ts-api-utils": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", - "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.12" - }, - "peerDependencies": { - "typescript": ">=4.8.4" - } - }, - "node_modules/ts-morph": { - "version": "26.0.0", - "resolved": "https://registry.npmjs.org/ts-morph/-/ts-morph-26.0.0.tgz", - "integrity": "sha512-ztMO++owQnz8c/gIENcM9XfCEzgoGphTv+nKpYNM1bgsdOVC/jRZuEBf6N+mLLDNg68Kl+GgUZfOySaRiG1/Ug==", - "dev": true, - "license": "MIT", - "dependencies": { - "@ts-morph/common": "~0.27.0", - "code-block-writer": "^13.0.3" - } - }, - "node_modules/tsconfig-paths": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", - "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", - "dev": true, - "license": "MIT", - "dependencies": { - "json5": "^2.2.2", - "minimist": "^1.2.6", - "strip-bom": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD" - }, - "node_modules/tw-animate-css": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/tw-animate-css/-/tw-animate-css-1.4.0.tgz", - "integrity": "sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/Wombosvideo" - } - }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/type-fest": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.5.0.tgz", - "integrity": "sha512-PlBfpQwiUvGViBNX84Yxwjsdhd1TUlXr6zjX7eoirtCPIr08NAmxwa+fcYBTeRQxHo9YC9wwF3m9i700sHma8g==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "dependencies": { - "tagged-tag": "^1.0.0" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/type-is": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", - "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", - "dev": true, - "license": "MIT", - "dependencies": { - "content-type": "^1.0.5", - "media-typer": "^1.1.0", - "mime-types": "^3.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/typescript-eslint": { - "version": "8.58.1", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.58.1.tgz", - "integrity": "sha512-gf6/oHChByg9HJvhMO1iBexJh12AqqTfnuxscMDOVqfJW3htsdRJI/GfPpHTTcyeB8cSTUY2JcZmVgoyPqcrDg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/eslint-plugin": "8.58.1", - "@typescript-eslint/parser": "8.58.1", - "@typescript-eslint/typescript-estree": "8.58.1", - "@typescript-eslint/utils": "8.58.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/undici-types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", - "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", - "dev": true, - "license": "MIT" - }, - "node_modules/unicorn-magic": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", - "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/until-async": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/until-async/-/until-async-3.0.2.tgz", - "integrity": "sha512-IiSk4HlzAMqTUseHHe3VhIGyuFmN90zMTpD3Z3y8jeQbzLIq500MVM7Jq2vUAnTKAFPJrqwkzr6PoTcPhGcOiw==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/kettanaito" - } - }, - "node_modules/update-browserslist-db": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", - "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/use-callback-ref": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz", - "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==", - "license": "MIT", - "dependencies": { - "tslib": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/use-sidecar": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz", - "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==", - "license": "MIT", - "dependencies": { - "detect-node-es": "^1.1.0", - "tslib": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/use-sync-external-store": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", - "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", - "license": "MIT", - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true, - "license": "MIT" - }, - "node_modules/validate-npm-package-name": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-7.0.2.tgz", - "integrity": "sha512-hVDIBwsRruT73PbK7uP5ebUt+ezEtCmzZz3F59BSr2F6OVFnJ/6h8liuvdLrQ88Xmnk6/+xGGuq+pG9WwTuy3A==", - "dev": true, - "license": "ISC", - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/vite": { - "version": "8.0.8", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.8.tgz", - "integrity": "sha512-dbU7/iLVa8KZALJyLOBOQ88nOXtNG8vxKuOT4I2mD+Ya70KPceF4IAmDsmU0h1Qsn5bPrvsY9HJstCRh3hG6Uw==", - "dev": true, - "license": "MIT", - "dependencies": { - "lightningcss": "^1.32.0", - "picomatch": "^4.0.4", - "postcss": "^8.5.8", - "rolldown": "1.0.0-rc.15", - "tinyglobby": "^0.2.15" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^20.19.0 || >=22.12.0", - "@vitejs/devtools": "^0.1.0", - "esbuild": "^0.27.0 || ^0.28.0", - "jiti": ">=1.21.0", - "less": "^4.0.0", - "sass": "^1.70.0", - "sass-embedded": "^1.70.0", - "stylus": ">=0.54.8", - "sugarss": "^5.0.0", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "@vitejs/devtools": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, - "node_modules/web-streams-polyfill": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", - "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/word-wrap": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", - "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/wrap-ansi/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi/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/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true, - "license": "ISC" - }, - "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/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true, - "license": "ISC" - }, - "node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/yargs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/yargs/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/yargs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/yocto-spinner": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/yocto-spinner/-/yocto-spinner-1.1.0.tgz", - "integrity": "sha512-/BY0AUXnS7IKO354uLLA2eRcWiqDifEbd6unXCsOxkFDAkhgUL3PH9X2bFoaU0YchnDXsF+iKleeTLJGckbXfA==", - "dev": true, - "license": "MIT", - "dependencies": { - "yoctocolors": "^2.1.1" - }, - "engines": { - "node": ">=18.19" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/yoctocolors": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.2.tgz", - "integrity": "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/yoctocolors-cjs": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.3.tgz", - "integrity": "sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/zod": { - "version": "3.25.76", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", - "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - }, - "node_modules/zod-to-json-schema": { - "version": "3.25.2", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", - "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", - "dev": true, - "license": "ISC", - "peerDependencies": { - "zod": "^3.25.28 || ^4" - } - } - } -} diff --git a/cmd/mirum-server/web/package.json b/cmd/mirum-server/web/package.json deleted file mode 100644 --- a/cmd/mirum-server/web/package.json +++ /dev/null @@ -1,61 +0,0 @@ -{ - "name": "@mirum/web", - "private": true, - "type": "module", - "scripts": { - "dev": "vite", - "build": "tsc --noEmit && vite build", - "size": "size-limit", - "lint": "eslint . && prettier --check .", - "fmt": "prettier --write ." - }, - "size-limit": [ - { - "name": "vendor (React runtime)", - "path": "../static/assets/vendor.*.js", - "limit": "60 KB" - }, - { - "name": "app (all other JS and CSS)", - "path": [ - "../static/assets/*.js", - "../static/assets/*.css", - "!../static/assets/vendor.*.js" - ], - "limit": "60 KB" - } - ], - "dependencies": { - "@bufbuild/protobuf": "^2.11.0", - "@connectrpc/connect": "^2.1.1", - "@connectrpc/connect-web": "^2.1.1", - "@fontsource-variable/geist": "^5.2.8", - "class-variance-authority": "^0.7.1", - "lucide-react": "^1.7.0", - "radix-ui": "^1.4.3", - "react": "^19.2.4", - "react-dom": "^19.2.4", - "tailwind-merge": "^3.5.0", - "tw-animate-css": "^1.4.0" - }, - "devDependencies": { - "@bufbuild/protoc-gen-es": "^2.11.0", - "@eslint/js": "^10.0.1", - "@size-limit/file": "^12.0.1", - "@tailwindcss/vite": "^4.2.2", - "@types/node": "^24.12.0", - "@types/react": "^19.2.14", - "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^6.0.1", - "eslint": "^10.2.0", - "eslint-config-prettier": "^10.1.8", - "prettier": "^3.8.2", - "prettier-plugin-tailwindcss": "^0.7.2", - "shadcn": "^4.1.2", - "size-limit": "^12.0.1", - "tailwindcss": "^4.2.2", - "typescript": "~5.9.3", - "typescript-eslint": "^8.58.1", - "vite": "^8.0.1" - } -} diff --git a/cmd/mirum-server/web/shadcn.css b/cmd/mirum-server/web/shadcn.css deleted file mode 100644 --- a/cmd/mirum-server/web/shadcn.css +++ /dev/null @@ -1,104 +0,0 @@ -/* SPDX-FileCopyrightText: 2023 shadcn - * SPDX-License-Identifier: MIT - * - * Inlined from `shadcn/tailwind.css` (npm package `shadcn`, MIT). Kept in - * tree so the shadcn CLI can stay in devDependencies — importing the npm - * package at build time would re-introduce its runtime dep chain (which, - * transitively, pulls a Python-2.0 licensed package that conflicts with - * the mirum commercial license). */ - -@theme inline { - @keyframes accordion-down { - from { - height: 0; - } - to { - height: var( - --radix-accordion-content-height, - var(--accordion-panel-height, auto) - ); - } - } - - @keyframes accordion-up { - from { - height: var( - --radix-accordion-content-height, - var(--accordion-panel-height, auto) - ); - } - to { - height: 0; - } - } -} - -/* Custom variants */ -@custom-variant data-open { - &:where([data-state="open"]), - &:where([data-open]:not([data-open="false"])) { - @slot; - } -} - -@custom-variant data-closed { - &:where([data-state="closed"]), - &:where([data-closed]:not([data-closed="false"])) { - @slot; - } -} - -@custom-variant data-checked { - &:where([data-state="checked"]), - &:where([data-checked]:not([data-checked="false"])) { - @slot; - } -} - -@custom-variant data-unchecked { - &:where([data-state="unchecked"]), - &:where([data-unchecked]:not([data-unchecked="false"])) { - @slot; - } -} - -@custom-variant data-selected { - &:where([data-selected="true"]) { - @slot; - } -} - -@custom-variant data-disabled { - &:where([data-disabled="true"]), - &:where([data-disabled]:not([data-disabled="false"])) { - @slot; - } -} - -@custom-variant data-active { - &:where([data-state="active"]), - &:where([data-active]:not([data-active="false"])) { - @slot; - } -} - -@custom-variant data-horizontal { - &:where([data-orientation="horizontal"]) { - @slot; - } -} - -@custom-variant data-vertical { - &:where([data-orientation="vertical"]) { - @slot; - } -} - -@utility no-scrollbar { - -ms-overflow-style: none; - scrollbar-width: none; - - &::-webkit-scrollbar { - display: none; - } -} diff --git a/cmd/mirum-server/web/shell.html b/cmd/mirum-server/web/shell.html deleted file mode 100644 --- a/cmd/mirum-server/web/shell.html +++ /dev/null @@ -1,54 +0,0 @@ -<!-- -SPDX-FileCopyrightText: 2026 Nikolay Govorov -SPDX-License-Identifier: AGPL-3.0-or-later ---> - -<!doctype html> -<html lang="en"> - <head> - <meta charset="utf-8" /> - <meta name="viewport" content="width=device-width, initial-scale=1" /> - <meta name="view-transition" content="same-origin" /> - <title>Mirum</title> - - {{- range .Preloads}} - <link rel="modulepreload" href="{{.Href}}" {{if .Integrity}} integrity="{{.Integrity}}" {{end}} /> - - {{- end}} {{- range .CSS}} - <link rel="stylesheet" href="{{.Href}}" {{if .Integrity}} integrity="{{.Integrity}}" {{end}} /> - {{- end}} - - <script type="application/json" id="__DATA__"> - {{.DataJSON}} - </script> - </head> - <body> - <div id="app"></div> - - <noscript> - <p - style=" - max-width: 32rem; - margin: 4rem auto; - padding: 1rem; - font: 14px system-ui, sans-serif; - text-align: center; - " - >Mirum requires JavaScript to run. Please enable it in your browser.</p> - </noscript> - - {{- if .Preamble}} - <script type="module"> - {{.Preamble}} - </script> - {{- end}} - - {{- range .Scripts}} - <script - type="module" - src="{{.Href}}" - {{if .Integrity}} integrity="{{.Integrity}}" {{end}} - ></script> - {{- end}} - </body> -</html> diff --git a/cmd/mirum-server/web/tsconfig.json b/cmd/mirum-server/web/tsconfig.json deleted file mode 100644 --- a/cmd/mirum-server/web/tsconfig.json +++ /dev/null @@ -1,33 +0,0 @@ -// SPDX-FileCopyrightText: 2026 Nikolay Govorov -// SPDX-License-Identifier: AGPL-3.0-or-later - -{ - "compilerOptions": { - "baseUrl": ".", - "paths": { "@/*": ["./*"] }, - - "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", - "target": "ES2023", - "useDefineForClassFields": true, - "lib": ["ES2023", "DOM", "DOM.Iterable"], - "module": "ESNext", - "types": ["vite/client"], - "skipLibCheck": true, - - /* Bundler mode */ - "moduleResolution": "bundler", - "allowImportingTsExtensions": true, - "verbatimModuleSyntax": true, - "moduleDetection": "force", - "noEmit": true, - "jsx": "react-jsx", - - /* Linting */ - "strict": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "noFallthroughCasesInSwitch": true, - "noUncheckedSideEffectImports": true - }, - "include": ["components", "api", "lib", "entries", "gen"] -} diff --git a/cmd/mirum-server/web/vite.config.ts b/cmd/mirum-server/web/vite.config.ts deleted file mode 100644 --- a/cmd/mirum-server/web/vite.config.ts +++ /dev/null @@ -1,52 +0,0 @@ -// SPDX-FileCopyrightText: 2026 Nikolay Govorov -// SPDX-License-Identifier: AGPL-3.0-or-later - -import { readdirSync } from "node:fs"; -import { resolve, parse } from "node:path"; -import { defineConfig } from "vite"; - -import react from "@vitejs/plugin-react"; -import tailwindcss from "@tailwindcss/vite"; - -const input = readdirSync(resolve(__dirname, "entries")).reduce( - (acc, file) => { - const { name } = parse(file); - acc[name] = resolve(__dirname, "entries", file); - return acc; - }, - {} as Record<string, string>, -); - -export default defineConfig({ - clearScreen: false, - plugins: [react(), tailwindcss()], - resolve: { - alias: { "@": resolve(__dirname) }, - }, - server: { - host: "127.0.0.1", - origin: "http://localhost:5173", - cors: { origin: "http://localhost:3000" }, - }, - build: { - outDir: "../static", - manifest: true, - emptyOutDir: true, - rollupOptions: { - input, - output: { - entryFileNames: "assets/[name].[hash].js", - chunkFileNames: "assets/[name].[hash].js", - assetFileNames: "assets/[name].[hash][extname]", - manualChunks(id) { - if ( - id.includes("node_modules/react") || - id.includes("node_modules/react-dom") - ) { - return "vendor"; - } - }, - }, - }, - }, -}); diff --git a/cmd/mirum-worker/Dockerfile b/cmd/mirum-worker/Dockerfile deleted file mode 100644 --- a/cmd/mirum-worker/Dockerfile +++ /dev/null @@ -1,26 +0,0 @@ -# SPDX-FileCopyrightText: 2026 Nikolay Govorov -# SPDX-License-Identifier: AGPL-3.0-or-later - -FROM docker.io/library/alpine:3.23.5@sha256:fd791d74b68913cbb027c6546007b3f0d3bc45125f797758156952bc2d6daf40 - -ARG TARGETARCH - -LABEL org.opencontainers.image.source="https://github.com/dimidiumlabs/mirum" \ - org.opencontainers.image.licenses="AGPL-3.0-or-later" \ - org.opencontainers.image.title="mirum-worker" - -RUN apk add --no-cache bash ca-certificates git && \ - addgroup -g 10000 mirum-worker && \ - adduser -D -H -u 10000 -G mirum-worker -h /var/lib/mirum-worker \ - -s /sbin/nologin mirum-worker && \ - install -d -o mirum-worker -g mirum-worker -m 0750 /var/lib/mirum-worker - -COPY --chown=root:root --chmod=0755 .container/mirum-worker-linux-${TARGETARCH} /usr/local/bin/mirum-worker -COPY LICENSE README.md /usr/share/doc/mirum/ - -USER 10000:10000 -WORKDIR /var/lib/mirum-worker -ENTRYPOINT ["/usr/local/bin/mirum-worker"] -CMD ["--config=/etc/mirum/config.yaml"] - -# syntax=docker/dockerfile: diff --git a/cmd/mirum-worker/client.go b/cmd/mirum-worker/client.go deleted file mode 100644 --- a/cmd/mirum-worker/client.go +++ /dev/null @@ -1,119 +0,0 @@ -// SPDX-FileCopyrightText: 2026 Nikolay Govorov -// SPDX-License-Identifier: AGPL-3.0-or-later - -package main - -import ( - "context" - "crypto/tls" - "crypto/x509" - "fmt" - "log/slog" - "net/http" - "os" - "runtime" - - "connectrpc.com/connect" - - "dimidiumlabs/mirum/internal/executor" - "dimidiumlabs/mirum/internal/protocol" - "dimidiumlabs/mirum/internal/protocol/wirepb" - "dimidiumlabs/mirum/internal/protocol/wirepb/wirepbconnect" - - _ "dimidiumlabs/mirum/internal/executor/host" // registers the host Runtime backend -) - -type client struct { - cfg *config - http *http.Client - handle wirepbconnect.WorkerClient -} - -func dial(ctx context.Context, cfg *config) (*client, error) { - name := cfg.Name - if name == "" { - name, _ = os.Hostname() - } - - meta := &protocol.WorkerMeta{ - Os: runtime.GOOS, - Arch: runtime.GOARCH, - Name: name, - Runtime: workerRuntime, - Version: protocol.VersionString(), - } - - tlsCfg := &tls.Config{ - NextProtos: []string{"h2"}, - MinVersion: tls.VersionTLS13, - GetClientCertificate: func(_ *tls.CertificateRequestInfo) (*tls.Certificate, error) { - key, err := protocol.LoadPrivateKey(cfg.KeyFile) - if err != nil { - return nil, fmt.Errorf("load key: %w", err) - } - - cert, err := protocol.SelfSignedCert(key, meta) - return &cert, err - }, - } - - if cfg.TLSCA != "" { - caCert, err := os.ReadFile(cfg.TLSCA) - if err != nil { - return nil, fmt.Errorf("read CA cert: %w", err) - } - - pool := x509.NewCertPool() - if !pool.AppendCertsFromPEM(caCert) { - return nil, fmt.Errorf("failed to parse CA cert") - } - - tlsCfg.RootCAs = pool - } - - c := &client{ - cfg: cfg, - http: &http.Client{ - Transport: &http.Transport{TLSClientConfig: tlsCfg, ForceAttemptHTTP2: true}, - }, - } - c.handle = wirepbconnect.NewWorkerClient(c.http, "https://"+cfg.Server, connect.WithGRPC()) - - slog.Info("dialing", "server", cfg.Server) - - return c, nil -} - -func (c *client) close() {} - -func (c *client) work(ctx context.Context) error { - for ctx.Err() == nil { - resp, err := c.handle.Poll(ctx, connect.NewRequest(&wirepb.PollRequest{})) - if err != nil { - return fmt.Errorf("poll: %w", err) - } - - for _, w := range resp.Header().Values("X-Warning") { - slog.Warn("server warning", "msg", w) - } - - task := resp.Msg - slog.Info("task received", "id", task.Id, "repo", task.RepoFullName) - - execErr := executor.Run(ctx, task.CloneUrl, task.Branch) - - result := &wirepb.TaskResult{TaskId: task.Id, Success: execErr == nil} - if execErr != nil { - result.Error = execErr.Error() - slog.Error("task failed", "id", task.Id, "err", execErr) - } else { - slog.Info("task passed", "id", task.Id) - } - - if _, err := c.handle.Complete(ctx, connect.NewRequest(result)); err != nil { - return fmt.Errorf("complete: %w", err) - } - } - - return ctx.Err() -} diff --git a/cmd/mirum-worker/config.go b/cmd/mirum-worker/config.go deleted file mode 100644 --- a/cmd/mirum-worker/config.go +++ /dev/null @@ -1,45 +0,0 @@ -// SPDX-FileCopyrightText: 2026 Nikolay Govorov -// SPDX-License-Identifier: AGPL-3.0-or-later - -package main - -import ( - "fmt" - "os" - - "gopkg.in/yaml.v3" -) - -// Runtime type of this worker binary. Different worker types -// (mirum-worker-vm, mirum-worker-docker, etc.) will have different values. -const workerRuntime = "host" - -type config struct { - Name string `yaml:"name"` - Server string `yaml:"server"` - KeyFile string `yaml:"key_file"` - TLSCA string `yaml:"tls_ca"` // custom CA cert for self-signed/dev -} - -func getConfig(filename string) (*config, error) { - cfg := &config{ - Server: "localhost:2026", - } - - if filename != "" { - data, err := os.ReadFile(filename) - if err != nil { - return nil, fmt.Errorf("couldn't read config: %w", err) - } - - if err := yaml.Unmarshal(data, &cfg); err != nil { - return nil, fmt.Errorf("couldn't parse config: %w", err) - } - } - - if cfg.KeyFile == "" { - return nil, fmt.Errorf("error: key_file is required") - } - - return cfg, nil -} diff --git a/cmd/mirum-worker/main.go b/cmd/mirum-worker/main.go deleted file mode 100644 --- a/cmd/mirum-worker/main.go +++ /dev/null @@ -1,62 +0,0 @@ -// SPDX-FileCopyrightText: 2026 Nikolay Govorov -// SPDX-License-Identifier: AGPL-3.0-or-later - -package main - -import ( - "context" - "flag" - "log/slog" - "os" - - "dimidiumlabs/mirum/internal/protocol" - "dimidiumlabs/mirum/internal/supervisor" -) - -func main() { - configFile := flag.String("config", "", "path to config file") - flag.Parse() - - cfg, err := getConfig(*configFile) - if err != nil { - slog.Error("config", "err", err) - os.Exit(1) - } - - sup := supervisor.Detect() - ctx := sup.WaitForStop(context.Background()) - - sup.Ready() - go sup.StartWatchdog(ctx) - - backoff := protocol.NewBackoff() - - for ctx.Err() == nil { - c, err := dial(ctx, cfg) - if err != nil { - slog.Error("connect failed", "err", err) - if !backoff.Wait(ctx) { - break - } - - continue - } - - slog.Info("connected", "server", cfg.Server) - - if err := c.work(ctx); err != nil && ctx.Err() == nil { - slog.Error("work loop failed", "err", err) - c.close() - if !backoff.Wait(ctx) { - break - } - continue - } - - backoff.Reset() - c.close() - } - - slog.Info("shutting down") - sup.Stopping() -} diff --git a/cmd/mirum/main.go b/cmd/mirum/main.go deleted file mode 100644 --- a/cmd/mirum/main.go +++ /dev/null @@ -1,33 +0,0 @@ -// SPDX-FileCopyrightText: 2026 Nikolay Govorov -// SPDX-License-Identifier: AGPL-3.0-or-later - -// Command mirum is the developer-facing CLI for the Mirum CI system. -// -// In a future iteration it will host the full set of commands described in -// docs/whitepaper.md (`mirum task`, `mirum run`, `mirum list`, `mirum try`, -// `mirum ssh`, `mirum eval`). The current revision is a scaffold that -// compiles and ships through the existing build pipeline so that subsequent -// changes only have to add subcommand implementations. -package main - -import ( - "os" - - "dimidiumlabs/mirum/internal/protocol" - - "github.com/spf13/cobra" -) - -func main() { - root := &cobra.Command{ - Use: "mirum", - Short: "Mirum CI client", - Long: "Mirum CI client. Reads Mirumfile from the repository root.", - Version: protocol.VersionString(), - } - root.SetVersionTemplate("mirum {{.Version}}\n") - - if err := root.Execute(); err != nil { - os.Exit(1) - } -} diff --git a/cmd/mirumd/main.go b/cmd/mirumd/main.go new file mode 100644 --- /dev/null +++ b/cmd/mirumd/main.go @@ -0,0 +1,269 @@ +// Copyright (c) 2026 Nikolay Govorov +// SPDX-License-Identifier: AGPL-3.0-or-later + +package main + +import ( + "bytes" + "crypto/hmac" + "crypto/sha256" + "encoding/hex" + "encoding/json" + "flag" + "fmt" + "io" + "log/slog" + "net/http" + "net/url" + "os" + "os/exec" + "path/filepath" + "strings" + + "go.starlark.net/starlark" +) + +var ( + addr = flag.String("addr", ":3000", "listen address") + secret = flag.String("secret", "", "GitHub webhook secret") + token = flag.String("token", "", "GitHub personal access token (required)") + script = flag.String("script", ".mirum/main.star", "starlark script to run from repo root") +) + +type pushEvent struct { + Ref string `json:"ref"` + After string `json:"after"` + Repo struct { + FullName string `json:"full_name"` + CloneURL string `json:"clone_url"` + } `json:"repository"` +} + +func processPush(push pushEvent) { + owner, repo := splitFullName(push.Repo.FullName) + sha := push.After + log := slog.With("repo", push.Repo.FullName, "sha", sha[:8]) + + if err := setStatus(owner, repo, sha, "pending", "Build started"); err != nil { + log.Error("set pending status", "err", err) + } + + dir, err := os.MkdirTemp("", "mirum-*") + if err != nil { + log.Error("build failed", "err", err) + _ = setStatus(owner, repo, sha, "failure", "Build failed") + return + } + defer os.RemoveAll(dir) + + branch := strings.TrimPrefix(push.Ref, "refs/heads/") + cloneURL := authURL(push.Repo.CloneURL) + + if out, err := runCmd(dir, "git", "clone", "--depth=1", "--branch", branch, cloneURL, "."); err != nil { + log.Error("build failed", "err", err, "output", out) + _ = setStatus(owner, repo, sha, "failure", "Build failed") + return + } + + if err := runStarlark(dir); err != nil { + log.Error("build failed", "err", err) + _ = setStatus(owner, repo, sha, "failure", "Build failed") + return + } + + log.Info("build passed") + _ = setStatus(owner, repo, sha, "success", "Build passed") +} + +func setStatus(owner, repo, sha, state, description string) error { + apiURL := fmt.Sprintf("https://api.github.com/repos/%s/%s/statuses/%s", owner, repo, sha) + + body, _ := json.Marshal(map[string]string{ + "state": state, + "description": description, + "context": "mirum", + }) + + req, err := http.NewRequest("POST", apiURL, bytes.NewReader(body)) + if err != nil { + return err + } + req.Header.Set("Authorization", "Bearer "+*token) + req.Header.Set("Accept", "application/vnd.github+json") + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + if resp.StatusCode >= 300 { + b, _ := io.ReadAll(resp.Body) + return fmt.Errorf("github api %d: %s", resp.StatusCode, b) + } + return nil +} + +func verifySignature(payload []byte, signature string) bool { + sig, ok := strings.CutPrefix(signature, "sha256=") + if !ok { + return false + } + decoded, err := hex.DecodeString(sig) + if err != nil { + return false + } + mac := hmac.New(sha256.New, []byte(*secret)) + mac.Write(payload) + return hmac.Equal(mac.Sum(nil), decoded) +} + +func authURL(cloneURL string) string { + if *token == "" { + return cloneURL + } + u, err := url.Parse(cloneURL) + if err != nil { + return cloneURL + } + u.User = url.UserPassword("x-access-token", *token) + return u.String() +} + +func splitFullName(fullName string) (string, string) { + parts := strings.SplitN(fullName, "/", 2) + if len(parts) != 2 { + return fullName, "" + } + return parts[0], parts[1] +} + +type taskCtx struct { + dir string +} + +var _ starlark.HasAttrs = (*taskCtx)(nil) + +func (c *taskCtx) String() string { return "ctx" } +func (c *taskCtx) Type() string { return "ctx" } +func (c *taskCtx) Freeze() {} +func (c *taskCtx) Truth() starlark.Bool { return true } +func (c *taskCtx) Hash() (uint32, error) { return 0, fmt.Errorf("unhashable: ctx") } +func (c *taskCtx) AttrNames() []string { return []string{"shell"} } + +func (c *taskCtx) Attr(name string) (starlark.Value, error) { + if name == "shell" { + return starlark.NewBuiltin("ctx.shell", c.shell), nil + } + return nil, nil +} + +func (c *taskCtx) shell(thread *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { + var cmd string + if err := starlark.UnpackPositionalArgs(fn.Name(), args, kwargs, 1, &cmd); err != nil { + return nil, err + } + proc := exec.Command("bash", "-c", cmd) + proc.Dir = c.dir + proc.Stdout = os.Stdout + proc.Stderr = os.Stderr + err := proc.Run() + if err != nil { + return nil, err + } + return starlark.None, nil +} + +func runStarlark(dir string) error { + thread := &starlark.Thread{Name: "mirum"} + globals, err := starlark.ExecFile(thread, filepath.Join(dir, *script), nil, nil) + if err != nil { + return err + } + + projectFn, ok := globals["project"] + if !ok { + return fmt.Errorf("%s: project() not defined", *script) + } + fn, ok := projectFn.(starlark.Callable) + if !ok { + return fmt.Errorf("%s: project is not a function", *script) + } + + ctx := &taskCtx{dir: dir} + _, err = starlark.Call(thread, fn, starlark.Tuple{ctx}, nil) + return err +} + +func runCmd(dir, name string, args ...string) (string, error) { + cmd := exec.Command(name, args...) + cmd.Dir = dir + var buf bytes.Buffer + cmd.Stdout = &buf + cmd.Stderr = &buf + err := cmd.Run() + return buf.String(), err +} + +func main() { + flag.Parse() + + if *token == "" { + fmt.Fprintln(os.Stderr, "error: --token is required") + flag.Usage() + os.Exit(1) + } + + mux := http.NewServeMux() + mux.HandleFunc("POST /webhook", func(w http.ResponseWriter, r *http.Request) { + body, err := io.ReadAll(r.Body) + if err != nil { + http.Error(w, "read body", http.StatusBadRequest) + return + } + + if len(*secret) > 0 && !verifySignature(body, r.Header.Get("X-Hub-Signature-256")) { + http.Error(w, "invalid signature", http.StatusUnauthorized) + return + } + + event := r.Header.Get("X-GitHub-Event") + if event == "ping" { + fmt.Fprintln(w, "pong") + return + } + + if event != "push" { + w.WriteHeader(http.StatusNoContent) + return + } + + var push pushEvent + if err := json.Unmarshal(body, &push); err != nil { + http.Error(w, "parse payload", http.StatusBadRequest) + return + } + + if push.After == "" || push.After == "0000000000000000000000000000000000000000" { + w.WriteHeader(http.StatusNoContent) + return + } + + if !strings.HasPrefix(push.Ref, "refs/heads/") { + w.WriteHeader(http.StatusNoContent) + return + } + + slog.Info("push", "repo", push.Repo.FullName, "ref", push.Ref, "sha", push.After[:8]) + w.WriteHeader(http.StatusAccepted) + + go processPush(push) + }) + + slog.Info("listening", "addr", *addr) + + if err := http.ListenAndServe(*addr, mux); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } +} diff --git a/docs/mirumfile.md b/docs/mirumfile.md deleted file mode 100644 --- a/docs/mirumfile.md +++ /dev/null @@ -1,590 +0,0 @@ -# Mirumfile - -> [!CAUTION] -> This document is the API reference for the `Mirumfile` format. The -> implementation is in progress and the runtime does not yet match this -> document. - -`Mirumfile` is a single Starlark file at the repository root. It defines -**tasks** (units of work, run on a host or in a VM) and **pipelines** -(graphs of tasks, scheduled across one or more VMs). The same file is -read by the local `mirum` CLI and by mirum-server in a cluster. - -## CLI - -``` -mirum task <name> [args...] Run a registered task. Positional args after - <name> become positional arguments to the - task function after tctx: - mirum task build linux amd64 - → build(tctx, "linux", "amd64") - -mirum run <pipeline> Run a registered pipeline. -``` - -## File format and discovery - -`Mirumfile` lives at the repository root. This is the only required file -for Mirum. - -The file is standard Starlark with the following predeclared globals: - -| Name | Kind | Purpose | -|-------------|---------------------|--------------------------------------| -| `task` | builtin function | Register a task | -| `pipeline` | builtin function | Register a pipeline | -| `fail` | standard Starlark | Abort with a message | -| `print` | standard Starlark | Write to the task log | -| `struct` | standard Starlark | Build anonymous records | - -Multi-file projects use standard Starlark `load()`: - -```python -load("//tasks/build.star", "build", "test") -load("//tasks/release.star", "package") -``` - -The mirum standard library is mounted under `@mirum//`: - -```python -load("@mirum//on.star", "git", "cron", "any_of") -load("@mirum//pkg.star", "install") -``` - -## Naming convention - -By convention, the first parameter of a function is named according to the -ctx kind it expects: - -- `tctx` for task functions and helpers that operate on a task ctx -- `pctx` for pipeline functions and helpers that operate on a pipeline ctx -- `event` for trigger predicates - -This convention is not enforced by the runner — it is just a readability -aid. A reader of `def lint(tctx):` immediately knows it expects a task -ctx. - -## Registration - -Tasks and pipelines are registered as **side effects** of top-level -calls, not as values bound to names. The function is defined first, then -registered: - -```python -def build(tctx): - """Build mirum-server""" - tctx.exec(["go", "build", "-o", "build/mirum-server", "./cmd/mirum-server"]) -task(build) - -def test(tctx): - """Run tests""" - tctx.need(build) - tctx.exec(["go", "test", "-race", "-count=1", "./..."]) -task(test) - -load("@mirum//on.star", "git") - -def ci(pctx): - """CI: build + test on Linux""" - pctx.run(test, image="mirum/ubuntu-24.04") -pipeline(ci, on=[git.push(branches=["main"])]) -``` - -The function being registered remains an ordinary callable. It can be -called directly (`build(tctx)`), passed as a value (`pctx.run(test, ...)`), -and referenced by `tctx.need`. Registration is metadata for the CLI and -the server — it does not wrap or replace the function. - -A function defined at the top level that is **not** registered is a -**helper**: invisible to `mirum list`, not invocable as `mirum task`, -no special calling convention. Helpers are just regular functions called -from registered ones. There is no `_` prefix convention. - -```python -def check_gofmt(tctx): # helper, not registered - r = tctx.exec(["gofmt", "-l", "."], check=False) - if r.stdout.strip(): - fail("gofmt: needs formatting:\n" + r.stdout) - -def lint(tctx): - """Run static checks""" - tctx.exec(["go", "vet", "./..."]) - check_gofmt(tctx) -task(lint) -``` - -### `task(fn, name=None)` - -Registers `fn` as a task. The default name is the Starlark function name; -the optional `name` keyword overrides it. Returns `None`. Re-registration -of the same name is a hard error at eval time. - -### `pipeline(fn, name=None, on=None)` - -Registers `fn` as a pipeline. Same naming rules as `task`. The `on` -parameter is a single predicate or a list of predicates that decide when -mirum-server triggers the pipeline (see [Triggers](#triggers)). Returns -`None`. - -## Triggers - -A trigger is a **predicate function**: it takes an event and returns a -bool. mirum-server runs each registered pipeline whose `on=` predicate -returns True for an incoming event. With a list, the pipeline runs if -**any** predicate matches (OR semantics). - -Predicates are ordinary Starlark functions, defined either by the user or -by the standard library. There is no separate trigger DSL. - -### Writing predicates by hand - -```python -def main_push(event): - return (event.kind == "git" - and event.type == "push" - and event.branch == "main") - -def go_changes(event): - if event.kind != "git": - return False - if event.type != "push" and event.type != "pull_request": - return False - return any([p.endswith(".go") or p == "go.mod" for p in event.paths]) - -def by_release_bot(event): - return (event.kind == "git" - and event.type == "push" - and event.author == "release-bot") - -def ci(pctx): - ... -pipeline(ci, on=[main_push, go_changes]) -``` - -### Standard library factories - -For common cases the `@mirum//on.star` stdlib provides predicate -factories — Starlark functions that return predicates. They are -themselves written in plain Starlark; users can fork the file or write -their own factories the same way. Factories for source-specific events -are grouped by source kind, exposed as structs: - -```python -load("@mirum//on.star", "git", "cron", "manual", "any_of", "all_of") - -def ci(pctx): - ... -pipeline(ci, on=[ - git.push(branches=["main"], paths=["**.go", "go.mod"]), - git.pull_request(branches=["main"]), -]) - -def release(pctx): - pctx.run(build_release, image="mirum/ubuntu-24.04") -pipeline(release, on=[git.tag(names=["v*"])]) - -def nightly(pctx): - ... -pipeline(nightly, on=[cron("0 6 * * *")]) -``` - -A factory is just a closure-returning function — there is nothing -special about it on the runtime side. A trimmed version of the stdlib's -`git.push`: - -```python -# @mirum//on.star -def _git_push(branches=None, paths=None): - def predicate(event): - if event.kind != "git" or event.type != "push": - return False - if branches and event.branch not in branches: - return False - if paths and not _any_glob(event.paths, paths): - return False - return True - return predicate - -git = struct( - push = _git_push, - tag = _git_tag, - pull_request = _git_pull_request, -) -``` - -When a non-git source is added — Perforce, Mercurial, Subversion — its -factories live in their own namespace alongside `git`: - -```python -load("@mirum//on.star", "git", "perforce") - -pipeline(ci, on=[ - git.push(branches=["main"]), - perforce.submit(branches=["//depot/main/..."]), -]) -``` - -User-written factories compose with stdlib ones identically. To require -that a build runs only when both `main` is pushed AND a release-bot is -the author, mix stdlib and hand-written predicates with `all_of`: - -```python -load("@mirum//on.star", "git", "all_of") - -def by_release_bot(event): - return (event.kind == "git" - and event.type == "push" - and event.author == "release-bot") - -def bot_release(pctx): - ... -pipeline(bot_release, on=[ - all_of(git.push(branches=["main"]), by_release_bot), -]) -``` - -### `EventCtx` - -The single argument passed to every predicate. Same value is also -available inside pipeline functions as `pctx.event`. EventCtx is -read-only. - -Only two fields are guaranteed on every event: - -| Field | Type | Meaning | -|-------------------|------|----------------------------------------| -| `event.kind` | str | Source family that produced the event: `"git"`, `"perforce"`, `"hg"`, `"cron"`, `"manual"`, `"s3"`, `"webhook"`, … | -| `event.timestamp` | int | Unix epoch seconds of when the event occurred | - -**Everything else depends on `event.kind`.** Each kind documents its -own fields. Source families that have multiple distinct event types -(e.g. `git` has push, tag, pull_request) also expose `event.type` as a -sub-discriminator. - -A predicate that wants to read kind-specific fields **must check -`event.kind` first** (and `event.type` if the kind has multiple types). -Accessing a field that does not exist for the current event raises an -error. Predicates that do not recognize a kind should return `False`, -not crash. New event kinds and types may be added at any time; existing -predicates that check for known kinds remain valid. - -**`kind == "manual"`** - -| Field | Type | Meaning | -|------------------|-------------|----------------------------------------| -| `event.user` | str | Identifier of the user who triggered | -| `event.reason` | str \| None | Optional reason supplied by the user | - -**`kind == "cron"`** - -| Field | Type | Meaning | -|------------------|------|---------------------------------------------| -| `event.schedule` | str | The cron expression that fired | - -**`kind == "git"`** - -Fields common to all git events: - -| Field | Type | Meaning | -|------------------|------|--------------------------------------------------| -| `event.type` | str | `"push"`, `"tag"`, or `"pull_request"` | -| `event.source` | str | Name of the source set | -| `event.commit` | str | Commit SHA | -| `event.author` | str | Author / committer / tagger / PR author | - -Additional fields by `event.type`: - -`type == "push"`: - -| Field | Type | Meaning | -|------------------|-----------|----------------------------------------| -| `event.branch` | str | Branch pushed to | -| `event.message` | str | Commit message | -| `event.paths` | list[str] | Paths changed by the push | - -`type == "tag"`: - -| Field | Type | Meaning | -|------------------|------|--------------------------------------------------| -| `event.tag` | str | Tag name | -| `event.message` | str | Tag annotation, if any | - -`type == "pull_request"`: - -| Field | Type | Meaning | -|------------------|-----------|----------------------------------------| -| `event.number` | int | PR number | -| `event.title` | str | PR title | -| `event.base` | str | Target branch | -| `event.head` | str | Source branch | -| `event.draft` | bool | Draft state | -| `event.labels` | list[str] | Labels currently applied | -| `event.paths` | list[str] | Paths changed in the PR | - -Additional event kinds (other VCS systems, S3 object events, generic -webhooks, file watchers, external systems) bring their own field sets -and are documented as they are added. Existing predicates are -unaffected: they match the kinds they know and return `False` for -everything else. - -## TaskCtx - -The argument passed to every task function. The only ctx that can -execute commands. - -### Execution - -``` -tctx.shell(script, **opts) -> Result -tctx.exec(argv, **opts) -> Result -``` - -`shell` runs `script` through a POSIX/bash interpreter with full support -for pipes, redirects, command substitution, here-docs, variable expansion, -and control flow. Same shell semantics on every platform; no `/bin/bash` -dependency. - -`exec` runs a single command without shell parsing. Use it when you have -an argv list and want zero shell interpretation. - -Common keyword options for both: - -| Option | Default | Meaning | -|------------|-------------|-----------------------------------------------------------| -| `env` | `{}` | Additional environment variables (added to `tctx.env`) | -| `cwd` | `None` | Working directory (relative to current `tctx.cwd`) | -| `stdin` | `None` | None / str / bytes / path | -| `stdout` | `None` | None (capture+stream) / path / `DEVNULL` | -| `stderr` | `None` | None (capture+stream) / path / `DEVNULL` / `STDOUT` | -| `append` | `False` | Open `stdout` / `stderr` paths in append mode | -| `timeout` | `None` | Seconds; kill on expiry | -| `check` | `True` | Non-zero exit raises `fail()` automatically | -| `capture` | `True` | Populate `Result.stdout` / `Result.stderr` | - -`Result` is a struct value with attributes: - -```python -result.stdout # str -result.stderr # str -result.code # int -result.ok # bool (code == 0) -``` - -`check=True` is the default — non-zero exits abort the task. Tasks that -want to inspect the exit code use `check=False`: - -```python -r = tctx.exec(["test", "-f", path], check=False) -if r.ok: - ... -``` - -### Immutable derive - -``` -tctx.with_env({"K": "V"}) -> tctx -tctx.with_cwd("subdir") -> tctx -``` - -Both return a new ctx with the modification applied. The original ctx is -unchanged. - -### Introspection - -``` -tctx.cwd # str — current working directory (absolute) -tctx.env # dict-like — current environment -tctx.os # "linux" | "darwin" | "freebsd" | "windows" | ... -tctx.arch # "amd64" | "arm64" | "riscv64" | ... -``` - -### `tctx.need(fn, *args, **kwargs) -> Result | None` - -Runs `fn(tctx, *args, **kwargs)` if it has not already been called with -the same arguments in this invocation; otherwise returns the cached -result. Use it for "make sure this happened" semantics; use a direct -call (`build(tctx)`) for "definitely run this now" semantics. - -Dedup key is `(fn, args, kwargs)`. Different arguments to the same -function are different invocations: - -```python -def build(tctx, goos="linux", goarch="amd64"): - ... - -def all_platforms(tctx): - for goos, goarch in [("linux", "amd64"), ("darwin", "arm64")]: - tctx.need(build, goos, goarch) # two distinct invocations -``` - -### `tctx.checkout(name="main", ref=None) -> str` - -Materializes a source set inside the task's environment and returns -the absolute path to it. The runtime is responsible for fetching the -right files; from the task's perspective the call is idempotent — -calling it again returns the same path without doing extra work. - -When `ref` is omitted, the runtime picks the natural ref for the -triggering event: - -| Event | Default ref | -|-----------------------------|---------------------------| -| `git push` | The pushed commit | -| `git tag` | The tagged commit | -| `git pull_request` | The PR head | -| `cron` | Default branch | -| `manual` / `mirum task` | Current working tree | - -To use a different ref explicitly, pass `ref=` (a SHA, branch, or tag -name). Pipelines that need to override the default usually do so by -forwarding event data through `args`: - -```python -def build_at(tctx, ref): - src = tctx.checkout(ref=ref) - ... -task(build_at) - -def replay(pctx): - pctx.run(build_at, image="...", args={"ref": pctx.event.commit}) -``` - -Multiple source sets are accessed by name: - -```python -def integration(tctx): - src = tctx.checkout() # default source set - fixtures = tctx.checkout("fixtures") # named source set - tctx.with_env({"FIXTURES": fixtures}).exec(["go", "test", "./tests/integration/..."]) -``` - -Source set names are defined in [Source sets](#source-sets), not in -pipeline or task code. A task that needs source files should call -`tctx.checkout()` explicitly — both as documentation of intent and -because some workers may stage source lazily on first call. - -### `tctx.upload(path, artifact="name")` and `tctx.download(name, dest=".")` - -`upload` registers a file as a named artifact under the current task. -`download` retrieves a previously uploaded artifact by name into a -destination directory. - -Locally, artifacts are tracked in `.mirum/local-artifacts.json` in the -repo root and persist between `mirum` invocations. In the cluster they -move through mirum-server. The task functions are unchanged either way. - -`download` of an artifact that was never uploaded fails with a clear -error. - -## PipelineCtx - -The argument passed to every pipeline function. **Cannot execute shell -commands.** Pipeline code may come from untrusted PRs; it is restricted -to orchestration. - -### `pctx.event` - -Read-only [`EventCtx`](#eventctx) for the event that triggered this -pipeline. The same value that was passed to the trigger predicates. - -When `mirum run` is invoked locally, the event is synthetic -(`{"kind": "manual"}` by default; overridable via CLI flags). - -### `pctx.run(task_fn, image=, args={}, setup=None, depends=[]) -> handle` - -Dispatches a task into a new VM. Returns a handle that can be passed as -`depends=[handle, ...]` to subsequent `pctx.run` calls. - -| Argument | Meaning | -|------------|--------------------------------------------------------------------------| -| `task_fn` | Task function to invoke (the function value, not the registered name) | -| `image` | OCI reference to the VM image | -| `args` | Keyword arguments forwarded to `task_fn(tctx, **args)` | -| `setup` | Optional setup function for cached snapshots | -| `depends` | Handles from prior `pctx.run` calls. This VM does not start until they finish, and inherits their `upload`'d artifacts | - -Source materialization is a task-side concern, not a pipeline-side one -— see [`tctx.checkout`](#tctxcheckoutnamemain---str). The pipeline does -not pass source refs to its tasks; each task asks for the source sets -it needs by name, and the runner resolves them based on the triggering -event. - -`depends=` expresses **VM topology**, not task dependency. Within a -single VM, task functions compose via `tctx.need(...)`. Across VMs, the -pipeline orchestrates via `pctx.run(..., depends=[...])`. These are two -distinct mechanisms and are not interchangeable. - -Example: cross-platform release that fans out builds and fans in publish. - -```python -load("@mirum//on.star", "git") - -PLATFORMS = [ - ("linux", "amd64"), ("linux", "arm64"), - ("darwin", "arm64"), - ("windows", "amd64"), - ("freebsd", "amd64"), -] - -IMAGES = { - "linux": "mirum/ubuntu-24.04", - "darwin": "mirum/macos-15", - "windows": "mirum/windows-2025", - "freebsd": "mirum/freebsd-14", -} - -def release(pctx): - builds = [] - for goos, goarch in PLATFORMS: - builds.append(pctx.run(build, - image=IMAGES[goos], - args={"goos": goos, "goarch": goarch})) - - pctx.run(publish, image=IMAGES["linux"], depends=builds) -pipeline(release, on=[git.tag(names=["v*"])]) -``` - -## Source sets - -Source URLs, refs, auth, and repo locations are **configuration**. -Pipeline and task code reference source sets only by **name**. - -In a cluster, source sets are configured per project in mirum-server's -WebUI. The server resolves names to URLs and credentials when staging a -VM, and credentials never reach the VM or the task code. - -Locally, source sets are defined in `.mirum/sources.json` in the repo -root: - -```json -{ - "main": ".", - "fixtures": "/home/user/work/test-fixtures" -} -``` - -Values are absolute paths to local checkouts. The `main` entry can be -omitted; it defaults to the directory containing `Mirumfile`. The file -is opt-in for git tracking — projects with shared layout may commit it, -projects with user-specific paths typically gitignore it. - -A `tctx.checkout("name")` for a name not in the configuration is a hard -error. - -## Safe shell interpolation - -Dynamic values are passed through the `env=` keyword and referenced as -quoted shell variables in the script body. This gives POSIX single-word -expansion semantics — the value becomes one literal argument and is -never re-parsed: - -```python -# Even if branch == "; rm -rf /", this is safe — it is one literal arg -tctx.shell('git log --format=%H "$BRANCH"', env={"BRANCH": branch}) -``` - -Never build shell scripts by string concatenation. Always pass dynamic -values through `env=`. - -`tctx.exec(argv, ...)` bypasses the shell entirely; use it when you -already have an argv list and want zero chance of shell interpretation. diff --git a/docs/whitepaper.md b/docs/whitepaper.md deleted file mode 100644 --- a/docs/whitepaper.md +++ /dev/null @@ -1,594 +0,0 @@ -# Mirum Whitepaper - -## Modules - -Four executable modules: - -``` -┌──────────────────────────────────────────────────────┐ -│ mirum-server │ -│ watch registry, task queue, log aggregation, WebUI │ -└─────────────────────────────────────────────────────┘ - ↑ gRPC (worker-initiated) ↑ gRPC - │ │ -┌────────┴──────────┐ ┌─────────┴─────────┐ -│ mirum-worker │ │ mirum-worker │ -│ Linux (KVM) │ │ macOS (Vz) │ -│ │ vsock │ │ │ vsock │ -│ ↓ │ │ ↓ │ -│ ┌────────────┐ │ │ ┌────────────┐ │ -│ │mirum-agent │ │ │ │mirum-agent │ │ -│ │ inside VM │ │ │ │ inside VM │ │ -│ └────────────┘ │ │ └────────────┘ │ -└───────────────────┘ └───────────────────┘ - -┌───────────────────┐ -│ mirum (CLI) │ -│ Starlark eval, │ -│ spawns worker │ -└───────────────────┘ -``` - -**mirum-server** — the orchestrator. -Contains a database, stores users, organizations, project and pipeline settings, -provides a WebUI and API, responds to webhooks, and distributes tasks to workers. -Collects logs and build results from workers. - -It's a control plane. - -**mirum-worker** — the task executor -Connects to the server via outbound gRPC, declares its capabilities, -and picks tasks from the queue that it can execute. - -Evaluates pipeline Starlark (coroutine model), starts a VM for each -task, runs the task function inside, and returns the results to the -server. If a pipeline yields on a task result it is not yet ready for, -the worker leaves the suspended coroutine in the queue and resumes it -when the awaited task completes. - -It's a data plane. _Only the worker has access to the user's secrets and code._ - -Workers come in different kinds: KVM worker for local VMs, -macOS worker with Vz.framework, windows worker with Hyper-V, -EC2 worker for cloud VMs, host worker for direct execution. -A new worker type joins the cluster and starts picking up tasks with no changes -to the server. - -**mirum-agent** — static binary pre-installed in every VM. - -A tiny bridge between the worker on the host and the tasks running in the VM. -Written in C99, no dependencies, posix-only. Implements a simple TLV for communicating -with the host (via virtio-vsock or tcp socket, depending on the hypervisor). - -Channels: control, stdin, stdout, stderr, file transfers, interactive shell sessions. - -Small and simple enough to be auditable. -This component lives close to the actual code, making it highly security-sensitive. - -**mirum (CLI)** — developer tool. -Contains a Starlark runtime for local eval and embeds a host worker for -in-process execution. Takes the server's role locally. - -Technically, it is a lightweight disposable server that runs a real worker locally -to replicate real-world conditions in a cluster as closely as possible. - -- `mirum task <name>`: run a single registered task on the host, no VM. -- `mirum run <pipeline>`: eval pipeline → spawn worker → dispatch tasks → display logs. -- `mirum list`: list registered tasks and pipelines. -- `mirum try <pipeline>`: send a local diff to the server. -- `mirum ssh <vm>`: shell into a failed VM. -- `mirum eval <pipeline>`: show the DAG without running anything. - -## Configuration Model - -Mirum operates on projects, a project consists of pipelines, -pipelines consist of tasks. A task is the minimum unit of execution — it always -runs in a single VM. Pipelines are DAGs (directed acyclic graphs) that invoke multiple -tasks and pass state between them. A project tracks a list of pipelines, their trigger -rules (watch, cron, manual, ...), and result notifications. - -A quick example, all in one starlark file: - -```python -# /Mirumfile — the only file required - -load("@mirum//on.star", "git") - -# A single pipeline describes a matrix of multiple operating systems and architectures. -# A single Linux host with KVM serves Linux, Windows, and BSD guests. -# A macOS host serves macOS, Linux, and BSD. -# Tasks adapt to the platform via `tctx.os` — they don't choose it. - -# pctx.run takes an optional setup function to indicate which steps -# are environment setup only, so the resulting image can be cached -def setup(tctx): - tctx.shell("apt-get update && apt-get install -y cargo") - -def build(tctx): - tctx.checkout() - tctx.shell("cargo build --release") - tctx.upload("target/release/myapp", artifact="bin") -task(build) - -def test(tctx): - # test knows nothing about build — only that it needs an artifact. - # that artifact could have been built right here, come from cache - # or a registry, or even uploaded from a developer's laptop - tctx.download("bin", dest=".") - tctx.shell("cargo test") -task(test) - -# A pipeline is a function that dispatches tasks. It is registered the -# same way tasks are, with a list of triggers. -def ci(pctx): - b = pctx.run(build, setup=setup, image="mirum/ubuntu-24.04") - pctx.run(test, setup=setup, depends=b, image="mirum/ubuntu-24.04") -pipeline(ci, on=[git.push(branches=["main"])]) -``` - -### Functions All the Way Down - -Mirum configuration is nested function composition in Starlark. There -are two kinds of registered things — **tasks** and **pipelines** — and -both are registered as side effects of top-level calls in the file: - -```python -def build(tctx): - ... -task(build) - -def ci(pctx): - ... -pipeline(ci, on=[git.push(branches=["main"])]) -``` - -A pipeline is a function that imperatively dispatches tasks and passes -dependencies between them. A task's result can be used to launch further -tasks, enabling dynamic task creation. There is no separate syntax for -"allow failure / retry / skip_if / matrix" — it is just `if`/`for` in -Starlark. - -``` -Mirumfile → the file the server looks for (one per repo, at root) - -triggers → event routing "when to run" - (predicates passed via pipeline(..., on=[...])) - -pipeline(pctx) → DAG of tasks "what to run, on which platforms" - -task(tctx) → scripts + artifacts "how to build" -``` - -Starlark supports imports, so splitting a large project works out of the -box. The only requirement is a `Mirumfile` at the repository root. - -The server reads `Mirumfile` (and any files it transitively `load()`s) -via the forge contents API, not via git clone. This allows: fetching -only configuration files without accessing source code, filtering -webhooks (push with no changes in `Mirumfile`'s closure → skip re-eval), -caching configuration by file SHAs. - -To reuse code both within and outside the project, Starlark `load` is used. - -``` -@mirum// Standard library (triggers, services, apt, bazel helpers) -@pkg// External packages (from deps.star, pinned by commit) -// Local files (relative to repo root) -``` - -### Coroutine Eval: Dynamic DAGs - -Pipeline functions execute as coroutines. `pctx.run()` without accessing -results is non-blocking — the server accumulates pending tasks. -Accessing a result (`.output()`) is a yield point: the server dispatches -all pending tasks, waits for the needed result, and resumes the pipeline -function. - -```python -def build(tctx): - ... -task(build) - -def discover_tests(tctx): - ... -task(discover_tests) - -def run_test(tctx, module): - ... -task(run_test) - -def ci(pctx): - # All pctx.run() calls before the first .output() accumulate and run in parallel - builds = {} - for os in ["linux", "mac"]: - builds[os] = pctx.run(build, - image="mirum/%s" % os, args={"os": os}) - - discovery = pctx.run(discover_tests, image="mirum/linux") - - # Yield: server dispatches builds + discovery in parallel, - # waits for discovery to complete, resumes with result - test_modules = discovery.output("modules") - - # Dynamic phase: use runtime data - for module in test_modules: - pctx.run(run_test, args={"module": module}, - depends=list(builds.values())) -pipeline(ci, on=[git.push(branches=["main"])]) -``` - -If a pipeline function never calls `.output()`, the entire DAG is built -in a single pass. A static DAG is a special case of the dynamic model. - -`mirum eval` executes the pipeline function locally without dispatching -tasks. For static DAGs it prints the full graph. For dynamic DAGs — -everything up to the first yield point, marked "depends on runtime data -beyond this point." - -### Example: Everything in One File - -```python -# /Mirumfile — complete CI - -load("@mirum//on.star", "git") - -# pctx.run takes an optional setup function to indicate which steps -# are environment setup only, so the resulting image can be cached -def setup(tctx): - tctx.shell("apt-get update && apt-get install -y cargo") - -def build(tctx): - tctx.checkout() - tctx.shell("cargo build --release") - tctx.upload("target/release/myapp", artifact="bin") -task(build) - -def test(tctx): - # test knows nothing about build — only that it needs an artifact. - # that artifact could have been built right here, come from cache - # or a registry, or even uploaded from a developer's laptop - tctx.download("bin", dest=".") - tctx.shell("cargo test") -task(test) - -def ci(pctx): - b = pctx.run(build, setup=setup, image="mirum/ubuntu-24.04") - pctx.run(test, setup=setup, depends=b, image="mirum/ubuntu-24.04") -pipeline(ci, on=[git.push(branches=["main"])]) -``` - -### Example: Cross-Platform Project - -```python -# tasks/setup.star -def cpp_toolchain(tctx): - if tctx.os == "linux": - tctx.shell("apt-get update && apt-get install -y cmake ninja-build") - elif tctx.os == "freebsd": - tctx.shell("pkg install -y cmake ninja") - elif tctx.os == "windows": - tctx.shell("choco install -y cmake ninja visualstudio2022-workload-vctools") - elif tctx.os == "macos": - tctx.shell("brew install cmake ninja") - -# tasks/build.star -def build(tctx): - tctx.checkout() - if tctx.os == "windows": - tctx.shell('cmake -G "Visual Studio 17 2022" -B build .') - elif tctx.os == "macos": - tctx.shell("cmake -B build -DCMAKE_OSX_DEPLOYMENT_TARGET=12.0 .") - else: - tctx.shell("cmake -B build .") - tctx.shell("cmake --build build --config Release") - tctx.upload("build/out/*", artifact="pkg") -task(build) - -def publish(tctx): - tctx.download("pkg", dest="release/") - tctx.shell('gh release create "$TAG" release/* --generate-notes') -task(publish) - -# /Mirumfile -load("@mirum//on.star", "git") -load("//tasks/setup.star", "cpp_toolchain") -load("//tasks/build.star", "build", "publish") - -IMAGES = { - "linux": "mirum/ubuntu-24.04", - "freebsd": "mirum/freebsd-14", - "windows": "mirum/windows-2025", - "macos": "mirum/macos-15", -} - -# Irregular matrix — just a list. No exclude needed. -PLATFORMS = [ - ("linux", "amd64"), - ("linux", "arm64"), - ("macos", "arm64"), - ("windows", "amd64"), - ("freebsd", "amd64"), -] - -def release(pctx): - # Build: loop over platforms, each task gets its own handle - builds = {} - for os, arch in PLATFORMS: - builds[(os, arch)] = pctx.run(build, - setup=cpp_toolchain, - image=IMAGES[os], - args={"os": os, "arch": arch}) - - # Publish: fan-in, waits for all builds - pctx.run(publish, depends=list(builds.values())) -pipeline(release, on=[git.tag(names=["v*"])]) -``` - -The pipeline decides WHERE (image, platforms). Setup decides WITH WHAT -(toolchain, cached snapshot). The task decides HOW (checkout, build, upload). -Tasks don't know what platform they're running on — `tctx.os` and `tctx.arch` -are injected by the pipeline. - -## Images - -The most tedious and time-consuming task is preparing images for various operating -systems. Some distributions distribute qcow2, some support cloud-init, and some only -offer an ISO installer. Some require a network connection for configuration, while -others work offline. - -Another problem is distribution. The reason containers are popular is the OCI registry. -A container is easy to upload to a server, and just as easy to download and deploy. -Nothing similar exists for VMs. - -An elegant solution was found in Tart by CirrusCI: use OCI as a black box for storing -the VM image. Load it into your existing infrastructure, easily update, and distribute. -A single distribution format for all platforms. Images are stored as compressed -raw disk chunks: - -``` -OCI Image Manifest: - config: - mediaType: "application/vnd.mirum.image.config.v1+json" - { mirum.version, os, arch, distro, distro_version, - agent_version, disk_size, chunk_size } - - layers: - - mediaType: "application/vnd.mirum.disk.raw.v1+zstd" - annotations: { "mirum.offset": "0", "mirum.length": "67108864" } - - ... - - # macOS additionally: - - mediaType: "application/vnd.mirum.aux.v1+zstd" - - mediaType: "application/vnd.mirum.hwmodel.v1+json" -``` - -Each chunk is independently zstd-compressed. The worker downloads and decompresses -in parallel. 64MB chunks for a 4GB disk ≈ 64 layers. On pull worker reassembles -raw disk from chunks → converts to hypervisor format -(qcow2, vhdx, Vz native) → caches → CoW clone per task. - -### Three Layers (VM Runtime) - -VM images are larger than container images, but there are fewer of them. -We can borrow the layer caching idea and apply it to image snapshots (like in qcow2+). - -``` -Layer 0: Base image (from OCI registry) - Golden (Mirum-maintained) or organization (external). - Worker downloads, converts to hypervisor format, caches locally. - -Layer 1: Setup (function from pctx.run(setup=...)) - Declared in the pipeline. Worker executes, takes a snapshot. - Cache is local, best-effort, evicted by LRU. - -Layer 2: Ephemeral overlay - CoW clone of setup cache (or base). Per-task. Destroyed. -``` - -### Setup as a Function - -The pipeline passes two functions to `pctx.run()`: -`setup` (optional) and the main task. The image is also specified in the pipeline: - -```python -def cpp_setup(tctx): - if tctx.os == "linux": - tctx.shell("apt-get update && apt-get install -y cmake ninja-build") - elif tctx.os == "freebsd": - tctx.shell("pkg install -y cmake ninja") - -def build(tctx): - tctx.checkout() - tctx.shell("cmake -B build . && cmake --build build") -task(build) - -def ci(pctx): - pctx.run(build, setup=cpp_setup, - image="mirum/ubuntu-24.04", - args={"os": "linux"}) -pipeline(ci, on=[git.push(branches=["main"])]) -``` - -The worker hashes `(image_digest, setup_function_hash, os, arch)`. -Cache hit → CoW clone, boot in milliseconds. Miss → boot base, run setup, -snapshot, cache. - -Setup is an ordinary Starlark function, composable via `load()`: - -```python -# @pkg//acme/setup.star -def cpp_toolchain(tctx): - if tctx.os == "linux": - tctx.shell("apt-get update && apt-get install -y cmake ninja-build") - elif tctx.os == "windows": - tctx.shell("choco install -y cmake ninja") -``` - -```python -load("@pkg//acme/setup.star", "cpp_toolchain") - -def ci(pctx): - for os in ["linux", "windows"]: - pctx.run(build, setup=cpp_toolchain, - image=IMAGES[os], args={"os": os}) -``` - -One `cpp_toolchain` across the entire organization — one hash — one snapshot per worker. - -Three levels, cleanly separated: - -- **Pipeline**: WHERE (image, platforms) -- **Setup**: WITH WHAT (toolchain, dependencies — cached snapshot) -- **Task**: HOW (checkout, build, test, upload) - -Organizations that need a fully pre-built image can publish it to any OCI registry -using external tooling and reference it directly: - -```python -# no need for setup with a preconfigured image -pctx.run(build, image="acme-registry.com/ci-base:latest") -``` - -### Golden Images - -Golden images are built by the Mirum team using Packer, not by users: - -| Platform | Packer builder | Install method | -| ------------------------- | ---------------------- | ----------------------------------- | -| Linux (Ubuntu, Fedora...) | `qemu` | cloud-init / preseed / kickstart | -| macOS | `tart` (Packer plugin) | VZMacOSInstaller + VNC boot_command | -| Windows | `qemu` | autounattend.xml (evaluation ISO) | -| NetBSD | `qemu` | sysinst auto | -| FreeBSD | `qemu` | bsdinstall scripted | -| OpenBSD | `qemu` | autoinstall response file | - -Windows: evaluation ISO is freely downloadable. The evaluation period (180 days) -is irrelevant for ephemeral CI VMs. Users activate with their own key if needed. - -macOS: `.ipsw` installed via Virtualization.framework. Setup Assistant automated -via VNC keystroke injection. Requires Apple hardware for building and running. - -## Extensibility - -Starlark simplifies building plugins and libraries. You already have `load`, -so you don't need to invent your own systems like reusable actions. - -**Transparent worker optimizations** — invisible to the task. -Configured in `worker.yaml`. The worker configures the VM environment before running -any scripts: apt mirror, cargo/npm cache mount, HTTP proxy. -`./ci.sh` with `apt-get install` inside simply runs faster. -Bash scripts speed up for free. - -```yaml -# worker.yaml -optimizations: - apt_mirror: "http://apt-cache.internal:3142" - http_proxy: "http://squid.internal:3128" - cargo_cache: "/mnt/shared/cargo" -``` - -**Starlark stdlib (@mirum//)** — for things that require an explicit decision. -This is a standard Starlark that, although it comes with an agent, -doesn't require any additional APIs (see the Bazel vs Buck configurations). -Users can read, fork, or write their own. - -Starlark sees capabilities via `tctx.worker.has("docker")`. -The stdlib adapts. No hidden magic — the code is readable. - -The dividing principle: if an optimization can be applied without changing -task behavior — it's a transparent worker optimization. If the task needs to know -(e.g. a postgres address) — it's Starlark stdlib with graceful degradation. - -Checks worker capabilities and adapts: - -```python -load("@mirum//services", "service") - -def test(tctx): - # docker on the worker? → sidecar container - # no docker? → install and run inside the VM - service(tctx, "postgres", image="postgres:16", port=5432) - tctx.shell("make test") -task(test) -``` - -Workers declare capabilities on registration: - -```yaml -# worker.yaml -capabilities: - kvm: true - gpu: false - docker: true -``` - -**Server plugins (traits)** — extend the platform. Implement trait interfaces. -Configured in `server.yaml`: - -| Trait | AGPL built-in | External plugin | -| ---------------- | ------------- | ---------------------- | -| AuthBackend | Token, basic | SAML, OIDC, LDAP | -| Source provider | Git, VSC, s3 | Mercurial, Perforse | -| SecretProvider | Env, systemd | Vault, AWS KMS | -| NotificationSink | — | Slack, email, webhooks | -| BillingHook | Noop | Usage metering | - -## Comparison - -We were inspired by many wonderful tools -- Buildbot: centralized master, property model, `try` for pre-commit testing, dynamic build steps; -- TeamCity: vsc roots, multi-tenant, role model, breadth of tool support; -- Concourse: the idea of universal input/output; -- SourceHut: SSH into VMs for debugging, BSD support; -- Cirrus CI: ephemeral VMs, bring-your-own cloud, Starlark, agent inside VM; -- GitHub Actions: how not to do it. - -### vs GitHub Actions - -Paid, closed, inseparable from Microsoft, only ubuntu/windows/macOS, -only x64/arm64, nodejs required in runtime, -yaml configs (and only for the current repository). -No cross-OS matrices out of the box (macOS runners are a paid add-on). -No local execution. No dynamic DAG. Caching is an action, not a primitive. - -### vs GitLab CI - -GitLab CI is part of GitLab. YAML. DAG via `needs:` — a hack on top of a stage-based model. -Runners are stateful machines or Docker. No VM isolation. `include:` / YAML anchors — fragile reuse. -Dynamic child pipelines — via YAML generation. - -### vs Jenkins - -Groovy DSL is powerful but allows arbitrary code (RCE when eval'ing PRs). -Plugin ecosystem is huge but fragile (Security Advisories every month). -Agents are stateful, workspace persists. Shared Libraries are Groovy classes, trusted/untrusted. - -### vs TeamCity - -Powerful and popular, with clever ideas (dedicated VCS root, for example). -But it's paid and expensive, closed-source, and difficult to use. -Kotlin DSL is typed with IDE support, but allows side effects (HTTP, filesystem). -Agents are stateful and require maintenance. Snapshot dependencies equal our source consistency. - -Templates (1:1) vs our `load()` (N:N) — Starlark is strictly more powerful. - -### vs Buildbot - -The most portable and flexible of all. However, it's outdated, difficult to configure, -and designed for hosted builds. Its architecture doesn't support SaaS. -Pure Python configurations on both the master and agents. No IaC out of the box. - -### vs Cirrus CI - -Great tool, but unfortunately still closed source and dependent on gcloud. -Starlark is only available as an advanced mode with yaml. -It lacks support for many BSDs (but FreeBSD is available!). - -## Licensing - -**AGPL-3.0** — all four modules, all built-in traits, all runtimes, standard library, -full CLI, basic Web UI, SQLite, single-tenant auth. - -**Commercial license** — For companies unwilling to use the AGPL, offer a commercial -license, certifications, and SLAs in SaaS. Don't hesitate to take money -from enterprises and spend it on open source. diff --git a/go.mod b/go.mod index 2dbba9d..632a160 100644 --- a/go.mod +++ b/go.mod @@ -1,260 +1,7 @@ -module dimidiumlabs/mirum +module mrdimidium/mirum -go 1.26.5 +go 1.26.1 -require ( - buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.11-20260209202127-80ab13bee0bf.1 - connectrpc.com/connect v1.19.1 - connectrpc.com/validate v0.6.0 - github.com/coreos/go-systemd/v22 v22.7.0 - github.com/github/go-spdx/v2 v2.4.0 - github.com/go-chi/chi/v5 v5.2.5 - github.com/go-chi/httprate v0.15.0 - github.com/google/licensecheck v0.3.1 - github.com/google/uuid v1.6.0 - github.com/huandu/go-sqlbuilder v1.40.1 - github.com/jackc/pgerrcode v0.0.0-20250907135507-afb5586c32a6 - github.com/jackc/pgx/v5 v5.9.2 - github.com/jackc/tern/v2 v2.3.6 - github.com/spf13/cobra v1.10.2 - github.com/spf13/pflag v1.0.10 - go.starlark.net v0.0.0-20260326113308-fadfc96def35 - golang.org/x/crypto v0.49.0 - google.golang.org/protobuf v1.36.11 - gopkg.in/yaml.v3 v3.0.1 -) +require go.starlark.net v0.0.0-20260326113308-fadfc96def35 -require ( - 4d63.com/gocheckcompilerdirectives v1.3.0 // indirect - 4d63.com/gochecknoglobals v0.2.2 // indirect - buf.build/go/protovalidate v1.1.3 // indirect - cel.dev/expr v0.25.1 // indirect - codeberg.org/chavacava/garif v0.2.0 // indirect - codeberg.org/polyfloyd/go-errorlint v1.9.0 // indirect - dario.cat/mergo v1.0.1 // indirect - dev.gaijin.team/go/exhaustruct/v4 v4.0.0 // indirect - dev.gaijin.team/go/golib v0.6.0 // indirect - github.com/4meepo/tagalign v1.4.3 // indirect - github.com/Abirdcfly/dupword v0.1.7 // indirect - github.com/AdminBenni/iota-mixing v1.0.0 // indirect - github.com/AlwxSin/noinlineerr v1.0.5 // indirect - github.com/Antonboom/errname v1.1.1 // indirect - github.com/Antonboom/nilnil v1.1.1 // indirect - github.com/Antonboom/testifylint v1.6.4 // indirect - github.com/BurntSushi/toml v1.6.0 // indirect - github.com/Djarvur/go-err113 v0.1.1 // indirect - github.com/Masterminds/goutils v1.1.1 // indirect - github.com/Masterminds/semver/v3 v3.4.0 // indirect - github.com/Masterminds/sprig/v3 v3.3.0 // indirect - github.com/MirrexOne/unqueryvet v1.5.4 // indirect - github.com/OpenPeeDeeP/depguard/v2 v2.2.1 // indirect - github.com/alecthomas/chroma/v2 v2.23.1 // indirect - github.com/alecthomas/go-check-sumtype v0.3.1 // indirect - github.com/alexkohler/nakedret/v2 v2.0.6 // indirect - github.com/alexkohler/prealloc v1.1.0 // indirect - github.com/alfatraining/structtag v1.0.0 // indirect - github.com/alingse/asasalint v0.0.11 // indirect - github.com/alingse/nilnesserr v0.2.0 // indirect - github.com/antlr4-go/antlr/v4 v4.13.1 // indirect - github.com/ashanbrown/forbidigo/v2 v2.3.0 // indirect - github.com/ashanbrown/makezero/v2 v2.1.0 // indirect - github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect - github.com/beorn7/perks v1.0.1 // indirect - github.com/bkielbasa/cyclop v1.2.3 // indirect - github.com/blizzy78/varnamelen v0.8.0 // indirect - github.com/bombsimon/wsl/v4 v4.7.0 // indirect - github.com/bombsimon/wsl/v5 v5.6.0 // indirect - github.com/breml/bidichk v0.3.3 // indirect - github.com/breml/errchkjson v0.4.1 // indirect - github.com/butuzov/ireturn v0.4.0 // indirect - github.com/butuzov/mirror v1.3.0 // indirect - github.com/catenacyber/perfsprint v0.10.1 // indirect - github.com/ccojocar/zxcvbn-go v1.0.4 // indirect - github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/charithe/durationcheck v0.0.11 // indirect - github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect - github.com/charmbracelet/lipgloss v1.1.0 // indirect - github.com/charmbracelet/x/ansi v0.10.1 // indirect - github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect - github.com/charmbracelet/x/term v0.2.1 // indirect - github.com/ckaznocha/intrange v0.3.1 // indirect - github.com/curioswitch/go-reassign v0.3.0 // indirect - github.com/daixiang0/gci v0.13.7 // indirect - github.com/dave/dst v0.27.3 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/denis-tingaikin/go-header v0.5.0 // indirect - github.com/dlclark/regexp2 v1.11.5 // indirect - github.com/ettle/strcase v0.2.0 // indirect - github.com/fatih/color v1.19.0 // indirect - github.com/fatih/structtag v1.2.0 // indirect - github.com/firefart/nonamedreturns v1.0.6 // indirect - github.com/fsnotify/fsnotify v1.5.4 // indirect - github.com/fzipp/gocyclo v0.6.0 // indirect - github.com/ghostiam/protogetter v0.3.20 // indirect - github.com/go-critic/go-critic v0.14.3 // indirect - github.com/go-toolsmith/astcast v1.1.0 // indirect - github.com/go-toolsmith/astcopy v1.1.0 // indirect - github.com/go-toolsmith/astequal v1.2.0 // indirect - github.com/go-toolsmith/astfmt v1.1.0 // indirect - github.com/go-toolsmith/astp v1.1.0 // indirect - github.com/go-toolsmith/strparse v1.1.0 // indirect - github.com/go-toolsmith/typep v1.1.0 // indirect - github.com/go-viper/mapstructure/v2 v2.5.0 // indirect - github.com/go-xmlfmt/xmlfmt v1.1.3 // indirect - github.com/gobwas/glob v0.2.3 // indirect - github.com/godoc-lint/godoc-lint v0.11.2 // indirect - github.com/gofrs/flock v0.13.0 // indirect - github.com/golang/protobuf v1.5.3 // indirect - github.com/golangci/asciicheck v0.5.0 // indirect - github.com/golangci/dupl v0.0.0-20250308024227-f665c8d69b32 // indirect - github.com/golangci/go-printf-func-name v0.1.1 // indirect - github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d // indirect - github.com/golangci/golangci-lint/v2 v2.11.4 // indirect - github.com/golangci/golines v0.15.0 // indirect - github.com/golangci/misspell v0.8.0 // indirect - github.com/golangci/plugin-module-register v0.1.2 // indirect - github.com/golangci/revgrep v0.8.0 // indirect - github.com/golangci/swaggoswag v0.0.0-20250504205917-77f2aca3143e // indirect - github.com/golangci/unconvert v0.0.0-20250410112200-a129a6e6413e // indirect - github.com/google/cel-go v0.27.0 // indirect - github.com/google/go-cmp v0.7.0 // indirect - github.com/gordonklaus/ineffassign v0.2.0 // indirect - github.com/gostaticanalysis/analysisutil v0.7.1 // indirect - github.com/gostaticanalysis/comment v1.5.0 // indirect - github.com/gostaticanalysis/forcetypeassert v0.2.0 // indirect - github.com/gostaticanalysis/nilerr v0.1.2 // indirect - github.com/hashicorp/go-immutable-radix/v2 v2.1.0 // indirect - github.com/hashicorp/go-version v1.8.0 // indirect - github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect - github.com/hashicorp/hcl v1.0.0 // indirect - github.com/hexops/gotextdiff v1.0.3 // indirect - github.com/huandu/go-clone v1.7.3 // indirect - github.com/huandu/xstrings v1.5.0 // indirect - github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/jackc/pgpassfile v1.0.0 // indirect - github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect - github.com/jackc/puddle/v2 v2.2.2 // indirect - github.com/jgautheron/goconst v1.8.2 // indirect - github.com/jingyugao/rowserrcheck v1.1.1 // indirect - github.com/jjti/go-spancheck v0.6.5 // indirect - github.com/julz/importas v0.2.0 // indirect - github.com/karamaru-alpha/copyloopvar v1.2.2 // indirect - github.com/kisielk/errcheck v1.10.0 // indirect - github.com/kkHAIKE/contextcheck v1.1.6 // indirect - github.com/klauspost/cpuid/v2 v2.2.10 // indirect - github.com/kulti/thelper v0.7.1 // indirect - github.com/kunwardeep/paralleltest v1.0.15 // indirect - github.com/lasiar/canonicalheader v1.1.2 // indirect - github.com/ldez/exptostd v0.4.5 // indirect - github.com/ldez/gomoddirectives v0.8.0 // indirect - github.com/ldez/grignotin v0.10.1 // indirect - github.com/ldez/structtags v0.6.1 // indirect - github.com/ldez/tagliatelle v0.7.2 // indirect - github.com/ldez/usetesting v0.5.0 // indirect - github.com/leonklingele/grouper v1.1.2 // indirect - github.com/lucasb-eyer/go-colorful v1.2.0 // indirect - github.com/macabu/inamedparam v0.2.0 // indirect - github.com/magiconair/properties v1.8.6 // indirect - github.com/manuelarte/embeddedstructfieldcheck v0.4.0 // indirect - github.com/manuelarte/funcorder v0.5.0 // indirect - github.com/maratori/testableexamples v1.0.1 // indirect - github.com/maratori/testpackage v1.1.2 // indirect - github.com/matoous/godox v1.1.0 // indirect - github.com/mattn/go-colorable v0.1.14 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mattn/go-runewidth v0.0.16 // indirect - github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect - github.com/mgechev/revive v1.15.0 // indirect - github.com/mitchellh/copystructure v1.2.0 // indirect - github.com/mitchellh/go-homedir v1.1.0 // indirect - github.com/mitchellh/mapstructure v1.5.0 // indirect - github.com/mitchellh/reflectwalk v1.0.2 // indirect - github.com/moricho/tparallel v0.3.2 // indirect - github.com/muesli/termenv v0.16.0 // indirect - github.com/nakabonne/nestif v0.3.1 // indirect - github.com/nishanths/exhaustive v0.12.0 // indirect - github.com/nishanths/predeclared v0.2.2 // indirect - github.com/nunnatsa/ginkgolinter v0.23.0 // indirect - github.com/pelletier/go-toml v1.9.5 // indirect - github.com/pelletier/go-toml/v2 v2.2.4 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/prometheus/client_golang v1.12.1 // indirect - github.com/prometheus/client_model v0.2.0 // indirect - github.com/prometheus/common v0.32.1 // indirect - github.com/prometheus/procfs v0.7.3 // indirect - github.com/quasilyte/go-ruleguard v0.4.5 // indirect - github.com/quasilyte/go-ruleguard/dsl v0.3.23 // indirect - github.com/quasilyte/gogrep v0.5.0 // indirect - github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 // indirect - github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 // indirect - github.com/raeperd/recvcheck v0.2.0 // indirect - github.com/rivo/uniseg v0.4.7 // indirect - github.com/rogpeppe/go-internal v1.14.1 // indirect - github.com/ryancurrah/gomodguard v1.4.1 // indirect - github.com/ryanrolds/sqlclosecheck v0.6.0 // indirect - github.com/sanposhiho/wastedassign/v2 v2.1.0 // indirect - github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 // indirect - github.com/sashamelentyev/interfacebloat v1.1.0 // indirect - github.com/sashamelentyev/usestdlibvars v1.29.0 // indirect - github.com/securego/gosec/v2 v2.24.8-0.20260309165252-619ce2117e08 // indirect - github.com/shopspring/decimal v1.4.0 // indirect - github.com/sirupsen/logrus v1.9.4 // indirect - github.com/sivchari/containedctx v1.0.3 // indirect - github.com/sonatard/noctx v0.5.1 // indirect - github.com/sourcegraph/go-diff v0.7.0 // indirect - github.com/spf13/afero v1.15.0 // indirect - github.com/spf13/cast v1.7.0 // indirect - github.com/spf13/jwalterweatherman v1.1.0 // indirect - github.com/spf13/viper v1.12.0 // indirect - github.com/ssgreg/nlreturn/v2 v2.2.1 // indirect - github.com/stbenjam/no-sprintf-host-port v0.3.1 // indirect - github.com/stretchr/objx v0.5.2 // indirect - github.com/stretchr/testify v1.11.1 // indirect - github.com/subosito/gotenv v1.4.1 // indirect - github.com/tetafro/godot v1.5.4 // indirect - github.com/timakin/bodyclose v0.0.0-20241222091800-1db5c5ca4d67 // indirect - github.com/timonwong/loggercheck v0.11.0 // indirect - github.com/tomarrell/wrapcheck/v2 v2.12.0 // indirect - github.com/tommy-muehle/go-mnd/v2 v2.5.1 // indirect - github.com/ultraware/funlen v0.2.0 // indirect - github.com/ultraware/whitespace v0.2.0 // indirect - github.com/uudashr/gocognit v1.2.1 // indirect - github.com/uudashr/iface v1.4.1 // indirect - github.com/xen0n/gosmopolitan v1.3.0 // indirect - github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect - github.com/yagipy/maintidx v1.0.0 // indirect - github.com/yeya24/promlinter v0.3.0 // indirect - github.com/ykadowak/zerologlint v0.1.5 // indirect - github.com/zeebo/xxh3 v1.0.2 // indirect - gitlab.com/bosi/decorder v0.4.2 // indirect - go-simpler.org/musttag v0.14.0 // indirect - go-simpler.org/sloglint v0.11.1 // indirect - go.augendre.info/arangolint v0.4.0 // indirect - go.augendre.info/fatcontext v0.9.0 // indirect - go.uber.org/multierr v1.10.0 // indirect - go.uber.org/zap v1.27.0 // indirect - go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/exp v0.0.0-20250911091902-df9299821621 // indirect - golang.org/x/exp/typeparams v0.0.0-20260209203927-2842357ff358 // indirect - golang.org/x/mod v0.37.0 // indirect - golang.org/x/sync v0.21.0 // indirect - golang.org/x/sys v0.46.0 // indirect - golang.org/x/telemetry v0.0.0-20260625142307-59b4966ccb57 // indirect - golang.org/x/text v0.39.0 // indirect - golang.org/x/tools v0.47.0 // indirect - golang.org/x/vuln v1.1.4 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20250922171735-9219d122eba9 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250922171735-9219d122eba9 // indirect - gopkg.in/ini.v1 v1.67.0 // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect - honnef.co/go/tools v0.7.0 // indirect - mvdan.cc/gofumpt v0.9.2 // indirect - mvdan.cc/unparam v0.0.0-20251027182757-5beb8c8f8f15 // indirect -) - -tool ( - connectrpc.com/connect/cmd/protoc-gen-connect-go - github.com/golangci/golangci-lint/v2/cmd/golangci-lint - golang.org/x/vuln/cmd/govulncheck - google.golang.org/protobuf/cmd/protoc-gen-go -) +require golang.org/x/sys v0.42.0 // indirect diff --git a/go.sum b/go.sum index c33eaa2..fa78fcb 100644 --- a/go.sum +++ b/go.sum @@ -1,1095 +1,8 @@ -4d63.com/gocheckcompilerdirectives v1.3.0 h1:Ew5y5CtcAAQeTVKUVFrE7EwHMrTO6BggtEj8BZSjZ3A= -4d63.com/gocheckcompilerdirectives v1.3.0/go.mod h1:ofsJ4zx2QAuIP/NO/NAh1ig6R1Fb18/GI7RVMwz7kAY= -4d63.com/gochecknoglobals v0.2.2 h1:H1vdnwnMaZdQW/N+NrkT1SZMTBmcwHe9Vq8lJcYYTtU= -4d63.com/gochecknoglobals v0.2.2/go.mod h1:lLxwTQjL5eIesRbvnzIP3jZtG140FnTdz+AlMa+ogt0= -buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.11-20260209202127-80ab13bee0bf.1 h1:PMmTMyvHScV9Mn8wc6ASge9uRcHy0jtqPd+fM35LmsQ= -buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.11-20260209202127-80ab13bee0bf.1/go.mod h1:tvtbpgaVXZX4g6Pn+AnzFycuRK3MOz5HJfEGeEllXYM= -buf.build/go/protovalidate v1.1.3 h1:m2GVEgQWd7rk+vIoAZ+f0ygGjvQTuqPQapBBdcpWVPE= -buf.build/go/protovalidate v1.1.3/go.mod h1:9XIuohWz+kj+9JVn3WQneHA5LZP50mjvneZMnbLkiIE= -cel.dev/expr v0.25.1 h1:1KrZg61W6TWSxuNZ37Xy49ps13NUovb66QLprthtwi4= -cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= -cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= -cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= -cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= -cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= -cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= -cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= -cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= -cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= -cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= -cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= -cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= -cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= -cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= -cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= -cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= -cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= -cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= -cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= -cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= -cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= -cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= -cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -codeberg.org/chavacava/garif v0.2.0 h1:F0tVjhYbuOCnvNcU3YSpO6b3Waw6Bimy4K0mM8y6MfY= -codeberg.org/chavacava/garif v0.2.0/go.mod h1:P2BPbVbT4QcvLZrORc2T29szK3xEOlnl0GiPTJmEqBQ= -codeberg.org/polyfloyd/go-errorlint v1.9.0 h1:VkdEEmA1VBpH6ecQoMR4LdphVI3fA4RrCh2an7YmodI= -codeberg.org/polyfloyd/go-errorlint v1.9.0/go.mod h1:GPRRu2LzVijNn4YkrZYJfatQIdS+TrcK8rL5Xs24qw8= -connectrpc.com/connect v1.19.1 h1:R5M57z05+90EfEvCY1b7hBxDVOUl45PrtXtAV2fOC14= -connectrpc.com/connect v1.19.1/go.mod h1:tN20fjdGlewnSFeZxLKb0xwIZ6ozc3OQs2hTXy4du9w= -connectrpc.com/validate v0.6.0 h1:DcrgDKt2ZScrUs/d/mh9itD2yeEa0UbBBa+i0mwzx+4= -connectrpc.com/validate v0.6.0/go.mod h1:ihrpI+8gVbLH1fvVWJL1I3j0CfWnF8P/90LsmluRiZs= -dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= -dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= -dev.gaijin.team/go/exhaustruct/v4 v4.0.0 h1:873r7aNneqoBB3IaFIzhvt2RFYTuHgmMjoKfwODoI1Y= -dev.gaijin.team/go/exhaustruct/v4 v4.0.0/go.mod h1:aZ/k2o4Y05aMJtiux15x8iXaumE88YdiB0Ai4fXOzPI= -dev.gaijin.team/go/golib v0.6.0 h1:v6nnznFTs4bppib/NyU1PQxobwDHwCXXl15P7DV5Zgo= -dev.gaijin.team/go/golib v0.6.0/go.mod h1:uY1mShx8Z/aNHWDyAkZTkX+uCi5PdX7KsG1eDQa2AVE= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/4meepo/tagalign v1.4.3 h1:Bnu7jGWwbfpAie2vyl63Zup5KuRv21olsPIha53BJr8= -github.com/4meepo/tagalign v1.4.3/go.mod h1:00WwRjiuSbrRJnSVeGWPLp2epS5Q/l4UEy0apLLS37c= -github.com/Abirdcfly/dupword v0.1.7 h1:2j8sInznrje4I0CMisSL6ipEBkeJUJAmK1/lfoNGWrQ= -github.com/Abirdcfly/dupword v0.1.7/go.mod h1:K0DkBeOebJ4VyOICFdppB23Q0YMOgVafM0zYW0n9lF4= -github.com/AdminBenni/iota-mixing v1.0.0 h1:Os6lpjG2dp/AE5fYBPAA1zfa2qMdCAWwPMCgpwKq7wo= -github.com/AdminBenni/iota-mixing v1.0.0/go.mod h1:i4+tpAaB+qMVIV9OK3m4/DAynOd5bQFaOu+2AhtBCNY= -github.com/AlwxSin/noinlineerr v1.0.5 h1:RUjt63wk1AYWTXtVXbSqemlbVTb23JOSRiNsshj7TbY= -github.com/AlwxSin/noinlineerr v1.0.5/go.mod h1:+QgkkoYrMH7RHvcdxdlI7vYYEdgeoFOVjU9sUhw/rQc= -github.com/Antonboom/errname v1.1.1 h1:bllB7mlIbTVzO9jmSWVWLjxTEbGBVQ1Ff/ClQgtPw9Q= -github.com/Antonboom/errname v1.1.1/go.mod h1:gjhe24xoxXp0ScLtHzjiXp0Exi1RFLKJb0bVBtWKCWQ= -github.com/Antonboom/nilnil v1.1.1 h1:9Mdr6BYd8WHCDngQnNVV0b554xyisFioEKi30sksufQ= -github.com/Antonboom/nilnil v1.1.1/go.mod h1:yCyAmSw3doopbOWhJlVci+HuyNRuHJKIv6V2oYQa8II= -github.com/Antonboom/testifylint v1.6.4 h1:gs9fUEy+egzxkEbq9P4cpcMB6/G0DYdMeiFS87UiqmQ= -github.com/Antonboom/testifylint v1.6.4/go.mod h1:YO33FROXX2OoUfwjz8g+gUxQXio5i9qpVy7nXGbxDD4= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk= -github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/Djarvur/go-err113 v0.1.1 h1:eHfopDqXRwAi+YmCUas75ZE0+hoBHJ2GQNLYRSxao4g= -github.com/Djarvur/go-err113 v0.1.1/go.mod h1:IaWJdYFLg76t2ihfflPZnM1LIQszWOsFDh2hhhAVF6k= -github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= -github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= -github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= -github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= -github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs= -github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0= -github.com/MirrexOne/unqueryvet v1.5.4 h1:38QOxShO7JmMWT+eCdDMbcUgGCOeJphVkzzRgyLJgsQ= -github.com/MirrexOne/unqueryvet v1.5.4/go.mod h1:fs9Zq6eh1LRIhsDIsxf9PONVUjYdFHdtkHIgZdJnyPU= -github.com/OpenPeeDeeP/depguard/v2 v2.2.1 h1:vckeWVESWp6Qog7UZSARNqfu/cZqvki8zsuj3piCMx4= -github.com/OpenPeeDeeP/depguard/v2 v2.2.1/go.mod h1:q4DKzC4UcVaAvcfd41CZh0PWpGgzrVxUYBlgKNGquUo= -github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= -github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= -github.com/alecthomas/chroma/v2 v2.23.1 h1:nv2AVZdTyClGbVQkIzlDm/rnhk1E9bU9nXwmZ/Vk/iY= -github.com/alecthomas/chroma/v2 v2.23.1/go.mod h1:NqVhfBR0lte5Ouh3DcthuUCTUpDC9cxBOfyMbMQPs3o= -github.com/alecthomas/go-check-sumtype v0.3.1 h1:u9aUvbGINJxLVXiFvHUlPEaD7VDULsrxJb4Aq31NLkU= -github.com/alecthomas/go-check-sumtype v0.3.1/go.mod h1:A8TSiN3UPRw3laIgWEUOHHLPa6/r9MtoigdlP5h3K/E= -github.com/alecthomas/repr v0.5.2 h1:SU73FTI9D1P5UNtvseffFSGmdNci/O6RsqzeXJtP0Qs= -github.com/alecthomas/repr v0.5.2/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= -github.com/alexkohler/nakedret/v2 v2.0.6 h1:ME3Qef1/KIKr3kWX3nti3hhgNxw6aqN5pZmQiFSsuzQ= -github.com/alexkohler/nakedret/v2 v2.0.6/go.mod h1:l3RKju/IzOMQHmsEvXwkqMDzHHvurNQfAgE1eVmT40Q= -github.com/alexkohler/prealloc v1.1.0 h1:cKGRBqlXw5iyQGLYhrXrDlcHxugXpTq4tQ5c91wkf8M= -github.com/alexkohler/prealloc v1.1.0/go.mod h1:fT39Jge3bQrfA7nPMDngUfvUbQGQeJyGQnR+913SCig= -github.com/alfatraining/structtag v1.0.0 h1:2qmcUqNcCoyVJ0up879K614L9PazjBSFruTB0GOFjCc= -github.com/alfatraining/structtag v1.0.0/go.mod h1:p3Xi5SwzTi+Ryj64DqjLWz7XurHxbGsq6y3ubePJPus= -github.com/alingse/asasalint v0.0.11 h1:SFwnQXJ49Kx/1GghOFz1XGqHYKp21Kq1nHad/0WQRnw= -github.com/alingse/asasalint v0.0.11/go.mod h1:nCaoMhw7a9kSJObvQyVzNTPBDbNpdocqrSP7t/cW5+I= -github.com/alingse/nilnesserr v0.2.0 h1:raLem5KG7EFVb4UIDAXgrv3N2JIaffeKNtcEXkEWd/w= -github.com/alingse/nilnesserr v0.2.0/go.mod h1:1xJPrXonEtX7wyTq8Dytns5P2hNzoWymVUIaKm4HNFg= -github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= -github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= -github.com/ashanbrown/forbidigo/v2 v2.3.0 h1:OZZDOchCgsX5gvToVtEBoV2UWbFfI6RKQTir2UZzSxo= -github.com/ashanbrown/forbidigo/v2 v2.3.0/go.mod h1:5p6VmsG5/1xx3E785W9fouMxIOkvY2rRV9nMdWadd6c= -github.com/ashanbrown/makezero/v2 v2.1.0 h1:snuKYMbqosNokUKm+R6/+vOPs8yVAi46La7Ck6QYSaE= -github.com/ashanbrown/makezero/v2 v2.1.0/go.mod h1:aEGT/9q3S8DHeE57C88z2a6xydvgx8J5hgXIGWgo0MY= -github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= -github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= -github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= -github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/bkielbasa/cyclop v1.2.3 h1:faIVMIGDIANuGPWH031CZJTi2ymOQBULs9H21HSMa5w= -github.com/bkielbasa/cyclop v1.2.3/go.mod h1:kHTwA9Q0uZqOADdupvcFJQtp/ksSnytRMe8ztxG8Fuo= -github.com/blizzy78/varnamelen v0.8.0 h1:oqSblyuQvFsW1hbBHh1zfwrKe3kcSj0rnXkKzsQ089M= -github.com/blizzy78/varnamelen v0.8.0/go.mod h1:V9TzQZ4fLJ1DSrjVDfl89H7aMnTvKkApdHeyESmyR7k= -github.com/bombsimon/wsl/v4 v4.7.0 h1:1Ilm9JBPRczjyUs6hvOPKvd7VL1Q++PL8M0SXBDf+jQ= -github.com/bombsimon/wsl/v4 v4.7.0/go.mod h1:uV/+6BkffuzSAVYD+yGyld1AChO7/EuLrCF/8xTiapg= -github.com/bombsimon/wsl/v5 v5.6.0 h1:4z+/sBqC5vUmSp1O0mS+czxwH9+LKXtCWtHH9rZGQL8= -github.com/bombsimon/wsl/v5 v5.6.0/go.mod h1:Uqt2EfrMj2NV8UGoN1f1Y3m0NpUVCsUdrNCdet+8LvU= -github.com/breml/bidichk v0.3.3 h1:WSM67ztRusf1sMoqH6/c4OBCUlRVTKq+CbSeo0R17sE= -github.com/breml/bidichk v0.3.3/go.mod h1:ISbsut8OnjB367j5NseXEGGgO/th206dVa427kR8YTE= -github.com/breml/errchkjson v0.4.1 h1:keFSS8D7A2T0haP9kzZTi7o26r7kE3vymjZNeNDRDwg= -github.com/breml/errchkjson v0.4.1/go.mod h1:a23OvR6Qvcl7DG/Z4o0el6BRAjKnaReoPQFciAl9U3s= -github.com/brianvoe/gofakeit/v6 v6.28.0 h1:Xib46XXuQfmlLS2EXRuJpqcw8St6qSZz75OUo0tgAW4= -github.com/brianvoe/gofakeit/v6 v6.28.0/go.mod h1:Xj58BMSnFqcn/fAQeSK+/PLtC5kSb7FJIq4JyGa8vEs= -github.com/butuzov/ireturn v0.4.0 h1:+s76bF/PfeKEdbG8b54aCocxXmi0wvYdOVsWxVO7n8E= -github.com/butuzov/ireturn v0.4.0/go.mod h1:ghI0FrCmap8pDWZwfPisFD1vEc56VKH4NpQUxDHta70= -github.com/butuzov/mirror v1.3.0 h1:HdWCXzmwlQHdVhwvsfBb2Au0r3HyINry3bDWLYXiKoc= -github.com/butuzov/mirror v1.3.0/go.mod h1:AEij0Z8YMALaq4yQj9CPPVYOyJQyiexpQEQgihajRfI= -github.com/catenacyber/perfsprint v0.10.1 h1:u7Riei30bk46XsG8nknMhKLXG9BcXz3+3tl/WpKm0PQ= -github.com/catenacyber/perfsprint v0.10.1/go.mod h1:DJTGsi/Zufpuus6XPGJyKOTMELe347o6akPvWG9Zcsc= -github.com/ccojocar/zxcvbn-go v1.0.4 h1:FWnCIRMXPj43ukfX000kvBZvV6raSxakYr1nzyNrUcc= -github.com/ccojocar/zxcvbn-go v1.0.4/go.mod h1:3GxGX+rHmueTUMvm5ium7irpyjmm7ikxYFOSJB21Das= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= -github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/charithe/durationcheck v0.0.11 h1:g1/EX1eIiKS57NTWsYtHDZ/APfeXKhye1DidBcABctk= -github.com/charithe/durationcheck v0.0.11/go.mod h1:x5iZaixRNl8ctbM+3B2RrPG5t856TxRyVQEnbIEM2X4= -github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs= -github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk= -github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY= -github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30= -github.com/charmbracelet/x/ansi v0.10.1 h1:rL3Koar5XvX0pHGfovN03f5cxLbCF2YvLeyz7D2jVDQ= -github.com/charmbracelet/x/ansi v0.10.1/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE= -github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd h1:vy0GVL4jeHEwG5YOXDmi86oYw2yuYUGqz6a8sLwg0X8= -github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs= -github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ= -github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/ckaznocha/intrange v0.3.1 h1:j1onQyXvHUsPWujDH6WIjhyH26gkRt/txNlV7LspvJs= -github.com/ckaznocha/intrange v0.3.1/go.mod h1:QVepyz1AkUoFQkpEqksSYpNpUo3c5W7nWh/s6SHIJJk= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/coreos/go-systemd/v22 v22.7.0 h1:LAEzFkke61DFROc7zNLX/WA2i5J8gYqe0rSj9KI28KA= -github.com/coreos/go-systemd/v22 v22.7.0/go.mod h1:xNUYtjHu2EDXbsxz1i41wouACIwT7Ybq9o0BQhMwD0w= -github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= -github.com/curioswitch/go-reassign v0.3.0 h1:dh3kpQHuADL3cobV/sSGETA8DOv457dwl+fbBAhrQPs= -github.com/curioswitch/go-reassign v0.3.0/go.mod h1:nApPCCTtqLJN/s8HfItCcKV0jIPwluBOvZP+dsJGA88= -github.com/daixiang0/gci v0.13.7 h1:+0bG5eK9vlI08J+J/NWGbWPTNiXPG4WhNLJOkSxWITQ= -github.com/daixiang0/gci v0.13.7/go.mod h1:812WVN6JLFY9S6Tv76twqmNqevN0pa3SX3nih0brVzQ= -github.com/dave/dst v0.27.3 h1:P1HPoMza3cMEquVf9kKy8yXsFirry4zEnWOdYPOoIzY= -github.com/dave/dst v0.27.3/go.mod h1:jHh6EOibnHgcUW3WjKHisiooEkYwqpHLBSX1iOBhEyc= -github.com/dave/jennifer v1.7.1 h1:B4jJJDHelWcDhlRQxWeo0Npa/pYKBLrirAQoTN45txo= -github.com/dave/jennifer v1.7.1/go.mod h1:nXbxhEmQfOZhWml3D1cDK5M1FLnMSozpbFN/m3RmGZc= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/denis-tingaikin/go-header v0.5.0 h1:SRdnP5ZKvcO9KKRP1KJrhFR3RrlGuD+42t4429eC9k8= -github.com/denis-tingaikin/go-header v0.5.0/go.mod h1:mMenU5bWrok6Wl2UsZjy+1okegmwQ3UgWl4V1D8gjlY= -github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ= -github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= -github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/ettle/strcase v0.2.0 h1:fGNiVF21fHXpX1niBgk0aROov1LagYsOwV/xqKDKR/Q= -github.com/ettle/strcase v0.2.0/go.mod h1:DajmHElDSaX76ITe3/VHVyMin4LWSJN5Z909Wp+ED1A= -github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w= -github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE= -github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4= -github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= -github.com/firefart/nonamedreturns v1.0.6 h1:vmiBcKV/3EqKY3ZiPxCINmpS431OcE1S47AQUwhrg8E= -github.com/firefart/nonamedreturns v1.0.6/go.mod h1:R8NisJnSIpvPWheCq0mNRXJok6D8h7fagJTF8EMEwCo= -github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= -github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= -github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= -github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= -github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo= -github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA= -github.com/ghostiam/protogetter v0.3.20 h1:oW7OPFit2FxZOpmMRPP9FffU4uUpfeE/rEdE1f+MzD0= -github.com/ghostiam/protogetter v0.3.20/go.mod h1:FjIu5Yfs6FT391m+Fjp3fbAYJ6rkL/J6ySpZBfnODuI= -github.com/github/go-spdx/v2 v2.4.0 h1:+4IwVwJJbm3rzvrQ6P1nI9BDMcy3la4RchRy5uehV/M= -github.com/github/go-spdx/v2 v2.4.0/go.mod h1:/5rwgS0txhGtRdUZwc02bTglzg6HK3FfuEbECKlK2Sg= -github.com/go-chi/chi/v5 v5.2.5 h1:Eg4myHZBjyvJmAFjFvWgrqDTXFyOzjj7YIm3L3mu6Ug= -github.com/go-chi/chi/v5 v5.2.5/go.mod h1:X7Gx4mteadT3eDOMTsXzmI4/rwUpOwBHLpAfupzFJP0= -github.com/go-chi/httprate v0.15.0 h1:j54xcWV9KGmPf/X4H32/aTH+wBlrvxL7P+SdnRqxh5g= -github.com/go-chi/httprate v0.15.0/go.mod h1:rzGHhVrsBn3IMLYDOZQsSU4fJNWcjui4fWKJcCId1R4= -github.com/go-critic/go-critic v0.14.3 h1:5R1qH2iFeo4I/RJU8vTezdqs08Egi4u5p6vOESA0pog= -github.com/go-critic/go-critic v0.14.3/go.mod h1:xwntfW6SYAd7h1OqDzmN6hBX/JxsEKl5up/Y2bsxgVQ= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= -github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= -github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= -github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= -github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI= -github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= -github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= -github.com/go-toolsmith/astcast v1.1.0 h1:+JN9xZV1A+Re+95pgnMgDboWNVnIMMQXwfBwLRPgSC8= -github.com/go-toolsmith/astcast v1.1.0/go.mod h1:qdcuFWeGGS2xX5bLM/c3U9lewg7+Zu4mr+xPwZIB4ZU= -github.com/go-toolsmith/astcopy v1.1.0 h1:YGwBN0WM+ekI/6SS6+52zLDEf8Yvp3n2seZITCUBt5s= -github.com/go-toolsmith/astcopy v1.1.0/go.mod h1:hXM6gan18VA1T/daUEHCFcYiW8Ai1tIwIzHY6srfEAw= -github.com/go-toolsmith/astequal v1.0.3/go.mod h1:9Ai4UglvtR+4up+bAD4+hCj7iTo4m/OXVTSLnCyTAx4= -github.com/go-toolsmith/astequal v1.1.0/go.mod h1:sedf7VIdCL22LD8qIvv7Nn9MuWJruQA/ysswh64lffQ= -github.com/go-toolsmith/astequal v1.2.0 h1:3Fs3CYZ1k9Vo4FzFhwwewC3CHISHDnVUPC4x0bI2+Cw= -github.com/go-toolsmith/astequal v1.2.0/go.mod h1:c8NZ3+kSFtFY/8lPso4v8LuJjdJiUFVnSuU3s0qrrDY= -github.com/go-toolsmith/astfmt v1.1.0 h1:iJVPDPp6/7AaeLJEruMsBUlOYCmvg0MoCfJprsOmcco= -github.com/go-toolsmith/astfmt v1.1.0/go.mod h1:OrcLlRwu0CuiIBp/8b5PYF9ktGVZUjlNMV634mhwuQ4= -github.com/go-toolsmith/astp v1.1.0 h1:dXPuCl6u2llURjdPLLDxJeZInAeZ0/eZwFJmqZMnpQA= -github.com/go-toolsmith/astp v1.1.0/go.mod h1:0T1xFGz9hicKs8Z5MfAqSUitoUYS30pDMsRVIDHs8CA= -github.com/go-toolsmith/pkgload v1.2.2 h1:0CtmHq/02QhxcF7E9N5LIFcYFsMR5rdovfqTtRKkgIk= -github.com/go-toolsmith/pkgload v1.2.2/go.mod h1:R2hxLNRKuAsiXCo2i5J6ZQPhnPMOVtU+f0arbFPWCus= -github.com/go-toolsmith/strparse v1.0.0/go.mod h1:YI2nUKP9YGZnL/L1/DLFBfixrcjslWct4wyljWhSRy8= -github.com/go-toolsmith/strparse v1.1.0 h1:GAioeZUK9TGxnLS+qfdqNbA4z0SSm5zVNtCQiyP2Bvw= -github.com/go-toolsmith/strparse v1.1.0/go.mod h1:7ksGy58fsaQkGQlY8WVoBFNyEPMGuJin1rfoPS4lBSQ= -github.com/go-toolsmith/typep v1.1.0 h1:fIRYDyF+JywLfqzyhdiHzRop/GQDxxNhLGQ6gFUNHus= -github.com/go-toolsmith/typep v1.1.0/go.mod h1:fVIw+7zjdsMxDA3ITWnH1yOiw1rnTQKCsF/sk2H/qig= -github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro= -github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= -github.com/go-xmlfmt/xmlfmt v1.1.3 h1:t8Ey3Uy7jDSEisW2K3somuMKIpzktkWptA0iFCnRUWY= -github.com/go-xmlfmt/xmlfmt v1.1.3/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM= -github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= -github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= -github.com/godoc-lint/godoc-lint v0.11.2 h1:Bp0FkJWoSdNsBikdNgIcgtaoo+xz6I/Y9s5WSBQUeeM= -github.com/godoc-lint/godoc-lint v0.11.2/go.mod h1:iVpGdL1JCikNH2gGeAn3Hh+AgN5Gx/I/cxV+91L41jo= -github.com/gofrs/flock v0.13.0 h1:95JolYOvGMqeH31+FC7D2+uULf6mG61mEZ/A8dRYMzw= -github.com/gofrs/flock v0.13.0/go.mod h1:jxeyy9R1auM5S6JYDBhDt+E2TCo7DkratH4Pgi8P+Z0= -github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= -github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golangci/asciicheck v0.5.0 h1:jczN/BorERZwK8oiFBOGvlGPknhvq0bjnysTj4nUfo0= -github.com/golangci/asciicheck v0.5.0/go.mod h1:5RMNAInbNFw2krqN6ibBxN/zfRFa9S6tA1nPdM0l8qQ= -github.com/golangci/dupl v0.0.0-20250308024227-f665c8d69b32 h1:WUvBfQL6EW/40l6OmeSBYQJNSif4O11+bmWEz+C7FYw= -github.com/golangci/dupl v0.0.0-20250308024227-f665c8d69b32/go.mod h1:NUw9Zr2Sy7+HxzdjIULge71wI6yEg1lWQr7Evcu8K0E= -github.com/golangci/go-printf-func-name v0.1.1 h1:hIYTFJqAGp1iwoIfsNTpoq1xZAarogrvjO9AfiW3B4U= -github.com/golangci/go-printf-func-name v0.1.1/go.mod h1:Es64MpWEZbh0UBtTAICOZiB+miW53w/K9Or/4QogJss= -github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d h1:viFft9sS/dxoYY0aiOTsLKO2aZQAPT4nlQCsimGcSGE= -github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d/go.mod h1:ivJ9QDg0XucIkmwhzCDsqcnxxlDStoTl89jDMIoNxKY= -github.com/golangci/golangci-lint/v2 v2.11.4 h1:GK+UlZBN5y7rh2PBnHA93XLSX6RaF7uhzJQ3JwU1wuA= -github.com/golangci/golangci-lint/v2 v2.11.4/go.mod h1:ODQDCASMA3VqfZYIbbQLpTRTzV7O/vjmIRF6u8NyFwI= -github.com/golangci/golines v0.15.0 h1:Qnph25g8Y1c5fdo1X7GaRDGgnMHgnxh4Gk4VfPTtRx0= -github.com/golangci/golines v0.15.0/go.mod h1:AZjXd23tbHMpowhtnGlj9KCNsysj72aeZVVHnVcZx10= -github.com/golangci/misspell v0.8.0 h1:qvxQhiE2/5z+BVRo1kwYA8yGz+lOlu5Jfvtx2b04Jbg= -github.com/golangci/misspell v0.8.0/go.mod h1:WZyyI2P3hxPY2UVHs3cS8YcllAeyfquQcKfdeE9AFVg= -github.com/golangci/plugin-module-register v0.1.2 h1:e5WM6PO6NIAEcij3B053CohVp3HIYbzSuP53UAYgOpg= -github.com/golangci/plugin-module-register v0.1.2/go.mod h1:1+QGTsKBvAIvPvoY/os+G5eoqxWn70HYDm2uvUyGuVw= -github.com/golangci/revgrep v0.8.0 h1:EZBctwbVd0aMeRnNUsFogoyayvKHyxlV3CdUA46FX2s= -github.com/golangci/revgrep v0.8.0/go.mod h1:U4R/s9dlXZsg8uJmaR1GrloUr14D7qDl8gi2iPXJH8k= -github.com/golangci/swaggoswag v0.0.0-20250504205917-77f2aca3143e h1:ai0EfmVYE2bRA5htgAG9r7s3tHsfjIhN98WshBTJ9jM= -github.com/golangci/swaggoswag v0.0.0-20250504205917-77f2aca3143e/go.mod h1:Vrn4B5oR9qRwM+f54koyeH3yzphlecwERs0el27Fr/s= -github.com/golangci/unconvert v0.0.0-20250410112200-a129a6e6413e h1:gD6P7NEo7Eqtt0ssnqSJNNndxe69DOQ24A5h7+i3KpM= -github.com/golangci/unconvert v0.0.0-20250410112200-a129a6e6413e/go.mod h1:h+wZwLjUTJnm/P2rwlbJdRPZXOzaT36/FwnPnY2inzc= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/cel-go v0.27.0 h1:e7ih85+4qVrBuqQWTW4FKSqZYokVuc3HnhH5keboFTo= -github.com/google/cel-go v0.27.0/go.mod h1:tTJ11FWqnhw5KKpnWpvW9CJC3Y9GK4EIS0WXnBbebzw= -github.com/google/go-cmdtest v0.4.1-0.20220921163831-55ab3332a786 h1:rcv+Ippz6RAtvaGgKxc+8FQIpxHgsF+HBzPyYL2cyVU= -github.com/google/go-cmdtest v0.4.1-0.20220921163831-55ab3332a786/go.mod h1:apVn/GCasLZUVpAJ6oWAuyP7Ne7CEsQbTnc0plM3m+o= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/licensecheck v0.3.1 h1:QoxgoDkaeC4nFrtGN1jV7IPmDCHFNIVh54e5hSt6sPs= -github.com/google/licensecheck v0.3.1/go.mod h1:ORkR35t/JjW+emNKtfJDII0zlciG9JgbT7SmsohlHmY= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83 h1:z2ogiKUYzX5Is6zr/vP9vJGqPwcdqsWjOt+V8J7+bTc= -github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI= -github.com/google/renameio v0.1.0 h1:GOZbcHa3HfsPKPlmyPyN2KEohoMXOhdMbHrvbpl2QaA= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/gordonklaus/ineffassign v0.2.0 h1:Uths4KnmwxNJNzq87fwQQDDnbNb7De00VOk9Nu0TySs= -github.com/gordonklaus/ineffassign v0.2.0/go.mod h1:TIpymnagPSexySzs7F9FnO1XFTy8IT3a59vmZp5Y9Lw= -github.com/gostaticanalysis/analysisutil v0.7.1 h1:ZMCjoue3DtDWQ5WyU16YbjbQEQ3VuzwxALrpYd+HeKk= -github.com/gostaticanalysis/analysisutil v0.7.1/go.mod h1:v21E3hY37WKMGSnbsw2S/ojApNWb6C1//mXO48CXbVc= -github.com/gostaticanalysis/comment v1.4.2/go.mod h1:KLUTGDv6HOCotCH8h2erHKmpci2ZoR8VPu34YA2uzdM= -github.com/gostaticanalysis/comment v1.5.0 h1:X82FLl+TswsUMpMh17srGRuKaaXprTaytmEpgnKIDu8= -github.com/gostaticanalysis/comment v1.5.0/go.mod h1:V6eb3gpCv9GNVqb6amXzEUX3jXLVK/AdA+IrAMSqvEc= -github.com/gostaticanalysis/forcetypeassert v0.2.0 h1:uSnWrrUEYDr86OCxWa4/Tp2jeYDlogZiZHzGkWFefTk= -github.com/gostaticanalysis/forcetypeassert v0.2.0/go.mod h1:M5iPavzE9pPqWyeiVXSFghQjljW1+l/Uke3PXHS6ILY= -github.com/gostaticanalysis/nilerr v0.1.2 h1:S6nk8a9N8g062nsx63kUkF6AzbHGw7zzyHMcpu52xQU= -github.com/gostaticanalysis/nilerr v0.1.2/go.mod h1:A19UHhoY3y8ahoL7YKz6sdjDtduwTSI4CsymaC2htPA= -github.com/gostaticanalysis/testutil v0.3.1-0.20210208050101-bfb5c8eec0e4/go.mod h1:D+FIZ+7OahH3ePw/izIEeH5I06eKs1IKI4Xr64/Am3M= -github.com/gostaticanalysis/testutil v0.5.0 h1:Dq4wT1DdTwTGCQQv3rl3IvD5Ld0E6HiY+3Zh0sUGqw8= -github.com/gostaticanalysis/testutil v0.5.0/go.mod h1:OLQSbuM6zw2EvCcXTz1lVq5unyoNft372msDY0nY5Hs= -github.com/hashicorp/go-immutable-radix/v2 v2.1.0 h1:CUW5RYIcysz+D3B+l1mDeXrQ7fUvGGCwJfdASSzbrfo= -github.com/hashicorp/go-immutable-radix/v2 v2.1.0/go.mod h1:hgdqLXA4f6NIjRVisM1TJ9aOJVNRqKZj+xDGF6m7PBw= -github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= -github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4= -github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= -github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= -github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= -github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= -github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= -github.com/huandu/go-assert v1.1.5/go.mod h1:yOLvuqZwmcHIC5rIzrBhT7D3Q9c3GFnd0JrPVhn/06U= -github.com/huandu/go-assert v1.1.6 h1:oaAfYxq9KNDi9qswn/6aE0EydfxSa+tWZC1KabNitYs= -github.com/huandu/go-assert v1.1.6/go.mod h1:JuIfbmYG9ykwvuxoJ3V8TB5QP+3+ajIA54Y44TmkMxs= -github.com/huandu/go-clone v1.7.3 h1:rtQODA+ABThEn6J5LBTppJfKmZy/FwfpMUWa8d01TTQ= -github.com/huandu/go-clone v1.7.3/go.mod h1:ReGivhG6op3GYr+UY3lS6mxjKp7MIGTknuU5TbTVaXE= -github.com/huandu/go-sqlbuilder v1.40.1 h1:Q2pNM8BAbaezO56ZzkbJNMGW/CTu+8+Qw7IxF4P3+7w= -github.com/huandu/go-sqlbuilder v1.40.1/go.mod h1:zdONH67liL+/TvoUMwnZP/sUYGSSvHh9psLe/HpXn8E= -github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI= -github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= -github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= -github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/jackc/pgerrcode v0.0.0-20250907135507-afb5586c32a6 h1:D/V0gu4zQ3cL2WKeVNVM4r2gLxGGf6McLwgXzRTo2RQ= -github.com/jackc/pgerrcode v0.0.0-20250907135507-afb5586c32a6/go.mod h1:a/s9Lp5W7n/DD0VrVoyJ00FbP2ytTPDVOivvn2bMlds= -github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= -github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= -github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= -github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgx/v5 v5.9.1 h1:uwrxJXBnx76nyISkhr33kQLlUqjv7et7b9FjCen/tdc= -github.com/jackc/pgx/v5 v5.9.1/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4= -github.com/jackc/pgx/v5 v5.9.2 h1:3ZhOzMWnR4yJ+RW1XImIPsD1aNSz4T4fyP7zlQb56hw= -github.com/jackc/pgx/v5 v5.9.2/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4= -github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= -github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= -github.com/jackc/tern/v2 v2.3.6 h1:sqBIZ/CBtfMLz7zdUof0N6cVUBRVGBZ7S+F2OdCp9XU= -github.com/jackc/tern/v2 v2.3.6/go.mod h1:SrtwsdBRKkeTOjuLd6ISNqaLOtaLX+jOTLrpP+lJQe0= -github.com/jgautheron/goconst v1.8.2 h1:y0XF7X8CikZ93fSNT6WBTb/NElBu9IjaY7CCYQrCMX4= -github.com/jgautheron/goconst v1.8.2/go.mod h1:A0oxgBCHy55NQn6sYpO7UdnA9p+h7cPtoOZUmvNIako= -github.com/jingyugao/rowserrcheck v1.1.1 h1:zibz55j/MJtLsjP1OF4bSdgXxwL1b+Vn7Tjzq7gFzUs= -github.com/jingyugao/rowserrcheck v1.1.1/go.mod h1:4yvlZSDb3IyDTUZJUmpZfm2Hwok+Dtp+nu2qOq+er9c= -github.com/jjti/go-spancheck v0.6.5 h1:lmi7pKxa37oKYIMScialXUK6hP3iY5F1gu+mLBPgYB8= -github.com/jjti/go-spancheck v0.6.5/go.mod h1:aEogkeatBrbYsyW6y5TgDfihCulDYciL1B7rG2vSsrU= -github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= -github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= -github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= -github.com/julz/importas v0.2.0 h1:y+MJN/UdL63QbFJHws9BVC5RpA2iq0kpjrFajTGivjQ= -github.com/julz/importas v0.2.0/go.mod h1:pThlt589EnCYtMnmhmRYY/qn9lCf/frPOK+WMx3xiJY= -github.com/karamaru-alpha/copyloopvar v1.2.2 h1:yfNQvP9YaGQR7VaWLYcfZUlRP2eo2vhExWKxD/fP6q0= -github.com/karamaru-alpha/copyloopvar v1.2.2/go.mod h1:oY4rGZqZ879JkJMtX3RRkcXRkmUvH0x35ykgaKgsgJY= -github.com/kisielk/errcheck v1.10.0 h1:Lvs/YAHP24YKg08LA8oDw2z9fJVme090RAXd90S+rrw= -github.com/kisielk/errcheck v1.10.0/go.mod h1:kQxWMMVZgIkDq7U8xtG/n2juOjbLgZtedi0D+/VL/i8= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/kkHAIKE/contextcheck v1.1.6 h1:7HIyRcnyzxL9Lz06NGhiKvenXq7Zw6Q0UQu/ttjfJCE= -github.com/kkHAIKE/contextcheck v1.1.6/go.mod h1:3dDbMRNBFaq8HFXWC1JyvDSPm43CmE6IuHam8Wr0rkg= -github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE= -github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/kulti/thelper v0.7.1 h1:fI8QITAoFVLx+y+vSyuLBP+rcVIB8jKooNSCT2EiI98= -github.com/kulti/thelper v0.7.1/go.mod h1:NsMjfQEy6sd+9Kfw8kCP61W1I0nerGSYSFnGaxQkcbs= -github.com/kunwardeep/paralleltest v1.0.15 h1:ZMk4Qt306tHIgKISHWFJAO1IDQJLc6uDyJMLyncOb6w= -github.com/kunwardeep/paralleltest v1.0.15/go.mod h1:di4moFqtfz3ToSKxhNjhOZL+696QtJGCFe132CbBLGk= -github.com/lasiar/canonicalheader v1.1.2 h1:vZ5uqwvDbyJCnMhmFYimgMZnJMjwljN5VGY0VKbMXb4= -github.com/lasiar/canonicalheader v1.1.2/go.mod h1:qJCeLFS0G/QlLQ506T+Fk/fWMa2VmBUiEI2cuMK4djI= -github.com/ldez/exptostd v0.4.5 h1:kv2ZGUVI6VwRfp/+bcQ6Nbx0ghFWcGIKInkG/oFn1aQ= -github.com/ldez/exptostd v0.4.5/go.mod h1:QRjHRMXJrCTIm9WxVNH6VW7oN7KrGSht69bIRwvdFsM= -github.com/ldez/gomoddirectives v0.8.0 h1:JqIuTtgvFC2RdH1s357vrE23WJF2cpDCPFgA/TWDGpk= -github.com/ldez/gomoddirectives v0.8.0/go.mod h1:jutzamvZR4XYJLr0d5Honycp4Gy6GEg2mS9+2YX3F1Q= -github.com/ldez/grignotin v0.10.1 h1:keYi9rYsgbvqAZGI1liek5c+jv9UUjbvdj3Tbn5fn4o= -github.com/ldez/grignotin v0.10.1/go.mod h1:UlDbXFCARrXbWGNGP3S5vsysNXAPhnSuBufpTEbwOas= -github.com/ldez/structtags v0.6.1 h1:bUooFLbXx41tW8SvkfwfFkkjPYvFFs59AAMgVg6DUBk= -github.com/ldez/structtags v0.6.1/go.mod h1:YDxVSgDy/MON6ariaxLF2X09bh19qL7MtGBN5MrvbdY= -github.com/ldez/tagliatelle v0.7.2 h1:KuOlL70/fu9paxuxbeqlicJnCspCRjH0x8FW+NfgYUk= -github.com/ldez/tagliatelle v0.7.2/go.mod h1:PtGgm163ZplJfZMZ2sf5nhUT170rSuPgBimoyYtdaSI= -github.com/ldez/usetesting v0.5.0 h1:3/QtzZObBKLy1F4F8jLuKJiKBjjVFi1IavpoWbmqLwc= -github.com/ldez/usetesting v0.5.0/go.mod h1:Spnb4Qppf8JTuRgblLrEWb7IE6rDmUpGvxY3iRrzvDQ= -github.com/leonklingele/grouper v1.1.2 h1:o1ARBDLOmmasUaNDesWqWCIFH3u7hoFlM84YrjT3mIY= -github.com/leonklingele/grouper v1.1.2/go.mod h1:6D0M/HVkhs2yRKRFZUoGjeDy7EZTfFBE9gl4kjmIGkA= -github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= -github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= -github.com/macabu/inamedparam v0.2.0 h1:VyPYpOc10nkhI2qeNUdh3Zket4fcZjEWe35poddBCpE= -github.com/macabu/inamedparam v0.2.0/go.mod h1:+Pee9/YfGe5LJ62pYXqB89lJ+0k5bsR8Wgz/C0Zlq3U= -github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= -github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= -github.com/manuelarte/embeddedstructfieldcheck v0.4.0 h1:3mAIyaGRtjK6EO9E73JlXLtiy7ha80b2ZVGyacxgfww= -github.com/manuelarte/embeddedstructfieldcheck v0.4.0/go.mod h1:z8dFSyXqp+fC6NLDSljRJeNQJJDWnY7RoWFzV3PC6UM= -github.com/manuelarte/funcorder v0.5.0 h1:llMuHXXbg7tD0i/LNw8vGnkDTHFpTnWqKPI85Rknc+8= -github.com/manuelarte/funcorder v0.5.0/go.mod h1:Yt3CiUQthSBMBxjShjdXMexmzpP8YGvGLjrxJNkO2hA= -github.com/maratori/testableexamples v1.0.1 h1:HfOQXs+XgfeRBJ+Wz0XfH+FHnoY9TVqL6Fcevpzy4q8= -github.com/maratori/testableexamples v1.0.1/go.mod h1:XE2F/nQs7B9N08JgyRmdGjYVGqxWwClLPCGSQhXQSrQ= -github.com/maratori/testpackage v1.1.2 h1:ffDSh+AgqluCLMXhM19f/cpvQAKygKAJXFl9aUjmbqs= -github.com/maratori/testpackage v1.1.2/go.mod h1:8F24GdVDFW5Ew43Et02jamrVMNXLUNaOynhDssITGfc= -github.com/matoous/godox v1.1.0 h1:W5mqwbyWrwZv6OQ5Z1a/DHGMOvXYCBP3+Ht7KMoJhq4= -github.com/matoous/godox v1.1.0/go.mod h1:jgE/3fUXiTurkdHOLT5WEkThTSuE7yxHv5iWPa80afs= -github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE= -github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= -github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= -github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= -github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/mgechev/revive v1.15.0 h1:vJ0HzSBzfNyPbHKolgiFjHxLek9KUijhqh42yGoqZ8Q= -github.com/mgechev/revive v1.15.0/go.mod h1:LlAKO3QQe9OJ0pVZzI2GPa8CbXGZ/9lNpCGvK4T/a8A= -github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= -github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= -github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= -github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= -github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= -github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/moricho/tparallel v0.3.2 h1:odr8aZVFA3NZrNybggMkYO3rgPRcqjeQUlBBFVxKHTI= -github.com/moricho/tparallel v0.3.2/go.mod h1:OQ+K3b4Ln3l2TZveGCywybl68glfLEwFGqvnjok8b+U= -github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= -github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= -github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/nakabonne/nestif v0.3.1 h1:wm28nZjhQY5HyYPx+weN3Q65k6ilSBxDb8v5S81B81U= -github.com/nakabonne/nestif v0.3.1/go.mod h1:9EtoZochLn5iUprVDmDjqGKPofoUEBL8U4Ngq6aY7OE= -github.com/nishanths/exhaustive v0.12.0 h1:vIY9sALmw6T/yxiASewa4TQcFsVYZQQRUQJhKRf3Swg= -github.com/nishanths/exhaustive v0.12.0/go.mod h1:mEZ95wPIZW+x8kC4TgC+9YCUgiST7ecevsVDTgc2obs= -github.com/nishanths/predeclared v0.2.2 h1:V2EPdZPliZymNAn79T8RkNApBjMmVKh5XRpLm/w98Vk= -github.com/nishanths/predeclared v0.2.2/go.mod h1:RROzoN6TnGQupbC+lqggsOlcgysk3LMK/HI84Mp280c= -github.com/nunnatsa/ginkgolinter v0.23.0 h1:x3o4DGYOWbBMP/VdNQKgSj+25aJKx2Pe6lHr8gBcgf8= -github.com/nunnatsa/ginkgolinter v0.23.0/go.mod h1:9qN1+0akwXEccwV1CAcCDfcoBlWXHB+ML9884pL4SZ4= -github.com/onsi/ginkgo/v2 v2.28.1 h1:S4hj+HbZp40fNKuLUQOYLDgZLwNUVn19N3Atb98NCyI= -github.com/onsi/ginkgo/v2 v2.28.1/go.mod h1:CLtbVInNckU3/+gC8LzkGUb9oF+e8W8TdUsxPwvdOgE= -github.com/onsi/gomega v1.39.1 h1:1IJLAad4zjPn2PsnhH70V4DKRFlrCzGBNrNaru+Vf28= -github.com/onsi/gomega v1.39.1/go.mod h1:hL6yVALoTOxeWudERyfppUcZXjMwIMLnuSfruD2lcfg= -github.com/otiai10/copy v1.2.0/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw= -github.com/otiai10/copy v1.14.0 h1:dCI/t1iTdYGtkvCuBG2BgR6KZa83PTclw4U5n2wAllU= -github.com/otiai10/copy v1.14.0/go.mod h1:ECfuL02W+/FkTWZWgQqXPWZgW9oeKCSQ5qVfSc4qc4w= -github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= -github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs= -github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo= -github.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= -github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= -github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= -github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= -github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= -github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= -github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= -github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= -github.com/prometheus/client_golang v1.12.1 h1:ZiaPsmm9uiBeaSMRznKsCDNtPCS0T3JVDGF+06gjBzk= -github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= -github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= -github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= -github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4= -github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= -github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= -github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= -github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/quasilyte/go-ruleguard v0.4.5 h1:AGY0tiOT5hJX9BTdx/xBdoCubQUAE2grkqY2lSwvZcA= -github.com/quasilyte/go-ruleguard v0.4.5/go.mod h1:Vl05zJ538vcEEwu16V/Hdu7IYZWyKSwIy4c88Ro1kRE= -github.com/quasilyte/go-ruleguard/dsl v0.3.23 h1:lxjt5B6ZCiBeeNO8/oQsegE6fLeCzuMRoVWSkXC4uvY= -github.com/quasilyte/go-ruleguard/dsl v0.3.23/go.mod h1:KeCP03KrjuSO0H1kTuZQCWlQPulDV6YMIXmpQss17rU= -github.com/quasilyte/gogrep v0.5.0 h1:eTKODPXbI8ffJMN+W2aE0+oL0z/nh8/5eNdiO34SOAo= -github.com/quasilyte/gogrep v0.5.0/go.mod h1:Cm9lpz9NZjEoL1tgZ2OgeUKPIxL1meE7eo60Z6Sk+Ng= -github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 h1:TCg2WBOl980XxGFEZSS6KlBGIV0diGdySzxATTWoqaU= -github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727/go.mod h1:rlzQ04UMyJXu/aOvhd8qT+hvDrFpiwqp8MRXDY9szc0= -github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 h1:M8mH9eK4OUR4lu7Gd+PU1fV2/qnDNfzT635KRSObncs= -github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567/go.mod h1:DWNGW8A4Y+GyBgPuaQJuWiy0XYftx4Xm/y5Jqk9I6VQ= -github.com/raeperd/recvcheck v0.2.0 h1:GnU+NsbiCqdC2XX5+vMZzP+jAJC5fht7rcVTAhX74UI= -github.com/raeperd/recvcheck v0.2.0/go.mod h1:n04eYkwIR0JbgD73wT8wL4JjPC3wm0nFtzBnWNocnYU= -github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= -github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -github.com/rodaine/protogofakeit v0.1.1 h1:ZKouljuRM3A+TArppfBqnH8tGZHOwM/pjvtXe9DaXH8= -github.com/rodaine/protogofakeit v0.1.1/go.mod h1:pXn/AstBYMaSfc1/RqH3N82pBuxtWgejz1AlYpY1mI0= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= -github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= -github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/ryancurrah/gomodguard v1.4.1 h1:eWC8eUMNZ/wM/PWuZBv7JxxqT5fiIKSIyTvjb7Elr+g= -github.com/ryancurrah/gomodguard v1.4.1/go.mod h1:qnMJwV1hX9m+YJseXEBhd2s90+1Xn6x9dLz11ualI1I= -github.com/ryanrolds/sqlclosecheck v0.6.0 h1:pEyL9okISdg1F1SEpJNlrEotkTGerv5BMk7U4AG0eVg= -github.com/ryanrolds/sqlclosecheck v0.6.0/go.mod h1:xyX16hsDaCMXHrMJ3JMzGf5OpDfHTOTTQrT7HOFUmeU= -github.com/sanposhiho/wastedassign/v2 v2.1.0 h1:crurBF7fJKIORrV85u9UUpePDYGWnwvv3+A96WvwXT0= -github.com/sanposhiho/wastedassign/v2 v2.1.0/go.mod h1:+oSmSC+9bQ+VUAxA66nBb0Z7N8CK7mscKTDYC6aIek4= -github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ= -github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU= -github.com/sashamelentyev/interfacebloat v1.1.0 h1:xdRdJp0irL086OyW1H/RTZTr1h/tMEOsumirXcOJqAw= -github.com/sashamelentyev/interfacebloat v1.1.0/go.mod h1:+Y9yU5YdTkrNvoX0xHc84dxiN1iBi9+G8zZIhPVoNjQ= -github.com/sashamelentyev/usestdlibvars v1.29.0 h1:8J0MoRrw4/NAXtjQqTHrbW9NN+3iMf7Knkq057v4XOQ= -github.com/sashamelentyev/usestdlibvars v1.29.0/go.mod h1:8PpnjHMk5VdeWlVb4wCdrB8PNbLqZ3wBZTZWkrpZZL8= -github.com/securego/gosec/v2 v2.24.8-0.20260309165252-619ce2117e08 h1:AoLtJX4WUtZkhhUUMFy3GgecAALp/Mb4S1iyQOA2s0U= -github.com/securego/gosec/v2 v2.24.8-0.20260309165252-619ce2117e08/go.mod h1:+XLCJiRE95ga77XInNELh2M6zQP+PdqiT9Zpm0D9Wpk= -github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= -github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= -github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= -github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= -github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= -github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= -github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= -github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w= -github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= -github.com/sivchari/containedctx v1.0.3 h1:x+etemjbsh2fB5ewm5FeLNi5bUjK0V8n0RB+Wwfd0XE= -github.com/sivchari/containedctx v1.0.3/go.mod h1:c1RDvCbnJLtH4lLcYD/GqwiBSSf4F5Qk0xld2rBqzJ4= -github.com/sonatard/noctx v0.5.1 h1:wklWg9c9ZYugOAk7qG4yP4PBrlQsmSLPTvW1K4PRQMs= -github.com/sonatard/noctx v0.5.1/go.mod h1:64XdbzFb18XL4LporKXp8poqZtPKbCrqQ402CV+kJas= -github.com/sourcegraph/go-diff v0.7.0 h1:9uLlrd5T46OXs5qpp8L/MTltk0zikUGi0sNNyCpA8G0= -github.com/sourcegraph/go-diff v0.7.0/go.mod h1:iBszgVvyxdc8SFZ7gm69go2KDdt3ag071iBaWPF6cjs= -github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I= -github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg= -github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w= -github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= -github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= -github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= -github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= -github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= -github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.12.0 h1:CZ7eSOd3kZoaYDLbXnmzgQI5RlciuXBMA+18HwHRfZQ= -github.com/spf13/viper v1.12.0/go.mod h1:b6COn30jlNxbm/V2IqWiNWkJ+vZNiMNksliPCiuKtSI= -github.com/ssgreg/nlreturn/v2 v2.2.1 h1:X4XDI7jstt3ySqGU86YGAURbxw3oTDPK9sPEi6YEwQ0= -github.com/ssgreg/nlreturn/v2 v2.2.1/go.mod h1:E/iiPB78hV7Szg2YfRgyIrk1AD6JVMTRkkxBiELzh2I= -github.com/stbenjam/no-sprintf-host-port v0.3.1 h1:AyX7+dxI4IdLBPtDbsGAyqiTSLpCP9hWRrXQDU4Cm/g= -github.com/stbenjam/no-sprintf-host-port v0.3.1/go.mod h1:ODbZesTCHMVKthBHskvUUexdcNHAQRXk9NpSsL8p/HQ= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= -github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= -github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= -github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs= -github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= -github.com/tenntenn/modver v1.0.1 h1:2klLppGhDgzJrScMpkj9Ujy3rXPUspSjAcev9tSEBgA= -github.com/tenntenn/modver v1.0.1/go.mod h1:bePIyQPb7UeioSRkw3Q0XeMhYZSMx9B8ePqg6SAMGH0= -github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3 h1:f+jULpRQGxTSkNYKJ51yaw6ChIqO+Je8UqsTKN/cDag= -github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3/go.mod h1:ON8b8w4BN/kE1EOhwT0o+d62W65a6aPw1nouo9LMgyY= -github.com/tetafro/godot v1.5.4 h1:u1ww+gqpRLiIA16yF2PV1CV1n/X3zhyezbNXC3E14Sg= -github.com/tetafro/godot v1.5.4/go.mod h1:eOkMrVQurDui411nBY2FA05EYH01r14LuWY/NrVDVcU= -github.com/timakin/bodyclose v0.0.0-20241222091800-1db5c5ca4d67 h1:9LPGD+jzxMlnk5r6+hJnar67cgpDIz/iyD+rfl5r2Vk= -github.com/timakin/bodyclose v0.0.0-20241222091800-1db5c5ca4d67/go.mod h1:mkjARE7Yr8qU23YcGMSALbIxTQ9r9QBVahQOBRfU460= -github.com/timonwong/loggercheck v0.11.0 h1:jdaMpYBl+Uq9mWPXv1r8jc5fC3gyXx4/WGwTnnNKn4M= -github.com/timonwong/loggercheck v0.11.0/go.mod h1:HEAWU8djynujaAVX7QI65Myb8qgfcZ1uKbdpg3ZzKl8= -github.com/tomarrell/wrapcheck/v2 v2.12.0 h1:H/qQ1aNWz/eeIhxKAFvkfIA+N7YDvq6TWVFL27Of9is= -github.com/tomarrell/wrapcheck/v2 v2.12.0/go.mod h1:AQhQuZd0p7b6rfW+vUwHm5OMCGgp63moQ9Qr/0BpIWo= -github.com/tommy-muehle/go-mnd/v2 v2.5.1 h1:NowYhSdyE/1zwK9QCLeRb6USWdoif80Ie+v+yU8u1Zw= -github.com/tommy-muehle/go-mnd/v2 v2.5.1/go.mod h1:WsUAkMJMYww6l/ufffCD3m+P7LEvr8TnZn9lwVDlgzw= -github.com/ultraware/funlen v0.2.0 h1:gCHmCn+d2/1SemTdYMiKLAHFYxTYz7z9VIDRaTGyLkI= -github.com/ultraware/funlen v0.2.0/go.mod h1:ZE0q4TsJ8T1SQcjmkhN/w+MceuatI6pBFSxxyteHIJA= -github.com/ultraware/whitespace v0.2.0 h1:TYowo2m9Nfj1baEQBjuHzvMRbp19i+RCcRYrSWoFa+g= -github.com/ultraware/whitespace v0.2.0/go.mod h1:XcP1RLD81eV4BW8UhQlpaR+SDc2givTvyI8a586WjW8= -github.com/uudashr/gocognit v1.2.1 h1:CSJynt5txTnORn/DkhiB4mZjwPuifyASC8/6Q0I/QS4= -github.com/uudashr/gocognit v1.2.1/go.mod h1:acaubQc6xYlXFEMb9nWX2dYBzJ/bIjEkc1zzvyIZg5Q= -github.com/uudashr/iface v1.4.1 h1:J16Xl1wyNX9ofhpHmQ9h9gk5rnv2A6lX/2+APLTo0zU= -github.com/uudashr/iface v1.4.1/go.mod h1:pbeBPlbuU2qkNDn0mmfrxP2X+wjPMIQAy+r1MBXSXtg= -github.com/xen0n/gosmopolitan v1.3.0 h1:zAZI1zefvo7gcpbCOrPSHJZJYA9ZgLfJqtKzZ5pHqQM= -github.com/xen0n/gosmopolitan v1.3.0/go.mod h1:rckfr5T6o4lBtM1ga7mLGKZmLxswUoH1zxHgNXOsEt4= -github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= -github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= -github.com/yagipy/maintidx v1.0.0 h1:h5NvIsCz+nRDapQ0exNv4aJ0yXSI0420omVANTv3GJM= -github.com/yagipy/maintidx v1.0.0/go.mod h1:0qNf/I/CCZXSMhsRsrEPDZ+DkekpKLXAJfsTACwgXLk= -github.com/yeya24/promlinter v0.3.0 h1:JVDbMp08lVCP7Y6NP3qHroGAO6z2yGKQtS5JsjqtoFs= -github.com/yeya24/promlinter v0.3.0/go.mod h1:cDfJQQYv9uYciW60QT0eeHlFodotkYZlL+YcPQN+mW4= -github.com/ykadowak/zerologlint v0.1.5 h1:Gy/fMz1dFQN9JZTPjv1hxEk+sRWm05row04Yoolgdiw= -github.com/ykadowak/zerologlint v0.1.5/go.mod h1:KaUskqF3e/v59oPmdq1U1DnKcuHokl2/K1U4pmIELKg= -github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ= -github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= -github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0= -github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= -gitlab.com/bosi/decorder v0.4.2 h1:qbQaV3zgwnBZ4zPMhGLW4KZe7A7NwxEhJx39R3shffo= -gitlab.com/bosi/decorder v0.4.2/go.mod h1:muuhHoaJkA9QLcYHq4Mj8FJUwDZ+EirSHRiaTcTf6T8= -go-simpler.org/assert v0.9.0 h1:PfpmcSvL7yAnWyChSjOz6Sp6m9j5lyK8Ok9pEL31YkQ= -go-simpler.org/assert v0.9.0/go.mod h1:74Eqh5eI6vCK6Y5l3PI8ZYFXG4Sa+tkr70OIPJAUr28= -go-simpler.org/musttag v0.14.0 h1:XGySZATqQYSEV3/YTy+iX+aofbZZllJaqwFWs+RTtSo= -go-simpler.org/musttag v0.14.0/go.mod h1:uP8EymctQjJ4Z1kUnjX0u2l60WfUdQxCwSNKzE1JEOE= -go-simpler.org/sloglint v0.11.1 h1:xRbPepLT/MHPTCA6TS/wNfZrDzkGvCCqUv4Bdwc3H7s= -go-simpler.org/sloglint v0.11.1/go.mod h1:2PowwiCOK8mjiF+0KGifVOT8ZsCNiFzvfyJeJOIt8MQ= -go.augendre.info/arangolint v0.4.0 h1:xSCZjRoS93nXazBSg5d0OGCi9APPLNMmmLrC995tR50= -go.augendre.info/arangolint v0.4.0/go.mod h1:l+f/b4plABuFISuKnTGD4RioXiCCgghv2xqst/xOvAA= -go.augendre.info/fatcontext v0.9.0 h1:Gt5jGD4Zcj8CDMVzjOJITlSb9cEch54hjRRlN3qDojE= -go.augendre.info/fatcontext v0.9.0/go.mod h1:L94brOAT1OOUNue6ph/2HnwxoNlds9aXDF2FcUntbNw= -go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.starlark.net v0.0.0-20260326113308-fadfc96def35 h1:VYAqieSOJNxBDX8KJneTAwvdf4J4zRDE2u+UFXtt9h4= go.starlark.net v0.0.0-20260326113308-fadfc96def35/go.mod h1:Iue6g6iirlfLoVi/DYCi5/x0h/bAOuWF3dULTKpt2Vo= -go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= -go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= -go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= -go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= -go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= -go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= -golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= -golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= -golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4= -golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= -golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= -golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= -golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20250911091902-df9299821621 h1:2id6c1/gto0kaHYyrixvknJ8tUK/Qs5IsmBtrc+FtgU= -golang.org/x/exp v0.0.0-20250911091902-df9299821621/go.mod h1:TwQYMMnGpvZyc+JpB/UAuTNIsVJifOlSkrZkhcvpVUk= -golang.org/x/exp/typeparams v0.0.0-20220428152302-39d4317da171/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= -golang.org/x/exp/typeparams v0.0.0-20230203172020-98cc5a0785f9/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= -golang.org/x/exp/typeparams v0.0.0-20260209203927-2842357ff358 h1:qWFG1Dj7TBjOjOvhEOkmyGPVoquqUKnIU0lEVLp8xyk= -golang.org/x/exp/typeparams v0.0.0-20260209203927-2842357ff358/go.mod h1:4Mzdyp/6jzw9auFDJ3OMF5qksa7UvPnzKqTVGcb04ms= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= -golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI= -golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY= -golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ= -golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= -golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= -golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0= -golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw= -golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= -golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= -golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= -golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= -golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM= -golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211105183446-c75c47738b0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= -golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw= -golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= -golang.org/x/telemetry v0.0.0-20260311193753-579e4da9a98c h1:6a8FdnNk6bTXBjR4AGKFgUKuo+7GnR3FX5L7CbveeZc= -golang.org/x/telemetry v0.0.0-20260311193753-579e4da9a98c/go.mod h1:TpUTTEp9frx7rTdLpC9gFG9kdI7zVLFTFFlqaH2Cncw= -golang.org/x/telemetry v0.0.0-20260625142307-59b4966ccb57 h1:nwGZBCt+FnXUrGsj5vjzAsEmkcaFvd82BbOjECiFYZc= -golang.org/x/telemetry v0.0.0-20260625142307-59b4966ccb57/go.mod h1:3AWMyWHS+caVoiEXpiq6+tzKA40J4vQT3MYr80ZtQpc= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= -golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= -golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= -golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8= -golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA= -golang.org/x/text v0.39.0 h1:UbZz4pLOvn600D6Oh6GGEI6VAmndrEBLv8/6BEXzyus= -golang.org/x/text v0.39.0/go.mod h1:3UwRclnC2g0TU9x8PZiyfOajCd1zaUNHF9cvqcQZ+ZM= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200329025819-fd4102a86c65/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200724022722-7017fd6b1305/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.1.1-0.20210205202024-ef80cdb6ec6d/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU= -golang.org/x/tools v0.1.1-0.20210302220138-2ac05c832e1a/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU= -golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= -golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= -golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s= -golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0= -golang.org/x/tools v0.47.0 h1:7Kn5x/d1svx/PzryTsqeoZN4TZwqeH5pGWjefhLi/1Q= -golang.org/x/tools v0.47.0/go.mod h1:dFHnyTvFWY212G+h7ZY4Vsp/K3U4/7W9TyVaAul8uCA= -golang.org/x/tools/go/expect v0.1.1-deprecated h1:jpBZDwmgPhXsKZC6WhL20P4b/wmnpsEAGHaNy0n/rJM= -golang.org/x/tools/go/expect v0.1.1-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY= -golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated h1:1h2MnaIAIXISqTFKdENegdpAgUXz6NrPEsbIeWaBRvM= -golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated/go.mod h1:RVAQXBGNv1ib0J382/DPCRS/BPnsGebyM1Gj5VSDpG8= -golang.org/x/vuln v1.1.4 h1:Ju8QsuyhX3Hk8ma3CesTbO8vfJD9EvUBgHvkxHBzj0I= -golang.org/x/vuln v1.1.4/go.mod h1:F+45wmU18ym/ca5PLTPLsSzr2KppzswxPP603ldA67s= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= -google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= -google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= -google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= -google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto/googleapis/api v0.0.0-20250922171735-9219d122eba9 h1:jm6v6kMRpTYKxBRrDkYAitNJegUeO1Mf3Kt80obv0gg= -google.golang.org/genproto/googleapis/api v0.0.0-20250922171735-9219d122eba9/go.mod h1:LmwNphe5Afor5V3R5BppOULHOnt2mCIf+NxMd4XiygE= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250922171735-9219d122eba9 h1:V1jCN2HBa8sySkR5vLcCSqJSTMv093Rw9EJefhQGP7M= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250922171735-9219d122eba9/go.mod h1:HSkG/KdJWusxU1F6CNrwNDjBMgisKxGnc5dAZfT0mjQ= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= -google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= -gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.7.0 h1:w6WUp1VbkqPEgLz4rkBzH/CSU6HkoqNLp6GstyTx3lU= -honnef.co/go/tools v0.7.0/go.mod h1:pm29oPxeP3P82ISxZDgIYeOaf9ta6Pi0EWvCFoLG2vc= -mvdan.cc/gofumpt v0.9.2 h1:zsEMWL8SVKGHNztrx6uZrXdp7AX8r421Vvp23sz7ik4= -mvdan.cc/gofumpt v0.9.2/go.mod h1:iB7Hn+ai8lPvofHd9ZFGVg2GOr8sBUw1QUWjNbmIL/s= -mvdan.cc/unparam v0.0.0-20251027182757-5beb8c8f8f15 h1:ssMzja7PDPJV8FStj7hq9IKiuiKhgz9ErWw+m68e7DI= -mvdan.cc/unparam v0.0.0-20251027182757-5beb8c8f8f15/go.mod h1:4M5MMXl2kW6fivUT6yRGpLLPNfuGtU2Z0cPvFquGDYU= -rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= -rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/internal/config/config.go b/internal/config/config.go deleted file mode 100644 --- a/internal/config/config.go +++ /dev/null @@ -1,54 +0,0 @@ -// SPDX-FileCopyrightText: 2026 Nikolay Govorov -// SPDX-License-Identifier: AGPL-3.0-or-later - -// Package config holds hard-coded tunables shared across mirum-server and mirum-worker: -// timeouts, sizes, intervals, and limits that are not (yet) exposed through -// the YAML user config. Grouping them here keeps magic numbers out of call -// sites and gives a single place to audit defaults. -package config - -import "time" - -// HTTP server hardening — applied to every *http.Server by hardenServer. -const ( - HTTPIdleTimeout = 120 * time.Second - HTTPReadHeaderTimeout = 10 * time.Second - HTTPMaxHeaderBytes = 1 << 16 // 64 KiB - HTTPShutdownTimeout = 30 * time.Second -) - -// Web router middleware — chi. -const ( - WebRequestTimeout = 30 * time.Second - WebMaxBodyBytes = 64 << 20 // 64 MiB - - // /auth routes: tighter body cap and per-IP rate limit. - AuthMaxBodyBytes = 4096 - AuthRateLimit = 10 - AuthRateWindow = time.Minute - - // /api/v1 routes: SPA-friendly per-IP rate limit. - APIRateLimit = 300 - APIRateWindow = time.Minute -) - -// Task queue — channel buffer between webhook handlers and gRPC Poll. -const TaskQueueCapacity = 100 - -// Sessions. -const ( - SessionTTL = 14 * 24 * time.Hour // cookie + DB row lifetime - SessionPurgeInterval = time.Hour // background PurgeExpiredSessions -) - -// Worker mTLS handshake. -const ( - WorkerCertLifetime = 24 * time.Hour // self-signed worker cert NotAfter - WorkerClockSkewLimit = time.Minute // max allowed skew between worker and server -) - -// Worker reconnection backoff (exponential, jittered). -const ( - WorkerBackoffMin = time.Second - WorkerBackoffMax = 60 * time.Second -) diff --git a/internal/executor/environment.go b/internal/executor/environment.go deleted file mode 100644 --- a/internal/executor/environment.go +++ /dev/null @@ -1,63 +0,0 @@ -// SPDX-FileCopyrightText: 2026 Nikolay Govorov -// SPDX-License-Identifier: AGPL-3.0-or-later - -package executor - -import ( - "context" - "fmt" - "os" - - "go.starlark.net/starlark" -) - -// SOTaskCtx is the `ctx` value passed to the Starlark project() function. It -// exposes the task's Runtime to the build script. -type SOTaskCtx struct { - ctx context.Context - rt Runtime -} - -var _ starlark.HasAttrs = (*SOTaskCtx)(nil) - -func (c *SOTaskCtx) Type() string { return "ctx" } -func (c *SOTaskCtx) Hash() (uint32, error) { return 0, fmt.Errorf("unhashable: ctx") } -func (c *SOTaskCtx) Truth() starlark.Bool { return true } -func (c *SOTaskCtx) String() string { return "ctx" } - -func (c *SOTaskCtx) Freeze() {} - -func (c *SOTaskCtx) AttrNames() []string { return []string{"shell"} } -func (c *SOTaskCtx) Attr(name string) (starlark.Value, error) { - switch name { - case "shell": - return starlark.NewBuiltin("ctx.shell", c.shell), nil - } - return nil, nil -} - -func (c *SOTaskCtx) shell( - thread *starlark.Thread, - fn *starlark.Builtin, - args starlark.Tuple, - kwargs []starlark.Tuple, -) (starlark.Value, error) { - var cmd string - if err := starlark.UnpackPositionalArgs(fn.Name(), args, kwargs, 1, &cmd); err != nil { - return nil, err - } - - res, err := c.rt.Exec(c.ctx, Command{ - Args: []string{"bash", "-c", cmd}, - Stdout: os.Stdout, - Stderr: os.Stderr, - }) - if err != nil { - return nil, err - } - if res.Code != 0 { - return nil, fmt.Errorf("shell: %q exited with code %d", cmd, res.Code) - } - - return starlark.None, nil -} diff --git a/internal/executor/executor.go b/internal/executor/executor.go deleted file mode 100644 --- a/internal/executor/executor.go +++ /dev/null @@ -1,68 +0,0 @@ -// SPDX-FileCopyrightText: 2026 Nikolay Govorov -// SPDX-License-Identifier: AGPL-3.0-or-later - -// Runtime executing the Starlark pipeline in one of the supported runtimes -package executor - -import ( - "bytes" - "context" - "fmt" - "log/slog" - - "go.starlark.net/starlark" - "go.starlark.net/syntax" -) - -const entry = ".mirum/project.star" - -// Run selects a Runtime, clones the repository into it, runs the Starlark -// build script, and discards the Runtime afterwards. -func Run(ctx context.Context, cloneURL, branch string) error { - rt, err := NewRuntime() - if err != nil { - return fmt.Errorf("create runtime: %w", err) - } - defer func() { - if err := rt.Close(); err != nil { - slog.Warn("executor: runtime cleanup failed", "err", err) - } - }() - - var clone bytes.Buffer - res, err := rt.Exec(ctx, Command{ - Args: []string{"git", "clone", "--depth=1", "--branch", branch, cloneURL, "."}, - Stdout: &clone, - Stderr: &clone, - }) - if err != nil { - return fmt.Errorf("git clone: %w", err) - } - if res.Code != 0 { - return fmt.Errorf("git clone exited with code %d: %s", res.Code, clone.String()) - } - - var script bytes.Buffer - if err := rt.FileRecv(ctx, entry, &script); err != nil { - return fmt.Errorf("read %s: %w", entry, err) - } - - thread := &starlark.Thread{Name: "mirum"} - globals, err := starlark.ExecFileOptions(&syntax.FileOptions{}, thread, entry, script.Bytes(), nil) - if err != nil { - return err - } - - projectFn, ok := globals["project"] - if !ok { - return fmt.Errorf("%s: project() not defined", entry) - } - fn, ok := projectFn.(starlark.Callable) - if !ok { - return fmt.Errorf("%s: project is not a function", entry) - } - - tctx := &SOTaskCtx{ctx: ctx, rt: rt} - _, err = starlark.Call(thread, fn, starlark.Tuple{tctx}, nil) - return err -} diff --git a/internal/executor/host/host.go b/internal/executor/host/host.go deleted file mode 100644 --- a/internal/executor/host/host.go +++ /dev/null @@ -1,190 +0,0 @@ -// SPDX-FileCopyrightText: 2026 Nikolay Govorov -// SPDX-License-Identifier: AGPL-3.0-or-later - -// Package host implements an executor.Runtime that runs task commands -// directly on the worker. It provides no isolation — it is the fast path -// for local iteration (mirum task) and trusted workloads. -package host - -import ( - "context" - "fmt" - "io" - "os" - "os/exec" - "path/filepath" - "runtime" - "time" - - "dimidiumlabs/mirum/internal/executor" -) - -// Host is an executor.Runtime backed by a temporary directory on the worker. -// Commands run as child processes of the worker, with no sandboxing. -type Host struct { - root string -} - -var _ executor.Runtime = (*Host)(nil) - -// New creates a Host runtime rooted at a fresh temporary directory. -func New() (*Host, error) { - root, err := os.MkdirTemp("", "mirum-host-*") - if err != nil { - return nil, fmt.Errorf("create work dir: %w", err) - } - return &Host{root: root}, nil -} - -func init() { - executor.RegisterRuntime(executor.RuntimeBackend{ - Name: "host", - Priority: executor.PriorityHost, - New: func() (executor.Runtime, error) { return New() }, - }) -} - -// Platform reports the worker's own OS and architecture. -func (h *Host) Platform() executor.Platform { - return executor.Platform{OS: runtime.GOOS, Arch: runtime.GOARCH} -} - -// resolve maps an env-relative slash path to an absolute path inside root, -// rejecting paths that would escape it. -func (h *Host) resolve(p string) (string, error) { - if p == "" { - return h.root, nil - } - local, err := filepath.Localize(p) - if err != nil { - return "", fmt.Errorf("invalid path %q: %w", p, err) - } - return filepath.Join(h.root, local), nil -} - -// Exec runs cmd as a child process of the worker. -func (h *Host) Exec(ctx context.Context, cmd executor.Command) (executor.Result, error) { - if len(cmd.Args) == 0 { - return executor.Result{}, fmt.Errorf("exec: empty args") - } - - dir, err := h.resolve(cmd.Dir) - if err != nil { - return executor.Result{}, err - } - - c := exec.CommandContext(ctx, cmd.Args[0], cmd.Args[1:]...) - c.Dir = dir - c.Stdin = cmd.Stdin - c.Stdout = cmd.Stdout - c.Stderr = cmd.Stderr - if len(cmd.Env) > 0 { - c.Env = os.Environ() - for k, v := range cmd.Env { - c.Env = append(c.Env, k+"="+v) - } - } - - start := time.Now() - runErr := c.Run() - wall := time.Since(start) - - if ctx.Err() != nil { - return executor.Result{}, ctx.Err() - } - - ps := c.ProcessState - if ps == nil { - // The process never started, e.g. the executable was not found. - return executor.Result{}, fmt.Errorf("exec %s: %w", cmd.Args[0], runErr) - } - - return executor.Result{ - Code: ps.ExitCode(), - Usage: executor.Usage{ - Wall: wall, - CPUUser: ps.UserTime(), - CPUSystem: ps.SystemTime(), - MaxRSS: maxRSS(ps), - }, - }, nil -} - -// FileSend writes a single file into the work directory, creating parent -// directories as needed. -func (h *Host) FileSend(ctx context.Context, path string, r io.Reader) error { - if err := ctx.Err(); err != nil { - return err - } - dst, err := h.resolve(path) - if err != nil { - return err - } - if err := os.MkdirAll(filepath.Dir(dst), 0o755); err != nil { - return err - } - f, err := os.Create(dst) - if err != nil { - return err - } - _, copyErr := io.Copy(f, r) - closeErr := f.Close() - if copyErr != nil { - return copyErr - } - return closeErr -} - -// FileRecv streams a single file out of the work directory. -func (h *Host) FileRecv(ctx context.Context, path string, w io.Writer) error { - if err := ctx.Err(); err != nil { - return err - } - src, err := h.resolve(path) - if err != nil { - return err - } - f, err := os.Open(src) - if err != nil { - return err - } - defer func() { _ = f.Close() }() - _, err = io.Copy(w, f) - return err -} - -// FileList returns the env-relative paths of every file under dir. -func (h *Host) FileList(ctx context.Context, dir string) ([]string, error) { - base, err := h.resolve(dir) - if err != nil { - return nil, err - } - - var out []string - err = filepath.WalkDir(base, func(p string, d os.DirEntry, err error) error { - if err != nil { - return err - } - if err := ctx.Err(); err != nil { - return err - } - if d.IsDir() { - return nil - } - rel, err := filepath.Rel(h.root, p) - if err != nil { - return err - } - out = append(out, filepath.ToSlash(rel)) - return nil - }) - if err != nil { - return nil, err - } - return out, nil -} - -// Close removes the work directory. -func (h *Host) Close() error { - return os.RemoveAll(h.root) -} diff --git a/internal/executor/host/maxrss_other.go b/internal/executor/host/maxrss_other.go deleted file mode 100644 --- a/internal/executor/host/maxrss_other.go +++ /dev/null @@ -1,11 +0,0 @@ -// SPDX-FileCopyrightText: 2026 Nikolay Govorov -// SPDX-License-Identifier: AGPL-3.0-or-later - -//go:build !unix - -package host - -import "os" - -// maxRSS returns 0 on platforms without POSIX getrusage. -func maxRSS(*os.ProcessState) int64 { return 0 } diff --git a/internal/executor/host/maxrss_unix.go b/internal/executor/host/maxrss_unix.go deleted file mode 100644 --- a/internal/executor/host/maxrss_unix.go +++ /dev/null @@ -1,27 +0,0 @@ -// SPDX-FileCopyrightText: 2026 Nikolay Govorov -// SPDX-License-Identifier: AGPL-3.0-or-later - -//go:build unix - -package host - -import ( - "os" - "runtime" - "syscall" -) - -// maxRSS extracts the peak resident set size of a finished process, -// normalized to bytes. POSIX getrusage reports ru_maxrss in kilobytes on -// Linux and the BSDs, but in bytes on macOS. -func maxRSS(ps *os.ProcessState) int64 { - ru, ok := ps.SysUsage().(*syscall.Rusage) - if !ok { - return 0 - } - rss := int64(ru.Maxrss) - if runtime.GOOS != "darwin" { - rss *= 1024 - } - return rss -} diff --git a/internal/executor/runtime.go b/internal/executor/runtime.go deleted file mode 100644 --- a/internal/executor/runtime.go +++ /dev/null @@ -1,125 +0,0 @@ -// SPDX-FileCopyrightText: 2026 Nikolay Govorov -// SPDX-License-Identifier: AGPL-3.0-or-later - -package executor - -import ( - "context" - "errors" - "fmt" - "io" - "log/slog" - "sort" - "time" -) - -// Platform identifies the OS and architecture of a Runtime, as observed by -// the task itself — the values behind tctx.os and tctx.arch. -type Platform struct { - OS string // "linux", "darwin", "windows", ... - Arch string // "amd64", "arm64", "riscv64", ... -} - -// Command is a single process to run inside a Runtime. -type Command struct { - Args []string // argv; Args[0] is the executable - Dir string // working directory, relative to the env root - Env map[string]string // variables added to the environment's own - Stdin io.Reader // standard input; nil means none - Stdout io.Writer // streamed as produced; nil means discard - Stderr io.Writer // streamed as produced; nil means discard -} - -// Usage reports the resources a Command consumed. It is collected from -// POSIX rusage — os.ProcessState.SysUsage on the Host runtime, wait4 inside -// the VM for the Qemu runtime — and feeds log output and billing metrics. A -// backend leaves a field zero when it cannot measure it. -type Usage struct { - Wall time.Duration // wall-clock time from start to exit - CPUUser time.Duration // CPU time spent in user mode - CPUSystem time.Duration // CPU time spent in kernel mode - MaxRSS int64 // peak resident set size, in bytes -} - -// Result is the outcome of a Command that ran to completion. -type Result struct { - Code int // process exit code; 0 means success - Usage Usage // resources the command consumed -} - -// Runtime is an isolation backend: the environment in which one task's -// commands run. The executor drives every Runtime identically and is -// unaware of how isolation is achieved. A Runtime hosts one task and is -// then discarded. -type Runtime interface { - // Platform reports the environment's OS and architecture. - Platform() Platform - - // Exec runs cmd to completion. The error covers failures to start or - // communicate with the process; a process that runs and exits non-zero - // is a successful Exec with a non-zero Result.Code. - Exec(ctx context.Context, cmd Command) (Result, error) - - // List returns the env-relative paths of the files under dir, so the - // executor can resolve upload globs and walk a source tree. - FileList(ctx context.Context, dir string) ([]string, error) - - // Send streams a single file into the environment at the env-relative - // path, creating parent directories as needed. Bytes flow straight from - // r — the worker never stages them on its own filesystem. - FileSend(ctx context.Context, path string, r io.Reader) error - - // Recv streams a single file out of the environment to w. Like Send, - // nothing is staged on the worker's filesystem. - FileRecv(ctx context.Context, path string, w io.Writer) error - - // Close discards the environment and releases its resources. - Close() error -} - -// Selection priorities for RuntimeBackend.Priority; NewRuntime prefers higher. -const ( - PriorityHost = 0 // unisolated, runs on the worker itself - PriorityVM = 100 // hardware-isolated guest (QEMU) -) - -// A RuntimeBackend describes one Runtime implementation. Backends register -// themselves with RegisterRuntime, so the executor never imports the backend -// packages directly. -type RuntimeBackend struct { - Name string - Priority int - - // New builds a fresh Runtime for one task. A nil Runtime with nil error - // means the backend does not apply on this worker, so NewRuntime falls - // through to the next. - New func() (Runtime, error) -} - -// runtimeBackends is kept sorted by descending priority. -var runtimeBackends []RuntimeBackend - -// RegisterRuntime adds a Runtime backend. Backends call it from an init -// function. -func RegisterRuntime(b RuntimeBackend) { - runtimeBackends = append(runtimeBackends, b) - sort.SliceStable(runtimeBackends, func(i, j int) bool { - return runtimeBackends[i].Priority > runtimeBackends[j].Priority - }) -} - -// NewRuntime builds a fresh Runtime for one task, choosing the highest-priority -// backend that applies. The caller must Close the returned Runtime. -func NewRuntime() (Runtime, error) { - for _, b := range runtimeBackends { - rt, err := b.New() - if err != nil { - return nil, fmt.Errorf("runtime %s: %w", b.Name, err) - } - if rt != nil { - slog.Info("executor: runtime selected", "backend", b.Name) - return rt, nil - } - } - return nil, errors.New("executor: no applicable runtime backend") -} diff --git a/internal/forges/forge.go b/internal/forges/forge.go deleted file mode 100644 --- a/internal/forges/forge.go +++ /dev/null @@ -1,47 +0,0 @@ -// SPDX-FileCopyrightText: 2026 Nikolay Govorov -// SPDX-License-Identifier: AGPL-3.0-or-later - -package forges - -import ( - "context" - "errors" - "net/http" -) - -// ErrInvalidSignature is returned when webhook signature verification fails. -var ErrInvalidSignature = errors.New("invalid webhook signature") - -// Status represents a normalized build status. -// Each forge maps these to its native values. -type Status string - -const ( - StatusPending Status = "pending" - StatusRunning Status = "running" - StatusSuccess Status = "success" - StatusFailure Status = "failure" -) - -// PushEvent is a forge-agnostic push event. -type PushEvent struct { - Owner string // repository owner or namespace (e.g. "group/subgroup" for GitLab) - Repo string // repository name - Branch string - SHA string - CloneURL string -} - -// Forge abstracts a Git hosting platform. -type Forge interface { - // Webhook validates and parses an incoming webhook request. - // Returns (nil, nil) for events that should be silently ignored. - // Returns ErrInvalidSignature if signature verification fails. - Webhook(r *http.Request, body []byte) (*PushEvent, error) - - // SetStatus reports build status for a commit. - SetStatus(ctx context.Context, ev *PushEvent, status Status, desc string) error - - // AuthURL returns a clone URL with embedded credentials. - AuthURL(cloneURL string) string -} diff --git a/internal/forges/github.go b/internal/forges/github.go deleted file mode 100644 --- a/internal/forges/github.go +++ /dev/null @@ -1,141 +0,0 @@ -// SPDX-FileCopyrightText: 2026 Nikolay Govorov -// SPDX-License-Identifier: AGPL-3.0-or-later - -package forges - -import ( - "bytes" - "context" - "crypto/hmac" - "crypto/sha256" - "encoding/hex" - "encoding/json" - "fmt" - "io" - "log/slog" - "net/http" - "net/url" - "strings" -) - -// GitHub implements the Forge interface for GitHub and compatible APIs. -type GitHub struct { - Secret string - Token string -} - -var githubStatusMap = map[Status]string{ - StatusPending: "pending", - StatusRunning: "pending", // GitHub has no "running" state - StatusSuccess: "success", - StatusFailure: "failure", -} - -type githubPush struct { - Ref string `json:"ref"` - After string `json:"after"` - Repo struct { - FullName string `json:"full_name"` - CloneURL string `json:"clone_url"` - } `json:"repository"` -} - -func (g *GitHub) Webhook(r *http.Request, body []byte) (*PushEvent, error) { - if g.Secret != "" && !g.verifySignature(body, r.Header.Get("X-Hub-Signature-256")) { - return nil, ErrInvalidSignature - } - - event := r.Header.Get("X-GitHub-Event") - if event != "push" { - return nil, nil - } - - var push githubPush - if err := json.Unmarshal(body, &push); err != nil { - return nil, fmt.Errorf("parse payload: %w", err) - } - - if push.After == "" || push.After == "0000000000000000000000000000000000000000" { - return nil, nil - } - - if !strings.HasPrefix(push.Ref, "refs/heads/") { - return nil, nil - } - - owner, repo := splitFullName(push.Repo.FullName) - return &PushEvent{ - Owner: owner, - Repo: repo, - Branch: strings.TrimPrefix(push.Ref, "refs/heads/"), - SHA: push.After, - CloneURL: push.Repo.CloneURL, - }, nil -} - -func (g *GitHub) SetStatus(ctx context.Context, ev *PushEvent, status Status, desc string) error { - apiURL := fmt.Sprintf("https://api.github.com/repos/%s/%s/statuses/%s", ev.Owner, ev.Repo, ev.SHA) - - body, _ := json.Marshal(map[string]string{ - "state": githubStatusMap[status], - "description": desc, - "context": "mirum", - }) - - req, err := http.NewRequestWithContext(ctx, "POST", apiURL, bytes.NewReader(body)) - if err != nil { - return err - } - req.Header.Set("Authorization", "Bearer "+g.Token) - req.Header.Set("Accept", "application/vnd.github+json") - - resp, err := http.DefaultClient.Do(req) - if err != nil { - return err - } - defer func() { - if err := resp.Body.Close(); err != nil { - slog.Warn("github: close response body", "err", err) - } - }() - - if resp.StatusCode >= 300 { - b, _ := io.ReadAll(resp.Body) - return fmt.Errorf("github api %d: %s", resp.StatusCode, b) - } - return nil -} - -func (g *GitHub) AuthURL(cloneURL string) string { - if g.Token == "" { - return cloneURL - } - u, err := url.Parse(cloneURL) - if err != nil { - return cloneURL - } - u.User = url.UserPassword("x-access-token", g.Token) - return u.String() -} - -func (g *GitHub) verifySignature(payload []byte, signature string) bool { - sig, ok := strings.CutPrefix(signature, "sha256=") - if !ok { - return false - } - decoded, err := hex.DecodeString(sig) - if err != nil { - return false - } - mac := hmac.New(sha256.New, []byte(g.Secret)) - mac.Write(payload) - return hmac.Equal(mac.Sum(nil), decoded) -} - -func splitFullName(fullName string) (string, string) { - parts := strings.SplitN(fullName, "/", 2) - if len(parts) != 2 { - return fullName, "" - } - return parts[0], parts[1] -} diff --git a/internal/protocol/backoff.go b/internal/protocol/backoff.go deleted file mode 100644 --- a/internal/protocol/backoff.go +++ /dev/null @@ -1,42 +0,0 @@ -// SPDX-FileCopyrightText: 2026 Nikolay Govorov -// SPDX-License-Identifier: AGPL-3.0-or-later - -package protocol - -import ( - "context" - "math/rand/v2" - "time" - - "dimidiumlabs/mirum/internal/config" -) - -type Backoff struct { - attempt int - Min, Max time.Duration -} - -func NewBackoff() *Backoff { - return &Backoff{Min: config.WorkerBackoffMin, Max: config.WorkerBackoffMax} -} - -func (b *Backoff) Reset() { - b.attempt = 0 -} - -// Wait sleeps with exponential backoff + jitter. Returns false if ctx is cancelled. -func (b *Backoff) Wait(ctx context.Context) bool { - d := min(b.Max, b.Min<<b.attempt) - - // Add jitter: 50%-100% of the computed duration - d = d/2 + time.Duration(rand.Int64N(int64(d/2))) - - b.attempt++ - - select { - case <-time.After(d): - return true - case <-ctx.Done(): - return false - } -} diff --git a/internal/protocol/backoff_test.go b/internal/protocol/backoff_test.go deleted file mode 100644 --- a/internal/protocol/backoff_test.go +++ /dev/null @@ -1,74 +0,0 @@ -// SPDX-FileCopyrightText: 2026 Nikolay Govorov -// SPDX-License-Identifier: AGPL-3.0-or-later - -package protocol - -import ( - "context" - "testing" - "time" -) - -func TestBackoff_ExponentialGrowth(t *testing.T) { - b := &Backoff{Min: 10 * time.Millisecond, Max: 200 * time.Millisecond} - - var durations []time.Duration - for range 5 { - start := time.Now() - b.Wait(context.Background()) - durations = append(durations, time.Since(start)) - } - - if durations[4] < durations[0] { - t.Errorf("no exponential growth: first=%v last=%v", durations[0], durations[4]) - } -} - -func TestBackoff_NeverExceedsMax(t *testing.T) { - b := &Backoff{Min: time.Millisecond, Max: 50 * time.Millisecond} - - for range 20 { - start := time.Now() - b.Wait(context.Background()) - d := time.Since(start) - if d > 80*time.Millisecond { - t.Fatalf("wait %v exceeded max %v", d, b.Max) - } - } -} - -func TestBackoff_Reset(t *testing.T) { - b := &Backoff{Min: time.Millisecond, Max: time.Second} - - for range 10 { - b.Wait(context.Background()) - } - - b.Reset() - - start := time.Now() - b.Wait(context.Background()) - d := time.Since(start) - - if d > 10*time.Millisecond { - t.Fatalf("after Reset, wait %v is too long", d) - } -} - -func TestBackoff_CancelledContext(t *testing.T) { - b := &Backoff{Min: time.Hour, Max: time.Hour} - - ctx, cancel := context.WithCancel(context.Background()) - cancel() - - start := time.Now() - ok := b.Wait(ctx) - d := time.Since(start) - - if ok { - t.Fatal("Wait returned true on cancelled context") - } - if d > 10*time.Millisecond { - t.Fatalf("Wait took %v on cancelled context", d) - } -} diff --git a/internal/protocol/handshake.go b/internal/protocol/handshake.go deleted file mode 100644 --- a/internal/protocol/handshake.go +++ /dev/null @@ -1,128 +0,0 @@ -// SPDX-FileCopyrightText: 2026 Nikolay Govorov -// SPDX-License-Identifier: AGPL-3.0-or-later - -package protocol - -import ( - "crypto/ed25519" - "crypto/rand" - "crypto/tls" - "crypto/x509" - "encoding/pem" - "errors" - "fmt" - "math/big" - "net/url" - "os" - "time" - - "dimidiumlabs/mirum/internal/config" -) - -var ( - ErrKeyNotPEM = errors.New("key file does not contain a PEM block") - ErrKeyNotPKCS8 = errors.New("key file does not contain a PKCS8 private key") - ErrKeyNotPKIX = errors.New("key file does not contain a PKIX public key") - ErrKeyNotEd25519 = errors.New("key file does not contain an ed25519 key") - - ErrClockSkew = errors.New("clock skew too large") -) - -// WorkerMeta describes the worker for embedding in a self-signed X.509 -// certificate as a URI SAN (mirum:worker?name=...&version=...&...). -type WorkerMeta struct { - Name string - Version string - Os string - Arch string - Runtime string -} - -// URI encodes worker metadata as a mirum: URI. -func (m *WorkerMeta) URI() *url.URL { - return &url.URL{ - Scheme: "mirum", - Opaque: "worker", - RawQuery: url.Values{ - "name": {m.Name}, - "version": {m.Version}, - "os": {m.Os}, - "arch": {m.Arch}, - "runtime": {m.Runtime}, - }.Encode(), - } -} - -// ParseWorkerMeta extracts WorkerMeta from a certificate's URI SANs. -// Returns nil if no mirum:worker URI is found. -func ParseWorkerMeta(cert *x509.Certificate) *WorkerMeta { - for _, u := range cert.URIs { - if u.Scheme == "mirum" && u.Opaque == "worker" { - q := u.Query() - return &WorkerMeta{ - Name: q.Get("name"), - Version: q.Get("version"), - Os: q.Get("os"), - Arch: q.Get("arch"), - Runtime: q.Get("runtime"), - } - } - } - return nil -} - -// LoadPrivateKey reads a PEM-encoded PKCS8 ed25519 private key from path. -func LoadPrivateKey(path string) (ed25519.PrivateKey, error) { - data, err := os.ReadFile(path) - if err != nil { - return nil, fmt.Errorf("read key: %w", err) - } - - block, _ := pem.Decode(data) - if block == nil { - return nil, ErrKeyNotPEM - } - - key, err := x509.ParsePKCS8PrivateKey(block.Bytes) - if err != nil { - return nil, fmt.Errorf("%w: %w", ErrKeyNotPKCS8, err) - } - - edKey, ok := key.(ed25519.PrivateKey) - if !ok { - return nil, ErrKeyNotEd25519 - } - - return edKey, nil -} - -// SelfSignedCert generates a self-signed X.509 certificate from an ed25519 -// private key with worker metadata encoded as a URI SAN. The server extracts -// the public key for authentication, metadata from the URI, and uses NotBefore -// for clock skew detection. -func SelfSignedCert(key ed25519.PrivateKey, meta *WorkerMeta) (tls.Certificate, error) { - serial, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128)) - if err != nil { - return tls.Certificate{}, fmt.Errorf("generate serial: %w", err) - } - - now := time.Now() - tmpl := &x509.Certificate{ - SerialNumber: serial, - NotBefore: now, - NotAfter: now.Add(config.WorkerCertLifetime), - KeyUsage: x509.KeyUsageDigitalSignature, - ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, - URIs: []*url.URL{meta.URI()}, - } - - certDER, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, key.Public(), key) - if err != nil { - return tls.Certificate{}, fmt.Errorf("create certificate: %w", err) - } - - return tls.Certificate{ - Certificate: [][]byte{certDER}, - PrivateKey: key, - }, nil -} diff --git a/internal/protocol/handshake_test.go b/internal/protocol/handshake_test.go deleted file mode 100644 --- a/internal/protocol/handshake_test.go +++ /dev/null @@ -1,183 +0,0 @@ -// SPDX-FileCopyrightText: 2026 Nikolay Govorov -// SPDX-License-Identifier: AGPL-3.0-or-later - -package protocol - -import ( - "crypto/ed25519" - "crypto/rand" - "crypto/x509" - "encoding/pem" - "os" - "path/filepath" - "testing" - "time" -) - -func writeTestKeyPair(t *testing.T) (privPath, pubPath string, pub ed25519.PublicKey) { - t.Helper() - pub, priv, err := ed25519.GenerateKey(rand.Reader) - if err != nil { - t.Fatal(err) - } - - dir := t.TempDir() - - privDER, err := x509.MarshalPKCS8PrivateKey(priv) - if err != nil { - t.Fatal(err) - } - privPath = filepath.Join(dir, "test.key") - if err := os.WriteFile(privPath, pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: privDER}), 0o600); err != nil { - t.Fatal(err) - } - - pubDER, err := x509.MarshalPKIXPublicKey(pub) - if err != nil { - t.Fatal(err) - } - pubPath = filepath.Join(dir, "test.pub") - if err := os.WriteFile(pubPath, pem.EncodeToMemory(&pem.Block{Type: "PUBLIC KEY", Bytes: pubDER}), 0o644); err != nil { - t.Fatal(err) - } - - return privPath, pubPath, pub -} - -func TestLoadPrivateKey(t *testing.T) { - privPath, _, wantPub := writeTestKeyPair(t) - - key, err := LoadPrivateKey(privPath) - if err != nil { - t.Fatal(err) - } - - gotPub := key.Public().(ed25519.PublicKey) - if !gotPub.Equal(wantPub) { - t.Fatal("public key mismatch") - } -} - -func TestLoadPrivateKey_NotFound(t *testing.T) { - _, err := LoadPrivateKey("/nonexistent/path") - if err == nil { - t.Fatal("expected error") - } -} - -func TestLoadPrivateKey_NotPEM(t *testing.T) { - path := filepath.Join(t.TempDir(), "bad.key") - if err := os.WriteFile(path, []byte("not pem"), 0o600); err != nil { - t.Fatal(err) - } - - _, err := LoadPrivateKey(path) - if err == nil { - t.Fatal("expected error") - } -} - -func TestLoadPrivateKey_WrongKeyType(t *testing.T) { - data := pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: []byte("garbage")}) - path := filepath.Join(t.TempDir(), "bad.key") - if err := os.WriteFile(path, data, 0o600); err != nil { - t.Fatal(err) - } - - _, err := LoadPrivateKey(path) - if err == nil { - t.Fatal("expected error") - } -} - -func TestSelfSignedCert(t *testing.T) { - _, priv, err := ed25519.GenerateKey(rand.Reader) - if err != nil { - t.Fatal(err) - } - - meta := &WorkerMeta{ - Name: "test-worker", - Version: "1.2.3", - Os: "linux", - Arch: "amd64", - Runtime: "host", - } - - cert, err := SelfSignedCert(priv, meta) - if err != nil { - t.Fatal(err) - } - - if len(cert.Certificate) != 1 { - t.Fatalf("expected 1 cert, got %d", len(cert.Certificate)) - } - - parsed, err := x509.ParseCertificate(cert.Certificate[0]) - if err != nil { - t.Fatal(err) - } - - // Public key matches - pubKey, ok := parsed.PublicKey.(ed25519.PublicKey) - if !ok { - t.Fatal("certificate does not contain an ed25519 public key") - } - if !pubKey.Equal(priv.Public().(ed25519.PublicKey)) { - t.Fatal("public key mismatch") - } - - // Key usage - if parsed.KeyUsage&x509.KeyUsageDigitalSignature == 0 { - t.Fatal("missing DigitalSignature key usage") - } - if len(parsed.ExtKeyUsage) != 1 || parsed.ExtKeyUsage[0] != x509.ExtKeyUsageClientAuth { - t.Fatal("missing ClientAuth extended key usage") - } - - // NotBefore is recent (used for clock skew) - if time.Since(parsed.NotBefore).Abs() > 5*time.Second { - t.Fatalf("NotBefore too far from now: %v", parsed.NotBefore) - } -} - -func TestSelfSignedCert_WorkerMeta(t *testing.T) { - _, priv, err := ed25519.GenerateKey(rand.Reader) - if err != nil { - t.Fatal(err) - } - - want := &WorkerMeta{ - Name: "my-worker", - Version: "0.5.1", - Os: "darwin", - Arch: "arm64", - Runtime: "docker", - } - - cert, err := SelfSignedCert(priv, want) - if err != nil { - t.Fatal(err) - } - - parsed, err := x509.ParseCertificate(cert.Certificate[0]) - if err != nil { - t.Fatal(err) - } - - got := ParseWorkerMeta(parsed) - if got == nil { - t.Fatal("ParseWorkerMeta returned nil") - } - - if *got != *want { - t.Fatalf("meta mismatch:\n got: %+v\nwant: %+v", got, want) - } -} - -func TestParseWorkerMeta_NoCert(t *testing.T) { - cert := &x509.Certificate{} - if meta := ParseWorkerMeta(cert); meta != nil { - t.Fatalf("expected nil, got %+v", meta) - } -} diff --git a/internal/protocol/platform.go b/internal/protocol/platform.go deleted file mode 100644 --- a/internal/protocol/platform.go +++ /dev/null @@ -1,104 +0,0 @@ -// SPDX-FileCopyrightText: 2026 Nikolay Govorov -// SPDX-License-Identifier: AGPL-3.0-or-later - -package protocol - -import ( - "errors" - "fmt" - "runtime" - "strconv" - "strings" - - mirum "dimidiumlabs/mirum" - "dimidiumlabs/mirum/internal/protocol/wirepb" -) - -var ErrInvalidVersion = errors.New("invalid version string") - -var ( - Major uint32 - Minor uint32 - Patch uint32 -) - -var osMap = map[string]wirepb.Os{ - "linux": wirepb.Os_OS_LINUX, - "darwin": wirepb.Os_OS_DARWIN, - "windows": wirepb.Os_OS_WINDOWS, - "freebsd": wirepb.Os_OS_FREEBSD, - "openbsd": wirepb.Os_OS_OPENBSD, - "netbsd": wirepb.Os_OS_NETBSD, - "dragonfly": wirepb.Os_OS_DRAGONFLY, - "illumos": wirepb.Os_OS_ILLUMOS, - "solaris": wirepb.Os_OS_SOLARIS, - "aix": wirepb.Os_OS_AIX, - "plan9": wirepb.Os_OS_PLAN9, - "android": wirepb.Os_OS_ANDROID, - "ios": wirepb.Os_OS_IOS, - "js": wirepb.Os_OS_JS, - "wasip1": wirepb.Os_OS_WASIP1, -} - -var archMap = map[string]wirepb.Arch{ - "amd64": wirepb.Arch_ARCH_AMD64, - "arm64": wirepb.Arch_ARCH_ARM64, - "386": wirepb.Arch_ARCH_386, - "arm": wirepb.Arch_ARCH_ARM, - "riscv64": wirepb.Arch_ARCH_RISCV64, - "ppc64le": wirepb.Arch_ARCH_PPC64LE, - "ppc64": wirepb.Arch_ARCH_PPC64, - "s390x": wirepb.Arch_ARCH_S390X, - "mips64le": wirepb.Arch_ARCH_MIPS64LE, - "mips64": wirepb.Arch_ARCH_MIPS64, - "mipsle": wirepb.Arch_ARCH_MIPSLE, - "mips": wirepb.Arch_ARCH_MIPS, - "loong64": wirepb.Arch_ARCH_LOONG64, - "wasm": wirepb.Arch_ARCH_WASM, -} - -func DetectOs() wirepb.Os { - if v, ok := osMap[runtime.GOOS]; ok { - return v - } - return wirepb.Os_OS_UNSPECIFIED -} - -func DetectArch() wirepb.Arch { - if v, ok := archMap[runtime.GOARCH]; ok { - return v - } - return wirepb.Arch_ARCH_UNSPECIFIED -} - -func init() { - var err error - Major, Minor, Patch, err = ParseVersion(mirum.Version) - if err != nil { - panic(fmt.Sprintf("version: %v", err)) - } -} - -// ParseVersion parses a "major.minor.patch" string. -func ParseVersion(s string) (major, minor, patch uint32, err error) { - s = strings.TrimSpace(s) - parts := strings.SplitN(s, ".", 3) - if len(parts) != 3 { - return 0, 0, 0, ErrInvalidVersion - } - n0, err0 := strconv.ParseUint(parts[0], 10, 32) - n1, err1 := strconv.ParseUint(parts[1], 10, 32) - n2, err2 := strconv.ParseUint(parts[2], 10, 32) - if err0 != nil || err1 != nil || err2 != nil { - return 0, 0, 0, ErrInvalidVersion - } - return uint32(n0), uint32(n1), uint32(n2), nil -} - -func VersionString() string { - return fmt.Sprintf("%d.%d.%d", Major, Minor, Patch) -} - -func VersionProto() *wirepb.Version { - return &wirepb.Version{Major: Major, Minor: Minor, Patch: Patch} -} diff --git a/internal/protocol/platform_test.go b/internal/protocol/platform_test.go deleted file mode 100644 --- a/internal/protocol/platform_test.go +++ /dev/null @@ -1,45 +0,0 @@ -// SPDX-FileCopyrightText: 2026 Nikolay Govorov -// SPDX-License-Identifier: AGPL-3.0-or-later - -package protocol - -import ( - "errors" - "testing" -) - -func TestParseVersion(t *testing.T) { - tests := []struct { - name string - in string - major, minor, patch uint32 - wantErr bool - }{ - {"valid", "0.1.0", 0, 1, 0, false}, - {"large", "10.20.30", 10, 20, 30, false}, - {"whitespace", " 1.2.3 ", 1, 2, 3, false}, - {"empty", "", 0, 0, 0, true}, - {"two parts", "1.2", 0, 0, 0, true}, - {"one part", "1", 0, 0, 0, true}, - {"letters", "a.b.c", 0, 0, 0, true}, - {"negative", "-1.0.0", 0, 0, 0, true}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - major, minor, patch, err := ParseVersion(tt.in) - if tt.wantErr { - if !errors.Is(err, ErrInvalidVersion) { - t.Fatalf("ParseVersion(%q) err = %v, want ErrInvalidVersion", tt.in, err) - } - return - } - if err != nil { - t.Fatalf("ParseVersion(%q) unexpected error: %v", tt.in, err) - } - if major != tt.major || minor != tt.minor || patch != tt.patch { - t.Fatalf("ParseVersion(%q) = %d.%d.%d, want %d.%d.%d", - tt.in, major, minor, patch, tt.major, tt.minor, tt.patch) - } - }) - } -} diff --git a/internal/protocol/proto/buf.gen.yaml b/internal/protocol/proto/buf.gen.yaml deleted file mode 100644 --- a/internal/protocol/proto/buf.gen.yaml +++ /dev/null @@ -1,11 +0,0 @@ -# SPDX-FileCopyrightText: 2026 Nikolay Govorov -# SPDX-License-Identifier: AGPL-3.0-or-later - -version: v2 -plugins: - - local: ["go", "tool", "google.golang.org/protobuf/cmd/protoc-gen-go"] - out: ../wirepb - opt: paths=source_relative - - local: ["go", "tool", "connectrpc.com/connect/cmd/protoc-gen-connect-go"] - out: ../wirepb - opt: paths=source_relative diff --git a/internal/protocol/proto/wire.proto b/internal/protocol/proto/wire.proto deleted file mode 100644 --- a/internal/protocol/proto/wire.proto +++ /dev/null @@ -1,141 +0,0 @@ -// SPDX-FileCopyrightText: 2026 Nikolay Govorov -// SPDX-License-Identifier: AGPL-3.0-or-later - -syntax = "proto3"; - -package mirum.wire; - -option go_package = "dimidiumlabs/mirum/internal/protocol/wirepb"; - -// Describes the contract between the worker and the server. -// GRPC is the only contract between them, so to implement your own worker, -// you only need to implement this service. -// -// Authentication is handled via mTLS: the worker presents a self-signed -// X.509 certificate containing its ed25519 public key. The server verifies -// the key against its database during the TLS handshake. -// -// Worker metadata (name, version, os, arch) and clock skew detection -// are embedded in the certificate (URI SAN and NotBefore). -service Worker { - // When a worker has free resources, it requests a task from the server. - // The call will block if the server currently has no tasks. - rpc Poll(PollRequest) returns (Task); - - // When the task is completed, the worker reports the result to the server. - rpc Complete(TaskResult) returns (CompleteResponse); -} - -message Version { - uint32 major = 1; - uint32 minor = 2; - uint32 patch = 3; -} - -enum Os { - OS_UNSPECIFIED = 0; - - // Major - OS_LINUX = 1; - OS_DARWIN = 2; - OS_WINDOWS = 3; - - // BSD family - OS_FREEBSD = 4; - OS_OPENBSD = 5; - OS_NETBSD = 6; - OS_DRAGONFLY = 7; - - // Unix / POSIX - OS_ILLUMOS = 8; - OS_SOLARIS = 9; - OS_AIX = 10; - OS_HURD = 11; - - // Research / exotic - OS_PLAN9 = 12; - OS_HAIKU = 13; - OS_REDOX = 14; - OS_FUCHSIA = 15; - OS_SERENITY = 16; - - // Embedded / real-time - OS_ZEPHYR = 17; - OS_NUTTX = 18; - - // Go-supported - OS_ANDROID = 19; - OS_IOS = 20; - OS_JS = 21; - OS_WASIP1 = 22; -} - -enum Arch { - ARCH_UNSPECIFIED = 0; - - // Common - ARCH_386 = 1; - ARCH_ARM = 2; - ARCH_AMD64 = 3; - ARCH_ARM64 = 4; - - // RISC-V - ARCH_RISCV64 = 5; - ARCH_RISCV32 = 6; - - // Power - ARCH_PPC = 7; - ARCH_PPC64 = 8; - ARCH_PPC64LE = 9; - - // IBM - ARCH_S390 = 10; - ARCH_S390X = 11; - - // MIPS - ARCH_MIPS64LE = 12; - ARCH_MIPS64 = 13; - ARCH_MIPSLE = 14; - ARCH_MIPS = 15; - - // Chinese - ARCH_LOONG64 = 16; - ARCH_SW64 = 17; - - // SPARC - ARCH_SPARC64 = 18; - ARCH_SPARC = 19; - - // Other - ARCH_ALPHA = 20; - ARCH_HPPA = 21; - ARCH_M68K = 22; - ARCH_SH4 = 23; - ARCH_IA64 = 24; - ARCH_WASM = 25; - ARCH_XTENSA = 26; - ARCH_ARC = 27; - ARCH_CSKY = 28; - ARCH_HEXAGON = 29; - ARCH_MICROBLAZE = 30; - ARCH_NIOS2 = 31; - ARCH_OPENRISC = 32; -} - -message PollRequest {} - -message Task { - string id = 1; - string clone_url = 2; - string branch = 3; - string sha = 4; - string repo_full_name = 5; -} - -message TaskResult { - string task_id = 1; - bool success = 2; - string error = 3; -} - -message CompleteResponse {} diff --git a/internal/supervisor/supervisor.go b/internal/supervisor/supervisor.go deleted file mode 100644 --- a/internal/supervisor/supervisor.go +++ /dev/null @@ -1,72 +0,0 @@ -// SPDX-FileCopyrightText: 2026 Nikolay Govorov -// SPDX-License-Identifier: AGPL-3.0-or-later - -// Package supervisor provides a platform-agnostic interface for process -// supervision (systemd, launchd, Windows Services, etc.). -// -// On systems without a recognized supervisor the functions are no-ops. -package supervisor - -import ( - "context" - "net" - "os/signal" - "syscall" -) - -// Supervisor communicates lifecycle events to the process supervisor -// and handles platform-specific shutdown signals. -type Supervisor interface { - // Ready signals that the service has started and is ready to serve. - Ready() - - // Stopping signals that the service has begun graceful shutdown. - Stopping() - - // StartWatchdog begins sending periodic keepalive pings. - // Blocks until ctx is cancelled; call as a goroutine. Returns - // immediately if the supervisor does not require keepalives. - StartWatchdog(ctx context.Context) - - // WaitForStop blocks until the supervisor or OS requests shutdown. - WaitForStop(ctx context.Context) context.Context - - // ActivationListeners returns named listeners passed by the service - // manager (e.g. systemd socket activation). Returns nil when the - // platform does not support listener inheritance. - ActivationListeners() (map[string][]net.Listener, error) -} - -var detectors []func() Supervisor - -func register(fn func() Supervisor) { - detectors = append(detectors, fn) -} - -// Detect returns a Supervisor for the current platform. -func Detect() Supervisor { - for _, fn := range detectors { - if s := fn(); s != nil { - return s - } - } - return &noop{} -} - -type noop struct{} - -func (*noop) Ready() {} - -func (*noop) Stopping() {} - -func (*noop) StartWatchdog(context.Context) {} - -func (*noop) WaitForStop(ctx context.Context) context.Context { - ctx, stop := signal.NotifyContext(ctx, syscall.SIGINT, syscall.SIGTERM) - _ = stop - return ctx -} - -func (*noop) ActivationListeners() (map[string][]net.Listener, error) { - return nil, nil -} diff --git a/internal/supervisor/systemd.go b/internal/supervisor/systemd.go deleted file mode 100644 --- a/internal/supervisor/systemd.go +++ /dev/null @@ -1,83 +0,0 @@ -// SPDX-FileCopyrightText: 2026 Nikolay Govorov -// SPDX-License-Identifier: AGPL-3.0-or-later - -//go:build linux - -package supervisor - -import ( - "context" - "log/slog" - "os" - "os/signal" - "strconv" - "syscall" - "time" - - "net" - - "github.com/coreos/go-systemd/v22/activation" - "github.com/coreos/go-systemd/v22/daemon" -) - -func init() { - register(func() Supervisor { - if os.Getenv("NOTIFY_SOCKET") != "" { - return &systemd{} - } - return nil - }) -} - -type systemd struct{} - -func (*systemd) WaitForStop(ctx context.Context) context.Context { - ctx, stop := signal.NotifyContext(ctx, syscall.SIGTERM, syscall.SIGINT) - _ = stop // stop allows you to cancel the observer, but it's not particularly useful - return ctx -} - -func (*systemd) Ready() { - if _, err := daemon.SdNotify(false, daemon.SdNotifyReady); err != nil { - slog.Warn("sd_notify ready", "err", err) - } -} - -func (*systemd) Stopping() { - if _, err := daemon.SdNotify(false, daemon.SdNotifyStopping); err != nil { - slog.Warn("sd_notify stopping", "err", err) - } -} - -func (*systemd) StartWatchdog(ctx context.Context) { - usecStr := os.Getenv("WATCHDOG_USEC") - if usecStr == "" { - return - } - - usec, err := strconv.ParseInt(usecStr, 10, 64) - if err != nil || usec <= 0 { - return - } - - interval := time.Duration(usec) * time.Microsecond / 2 - ticker := time.NewTicker(interval) - defer ticker.Stop() - - for { - if _, err := daemon.SdNotify(false, daemon.SdNotifyWatchdog); err != nil { - slog.Warn("sd_notify watchdog", "err", err) - return - } - - select { - case <-ticker.C: - case <-ctx.Done(): - return - } - } -} - -func (*systemd) ActivationListeners() (map[string][]net.Listener, error) { - return activation.ListenersWithNames() -} diff --git a/mise.lock b/mise.lock deleted file mode 100644 --- a/mise.lock +++ /dev/null @@ -1,84 +0,0 @@ -# @generated - this file is auto-generated by `mise lock` https://mise.jdx.dev/dev-tools/mise-lock.html - -[[tools.buf]] -version = "1.67.0" -backend = "aqua:bufbuild/buf" - -[tools.buf."platforms.linux-arm64"] -checksum = "sha256:3d542f0f99159c8b6442f4c1f6d8f59fd669356b97aa6f6b67ea4a5af7f81ec7" -url = "https://github.com/bufbuild/buf/releases/download/v1.67.0/buf-Linux-aarch64.tar.gz" -url_api = "https://api.github.com/repos/bufbuild/buf/releases/assets/386412995" -provenance = "minisign" - -[tools.buf."platforms.linux-x64"] -checksum = "sha256:b8682c2ea6f377cf1441e4fbc54ed1ecccbb9f2d887560c4320cf7807f015519" -url = "https://github.com/bufbuild/buf/releases/download/v1.67.0/buf-Linux-x86_64.tar.gz" -url_api = "https://api.github.com/repos/bufbuild/buf/releases/assets/386413039" -provenance = "minisign" - -[[tools.gh]] -version = "2.96.0" -backend = "aqua:cli/cli" - -[tools.gh."platforms.linux-arm64"] -checksum = "sha256:06f86ec7103d41993b76cd78072f43595c34aaa56506d971d9860e67140bf909" -url = "https://github.com/cli/cli/releases/download/v2.96.0/gh_2.96.0_linux_arm64.tar.gz" -url_api = "https://api.github.com/repos/cli/cli/releases/assets/464728549" -provenance = "github-attestations" - -[tools.gh."platforms.linux-x64"] -checksum = "sha256:83d5c2ccad5498f58bf6368acb1ab32588cf43ab3a4b1c301bf36328b1c8bd60" -url = "https://github.com/cli/cli/releases/download/v2.96.0/gh_2.96.0_linux_amd64.tar.gz" -url_api = "https://api.github.com/repos/cli/cli/releases/assets/464728543" -provenance = "github-attestations" - -[[tools.go]] -version = "1.26.6" -backend = "core:go" - -[tools.go."platforms.linux-arm64"] -checksum = "sha256:d0507e9e9d7fe012aae570108cbd76c15de879e17130ab8cb90d4d7445cb1f2e" -url = "https://dl.google.com/go/go1.26.6.linux-arm64.tar.gz" - -[tools.go."platforms.linux-x64"] -checksum = "sha256:708effb774be8237570d0add163225abbdfaf4fca28b2611df167beba4feef89" -url = "https://dl.google.com/go/go1.26.6.linux-amd64.tar.gz" - -[[tools.node]] -version = "24.18.0" -backend = "core:node" - -[tools.node."platforms.linux-arm64"] -checksum = "sha256:6b4484c2190274175df9aa8f28e2d758a819cb1c1fe6ab481e2f95b463ab8508" -url = "https://nodejs.org/dist/v24.18.0/node-v24.18.0-linux-arm64.tar.gz" - -[tools.node."platforms.linux-x64"] -checksum = "sha256:783130984963db7ba9cbd01089eaf2c2efb055c7c1693c943174b967b3050cb8" -url = "https://nodejs.org/dist/v24.18.0/node-v24.18.0-linux-x64.tar.gz" - -[[tools.task]] -version = "3.52.0" -backend = "aqua:go-task/task" - -[tools.task."platforms.linux-arm64"] -checksum = "sha256:7e0044108830cec0534577b289564e3b7c83e6df276feb631a1edc63d04e4ebe" -url = "https://github.com/go-task/task/releases/download/v3.52.0/task_linux_arm64.tar.gz" -url_api = "https://api.github.com/repos/go-task/task/releases/assets/464613352" - -[tools.task."platforms.linux-x64"] -checksum = "sha256:02c679ffae53dca791804847d78b31731615894e292948397c971c87ac9e95bd" -url = "https://github.com/go-task/task/releases/download/v3.52.0/task_linux_amd64.tar.gz" -url_api = "https://api.github.com/repos/go-task/task/releases/assets/464613330" - -[[tools.zig]] -version = "0.16.0" -backend = "core:zig" - -[tools.zig."platforms.linux-arm64"] -checksum = "sha256:ea4b09bfb22ec6f6c6ceac57ab63efb6b46e17ab08d21f69f3a48b38e1534f17" -url = "https://ziglang.org/download/0.16.0/zig-aarch64-linux-0.16.0.tar.xz" - -[tools.zig."platforms.linux-x64"] -checksum = "sha256:70e49664a74374b48b51e6f3fdfbf437f6395d42509050588bd49abe52ba3d00" -url = "https://ziglang.org/download/0.16.0/zig-x86_64-linux-0.16.0.tar.xz" -provenance = "minisign" diff --git a/mise.toml b/mise.toml deleted file mode 100644 --- a/mise.toml +++ /dev/null @@ -1,38 +0,0 @@ -# SPDX-FileCopyrightText: 2026 Nikolay Govorov -# SPDX-License-Identifier: AGPL-3.0-or-later - -min_version = "2026.7.5" - -[settings] -experimental = true - -[tools] -buf = "1.67.0" -gh = "2.96.0" -go = "1.26.6" -node = "24.18.0" -task = "3.52.0" # Removed after Taskfile.yml is migrated to mise tasks. -zig = "0.16.0" - -[bootstrap.packages] -# APT -"apt:apt-utils" = "latest" -"apt:createrepo-c" = "latest" -"apt:debsigs" = "latest" -"apt:gnupg" = "latest" -"apt:openssl" = "latest" -"apt:rpm" = "latest" -"apt:zip" = "latest" - -# DNF -"dnf:createrepo_c" = "latest" -"dnf:gnupg2" = "latest" -"dnf:openssl" = "latest" -"dnf:rpm" = "latest" -"dnf:zip" = "latest" - -[task_config] -dir = "{{cwd}}" -includes = [ - "git::https://github.com/dimidiumlabs/platform.git//tasks?ref=8bc35fe8be889c50db2d1fb4425cc1b4097dc6b8", -] diff --git a/nfpm.yaml b/nfpm.yaml deleted file mode 100644 --- a/nfpm.yaml +++ /dev/null @@ -1,132 +0,0 @@ -# SPDX-FileCopyrightText: 2026 Nikolay Govorov -# SPDX-License-Identifier: AGPL-3.0-or-later - -# yaml-language-server: $schema=https://nfpm.goreleaser.com/schema.json -# vim: set ts=2 sw=2 tw=0 fo=cnqoj - -name: mirum -arch: ${ARCH} -version: ${VERSION} -license: AGPL-3.0-or-later -platform: linux -maintainer: Nikolay Govorov <me@govorov.online> -description: Modern CI platform - -contents: - - src: ./LICENSES/* - dst: /usr/share/doc/mirum/LICENSES/ - - src: ./README.md - dst: /usr/share/doc/mirum/README.md - - - src: build/tmp/mirum-server - dst: /usr/local/bin/mirum-server - file_info: - mode: 0755 - - - src: build/tmp/mirum-worker - dst: /usr/local/bin/mirum-worker - file_info: - mode: 0755 - - - src: build/tmp/mirum - dst: /usr/local/bin/mirum - file_info: - mode: 0755 - - # mirum-agent guest binaries — payload the worker injects into VMs. - # Not host executables; the full guest matrix ships in every package. - - src: build/agent/mirum-agent-* - dst: /usr/lib/mirum/agent/ - file_info: - mode: 0755 - - - src: packaging/server/config.yaml - dst: /etc/mirum/server/config.yaml - type: config|noreplace - file_info: - mode: 0640 - owner: root - group: mirum-server - - - src: packaging/worker/default.yaml - dst: /etc/mirum/worker/default.yaml - type: config|noreplace - file_info: - mode: 0640 - owner: root - group: mirum-worker - - - dst: /var/lib/mirum-server - type: dir - file_info: - mode: 0750 - owner: mirum-server - group: mirum-server - - - dst: /var/lib/mirum-worker - type: dir - file_info: - mode: 0750 - owner: mirum-worker - group: mirum-worker - - # nfpm's `packager` field is a single value, so systemd units get listed - # twice (once per non-apk packager) to scope them away from Alpine. - - src: packaging/mirum-server.service - dst: /usr/lib/systemd/system/mirum-server.service - packager: deb - file_info: - mode: 0644 - - src: packaging/mirum-server.service - dst: /usr/lib/systemd/system/mirum-server.service - packager: rpm - file_info: - mode: 0644 - - - src: packaging/mirum-worker@.service - dst: /usr/lib/systemd/system/mirum-worker@.service - packager: deb - file_info: - mode: 0644 - - src: packaging/mirum-worker@.service - dst: /usr/lib/systemd/system/mirum-worker@.service - packager: rpm - file_info: - mode: 0644 - - - src: packaging/mirum-server.initd - dst: /etc/init.d/mirum-server - packager: apk - file_info: - mode: 0755 - - - src: packaging/mirum-worker.initd - dst: /etc/init.d/mirum-worker - packager: apk - file_info: - mode: 0755 - - -scripts: - preinstall: packaging/scripts/preinstall.sh - postinstall: packaging/scripts/postinstall.sh - preremove: packaging/scripts/preremove.sh - -deb: - signature: - method: debsign - key_id: ${GPG_KEY_ID} - key_file: ${SIGNING_PRIVATE_KEY} - -rpm: - group: System Environment/Daemons - signature: - key_id: ${GPG_KEY_ID} - key_file: ${SIGNING_PRIVATE_KEY} - -apk: - signature: - key_file: ${APK_SIGNING_KEY} - # nFPM appends ".rsa.pub"; the versioned basename lets clients retain - # historical package keys during rotation. - key_name: packages.${PACKAGE_KEY_VERSION} diff --git a/packaging/dl/404.html b/packaging/dl/404.html deleted file mode 100644 --- a/packaging/dl/404.html +++ /dev/null @@ -1,17 +0,0 @@ -<!-- - SPDX-FileCopyrightText: 2026 Nikolay Govorov - SPDX-License-Identifier: AGPL-3.0-or-later ---> -<!DOCTYPE html> -<html lang="en"> -<head> - <meta charset="utf-8"> - <meta http-equiv="refresh" content="0; URL=https://mirum.dev" /> - - <title>404 - Mirum</title> -</head> -<body> - <h1>404</h1> - <p>Looking for the mirum docs? Visit <a href="https://mirum.dev">mirum site</a>.</p> -</body> -</html> diff --git a/packaging/dl/index.html b/packaging/dl/index.html deleted file mode 100644 --- a/packaging/dl/index.html +++ /dev/null @@ -1,17 +0,0 @@ -<!-- - SPDX-FileCopyrightText: 2026 Nikolay Govorov - SPDX-License-Identifier: AGPL-3.0-or-later ---> -<!DOCTYPE html> -<html lang="en"> -<head> - <meta charset="utf-8"> - <meta name="viewport" content="width=device-width, initial-scale=1"> - <meta http-equiv="refresh" content="0; URL=https://mirum.dev" /> - - <title>Mirum - Package Repository</title> -</head> -<body> - <p>Visit <a href="https://mirum.dev">mirum site</a>.</p> -</body> -</html> diff --git a/packaging/dl/robots.txt b/packaging/dl/robots.txt deleted file mode 100644 --- a/packaging/dl/robots.txt +++ /dev/null @@ -1,2 +0,0 @@ -User-Agent: * -Disallow: / diff --git a/packaging/logo.svg b/packaging/logo.svg deleted file mode 100644 --- a/packaging/logo.svg +++ /dev/null @@ -1,25 +0,0 @@ -<?xml version="1.0" encoding="UTF-8" standalone="no"?> -<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> -<svg viewBox="-150 -150 800 800" xmlns="http://www.w3.org/2000/svg" fill="#00afaf"> - <rect x="0" y="100" width="100" height="100"></rect> - <rect x="0" y="200" width="100" height="100"></rect> - <rect x="0" y="300" width="100" height="100"></rect> - <rect x="0" y="400" width="100" height="100"></rect> - <rect x="100" y="0" width="100" height="100"></rect> - <rect x="100" y="100" width="100" height="100"></rect> - <rect x="100" y="300" width="100" height="100"></rect> - <rect x="100" y="400" width="100" height="100"></rect> - <rect x="200" y="0" width="100" height="100"></rect> - <rect x="200" y="100" width="100" height="100"></rect> - <rect x="200" y="200" width="100" height="100"></rect> - <rect x="200" y="300" width="100" height="100"></rect> - <rect x="200" y="400" width="100" height="100"></rect> - <rect x="400" y="100" width="100" height="100"></rect> - <rect x="400" y="200" width="100" height="100"></rect> - <rect x="400" y="300" width="100" height="100"></rect> - <rect x="400" y="400" width="100" height="100"></rect> - <rect x="300" y="0" width="100" height="100"></rect> - <rect x="300" y="100" width="100" height="100"></rect> - <rect x="300" y="300" width="100" height="100"></rect> - <rect x="300" y="400" width="100" height="100"></rect> -</svg> diff --git a/packaging/mirum-server.initd b/packaging/mirum-server.initd deleted file mode 100644 --- a/packaging/mirum-server.initd +++ /dev/null @@ -1,24 +0,0 @@ -#!/sbin/openrc-run -# SPDX-FileCopyrightText: 2026 Nikolay Govorov -# SPDX-License-Identifier: AGPL-3.0-or-later - -description="Mirum daemon (modern CI platform)" - -command="/usr/local/bin/mirum-server" -command_args="daemon --config=/etc/mirum/server/config.yaml" -command_user="mirum-server:mirum-server" - -supervisor="supervise-daemon" -respawn_delay=30 -output_log="/var/log/mirum-server/out.log" -error_log="/var/log/mirum-server/err.log" - -depend() { - need net - after firewall postgresql -} - -start_pre() { - checkpath -d -o mirum-server:mirum-server -m 0750 /var/log/mirum-server - checkpath -d -o mirum-server:mirum-server -m 0750 /run/mirum-server -} diff --git a/packaging/mirum-server.service b/packaging/mirum-server.service deleted file mode 100644 --- a/packaging/mirum-server.service +++ /dev/null @@ -1,64 +0,0 @@ -# SPDX-FileCopyrightText: 2026 Nikolay Govorov -# SPDX-License-Identifier: AGPL-3.0-or-later - -[Unit] -Description=Mirum daemon (modern CI platform) -Requires=network-online.target -After=time-sync.target network-online.target remote-fs.target nss-lookup.target postgresql.service -Wants=time-sync.target - -# Socket activation (optional): -# Create mirum-server.socket with named file descriptors "web" and "grpc": -# -# [Socket] -# ListenStream=0.0.0.0:3000 -# FileDescriptorName=web -# -# [Socket] -# ListenStream=0.0.0.0:2026 -# FileDescriptorName=grpc -# -# Without socket activation the daemon binds www_addr and grpc_addr from config. - -[Service] -Type=notify -User=mirum-server -Group=mirum-server -Restart=always -RestartSec=30 -WatchdogSec=30 -NotifyAccess=main -ExecPaths=/usr/local/bin/mirum-server /usr/lib -ExecStart=/usr/local/bin/mirum-server daemon --config=/etc/mirum/server/config.yaml -LimitCORE=infinity -LimitNOFILE=500000 -AmbientCapabilities=CAP_NET_BIND_SERVICE - -# %p is resolved to the systemd unit name -LogsDirectory=%p -StateDirectory=%p -CacheDirectory=%p -RuntimeDirectory=%p - -UMask=0077 -LockPersonality=yes -NoNewPrivileges=yes -PrivateDevices=yes -PrivateTmp=true -ProcSubset=pid -ProtectClock=yes -ProtectControlGroups=yes -ProtectHome=yes -ProtectHostname=yes -ProtectKernelLogs=yes -ProtectKernelModules=yes -ProtectKernelTunables=yes -ProtectProc=invisible -ProtectSystem=strict -RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX -RestrictNamespaces=yes -RestrictSUIDSGID=yes - -[Install] -# service should not start from the rescue shell (rescue.target). -WantedBy=multi-user.target diff --git a/packaging/mirum-worker.initd b/packaging/mirum-worker.initd deleted file mode 100644 --- a/packaging/mirum-worker.initd +++ /dev/null @@ -1,35 +0,0 @@ -#!/sbin/openrc-run -# SPDX-FileCopyrightText: 2026 Nikolay Govorov -# SPDX-License-Identifier: AGPL-3.0-or-later - -# Templated service. To run a worker named "default": -# ln -s mirum-worker /etc/init.d/mirum-worker.default -# rc-service mirum-worker.default start -# rc-update add mirum-worker.default default -# -# The config file is /etc/mirum/worker/<instance>.yaml. - -case "$RC_SVCNAME" in - mirum-worker) instance=default ;; - mirum-worker.*) instance="${RC_SVCNAME#mirum-worker.}" ;; -esac - -description="Mirum worker (${instance})" - -command="/usr/local/bin/mirum-worker" -command_args="--config=/etc/mirum/worker/${instance}.yaml" -command_user="mirum-worker:mirum-worker" -supervisor="supervise-daemon" -respawn_delay=30 -output_log="/var/log/mirum-worker/${instance}.out.log" -error_log="/var/log/mirum-worker/${instance}.err.log" - -depend() { - need net - after firewall -} - -start_pre() { - checkpath -d -o mirum-worker:mirum-worker -m 0750 /var/log/mirum-worker - checkpath -d -o mirum-worker:mirum-worker -m 0750 /run/mirum-worker -} diff --git a/packaging/mirum-worker@.service b/packaging/mirum-worker@.service deleted file mode 100644 --- a/packaging/mirum-worker@.service +++ /dev/null @@ -1,48 +0,0 @@ -# SPDX-FileCopyrightText: 2026 Nikolay Govorov -# SPDX-License-Identifier: AGPL-3.0-or-later - -[Unit] -Description=Mirum worker %i (modern CI platform) -Requires=network-online.target -After=time-sync.target network-online.target remote-fs.target nss-lookup.target -Wants=time-sync.target - -[Service] -Type=notify -User=mirum-worker -Group=mirum-worker -Restart=always -RestartSec=30 -WatchdogSec=30 -NotifyAccess=main -ExecPaths=/usr/local/bin/mirum-worker -ExecStart=/usr/local/bin/mirum-worker --config=/etc/mirum/worker/%i.yaml -LimitCORE=infinity -LimitNOFILE=500000 -AmbientCapabilities= - -# %p is resolved to the systemd unit name -LogsDirectory=%p -StateDirectory=%p -CacheDirectory=%p -RuntimeDirectory=%p - -UMask=0077 -LockPersonality=yes -NoNewPrivileges=yes -PrivateTmp=true -ProtectClock=yes -ProtectControlGroups=yes -ProtectHome=yes -ProtectHostname=yes -ProtectKernelLogs=yes -ProtectKernelModules=yes -ProtectKernelTunables=yes -ProtectSystem=strict -RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX -RestrictNamespaces=yes -RestrictSUIDSGID=yes - -[Install] -# service should not start from the rescue shell (rescue.target). -WantedBy=multi-user.target diff --git a/packaging/scripts/postinstall.sh b/packaging/scripts/postinstall.sh deleted file mode 100644 --- a/packaging/scripts/postinstall.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/sh -# SPDX-FileCopyrightText: 2026 Nikolay Govorov -# SPDX-License-Identifier: AGPL-3.0-or-later - -set -e - -if [ -x "/bin/systemctl" ] && [ -d /run/systemd/system ] && [ -f /usr/lib/systemd/system/mirum-server.service ]; then - /bin/systemctl daemon-reload - - # Don't enable by default, don't know in advance whether it's a daemon or a worker - # /bin/systemctl enable mirum-server - # /bin/systemctl enable mirum-worker -fi diff --git a/packaging/scripts/preinstall.sh b/packaging/scripts/preinstall.sh deleted file mode 100644 --- a/packaging/scripts/preinstall.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/bin/sh -# SPDX-FileCopyrightText: 2026 Nikolay Govorov -# SPDX-License-Identifier: AGPL-3.0-or-later - -set -e - -nologin=/usr/sbin/nologin -[ -x "$nologin" ] || nologin=/sbin/nologin -[ -x "$nologin" ] || nologin=/bin/false - -for svc in mirum-server mirum-worker; do - if ! getent group "$svc" >/dev/null; then - if command -v groupadd >/dev/null; then - groupadd --system "$svc" - else - addgroup -S "$svc" - fi - fi - if ! getent passwd "$svc" >/dev/null; then - if command -v useradd >/dev/null; then - useradd --system --gid "$svc" --no-create-home --shell "$nologin" "$svc" - else - adduser -S -H -G "$svc" -s "$nologin" "$svc" - fi - fi -done diff --git a/packaging/scripts/preremove.sh b/packaging/scripts/preremove.sh deleted file mode 100644 --- a/packaging/scripts/preremove.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/bin/sh -# SPDX-FileCopyrightText: 2026 Nikolay Govorov -# SPDX-License-Identifier: AGPL-3.0-or-later - -set -e - -if [ -x "/bin/systemctl" ] && [ -d /run/systemd/system ]; then - /bin/systemctl stop mirum-server.service || true - /bin/systemctl disable mirum-server.service || true - - /bin/systemctl stop 'mirum-worker@*' || true - /bin/systemctl disable mirum-worker@.service || true -fi - -if command -v rc-service >/dev/null; then - rc-service mirum-server stop || true - rc-update del mirum-server || true - - for link in /etc/init.d/mirum-worker.*; do - [ -e "$link" ] || continue - svc=$(basename "$link") - rc-service "$svc" stop || true - rc-update del "$svc" || true - done -fi diff --git a/packaging/server/config.yaml b/packaging/server/config.yaml deleted file mode 100644 --- a/packaging/server/config.yaml +++ /dev/null @@ -1,27 +0,0 @@ -# SPDX-FileCopyrightText: 2026 Nikolay Govorov -# SPDX-License-Identifier: AGPL-3.0-or-later - -# Ignored when the corresponding systemd socket activation fd is present. -# See mirum-server.socket for details (FileDescriptorName=grpc / web). -grpc_addr: :2026 -web_addr: :3000 -admin_socket: /run/mirum-server/admin.sock -database_uri: "" -webhook_secret: "" -token: "" -pepper: "" - -grpc_tls: - cert: "" - key: "" - -# Optional — omit or leave empty to disable TLS on the web listener. -# web_tls: -# cert: "" -# key: "" - -# CIDR list of trusted reverse proxies for X-Forwarded-For resolution. -# Empty = trust RemoteAddr only (safe default). -trusted_proxies: - - 127.0.0.0/8 - - ::1/128 diff --git a/packaging/worker/default.yaml b/packaging/worker/default.yaml deleted file mode 100644 --- a/packaging/worker/default.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# SPDX-FileCopyrightText: 2026 Nikolay Govorov -# SPDX-License-Identifier: AGPL-3.0-or-later - -server: localhost:2026 - -# Ed25519 private key for worker authentication (PEM-encoded PKCS8). -# Generate with: -# openssl genpkey -algorithm Ed25519 -out /etc/mirum/worker/default.key -# chmod 0640 /etc/mirum/worker/default.key -# chown root:mirum-worker /etc/mirum/worker/default.key -# -# Register the public key on the daemon: -# PUBKEY=$(openssl pkey -in /etc/mirum/worker/default.key -pubout -outform der | base64 -w0) -# mirum-server --socket /run/mirum-server/admin.sock worker create --pubkey "$PUBKEY" -key_file: /etc/mirum/worker/default.key - -# Custom CA certificate for self-signed/dev TLS. -# Leave empty to use system trust store. -tls_ca: "" diff --git a/tools/licensegen/main.go b/tools/licensegen/main.go deleted file mode 100644 --- a/tools/licensegen/main.go +++ /dev/null @@ -1,224 +0,0 @@ -// SPDX-FileCopyrightText: 2026 Nikolay Govorov -// SPDX-License-Identifier: AGPL-3.0-or-later - -// Command licensegen writes build/licenses.json — the third-party dependency -// manifest embedded into mirum binaries. Output is pre-grouped: each -// ecosystem contains SPDX groups; each group contains text variants (packages -// sharing identical LICENSE text collapse into one variant); each variant -// lists its deps. The frontend renders without further transformation. -package main - -import ( - "cmp" - "encoding/json" - "flag" - "fmt" - "log" - "os" - "path/filepath" - "slices" - "strconv" - "strings" - "time" - - "github.com/github/go-spdx/v2/spdxexp" -) - -// allowedSPDX are SPDX ids approved for runtime deps. mirum sells a -// commercial license, so GPL-family ids are excluded even though the -// upstream distribution is AGPL-3.0-or-later. -var allowedSPDX = []string{ - "0BSD", "Apache-2.0", "BSD-2-Clause", "BSD-3-Clause", - "CC0-1.0", "ISC", "MIT", "OFL-1.1", - "Unicode-3.0", "Unlicense", "Zlib", -} - -// collapsedScopes are npm scopes whose sub-packages come from a single -// upstream monorepo and should render as one "@scope" row. All sub-packages -// of a collapsed scope must declare the same SPDX — mismatch aborts. -var collapsedScopes = []string{"@radix-ui"} - -type Dep struct { - Name string `json:"name"` - Version string `json:"version,omitempty"` - SPDX string `json:"spdx"` - URL string `json:"url,omitempty"` - Count int `json:"count,omitempty"` // >0 means a collapsed scope entry covering N sub-packages - - atoms []string // internal: atomic SPDX ids for cross-listing - text string // internal: verbatim LICENSE text -} - -type Variant struct { - Text string `json:"text"` - Deps []Dep `json:"deps"` -} - -type Group struct { - SPDX string `json:"spdx"` - Total int `json:"total"` - Variants []Variant `json:"variants"` -} - -type Ecosystem struct { - Total int `json:"total"` - Groups []Group `json:"groups"` -} - -type Manifest struct { - GeneratedAt string `json:"generated_at"` - Go Ecosystem `json:"go"` - NPM Ecosystem `json:"npm"` -} - -func main() { - log.SetFlags(0) - log.SetPrefix("licensegen: ") - - out := flag.String("out", "", "output path for licenses.json") - repo := flag.String("repo", "", "repo root (defaults to walking up from cwd)") - flag.Parse() - - if *out == "" { - log.Fatal("missing required -out flag") - } - root, err := resolveRepoRoot(*repo) - if err != nil { - log.Fatal(err) - } - - goDeps, err := scanGo(root) - if err != nil { - log.Fatalf("scan go: %v", err) - } - npmDeps, err := scanNPM(root) - if err != nil { - log.Fatalf("scan npm: %v", err) - } - - m := Manifest{ - GeneratedAt: sourceDateEpoch().UTC().Format(time.RFC3339), - Go: group(goDeps), - NPM: group(npmDeps), - } - - body, err := json.MarshalIndent(m, "", " ") - if err != nil { - log.Fatal(err) - } - if err := os.WriteFile(*out, append(body, '\n'), 0o644); err != nil { - log.Fatalf("write %s: %v", *out, err) - } - log.Printf("wrote %s (go=%d npm=%d)", *out, m.Go.Total, m.NPM.Total) -} - -// group assembles deps into SPDX atoms × text variants. A dep with a compound -// expression ("A AND B") is cross-listed under every atom. -// Unexported Dep fields (atoms, text) are dropped by encoding/json. -func group(deps []Dep) Ecosystem { - // atom → text → *Variant - byAtom := map[string]map[string]*Variant{} - for _, d := range deps { - for _, atom := range d.atoms { - byText := byAtom[atom] - if byText == nil { - byText = map[string]*Variant{} - byAtom[atom] = byText - } - v := byText[d.text] - if v == nil { - v = &Variant{Text: d.text} - byText[d.text] = v - } - v.Deps = append(v.Deps, d) - } - } - - groups := make([]Group, 0, len(byAtom)) - for atom, byText := range byAtom { - variants := make([]Variant, 0, len(byText)) - total := 0 - for _, v := range byText { - slices.SortFunc(v.Deps, func(a, b Dep) int { return strings.Compare(a.Name, b.Name) }) - variants = append(variants, *v) - total += len(v.Deps) - } - slices.SortFunc(variants, func(a, b Variant) int { - return cmp.Or( - cmp.Compare(len(b.Deps), len(a.Deps)), // desc - cmp.Compare(a.Deps[0].Name, b.Deps[0].Name), - ) - }) - groups = append(groups, Group{SPDX: atom, Total: total, Variants: variants}) - } - slices.SortFunc(groups, func(a, b Group) int { - return cmp.Or(cmp.Compare(b.Total, a.Total), cmp.Compare(a.SPDX, b.SPDX)) - }) - return Ecosystem{Total: len(deps), Groups: groups} -} - -// validateSPDX checks expr against allowedSPDX and returns its atomic ids. -func validateSPDX(expr string) ([]string, error) { - ok, err := spdxexp.Satisfies(expr, allowedSPDX) - if err != nil { - return nil, err - } - if !ok { - return nil, fmt.Errorf("SPDX %q not allowed", expr) - } - return spdxexp.ExtractLicenses(expr) -} - -// readLicenseFile returns the contents and path of the first LICENSE-like -// file in dir. "LICENSE", "LICENCE", "COPYING" prefixes with any extension. -func readLicenseFile(dir string) (string, string, error) { - entries, err := os.ReadDir(dir) - if err != nil { - return "", "", err - } - for _, e := range entries { - n := strings.ToLower(e.Name()) - if strings.HasPrefix(n, "license") || strings.HasPrefix(n, "licence") || strings.HasPrefix(n, "copying") { - p := filepath.Join(dir, e.Name()) - if b, err := os.ReadFile(p); err == nil { - return normalize(string(b)), p, nil - } - } - } - return "", "", os.ErrNotExist -} - -// normalize strips BOM + trims whitespace + LF line endings, so near-identical -// texts (differing only by trailing blank lines or CRLF) dedupe. -func normalize(s string) string { - s = strings.TrimPrefix(s, "\ufeff") - s = strings.ReplaceAll(s, "\r\n", "\n") - return strings.TrimSpace(s) + "\n" -} - -func sourceDateEpoch() time.Time { - if v, _ := strconv.ParseInt(os.Getenv("SOURCE_DATE_EPOCH"), 10, 64); v > 0 { - return time.Unix(v, 0) - } - return time.Now() -} - -func resolveRepoRoot(explicit string) (string, error) { - if explicit != "" { - return filepath.Abs(explicit) - } - dir, err := os.Getwd() - if err != nil { - return "", err - } - for { - if _, err := os.Stat(filepath.Join(dir, "go.mod")); err == nil { - return dir, nil - } - p := filepath.Dir(dir) - if p == dir { - return "", os.ErrNotExist - } - dir = p - } -} diff --git a/tools/licensegen/scan_go.go b/tools/licensegen/scan_go.go deleted file mode 100644 --- a/tools/licensegen/scan_go.go +++ /dev/null @@ -1,102 +0,0 @@ -// SPDX-FileCopyrightText: 2026 Nikolay Govorov -// SPDX-License-Identifier: AGPL-3.0-or-later - -package main - -import ( - "encoding/json" - "errors" - "fmt" - "io" - "os" - "os/exec" - - "github.com/google/licensecheck" -) - -// goEntrypoints are the main packages whose linker inputs form the runtime -// graph. Every production binary we ship lives under cmd/. -var goEntrypoints = []string{ - "./cmd/mirum-server", - "./cmd/mirum-worker", - "./cmd/mirum", -} - -// scanGo reads the prod dependency graph via `go list -deps -json` and -// classifies each module's LICENSE file with google/licensecheck at a 75% -// coverage threshold. Below that we refuse to guess. -func scanGo(root string) ([]Dep, error) { - args := append([]string{"list", "-tags=licensegen", "-deps", "-json"}, goEntrypoints...) - cmd := exec.Command("go", args...) - cmd.Dir = root - cmd.Stderr = os.Stderr - stdout, err := cmd.StdoutPipe() - if err != nil { - return nil, err - } - if err := cmd.Start(); err != nil { - return nil, err - } - - type mod struct { - Path, Version, Dir string - Main bool - Replace *mod - } - type pkg struct { - Standard bool - Module *mod - } - - mods := map[string]*mod{} - dec := json.NewDecoder(stdout) - for { - var p pkg - if err := dec.Decode(&p); err != nil { - if errors.Is(err, io.EOF) { - break - } - return nil, err - } - if p.Standard || p.Module == nil || p.Module.Main { - continue - } - m := p.Module - if m.Replace != nil { - m = m.Replace - } - if m.Dir == "" { - return nil, fmt.Errorf("%s: empty Dir (run `go mod download`)", m.Path) - } - mods[m.Path+"@"+m.Version] = m - } - if err := cmd.Wait(); err != nil { - return nil, err - } - - deps := make([]Dep, 0, len(mods)) - for _, m := range mods { - text, path, err := readLicenseFile(m.Dir) - if err != nil { - return nil, fmt.Errorf("%s@%s: no LICENSE file", m.Path, m.Version) - } - cov := licensecheck.Scan([]byte(text)) - if cov.Percent < 75 || len(cov.Match) == 0 { - return nil, fmt.Errorf("%s@%s: cannot classify %s (%.0f%%)", m.Path, m.Version, path, cov.Percent) - } - spdx := cov.Match[0].ID - atoms, err := validateSPDX(spdx) - if err != nil { - return nil, fmt.Errorf("%s@%s: %w", m.Path, m.Version, err) - } - deps = append(deps, Dep{ - Name: m.Path, - Version: m.Version, - SPDX: spdx, - URL: "https://pkg.go.dev/" + m.Path + "@" + m.Version, - atoms: atoms, - text: text, - }) - } - return deps, nil -} diff --git a/tools/licensegen/scan_npm.go b/tools/licensegen/scan_npm.go deleted file mode 100644 --- a/tools/licensegen/scan_npm.go +++ /dev/null @@ -1,238 +0,0 @@ -// SPDX-FileCopyrightText: 2026 Nikolay Govorov -// SPDX-License-Identifier: AGPL-3.0-or-later - -package main - -import ( - "encoding/json" - "fmt" - "os" - "path/filepath" - "slices" - "strings" -) - -const webDir = "cmd/mirum-server/web" - -// scanNPM reads package-lock.json (v3+), filters to runtime packages, reads -// each package's LICENSE verbatim (or synthesizes a copyright notice when -// none ships — canonical SPDX text is never substituted), and collapses the -// scopes listed in collapsedScopes into single "@scope" rows. -func scanNPM(root string) ([]Dep, error) { - var lock struct { - LockfileVersion int `json:"lockfileVersion"` - Packages map[string]struct { - Version string `json:"version"` - License any `json:"license"` - Dev, DevOptional, Link, Peer bool - } `json:"packages"` - } - raw, err := os.ReadFile(filepath.Join(root, webDir, "package-lock.json")) - if err != nil { - return nil, err - } - if err := json.Unmarshal(raw, &lock); err != nil { - return nil, err - } - if lock.LockfileVersion < 3 { - return nil, fmt.Errorf("lockfileVersion %d unsupported, regenerate with npm v7+", lock.LockfileVersion) - } - - // npm hoists identical name@version under multiple paths; skip duplicates - // before the expensive LICENSE read. - seen := map[string]bool{} - var deps []Dep - for path, pkg := range lock.Packages { - if path == "" || pkg.Link || pkg.Dev || pkg.DevOptional { - continue - } - name := npmName(path) - key := name + "@" + pkg.Version - if seen[key] { - continue - } - seen[key] = true - - expr, err := npmSPDX(pkg.License) - if err != nil { - return nil, fmt.Errorf("%s: %w", key, err) - } - - atoms, err := validateSPDX(expr) - if err != nil { - return nil, fmt.Errorf("%s declares %q: %w", key, expr, err) - } - - pkgDir := filepath.Join(root, webDir, path) - deps = append(deps, Dep{ - Name: name, - Version: pkg.Version, - SPDX: expr, - URL: "https://www.npmjs.com/package/" + name + "/v/" + pkg.Version, - atoms: atoms, - text: npmText(pkgDir, name), - }) - } - - scopeOf := func(name string) string { - if !strings.HasPrefix(name, "@") { - return "" - } - scope, _, ok := strings.Cut(name, "/") - if !ok { - return "" - } - return scope - } - - buckets := map[string][]Dep{} - var out []Dep - for _, d := range deps { - if s := scopeOf(d.Name); slices.Contains(collapsedScopes, s) { - buckets[s] = append(buckets[s], d) - continue - } - out = append(out, d) - } - - for scope, items := range buckets { - spdx := items[0].SPDX - version := items[0].Version - for _, d := range items[1:] { - if d.SPDX != spdx { - return nil, fmt.Errorf("scope %s: mixed SPDX %q vs %q (%s)", scope, spdx, d.SPDX, d.Name) - } - if d.Version != version { - version = "" - } - } - - // Prefer umbrella LICENSE, then any sub-package's own. - text, _, err := readLicenseFile(filepath.Join(root, webDir, "node_modules", strings.TrimPrefix(scope, "@"))) - if err != nil { - text = items[0].text - for _, d := range items { - if !strings.HasPrefix(d.text, "Copyright (c) contributors to ") { - text = d.text - break - } - } - } - - out = append(out, Dep{ - Name: strings.TrimPrefix(scope, "@"), - Version: version, - SPDX: spdx, - URL: "https://www.npmjs.com/~" + strings.TrimPrefix(scope, "@"), - Count: len(items), - atoms: items[0].atoms, - text: text, - }) - } - return out, nil -} - -// npmName extracts the package name from an npm lockfile key like -// "node_modules/foo" or "node_modules/foo/node_modules/@scope/bar". -func npmName(path string) string { - i := strings.LastIndex(path, "node_modules/") - if i < 0 { - return "" - } - n := path[i+len("node_modules/"):] - if strings.HasPrefix(n, "@") { - return n // scoped "@scope/name" is one name - } - head, _, _ := strings.Cut(n, "/") - return head -} - -// npmSPDX normalises package.json's `license` field. Modern packages use a -// string; we accept legacy array-of-objects too. Outer parens are stripped -// so "(MIT OR Apache-2.0)" displays as "MIT OR Apache-2.0". -func npmSPDX(v any) (string, error) { - var s string - switch x := v.(type) { - case string: - s = strings.TrimSpace(x) - case []any: - var ids []string - for _, it := range x { - if m, ok := it.(map[string]any); ok { - if t, ok := m["type"].(string); ok && t != "" { - ids = append(ids, t) - } - } - } - s = strings.Join(ids, " OR ") - } - if s == "" { - return "", fmt.Errorf("no license field") - } - for strings.HasPrefix(s, "(") && strings.HasSuffix(s, ")") { - s = strings.TrimSpace(s[1 : len(s)-1]) - } - return s, nil -} - -// npmText returns the LICENSE text shipped with a package, or a copyright -// notice derived from package.json when no LICENSE file exists. Canonical -// SPDX text is never substituted — doing so would claim the author wrote -// something they didn't ship. -func npmText(pkgDir, name string) string { - if text, _, err := readLicenseFile(pkgDir); err == nil { - return text - } - if c := copyrightFromPackageJSON(pkgDir); c != "" { - return c + "\n" - } - return "Copyright (c) contributors to " + name + "\n" -} - -// copyrightFromPackageJSON builds a "Copyright (c) ..." line from the -// package's author/contributors fields. Returns empty if neither is present. -func copyrightFromPackageJSON(pkgDir string) string { - data, err := os.ReadFile(filepath.Join(pkgDir, "package.json")) - if err != nil { - return "" - } - var pj struct { - Author any `json:"author"` - Contributors []any `json:"contributors"` - } - if err := json.Unmarshal(data, &pj); err != nil { - return "" - } - var names []string - for _, v := range append([]any{pj.Author}, pj.Contributors...) { - if s := personName(v); s != "" { - names = append(names, s) - } - } - if len(names) == 0 { - return "" - } - return "Copyright (c) " + strings.Join(names, ", ") -} - -// personName renders an npm author/contributor entry (string or {name,email}) -// as "Name <email>" or just "Name". -func personName(v any) string { - switch x := v.(type) { - case string: - return strings.TrimSpace(x) - case map[string]any: - name, _ := x["name"].(string) - email, _ := x["email"].(string) - name = strings.TrimSpace(name) - email = strings.TrimSpace(email) - if name == "" { - return "" - } - if email == "" { - return name - } - return name + " <" + email + ">" - } - return "" -} |
