pax_global_header                                                                                   0000666 0000000 0000000 00000000064 15043744726 0014526 g                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        52 comment=4b0806206b33c90df02c2378ea802f321b16b550
                                                                                                                                                                                                                                                                                                                                                                                                                                                                            autosuspend-9.0.0/                                                                                  0000775 0000000 0000000 00000000000 15043744726 0014106 5                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        autosuspend-9.0.0/.editorconfig                                                                     0000664 0000000 0000000 00000000251 15043744726 0016561 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        root = true
[*]
end_of_line = lf
insert_final_newline = true
charset = utf-8
indent_style = space
[*.py]
indent_size = 4
max_line_length = 88
[*.rst]
indent_size = 3
                                                                                                                                                                                                                                                                                                                                                       autosuspend-9.0.0/.github/                                                                          0000775 0000000 0000000 00000000000 15043744726 0015446 5                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        autosuspend-9.0.0/.github/workflows/                                                                0000775 0000000 0000000 00000000000 15043744726 0017503 5                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        autosuspend-9.0.0/.github/workflows/ci.yml                                                          0000664 0000000 0000000 00000011235 15043744726 0020623 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        name: CI build
on:
  push:
    branches:
      - main
  pull_request: {}
  schedule:
    - cron: "0 0 * * 0"
jobs:
  lint-commits:
    runs-on: ubuntu-latest
    if: ${{ github.event_name == 'pull_request' }}
    steps:
      - name: install cairo
        run: sudo apt-get update && sudo apt-get install -y libcairo2-dev
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - uses: wagoid/commitlint-github-action@v6
  lint-code:
    runs-on: ubuntu-latest
    steps:
      - name: Clone repo
        uses: actions/checkout@v4
      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.13"
      - name: Install native dependencies
        run: sudo apt-get update && sudo apt-get -y install libdbus-1-dev libgirepository-2.0-dev libcairo2-dev
      - name: Cache Python packages
        uses: actions/cache@v4
        with:
          path: ~/.cache/pip
          key: lint-code-${{ hashFiles('setup.py') }}-${{ hashFiles('tox.ini') }}
      - name: Install tox
        run: |
          python -m pip install --upgrade pip
          pip install tox
      - name: Lint with tox
        run: tox -e check
  docs:
    runs-on: ubuntu-latest
    steps:
      - name: Clone repo
        uses: actions/checkout@v4
      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.13"
      - name: Install native dependencies
        run: sudo apt-get update && sudo apt-get -y install libdbus-1-dev libgirepository-2.0-dev plantuml libcairo2-dev
      - name: Cache Python packages
        uses: actions/cache@v4
        with:
          path: ~/.cache/pip
          key: docs-${{ hashFiles('setup.py') }}-${{ hashFiles('tox.ini') }}-${{ hashFiles('requirements-doc.txt') }}
      - name: Install tox
        run: |
          python -m pip install --upgrade pip
          pip install tox
      - name: Build Sphinx docs
        run: tox -e docs
  test-mindeps:
    runs-on: ubuntu-latest
    steps:
      - name: Clone repo
        uses: actions/checkout@v4
      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.13"
      - name: Cache Python packages
        uses: actions/cache@v4
        with:
          path: ~/.cache/pip
          key: test-mindeps-${{ hashFiles('setup.py') }}-${{ hashFiles('tox.ini') }}
      - name: Install tox
        run: |
          python -m pip install --upgrade pip
          pip install tox
      - name: Test execution with minimal dependencies
        run: tox -e mindeps
  test:
    runs-on: ubuntu-latest
    strategy:
      max-parallel: 4
      matrix:
        python-version: ["3.11", "3.12", "3.13"]
    steps:
      - name: Clone repo
        uses: actions/checkout@v4
      - name: Set up Python ${{ matrix.python-version }}
        uses: actions/setup-python@v5
        with:
          python-version: ${{ matrix.python-version }}
      - name: Install native dependencies
        run: sudo apt-get update && sudo apt-get -y install libdbus-1-dev libgirepository-2.0-dev libcairo2-dev
      - name: Cache Python packages
        uses: actions/cache@v4
        with:
          path: ~/.cache/pip
          key: test-${{ hashFiles('setup.py') }}-${{ hashFiles('tox.ini') }}-${{ matrix.python-version }}
      - name: Install Python dependencies
        run: |
          python -m pip install --upgrade pip
          pip install coverage tox tox-gh-actions
      - name: Test with tox
        run: |
          tox
          coverage xml --rcfile=setup.cfg
      - name: Publish coverage to codecov.io
        uses: codecov/codecov-action@v5
        with:
          token: ${{ secrets.CODECOV_TOKEN }}
  release:
    runs-on: ubuntu-latest
    if: ${{ github.ref == 'refs/heads/main' }}
    needs:
      - lint-code
      - test-mindeps
      - test
      - docs
    steps:
      - name: "Generate token"
        id: generate_token
        uses: tibdex/github-app-token@v2
        with:
          app_id: ${{ secrets.RELEASE_APP_ID }}
          private_key: ${{ secrets.RELEASE_PRIVATE_KEY }}
      - name: Checkout
        uses: actions/checkout@v4
        with:
          fetch-depth: 0
          token: ${{ steps.generate_token.outputs.token }}
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 22
      - name: Cache Node packages
        uses: actions/cache@v4
        with:
          path: node_modules
          key: release-${{ hashFiles('package.json') }}-${{ hashFiles('package-lock.json') }}
      - name: Install dependencies
        run: npm ci
      - name: Release
        run: npx semantic-release
        env:
          GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }}
                                                                                                                                                                                                                                                                                                                                                                   autosuspend-9.0.0/.gitignore                                                                        0000664 0000000 0000000 00000000322 15043744726 0016073 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        /.cache
/.coverage*
*.egg-info
/.eggs
/build
/dist
/htmlcov
/tags
__pycache__
/pytestdebug.log
/doc/build/
/env/
/.ropeproject/
/.mypy_cache/
/.pytest_cache/
/.python-version
/.tox/
/Session.vim
/node_modules/
                                                                                                                                                                                                                                                                                                              autosuspend-9.0.0/.readthedocs.yaml                                                                 0000664 0000000 0000000 00000000357 15043744726 0017342 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        ---
version: 2
sphinx:
  configuration: doc/source/conf.py
build:
  os: ubuntu-22.04
  tools:
    python: "3.11"
  apt_packages:
    - plantuml
python:
  install:
    - requirements: requirements-doc.txt
    - method: pip
      path: .
                                                                                                                                                                                                                                                                                 autosuspend-9.0.0/.releaserc.json                                                                   0000664 0000000 0000000 00000001401 15043744726 0017020 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        {
  "branches": [
    "main"
  ],
  "plugins": [
    [
      "@semantic-release/commit-analyzer",
      {
        "preset": "conventionalcommits"
      }
    ],
    [
      "@semantic-release/release-notes-generator",
      {
        "preset": "angular"
      }
    ],
    [
      "@semantic-release/changelog",
      {
        "changelogFile": "doc/source/generated_changelog.md"
      }
    ],
    [
      "@semantic-release/exec",
      {
        "prepareCmd": "echo $(echo ${nextRelease.version} | cut -d '.' -f 1-2)'\n${nextRelease.version}' > ./VERSION"
      }
    ],
    [
      "@semantic-release/git",
      {
        "assets": [
          "VERSION",
          "doc/source/generated_changelog.md"
        ]
      }
    ],
    "@semantic-release/github"
  ]
}
                                                                                                                                                                                                                                                               autosuspend-9.0.0/LICENSE.txt                                                                       0000664 0000000 0000000 00000035562 15043744726 0015744 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        GNU GENERAL PUBLIC LICENSE
                       Version 2, June 1991
 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 Everyone is permitted to copy and distribute verbatim copies
 of this license document, but changing it is not allowed.
                            Preamble
  The licenses for most software are designed to take away your
freedom to share and change it.  By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users.  This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it.  (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.)  You can apply it to
your programs, too.
  When we speak of free software, we are referring to freedom, not
price.  Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
  To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
  For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have.  You must make sure that they, too, receive or can get the
source code.  And you must show them these terms so they know their
rights.
  We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
  Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software.  If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
  Finally, any free program is threatened constantly by software
patents.  We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary.  To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
  The precise terms and conditions for copying, distribution and
modification follow.
                    GNU GENERAL PUBLIC LICENSE
   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
  0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License.  The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language.  (Hereinafter, translation is included without limitation in
the term "modification".)  Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope.  The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
  1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
  2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
    a) You must cause the modified files to carry prominent notices
    stating that you changed the files and the date of any change.
    b) You must cause any work that you distribute or publish, that in
    whole or in part contains or is derived from the Program or any
    part thereof, to be licensed as a whole at no charge to all third
    parties under the terms of this License.
    c) If the modified program normally reads commands interactively
    when run, you must cause it, when started running for such
    interactive use in the most ordinary way, to print or display an
    announcement including an appropriate copyright notice and a
    notice that there is no warranty (or else, saying that you provide
    a warranty) and that users may redistribute the program under
    these conditions, and telling the user how to view a copy of this
    License.  (Exception: if the Program itself is interactive but
    does not normally print such an announcement, your work based on
    the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole.  If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works.  But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
  3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
    a) Accompany it with the complete corresponding machine-readable
    source code, which must be distributed under the terms of Sections
    1 and 2 above on a medium customarily used for software interchange; or,
    b) Accompany it with a written offer, valid for at least three
    years, to give any third party, for a charge no more than your
    cost of physically performing source distribution, a complete
    machine-readable copy of the corresponding source code, to be
    distributed under the terms of Sections 1 and 2 above on a medium
    customarily used for software interchange; or,
    c) Accompany it with the information you received as to the offer
    to distribute corresponding source code.  (This alternative is
    allowed only for noncommercial distribution and only if you
    received the program in object code or executable form with such
    an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it.  For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable.  However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
  4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License.  Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
  5. You are not required to accept this License, since you have not
signed it.  However, nothing else grants you permission to modify or
distribute the Program or its derivative works.  These actions are
prohibited by law if you do not accept this License.  Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
  6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions.  You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
  7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License.  If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all.  For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices.  Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
  8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded.  In such case, this License incorporates
the limitation as if written in the body of this License.
  9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time.  Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number.  If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation.  If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
  10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission.  For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this.  Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
                            NO WARRANTY
  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
                     END OF TERMS AND CONDITIONS
                                                                                                                                              autosuspend-9.0.0/MANIFEST.in                                                                       0000664 0000000 0000000 00000000020 15043744726 0015634 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        include VERSION
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                autosuspend-9.0.0/README.md                                                                         0000664 0000000 0000000 00000002003 15043744726 0015360 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        # autosuspend
[](https://github.com/languitar/autosuspend/actions) [](https://codecov.io/gh/languitar/autosuspend) [](http://autosuspend.readthedocs.io/en/latest/?badge=latest)
`autosuspend` is a python daemon that suspends a system if certain conditions are met, or not met. This enables a server to sleep in case of inactivity without depending on the X infrastructure usually used by normal desktop environments.
Documentation is [available here](https://autosuspend.readthedocs.io).
## Packages
[](https://repology.org/project/autosuspend/versions)
## License
This software is licensed using the [GPL2 license](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html).
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             autosuspend-9.0.0/VERSION                                                                           0000664 0000000 0000000 00000000012 15043744726 0015147 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        9.0
9.0.0
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      autosuspend-9.0.0/commitlint.config.js                                                              0000664 0000000 0000000 00000000101 15043744726 0020057 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        module.exports = {extends: ['@commitlint/config-conventional']};
                                                                                                                                                                                                                                                                                                                                                                                                                                                               autosuspend-9.0.0/cosmic-ray.toml                                                                   0000664 0000000 0000000 00000000427 15043744726 0017054 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        [cosmic-ray]
module-path = "src/autosuspend"
python-version = ""
timeout = 20.0
excluded-modules = []
test-command = "env PYTHONPATH=`pwd`/src pytest -x"
[cosmic-ray.execution-engine]
name = "local"
[cosmic-ray.cloning]
method = "copy"
commands = [
    "pip install .[test]"
]
                                                                                                                                                                                                                                         autosuspend-9.0.0/data/                                                                             0000775 0000000 0000000 00000000000 15043744726 0015017 5                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        autosuspend-9.0.0/data/autosuspend-detect-suspend.service                                           0000664 0000000 0000000 00000000444 15043744726 0023702 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        [Unit]
Description=Notifies autosuspend about suspension
Documentation=https://autosuspend.readthedocs.io/en/latest/systemd_integration.html
Before=sleep.target
[Service]
Type=simple
ExecStart=/usr/bin/autosuspend -l /etc/autosuspend-logging.conf presuspend
[Install]
WantedBy=sleep.target
                                                                                                                                                                                                                            autosuspend-9.0.0/data/autosuspend-logging.conf                                                     0000664 0000000 0000000 00000001033 15043744726 0021661 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        [loggers]
keys=root,autosuspend,checks
[handlers]
keys=consoleHandler
[formatters]
keys=simpleFormatter
[logger_root]
level=INFO
handlers=consoleHandler
[logger_autosuspend]
qualname=autosuspend
propagate=0
level=INFO
handlers=consoleHandler
[logger_checks]
qualname=autosuspend.checks
propagate=0
level=INFO
handlers=consoleHandler
[handler_consoleHandler]
class=StreamHandler
level=DEBUG
formatter=simpleFormatter
args=(sys.stdout,)
[formatter_simpleFormatter]
format=%(asctime)s - %(name)s - %(levelname)s - %(message)s
datefmt=
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     autosuspend-9.0.0/data/autosuspend.conf                                                             0000664 0000000 0000000 00000003401 15043744726 0020236 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        ## This is an exemplary documentation file that mainly serves as a syntax explanation.
## For a list of available options and checks, please refer to `man autosuspend.conf` or the online documentation.
[general]
interval = 30
idle_time = 900
suspend_cmd = /usr/bin/systemctl suspend
wakeup_cmd = sh -c 'echo 0 > /sys/class/rtc/rtc0/wakealarm && echo {timestamp:.0f} > /sys/class/rtc/rtc0/wakealarm'
woke_up_file = /var/run/autosuspend-just-woke-up
lock_file = /var/lock/autosuspend.lock
lock_timeout = 30
# Can be used to call a command before suspending, either with scheduled wake up or not.
# notify_cmd_wakeup = su myuser -c notify-send -a autosuspend 'Suspending the system. Wake up at {iso}'
# notify_cmd_no_wakeup = su myuser -c notify-send -a autosuspend 'Suspending the system.'
# Basic activity check configuration.
# The check class name is derived from the section header (Ping in this case).
# Remember to enable desired checks. They are disabled by default.
[check.Ping]
enabled = true
hosts = 192.168.0.7
# This check is disabled.
[check.Smb]
enabled = false
# Example for a custom check name.
# This will use the Users check with the custom name RemoteUsers.
# Custom names are necessary in case a check class is used multiple times.
# Custom names can also be used for clarification.
[check.RemoteUsers]
class = Users
enabled = true
name = .*
terminal = .*
host = [0-9].*
# Here the Users activity check is used again with different settings and a different name
[check.LocalUsers]
class = Users
enabled = true
name = .*
terminal = .*
host = localhost
# Checks to determine the next scheduled wakeup are prefixed with 'wakeup'.
[wakeup.Calendar]
enabled = true
url = http://example.org/test.ics
# Apart from this, wake up checks reuse the same configuration mechanism.
                                                                                                                                                                                                                                                               autosuspend-9.0.0/data/autosuspend.service                                                          0000664 0000000 0000000 00000000522 15043744726 0020752 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        [Unit]
Description=A daemon to suspend your server in case of inactivity
Documentation=https://autosuspend.readthedocs.io/en/latest/systemd_integration.html
After=network.target
[Service]
ExecStart=/usr/bin/autosuspend -l /etc/autosuspend-logging.conf daemon
[Install]
WantedBy=multi-user.target
Also=autosuspend-detect-suspend.service
                                                                                                                                                                              autosuspend-9.0.0/doc/                                                                              0000775 0000000 0000000 00000000000 15043744726 0014653 5                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        autosuspend-9.0.0/doc/source/                                                                       0000775 0000000 0000000 00000000000 15043744726 0016153 5                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        autosuspend-9.0.0/doc/source/api.rst                                                                0000664 0000000 0000000 00000000442 15043744726 0017456 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        Python API documentation
########################
In case custom checks are required, the following classes have to be subclassed.
.. autoclass:: autosuspend.checks.Activity
   :members:
   :inherited-members:
.. autoclass:: autosuspend.checks.Wakeup
   :members:
   :inherited-members:
                                                                                                                                                                                                                              autosuspend-9.0.0/doc/source/available_checks.rst                                                   0000664 0000000 0000000 00000036574 15043744726 0022164 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        .. _available-checks:
Available activity checks
#########################
The following checks for activity are currently implemented.
Each of the is described with its available configuration options and required optional dependencies.
.. _check-active-calendar-event:
ActiveCalendarEvent
*******************
.. program:: check-active-calendar-event
Checks an online `iCalendar`_ file for events that are currently running.
If so, this indicates activity and prevents suspending the system.
Thus, a calendar can be provided with times at which the system should not go to sleep.
If this calendar resides on an online service like a groupware it might even be possible to invite the system.
Options
=======
.. option:: url
   The URL to query for the iCalendar file
.. option:: timeout
   Timeout for executed requests in seconds. Default: 5.
.. option:: username
   Optional user name to use for authenticating at a server requiring authentication.
   If used, also a password must be provided.
.. option:: password
   Optional password to use for authenticating at a server requiring authentication.
   If used, also a user name must be provided.
Requirements
============
* `requests`_
* `icalendar `_
* `dateutil`_
* `tzlocal`_
.. _check-active-connection:
ActiveConnection
****************
.. program:: check-active-connection
Checks whether there is currently a client connected to a TCP server at certain ports.
Can be used to e.g. block suspending the system in case SSH users are connected or a web server is used by clients.
Options
=======
.. option:: ports
   list of comma-separated port numbers
Requirements
============
.. _check-external-command:
ExternalCommand
***************
.. program:: check-external-command
Executes an arbitrary command.
In case this command returns 0, the system is assumed to be active.
The command is executed as is using shell execution.
Beware of malicious commands in obtained configuration files.
.. seealso::
   * :ref:`external-command-activity-scripts` for a collection of user-provided scripts for some common use cases.
Options
=======
.. option:: command
   The command to execute including all arguments
Requirements
============
.. _check-jsonpath:
JsonPath
********
.. program:: check-jsonpath
A generic check which queries a configured URL and expects the reply to contain JSON data.
The returned JSON document is checked against a configured `JSONPath`_ expression and in case the expression matches, the system is assumed to be active.
Options
=======
.. option:: url
   The URL to query for the XML reply.
.. option:: jsonpath
   The `JSONPath`_ query to execute.
   In case it returns a result, the system is assumed to be active.
.. option:: timeout
   Timeout for executed requests in seconds. Default: 5.
.. option:: username
   Optional user name to use for authenticating at a server requiring authentication.
   If used, also a password must be provided.
.. option:: password
   Optional password to use for authenticating at a server requiring authentication.
   If used, also a user name must be provided.
Requirements
============
-  `requests`_
-  `jsonpath-ng`_
.. _check-kodi:
Kodi
****
.. program:: check-kodi
Checks whether an instance of `Kodi`_ is currently playing.
Options
=======
.. option:: url
   Base URL of the JSON RPC API of the Kodi instance, default: ``http://localhost:8080/jsonrpc``
.. option:: timeout
   Request timeout in seconds, default: ``5``
.. option:: username
   Optional user name to use for authenticating at a server requiring authentication.
   If used, also a password must be provided.
.. option:: password
   Optional password to use for authenticating at a server requiring authentication.
   If used, also a user name must be provided.
.. option:: suspend_while_paused
   Also suspend the system when media playback is paused instead of only suspending
   when playback is stopped.
   Default: ``false``
Requirements
============
-  `requests`_
.. _check-kodi-idle-time:
KodiIdleTime
************
.. program:: check-kodi-idle-time
Checks whether there has been interaction with the Kodi user interface recently.
This prevents suspending the system in case someone is currently browsing collections etc.
This check is redundant to :ref:`check-xidletime` on systems using an X server, but might be necessary in case Kodi is used standalone.
It does not replace the :ref:`check-kodi` check, as the idle time is not updated when media is playing.
Options
=======
.. option:: idle_time
   Marks the system active in case a user interaction has appeared within the this amount of seconds until now.
   Default: ``120``
.. option:: url
   Base URL of the JSON RPC API of the Kodi instance, default: ``http://localhost:8080/jsonrpc``
.. option:: timeout
   Request timeout in seconds, default: ``5``
.. option:: username
   Optional user name to use for authenticating at a server requiring authentication.
   If used, also a password must be provided.
.. option:: password
   Optional password to use for authenticating at a server requiring authentication.
   If used, also a user name must be provided.
Requirements
============
-  `requests`_
.. _check-last-log-activity:
LastLogActivity
***************
.. program:: check-last-log-activity
Parses a log file and uses the most recent time contained in the file to determine activity.
For this purpose, the log file lines are iterated from the back until a line matching a configurable regular expression is found.
This expression is used to extract the contained timestamp in that log line, which is then compared to the current time with an allowed delta.
The check only looks at the first line from the back that contains a timestamp.
Further lines are ignored.
A typical use case for this check would be a web server access log file.
This check supports all date formats that are supported by the `dateutil parser `_.
Options
=======
.. option:: log_file
   path to the log file that should be analyzed
.. option:: pattern
   A regular expression used to determine whether a line of the log file contains a timestamp to look at.
   The expression must contain exactly one matching group.
   For instance, ``^\[(.*)\] .*$`` might be used to find dates in square brackets at line beginnings.
.. option:: minutes
   The number of minutes to allow log file timestamps to be in the past for detecting activity.
   If a timestamp is older than `` - `` no activity is detected.
   default: 10
.. option:: encoding
   The encoding with which to parse the log file. default: ascii
.. option:: timezone
   The timezone to assume in case a timestamp extracted from the log file has not associated timezone information.
   Timezones are expressed using the names from the Olson timezone database (e.g. ``Europe/Berlin``).
   default: ``UTC``
Requirements
============
* `dateutil`_
* `tzdata`_
.. _check-load:
Load
****
.. program:: check-load
Checks whether the `system load 5 `__ is below a certain value.
Options
=======
.. option:: threshold
   a float for the maximum allowed load value, default: 2.5
Requirements
============
.. _check-logind-session-idle:
LogindSessionsIdle
******************
.. program:: check-logind-session-idle
Prevents suspending in case ``IdleHint`` for one of the running sessions `logind`_ sessions is set to ``no``.
Support for setting this hint currently varies greatly across display managers, screen lockers etc.
Thus, check exactly whether the hint is set on your system via ``loginctl show-session``.
Options
=======
.. option:: types
   A comma-separated list of sessions types to inspect for activity.
   The check ignores sessions of other types.
   Default: ``tty``, ``x11``, ``wayland``
.. option:: states
   A comma-separated list of session states to inspect.
   For instance, ``lingering`` sessions used for background programs might not be of interest.
   Default: ``active``, ``online``
.. option:: classes
   A comma-separated list of session classes to inspect.
   For instance, ``greeter`` sessions used by greeters such as lightdm might not be of interest.
   Default: ``user``
Requirements
============
-  `dbus-python`_
.. _check-mpd:
Mpd
***
.. program:: check-mpd
Checks whether an instance of `MPD`_ is currently playing music.
Options
=======
.. option:: host
   Host containing the MPD daemon, default: ``localhost``
.. option:: port
   Port to connect to the MPD daemon, default: ``6600``
.. option:: timeout
   .. _mpd-timeout:
   Request timeout in seconds, default: ``5``
Requirements
============
-  `python-mpd2`_
.. _check-network-bandwidth:
NetworkBandwidth
****************
.. program:: check-network-bandwidth
Checks whether more network bandwidth is currently being used than specified.
A set of specified interfaces is checked in this regard, each of the individually, based on the average bandwidth on that interface.
This average is based on the global checking interval specified in the configuration file via the :option:`interval ` option.
.. note::
   This check assumes stable network interface names.
   If this is not the case for your system, consider adding the required udev rules to ensure persistent device names.
   The `Archlinux Wiki page on network configuration `__ explains the necessary configuration steps.
Options
=======
.. option:: interfaces
   Comma-separated list of network interfaces to check
.. option:: threshold_send 
   If the average sending bandwidth of one of the specified interfaces is above this threshold, then activity is detected. Specified in bytes/s, default: ``100``
.. option:: threshold_receive 
   If the average receive bandwidth of one of the specified interfaces is above this threshold, then activity is detected. Specified in bytes/s, default: ``100``
Requirements
============
.. _check-ping:
Ping
****
.. program:: check-ping
Checks whether one or more hosts answer to ICMP requests.
Options
=======
.. option:: hosts
   Comma-separated list of host names or IPs.
Requirements
============
.. _check-processes:
Processes
*********
.. program:: check-processes
If currently running processes match an expression, the suspend will be blocked.
You might use this to hinder the system from suspending when for example your rsync runs.
Options
=======
.. option:: processes
   list of comma-separated process names to check for
Requirements
============
.. _check-smb:
Smb
***
.. program:: check-smb
Any active Samba connection will block suspend.
Options
=======
.. option:: smbstatus
   executable needs to be present.
Requirements
============
.. _check-users:
Users
*****
.. program:: check-users
Checks whether a user currently logged in at the system matches several criteria.
All provided criteria must match to indicate activity on the host.
To find the applicable values for a given scenario on a system, use the following command:
.. code-block:: console
   $ python3 -c "import psutil; print(psutil.users())"
   [suser(name='someone', terminal='tty7', host='', started=1670269568.0, pid=77179)]
Options
=======
All regular expressions are applied against the full string.
Capturing substrings needs to be explicitly enabled using wildcard matching.
.. option:: name
   A regular expression specifying which users to capture, default: ``.*``.
.. option:: terminal
   A regular expression specifying the terminal on which the user needs to be logged in, default: ``.*``.
.. option:: host
   A regular expression specifying the host from which a user needs to be logged in.
   Users logged in locally on the machine are usually reported with an empty string as the host value.
   In case this check should only match local users, use ``^$`` as the value for this option.
   default: ``.*`` (i.e. accept users from any host).
Requirements
============
.. _check-xidletime:
XIdleTime
*********
.. program:: check-xidletime
Checks whether all active local X displays have been idle for a sufficiently long time.
Determining which X11 sessions currently exist on a running system is a harder problem than one might expect.
Sometimes, the server runs as root, sometimes under the real user, and many other configuration variants exist.
Thus, multiple sources for active X serer instances are implemented for this check, each of them having different requirements and limitations.
They can be changed using the provided configuration option.
Options
=======
.. option:: timeout
   required idle time in seconds
.. option:: method
   The method to use for acquiring running X sessions.
   Valid options are ``sockets`` and ``logind``.
   The default is ``sockets``.
   ``sockets``
     Uses the X server sockets files found in :file:`/tmp/.X11-unix`.
     This method requires that all X server instances run with user permissions and not as root.
   ``logind``
     Uses `logind`_ to obtain the running X server instances.
     This does not support manually started servers.
.. option:: ignore_if_process
   A regular expression to match against the process names executed by each X session owner.
   In case the use has a running process that matches this expression, the X idle time is ignored and the check continues as if there was no activity.
   This can be useful in case of processes which inevitably tinker with the idle time.
.. option:: ignore_users
   Do not check sessions of users matching this regular expressions.
Requirements
============
* `dbus-python`_ for the ``logind`` method
.. _check-xpath:
XPath
*****
.. program:: check-xpath
A generic check which queries a configured URL and expects the reply to contain XML data.
The returned XML document is checked against a configured `XPath`_ expression and in case the expression matches, the system is assumed to be active.
Some common applications and their respective configuration are:
`tvheadend`_
    The required URL for `tvheadend`_ is (if running on the same host)::
        http://127.0.0.1:9981/status.xml
    In case you want to prevent suspending in case there are active subscriptions or recordings, use the following XPath::
        /currentload/subscriptions[number(.) > 0] | /currentload/recordings/recording/start
    If you have a permantently running subscriber like `Kodi`_, increase the ``0`` to ``1``.
`Plex`_
    For `Plex`_, use the following URL (if running on the same host)::
        http://127.0.0.1:32400/status/sessions/?X-Plex-Token={TOKEN}
    Where acquiring the token is `documented here `_.
    If suspending should be prevented in case of any activity, this simple `XPath`_ expression will suffice::
        /MediaContainer[@size > 2]
Options
=======
.. option:: url
   The URL to query for the XML reply.
.. option:: xpath
   The XPath query to execute.
   In case it returns a result, the system is assumed to be active.
.. option:: timeout
   Timeout for executed requests in seconds. Default: 5.
.. option:: username
   Optional user name to use for authenticating at a server requiring authentication.
   If used, also a password must be provided.
.. option:: password
   Optional password to use for authenticating at a server requiring authentication.
   If used, also a user name must be provided.
Requirements
============
* `requests`_
* `lxml`_
                                                                                                                                    autosuspend-9.0.0/doc/source/available_wakeups.rst                                                  0000664 0000000 0000000 00000012067 15043744726 0022372 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        .. _available-wakeups:
Available wake up checks
########################
The following checks for wake up times are currently implemented.
Each of the checks is described with its available configuration options and required optional dependencies.
.. _wakeup-calendar:
Calendar
********
.. program:: wakeup-calendar
Determines next wake up time from an `iCalendar`_ file.
The next event that starts after the current time is chosen as the next wake up time.
Remember that updates to the calendar can only be reflected in case the system currently running.
Changes to the calendar made while the system is sleeping will obviously not trigger an earlier wake up.
Options
=======
.. option:: url
   The URL to query for the XML reply.
.. option:: username
   Optional user name to use for authenticating at a server requiring authentication.
   If used, also a password must be provided.
.. option:: password
   Optional password to use for authenticating at a server requiring authentication.
   If used, also a user name must be provided.
.. option:: xpath
   The XPath query to execute.
   Must always return number strings or nothing.
.. option:: timeout
   Timeout for executed requests in seconds. Default: 5.
Requirements
============
* `requests`_
* `icalendar `_
* `dateutil`_
* `tzlocal`_
.. _wakeup-command:
Command
*******
.. program:: wakeup-command
Determines the wake up time by calling an external command
The command always has to succeed.
If something is printed on stdout by the command, this has to be the next wake up time in UTC seconds.
The command is executed as is using shell execution.
Beware of malicious commands in obtained configuration files.
Options
=======
.. option:: command
   The command to execute including all arguments
.. _wakeup-file:
File
****
.. program:: wakeup-file
Determines the wake up time by reading a file from a configured location.
The file has to contains the planned wake up time as an int or float in seconds UTC.
Options
=======
.. option:: path
   path of the file to read in case it is present
.. _wakeup-periodic:
Periodic
********
.. program:: wakeup-periodic
Always schedules a wake up at a specified delta from now on.
Can be used to let the system wake up every once in a while, for instance, to refresh the calendar used in the :ref:`wakeup-calendar` check.
Options
=======
.. option:: unit
   A string indicating in which unit the delta is specified.
   Valid options are: ``microseconds``, ``milliseconds``, ``seconds``, ``minutes``, ``hours``, ``days``, ``weeks``.
.. option:: value
   The value of the delta as an int.
.. _wakeup-systemd-timer:
SystemdTimer
************
.. program:: wakeup-systemd-timer
Ensures that the system is active when a `systemd`_ timer is scheduled to run next.
Options
=======
.. option:: match
   A regular expression selecting the `systemd`_ timers to check.
   This expression matches against the names of the timer units, for instance ``logrotate.timer``.
   Use ``systemctl list-timers`` to find out which timers exists.
.. _wakeup-xpath:
XPath
*****
.. program:: wakeup-xpath
A generic check which queries a configured URL and expects the reply to contain XML data.
The returned XML document is parsed using a configured `XPath`_ expression that has to return timestamps UTC (as strings, not elements).
These are interpreted as the wake up times.
In case multiple entries exist, the soonest one is used.
Options
=======
.. option:: url
   The URL to query for the XML reply.
.. option:: xpath
   The XPath query to execute.
   Must always return number strings or nothing.
.. option:: timeout
   Timeout for executed requests in seconds. Default: 5.
.. option:: username
   Optional user name to use for authenticating at a server requiring authentication.
   If used, also a password must be provided.
.. option:: password
   Optional password to use for authenticating at a server requiring authentication.
   If used, also a user name must be provided.
.. _wakeup-xpath-delta:
XPathDelta
**********
.. program:: wakeup-xpath-delta
Comparable to :ref:`wakeup-xpath`, but expects that the returned results represent the wake up time as a delta to the current time in a configurable unit.
This check can for instance be used for `tvheadend`_ with the following expression::
    //recording/next/text()
Options
=======
.. option:: url
   The URL to query for the XML reply.
.. option:: username
   Optional user name to use for authenticating at a server requiring authentication.
   If used, also a password must be provided.
.. option:: password
   Optional password to use for authenticating at a server requiring authentication.
   If used, also a user name must be provided.
.. option:: xpath
   The XPath query to execute.
   Must always return number strings or nothing.
.. option:: timeout
   Timeout for executed requests in seconds. Default: 5.
.. option:: unit
   A string indicating in which unit the delta is specified.
   Valid options are: ``microseconds``, ``milliseconds``, ``seconds``, ``minutes``, ``hours``, ``days``, ``weeks``.
   Default: minutes
                                                                                                                                                                                                                                                                                                                                                                                                                                                                         autosuspend-9.0.0/doc/source/changelog.rst                                                          0000664 0000000 0000000 00000000464 15043744726 0020640 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        Changelog
#########
|project| follows `Semantic Versioning `_.
Hence, any breaking change to the configuration, command line interface, `systemd`_
interface, etc. will result in a new major release of |project|.
.. toctree::
   :maxdepth: 1
   
   generated_changelog
   old_changelog
                                                                                                                                                                                                            autosuspend-9.0.0/doc/source/conf.py                                                                0000664 0000000 0000000 00000006315 15043744726 0017457 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        #!/usr/bin/env python3
# -*- coding: utf-8 -*-
import os
import os.path
# needs_sphinx = '1.0'
extensions = [
    "sphinx.ext.ifconfig",
    "sphinx.ext.intersphinx",
    "sphinx.ext.napoleon",
    "sphinx.ext.autodoc",
    "sphinx_autodoc_typehints",
    "sphinxcontrib.plantuml",
    "sphinx_issues",
    "recommonmark",
]
templates_path = ['_templates']
source_suffix = '.rst'
master_doc = 'index'
project = 'autosuspend'
copyright = '2017, Johannes Wienke'
author = 'Johannes Wienke'
with open(os.path.join(
        os.path.abspath(os.path.dirname(os.path.realpath(__file__))),
        '../..',
        'VERSION'), 'r') as version_file:
    lines = version_file.readlines()
version = lines[0].strip()
release = lines[1].strip()
language = "en"
exclude_patterns = []
pygments_style = 'sphinx'
todo_include_todos = False
rst_epilog = '''
.. _autosuspend: https://github.com/languitar/autosuspend
.. _Python 3: https://docs.python.org/3/
.. _Python: https://docs.python.org/3/
.. _setuptools: https://setuptools.readthedocs.io
.. _configparser: https://docs.python.org/3/library/configparser.html
.. _psutil: https://github.com/giampaolo/psutil
.. _lxml: http://lxml.de/
.. _MPD: http://www.musicpd.org/
.. _python-mpd2: https://pypi.python.org/pypi/python-mpd2
.. _dbus-python: https://cgit.freedesktop.org/dbus/dbus-python/
.. _Kodi: https://kodi.tv/
.. _requests: https://pypi.python.org/pypi/requests
.. _systemd: https://www.freedesktop.org/wiki/Software/systemd/
.. _systemd service files: http://www.freedesktop.org/software/systemd/man/systemd.service.html
.. _broadcast-logging: https://github.com/languitar/broadcast-logging
.. _tvheadend: https://tvheadend.org/
.. _XPath: https://www.w3.org/TR/xpath/
.. _logind: https://www.freedesktop.org/wiki/Software/systemd/logind/
.. _iCalendar: https://tools.ietf.org/html/rfc5545
.. _dateutil: https://dateutil.readthedocs.io
.. _python-icalendar: https://icalendar.readthedocs.io
.. _tzlocal: https://pypi.org/project/tzlocal/
.. _requests-file: https://github.com/dashea/requests-file
.. _Plex: https://www.plex.tv/
.. _portalocker: https://portalocker.readthedocs.io
.. _jsonpath-ng: https://github.com/h2non/jsonpath-ng
.. _JSONPath: https://goessner.net/articles/JsonPath/
.. _tzdata: https://pypi.org/project/tzdata/:w
.. |project| replace:: {project}
.. |project_bold| replace:: **{project}**
.. |project_program| replace:: :program:`{project}`'''.format(project=project)
# Intersphinx
intersphinx_mapping = {'python': ('https://docs.python.org/3.7', None)}
# HTML options
html_theme = 'furo'
# html_theme_options = {}
# html_static_path = ['_static']
html_sidebars = {
}
# MANPAGE options
man_pages = [
    ('man_command',
     'autosuspend',
     'autosuspend Documentation',
     [author],
     1),
    ('man_config',
     'autosuspend.conf',
     'autosuspend config file Documentation',
     [author],
     5),
]
man_show_urls = True
# issues
issues_github_path = 'languitar/autosuspend'
# napoleon
napoleon_google_docstring = True
napoleon_numpye_docstring = False
napoleon_include_init_with_doc = True
typehints_fully_qualified = True
def setup(app):
    app.add_config_value(
        'is_preview',
        os.environ.get('READTHEDOCS_VERSION', '') == 'latest',
        'env',
    )
                                                                                                                                                                                                                                                                                                                   autosuspend-9.0.0/doc/source/configuration_file.inc                                                 0000664 0000000 0000000 00000013005 15043744726 0022513 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        Syntax
~~~~~~
The |project_program| configuration file uses INI syntax and needs to be processable by the Python `configparser`_ module.
A simple configuration file could look like:
.. code-block:: ini
   [general]
   interval = 30
   idle_time = 900
   suspend_cmd = /usr/bin/systemctl suspend
   wakeup_cmd = echo {timestamp:.0f} > /sys/class/rtc/rtc0/wakealarm
   notify_cmd_wakeup = su myuser -c notify-send -a autosuspend 'Suspending the system. Wake up at {iso}'
   notify_cmd_no_wakeup = su myuser -c notify-send -a autosuspend 'Suspending the system.'
   lock_file = /var/lock/autosuspend.lock
   lock_timeout = 30
   
   [check.Ping]
   enabled = false
   hosts = 192.168.0.7
   
   [check.RemoteUsers]
   class = Users
   enabled = true
   name = .*
   terminal = .*
   host = [0-9].*
   
   [wakeup.File]
   enabled = True
   path = /var/run/autosuspend/wakeup
The configuration file consists of a ``[general]`` section, which specifies general processing options, and multiple sections of the format ``[check.*]`` and ``[wakeup.*]``.
These sections describe the activity and wake up checks to execute.
General configuration
~~~~~~~~~~~~~~~~~~~~~
.. program:: config-general
The ``[general]`` section contains options controlling the overall behavior of the |project_program| daemon. These are:
.. option:: interval
   The time to wait after executing all checks in seconds.
.. option:: idle_time
   The required amount of time in seconds with no detected activity before the host will be suspended.
   Default: 300 seconds
.. option:: min_sleep_time
   The minimal amount of time in seconds the system has to sleep for actually triggering suspension.
   If a scheduled wake up results in an effective time below this value, the system will not sleep.
   Default: 1200 seconds
.. option:: wakeup_delta
   Wake up the system this amount of seconds earlier than the time that was determined for an event that requires the system to be up.
   This value adds a safety margin for the time a the wake up effectively takes.
   Default: 30 seconds
.. option:: suspend_cmd
   The command to execute in case the host shall be suspended.
   This line can contain additional command line arguments to the command to execute.
.. option:: wakeup_cmd
   The command to execute for scheduling a wake up of the system.
   The given string is processed using Python's :meth:`str.format` and a format argument called ``timestamp`` encodes the UTC timestamp of the planned wake up time (float).
   Additionally ``iso`` can be used to acquire the timestamp in ISO 8601 format.
.. option:: notify_cmd_wakeup
   A command to execute before the system is going to suspend for the purpose of notifying interested clients.
   This command is only called in case a wake up is scheduled.
   The given string is processed using Python's :meth:`str.format` and a format argument called ``timestamp`` encodes the UTC timestamp of the planned wake up time (float).
   Additionally ``iso`` can be used to acquire the timestamp in ISO 8601 format.
   If empty or not specified, no command will be called.
.. option:: notify_cmd_no_wakeup
   A command to execute before the system is going to suspend for the purpose of notifying interested clients.
   This command is only called in case NO wake up is scheduled.
   Hence, no string formatting options are available.
   If empty or not specified, no command will be called.
.. option:: woke_up_file
   Location of a file that indicates to |project_program| that the computer has suspended since the last time checks were executed.
   This file is usually created by a `systemd`_ service.
   Thus, changing the location also requires adapting the respective service.
   Refer to :ref:`systemd-integration` for further details.
.. option:: lock_file
   Location of a file that is used to synchronize the continuously running daemon and the systemd callback.
.. option:: lock_timeout
   Timeout in seconds used when trying to acquire the lock.
   This should be longer than the maximum run time of all configured checks.
   In the worst cases, suspending the system is delayed by this amount of time because ``presuspend`` hook has to wait before all checks have passed.
Activity check configuration
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. program:: config-check
For each activity check to execute, a section with the name format ``[check.*]`` needs to be created.
Each check has a name and an executing class which implements the behavior.
The fraction of the section name ``check.`` determines the name, and in case no class option is given inside the section, also the class which implements the check.
In case the :option:`class` option is specified, the name is completely user-defined and the same check can even be instantiated multiple times with differing names.
For each check, these generic options can be specified:
.. option:: class
   Name of the class implementing the check.
   If the name does not contain a dot (``.``), this is assumed to be one of the checks provided by |project| internally.
   Otherwise, this can be used to pull in third-party checks.
   If this option is not specified, the section name must represent a valid internal check class.
.. option:: enabled
   Needs to be ``true`` for a check to actually execute.
   ``false`` is assumed if not specified.
Furthermore, each check might have custom options.
Wake up check configuration
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Wake up checks uses the same configuration logic as the previously described activity checks.
However, the configuration file sections start with ``wakeup.`` instead of ``check.``.
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           autosuspend-9.0.0/doc/source/configuration_file.rst                                                 0000664 0000000 0000000 00000000264 15043744726 0022555 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        Configuration file
##################
.. include:: configuration_file.inc
For options of individual checks, please refer to :ref:`available-checks` and :ref:`available-wakeups`.
                                                                                                                                                                                                                                                                                                                                            autosuspend-9.0.0/doc/source/debugging.rst                                                          0000664 0000000 0000000 00000002611 15043744726 0020640 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        Debugging
#########
In case you need to track configuration issues to understand why a system suspends or does not, the extensive logging output of |project_program| might be used.
Each iteration of the daemon logs exactly which condition detected activity or not.
So you should be able to find out what is going on.
The command line flag :option:`autosuspend -l` allows specifying a Python logging configuration file which specifies what to log.
The provided `systemd`_ service files (see :ref:`systemd-integration`) already use :file:`/etc/autosuspend-logging.conf` as the standard location and a default file is usually installed.
If you launch |project_program| manually from the console, the command line flag :option:`autosuspend -d` might also be used to get full logging to the console instead.
In case one of the conditions you monitor prevents suspending the system if an external connection is established (logged-in users, open TCP port), then the logging configuration file can be changed to use the `broadcast-logging`_ package.
This way, the server will broadcast new log messages on the network and external clients on the same network can listen to these messages without creating an explicit connection.
Please refer to the documentation of the `broadcast-logging`_ package on how to enable and use it.
Additionally, one might also examine the ``journalctl`` for |project_program| after the fact.
                                                                                                                       autosuspend-9.0.0/doc/source/description.inc                                                        0000664 0000000 0000000 00000001656 15043744726 0021201 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        |project_program| is a daemon that periodically suspends a system on inactivity and wakes it up again automatically in case it is needed.
For this purpose, |project_program| periodically iterates a number of user-configurable activity checks, which indicate whether an activity on the host is currently present that should prevent the host from suspending.
In case one of the checks indicates such activity, no action is taken and periodic checking continues.
Otherwise, in case no activity can be detected, this state needs to be present for a specified amount of time before the host is suspended by |project_program|.
In addition to the activity checks, wake up checks are used to determine planned future activities of the system (for instance, a TV recording or a periodic backup).
In case such activities are known before suspending, |project_program| triggers a command to wake up the system automatically before the soonest activity.
                                                                                  autosuspend-9.0.0/doc/source/external_command_activity_scripts.rst                                  0000664 0000000 0000000 00000001753 15043744726 0025716 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        .. _external-command-activity-scripts:
External command scripts for activity detection
###############################################
A collection of user-provided scripts to use with the :ref:`check-external-command` check for activity detection.
pyLoad
******
`pyLoad `_ uses an uncommon login theme for its API and hence two separate requests are required to query for active downloads.
Use something along the following lines to query `pyLoad`_.
.. code-block:: bash
   #!/bin/bash
   SessionID=$(curl -s "http://127.0.0.1:8000/api/login" -g -H "Host: 127.0.0.1:8000" -H "Content-Type: application/x-www-form-urlencoded" --data "username=user&password=password" | jq -r)
   SessionStatus=$(curl -s  "http://127.0.0.1:8000/api/statusServer" -g -H "Host: 127.0.0.1:8000" -H "Content-Type: application/x-www-form-urlencoded" --data "session=$SessionID" | jq -r '.active')
   if [ $SessionStatus -eq 1 ]
   then
     exit 0
   else
     exit 1
   fi
Source: :issue:`102`
                     autosuspend-9.0.0/doc/source/faq.rst                                                                0000664 0000000 0000000 00000011005 15043744726 0017451 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        .. _faq:
Frequently Asked Questions
##########################
Usage
*****
How to check unsupported software?
==================================
In case you want to detect if some piece of software running on your system that is not officially supported is performing relevant activity you have two options:
* Use a script with the :ref:`check-external-command` check.
* Implement a Python module with you check being a subclass of
  :class:`autosuspend.checks.Activity` or
  :class:`autosuspend.checks.Wakeup` and install it alongside |project|.
  The custom check class can then be referenced in the config with its full dotted path, for instance, ``mymodule.MyCheck``, in the `class` field.
How do I wake up my system if needed?
=====================================
|project_bold| itself only handles wake ups for events that were foreseeable at the time the system was put into sleep mode.
In case the system also has to be used on-demand, a simple way to wake up the system is to enable `Wake on LAN `_.
Here, a special network packet can be used to wake up the system again.
Multiple front-ends exist to send these magic packets.
The typical usage scenario with this approach is to manually send the magic packet when the system is needed, wait a few seconds, and then to perform the intended tasks with the system.
Wake on LAN needs to be specifically enabled on the system.
Typically, the documentation of common Linux distributions explains how to enable Wake on LAN:
* `Archlinux `__
* `Debian `__
* `Ubuntu `__
A set of front-ends for various platforms allows sending the magic packets.
For instance:
* `gWakeOnLan `__: GTK GUI, Linux
* `wol `__: command line, Linux
* `Wake On Lan `__: GUI, Windows
* `Wake On Lan `__: Android
* `Wake On Lan `__: Android, open-source
* `Kore (Kodi remote control) `__: Android, for Kodi users
* `Mocha WOL `__: iOS
How do I keep a system active at daytime
========================================
Imagine you want to have a NAS that is always available between 7 a.m. and 8 p.m.
After 8 p.m. the system should go to sleep in case no one else is using it.
Every morning at 7 a.m. it should wake up automatically.
This workflow can be realized using the :ref:`wakeup-calendar` wakeup check and the :ref:`check-active-calendar-event` activity check based on an `iCalendar`_ file residing on the local file system of the NAS.
The former check ensures that the system wakes up at the desired time of the day while the latter ensure that it stays active at daytime.
The first step is to create the `iCalendar`_ file, which can conveniently and graphically be edited with `Thunderbird Lightning `_ or any other calendar frontend.
Essentially, the ``*.ics`` may look like this::
   BEGIN:VCALENDAR
   PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN
   VERSION:2.0
   BEGIN:VEVENT
   CREATED:20180602T151701Z
   LAST-MODIFIED:20180602T152732Z
   DTSTAMP:20180602T152732Z
   UID:0ef23894-702e-40ac-ab09-94fa8c9c51fd
   SUMMARY:keep active
   RRULE:FREQ=DAILY
   DTSTART:20180612T070000
   DTEND:20180612T200000
   TRANSP:OPAQUE
   SEQUENCE:3
   END:VEVENT
   END:VCALENDAR
Afterwards, edit ``autosuspend.conf`` to contain the two aforementioned checks based on the created ``ics`` file.
This will end up with at least this config:
.. code-block:: ini
   [general]
   interval = 30
   suspend_cmd = /usr/bin/systemctl suspend
   wakeup_cmd = echo {timestamp:.0f} > /sys/class/rtc/rtc0/wakealarm
   woke_up_file = /var/run/autosuspend-just-woke-up
   [check.ActiveCalendarEvent]
   enabled = true
   url = file:///path/to/your.ics
   [wakeup.Calendar]
   enabled = true
   url = file:///path/to/your.ics
Adding other activity checks will ensure that the system stays awake event after 8 p.m. if it is still used.
Error messages
**************
No connection adapters were found for '\file://\*'
==================================================
You need to install the `requests-file`_ package for ``file://`` URIs to work.
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           autosuspend-9.0.0/doc/source/generated_changelog.md                                                 0000664 0000000 0000000 00000017153 15043744726 0022451 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        # [9.0.0](https://github.com/languitar/autosuspend/compare/v8.0.0...v9.0.0) (2025-08-03)
* build!: drop Python 3.10 support ([fa4b20c](https://github.com/languitar/autosuspend/commit/fa4b20c50ce5bcfd26ced5c460868fe0bd41f219))
### BREAKING CHANGES
* official support for Python 3.10 has been dropped
# [8.0.0](https://github.com/languitar/autosuspend/compare/v7.2.0...v8.0.0) (2025-06-21)
### Code Refactoring
* **deps:** remove pytz, require tzdata ([af7ea3a](https://github.com/languitar/autosuspend/commit/af7ea3a400425933fd44ec691b9b7f812055fd71))
### BREAKING CHANGES
* **deps:** new optional dependency tzdata when doing log parsing
# [7.2.0](https://github.com/languitar/autosuspend/compare/v7.1.0...v7.2.0) (2025-02-23)
### Features
* **systemd:** automatically enable/disable suspend hook ([772797a](https://github.com/languitar/autosuspend/commit/772797a8e4d9b3db7f609d20e73a0e66805ba2f1)), closes [#625](https://github.com/languitar/autosuspend/issues/625)
# [7.1.0](https://github.com/languitar/autosuspend/compare/v7.0.3...v7.1.0) (2025-01-12)
### Features
* official Python 3.13 support ([a8ea72d](https://github.com/languitar/autosuspend/commit/a8ea72d414621a13ff7705330bd731ffe94eeef8))
## [7.0.3](https://github.com/languitar/autosuspend/compare/v7.0.2...v7.0.3) (2024-11-19)
### Bug Fixes
* treat temporary failures as activity ([8c96853](https://github.com/languitar/autosuspend/commit/8c968530f011dad814df8c55794b058f3c751e8d)), closes [#589](https://github.com/languitar/autosuspend/issues/589)
## [7.0.2](https://github.com/languitar/autosuspend/compare/v7.0.1...v7.0.2) (2024-10-13)
### Bug Fixes
* **icalendar:** support icalendar v6 ([49bc89f](https://github.com/languitar/autosuspend/commit/49bc89fb2461758dd4d4f07016e88c8458192161))
## [7.0.1](https://github.com/languitar/autosuspend/compare/v7.0.0...v7.0.1) (2024-09-22)
### Bug Fixes
* **kodi-idle-time:** Send proper request ([8bb6dad](https://github.com/languitar/autosuspend/commit/8bb6dad7f325024d011008fc1f0e3d52a0b9f222))
# [7.0.0](https://github.com/languitar/autosuspend/compare/v6.1.1...v7.0.0) (2024-04-25)
* build!: drop Python 3.9 support ([3c4ae32](https://github.com/languitar/autosuspend/commit/3c4ae32c8e52f022f41e94c3a49dd89b9d02dcf2))
### Features
* officially support Python 3.12 ([de2f180](https://github.com/languitar/autosuspend/commit/de2f18010d166eb86fe15665aa7769f2105b02aa))
### BREAKING CHANGES
* Python 3.9 is not supported officially anymore. Python
    3.10 is the supported minimum version.
## [6.1.1](https://github.com/languitar/autosuspend/compare/v6.1.0...v6.1.1) (2024-02-12)
### Bug Fixes
* **docs:** add missing docs for new version subcommand ([fb248f7](https://github.com/languitar/autosuspend/commit/fb248f7a5706f81c20f7e88907e22cbd5c895cbb))
# [6.1.0](https://github.com/languitar/autosuspend/compare/v6.0.0...v6.1.0) (2024-02-11)
### Features
* **cli:** provide a version subcommand ([d51d836](https://github.com/languitar/autosuspend/commit/d51d836564a53b0dd5017fcd801e43b117542ebc)), closes [#482](https://github.com/languitar/autosuspend/issues/482)
# [6.0.0](https://github.com/languitar/autosuspend/compare/v5.0.0...v6.0.0) (2023-09-18)
* build!: modernize supported Python version ([31c8ccc](https://github.com/languitar/autosuspend/commit/31c8cccb503218691ffb045142b1297133ce5340))
### BREAKING CHANGES
* Python 3.8 has been deprecated and is not officially
  supported anymore.
# [5.0.0](https://github.com/languitar/autosuspend/compare/v4.3.3...v5.0.0) (2023-08-13)
* feat(logind)!: configure which session classes to process ([986e558](https://github.com/languitar/autosuspend/commit/986e558c2913bf30ebbab87025fe9722d5997aa7)), closes [#366](https://github.com/languitar/autosuspend/issues/366)
### BREAKING CHANGES
* LogindSessionIdle now only processes sessions of type
    "user" by default. Use the new configuration option classes to also
    include other types in case you need to include them in the checks.
## [4.3.3](https://github.com/languitar/autosuspend/compare/v4.3.2...v4.3.3) (2023-08-10)
### Bug Fixes
* **systemd:** handle timers without next execution time ([9fb83ea](https://github.com/languitar/autosuspend/commit/9fb83eac7d6cbe981e2ebfc1ec3c3b54fca19804)), closes [#403](https://github.com/languitar/autosuspend/issues/403)
## [4.3.2](https://github.com/languitar/autosuspend/compare/v4.3.1...v4.3.2) (2023-06-05)
### Bug Fixes
* release for sphinx 7 support ([569dfa5](https://github.com/languitar/autosuspend/commit/569dfa5954617929ae11529ece84f32810e10bee))
## [4.3.1](https://github.com/languitar/autosuspend/compare/v4.3.0...v4.3.1) (2023-05-16)
### Bug Fixes
* **ical:** support all versions of tzlocal ([9eb0b95](https://github.com/languitar/autosuspend/commit/9eb0b9549e11b612d47d007777cb83eac4c53f31))
# [4.3.0](https://github.com/languitar/autosuspend/compare/v4.2.0...v4.3.0) (2022-12-08)
### Features
* add seconds since the system became idle to logs ([cba13db](https://github.com/languitar/autosuspend/commit/cba13db8c50a5fbab05447c3f6ce74cf85898100)), closes [#281](https://github.com/languitar/autosuspend/issues/281)
# [4.2.0](https://github.com/languitar/autosuspend/compare/v4.1.1...v4.2.0) (2022-07-24)
### Features
* **wakeup:** add a systemd timer wakeup check ([7c687a2](https://github.com/languitar/autosuspend/commit/7c687a23f705d46c65ef400332483a32ff6eaa79))
## [4.1.1](https://github.com/languitar/autosuspend/compare/v4.1.0...v4.1.1) (2022-03-10)
### Bug Fixes
* allow tzlocal version >= 4 ([58e8634](https://github.com/languitar/autosuspend/commit/58e8634347cc5bf25cbfbfccfe874d05420bb995))
# [4.1.0](https://github.com/languitar/autosuspend/compare/v4.0.1...v4.1.0) (2021-12-28)
### Features
* add official Python 3.10 support ([e5b2e49](https://github.com/languitar/autosuspend/commit/e5b2e494986d13ac29a06cfac0c5a6601c372671))
## [4.0.1](https://github.com/languitar/autosuspend/compare/v4.0.0...v4.0.1) (2021-10-26)
### Bug Fixes
* **activity:** detect ipv4 mapped ipv6 connections ([a81e456](https://github.com/languitar/autosuspend/commit/a81e456aa89737a0a2f03ec5af5ffaf2e7738073)), closes [#116](https://github.com/languitar/autosuspend/issues/116)
# [4.0.0](https://github.com/languitar/autosuspend/compare/v3.1.4...v4.0.0) (2021-09-20)
* chore(build)!: drop tests on Python 3.7 ([06dce98](https://github.com/languitar/autosuspend/commit/06dce98882d5c8fa4d5e90623660c43d006eefa0))
### BREAKING CHANGES
* Python 3.7 isn't used anymore on any LTS Ubuntu or
    Debian release. No need to support such an old version anymore.
## [3.1.4](https://github.com/languitar/autosuspend/compare/v3.1.3...v3.1.4) (2021-09-20)
### Bug Fixes
* **ical:** limit tzlocal to version <3 ([623cd37](https://github.com/languitar/autosuspend/commit/623cd371df03a6fe3305eca4cf9e57c4d76b5c8a))
## [3.1.3](https://github.com/languitar/autosuspend/compare/v3.1.2...v3.1.3) (2021-03-29)
## [3.1.2](https://github.com/languitar/autosuspend/compare/v3.1.1...v3.1.2) (2021-03-29)
## [3.1.1](https://github.com/languitar/autosuspend/compare/v3.1.0...v3.1.1) (2021-03-28)
### Bug Fixes
* fix automatic version file generation ([aeb601d](https://github.com/languitar/autosuspend/commit/aeb601d523791780e5da592476b365bbc4b3f4c5))
## [3.1.0](https://github.com/languitar/autosuspend/compare/v3.0.1...v3.1.0) (2021-03-28)
### Features
* add semantic-release for automatic releases ([ac5ec86](https://github.com/languitar/autosuspend/commit/ac5ec8617681b537714f8eb8fef4ce0872989f2a))
### Bug Fixes
* use jsonpath ext to support filter expressions ([24d1be1](https://github.com/languitar/autosuspend/commit/24d1be1fcbd59d8e29a1bbfdc162e253e2f239c4)), closes [#102](https://github.com/languitar/autosuspend/issues/102)
                                                                                                                                                                                                                                                                                                                                                                                                                     autosuspend-9.0.0/doc/source/index.rst                                                              0000664 0000000 0000000 00000003343 15043744726 0020017 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        |project| - a daemon to automatically suspend and wake up a system
##################################################################
.. ifconfig:: is_preview
   .. warning::
      This is the documentation for an unreleased preview version of |project|.
.. include:: description.inc
The following diagram visualizes the periodic processing performed by |project|.
.. uml::
   @startuml
   skinparam shadowing false
   skinparam backgroundcolor #eeeeee
   skinparam Padding 8
   skinparam ActivityBackgroundColor #FFFFFF
   skinparam ActivityDiamondBackgroundColor #FFFFFF
   skinparam ActivityBorderColor #333333
   skinparam ActivityDiamondBorderColor #333333
   skinparam ArrowColor #333333
   start
   :Execute activity checks;
   if (Is the system active?) then (no)
     if (Was the system idle before?) then (no)
       :Remember current time as start of system inactivity;
     else (yes)
     endif
     if (Is system idle long enough?) then (yes)
       :Execute wake up checks;
       if (Is a wake up required soon?) then (yes)
         stop
       else
         if (Is any wake up required?) then (yes)
           #BBFFBB:Schedule the earliest wake up;
         else (no)
         endif
       endif
       #BBFFBB:Suspend the system;
     else (no)
       stop
     endif
   else (yes)
     :Forget start of system inactivity;
     stop
   endif
   stop
   @enduml
.. toctree::
   :maxdepth: 2
   :caption: Usage
   installation
   options
   configuration_file
   available_checks
   available_wakeups
   systemd_integration
   external_command_activity_scripts
   api
.. toctree::
   :maxdepth: 2
   :caption: Support
   faq
   debugging
   support
   changelog
Indices and tables
##################
* :ref:`genindex`
                                                                                                                                                                                                                                                                                             autosuspend-9.0.0/doc/source/installation.rst                                                       0000664 0000000 0000000 00000005323 15043744726 0021411 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        .. _installation:
Installation instructions
#########################
|project_program| is designed for Python **3** and does not work with Python 2.
.. note::
   After installation, do not forget to enable and start |project| vis `systemd`_ as described in :ref:`systemd-integration`.
Requirements
************
The minimal requirements are.
* `Python 3`_ >= 3.11
* `psutil`_
* `portalocker`_
Additionally, the some checks need further dependencies to function properly.
Please refer to :ref:`available-checks` for individual requirements.
If checks using URLs to load data should support ``file://`` URLs, `requests-file`_ is needed.
Binary packages
***************
.. image:: https://repology.org/badge/vertical-allrepos/autosuspend.svg
   :target: https://repology.org/project/autosuspend/versions
Debian
======
Installation from official package sources::
    apt-get install autosuspend
Archlinux (AUR)
===============
|project| is available as an `Archlinux AUR package `_.
Installation via some `AUR helpers ` such as :program:`paru`::
    paru -S autosuspend
Other AUR helpers may be used, too.
Gentoo
======
Patrick Holthaus has provided an ebuild for Gentoo in `his overlay `_.
You can use it as follows::
    eselect repository enable pholthaus-overlay
    emaint sync -r pholthaus-overlay
    emerge sys-apps/autosuspend
Other distributions
===================
In case you want to generate a package for a different Linux distribution, I'd be glad to hear about that.
Manual installation
*******************
|project_program| is a usual Python_ package and hence can be installed using the common Python_ packaging tools.
Briefly, the following steps can be used to install |project_program| from source in a system-wide location (as ``root`` user):
.. code-block:: bash
   python3 -m venv /opt/autosuspend
   /opt/autosuspend/bin/pip install git+https://github.com/languitar/autosuspend.git@#egg=autosuspend[all]
.. note::
   Replace the angle brackets with desired Git tag or branch.
   Use ``main`` for the latest development release.
.. note::
   The ``all`` in the square brackets ensures that |project_program| is installed with all optional dependencies.
   That way all available checks can be used.
   In case you only need a subset of optional requirements, replace ``all`` with a comma-separated list of package extras.
   The names of these extras can be found in :file:`setup.py`.
Afterwards, copy the systemd_ unit files found in ``/opt/autosuspend/lib/systemd/system/`` to ``/etc/systemd`` and adapt the contained paths to the installation location.
                                                                                                                                                                                                                                                                                                             autosuspend-9.0.0/doc/source/man_command.rst                                                        0000664 0000000 0000000 00000001330 15043744726 0021153 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        :orphan:
.. _man-command:
|project|
#########
Synopsis
********
|project_bold| [*options*] **daemon|presuspend|version** [*subcommand options*]
Description
***********
.. include:: description.inc
If not specified via a command line argument, |project_program| looks for a default configuration at :file:`/etc/autosuspend.conf`.
:manpage:`autosuspend.conf(5)` describes the configuration file, the available checks, and their configuration options.
Options
*******
.. toctree::
   options
Bugs
****
Please report bugs at the project repository at https://github.com/languitar/autosuspend.
See also
********
:manpage:`autosuspend.conf(5)`, online documentation including FAQs at https://autosuspend.readthedocs.io/
                                                                                                                                                                                                                                                                                                        autosuspend-9.0.0/doc/source/man_config.rst                                                         0000664 0000000 0000000 00000000574 15043744726 0021013 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        :orphan:
|project|.conf
##############
Synopsis
********
:file:`/etc/autosuspend.conf`
General Configuration
*********************
Configures the |project_program| daemon.
.. toctree::
   configuration_file
Available Activity Check
************************
.. toctree::
   available_checks
Available Wakeup Check
**********************
.. toctree::
   available_wakeups
                                                                                                                                    autosuspend-9.0.0/doc/source/old_changelog.rst                                                      0000664 0000000 0000000 00000014017 15043744726 0021475 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        3.1
***
New features
============
New activity checks
-------------------
* :ref:`check-jsonpath`: Similar to the existing :ref`check-xpath`, the new checks requests a JSON URL and evaluates it against a `JSONPath`_ expression to determine activity (:issue:`81`, :issue:`103`).
* :ref:`check-last-log-activity`: Check log files for most recent contained timestamps (:issue:`98`, :issue:`99`).
Fixed bugs
==========
* Connection errors are now properly handled by :ref:`check-mpd` (:issue:`77`).
Notable changes
===============
* The required `Python`_ version is now declared in the :ref:`installation` and :file:`setup.py` (:issue:`76`)
* Python 3.9 is officially supported and tested (:issue:`89`).
* Some code cleanup work has been performed (:issue:`93` and :issue:`92`).
* The daemon now better distinguished between temporary and permanent issues, for instance, by terminating in case a required program is not installed (:issue:`78`).
3.0
***
This version splits the executable into two distinct subcommands, one for activity checking and one for scheduling wake ups.
This way, the wake up scheduling mechanism can be hooked into system tools such as `systemd`_ to ensure that wake ups are scheduled correctly every time the system suspends.
This increases the reliability of the mechanism but also changes the way |project_program| has to be called.
You now need to enable two `systemd`_ units as describe in :ref:`systemd-integration` and the command line interface has changed.
New features
============
* The :ref:`check-kodi-idle-time` activity check can now be parameterized whether to indicate activity on a paused player or not (:issue:`59`, :issue:`60`).
* New structure as described above in the version introduction (:issue:`43`).
Fixed bugs
==========
* Documented default URL for the ``Kodi*`` checks did not actually exist in code, which has been fixed now (:issue:`58`, :issue:`61`).
* A bug in :ref:`check-logind-session-idle` has been fixed (:issue:`71`, :issue:`72`).
Notable changes
===============
* The executable now uses subcommands.
  The previous behavior as a long-running daemon is now available under the ``daemon`` subcommand.
* The command line flags for logging have changed.
  The previous ``-l`` flag, which combined boolean behavior and file reading, has been split into two distinct flags: ``-d`` is a boolean switch to enable full debug logging to console, whereas the old ``-l`` is now only used for reading logging configuration files.
  This change prevents nasty subtleties and issues when parsing the command line and became mandatory to support subcommands after the general configuration arguments such as logging.
* Dropped support for Python 3.6 and included Python 3.8 in CI infrastructure.
  Everything works on Python 3.8.
* The documentation has been restructured and improved. For instance, there is now a :ref:`faq` section.
* Some build and test dependencies have changed.
* CI-builds have been converted to Github Actions.
2.0.4
*****
This is a minor bug fix release.
Fixed bugs
==========
* :ref:`check-active-connection` did not handle local IPv6 addresses with scope such as ``fe80::5193:518c:5c69:aedb%enp3s0`` (:issue:`50`)
2.0.3
*****
This is a minor bug fix release.
Fixed bugs
==========
* :ref:`check-network-bandwidth` did not update its internal state and therefore did not work as documented (:issue:`49`)
2.0.2
*****
This is a minor bug fix release.
Fixed bugs
==========
* :ref:`check-kodi` and :ref:`check-kodi-idle-time` checks now catch ``JSONDecodeErrors`` (:issue:`45`)
* :ref:`check-kodi` and :ref:`check-kodi-idle-time` checks now support authentication (:issue:`47`)
2.0
***
This version adds scheduled wake ups as its main features.
In addition to checks for activity, a set of checks for future activities can now be configured to determine times at which the systems needs to be online again.
The daemon will start suspending in case the next detected wake up time is far enough in the future and schedule an automatic system wake up at the closest determined wake up time.
This can, for instance, be used to ensure that the system is up again when a TV show has to be recorded to disk.
Below is a detailed list of notable changes.
New features
============
* Scheduled wake ups (:issue:`9`).
* Ability to call configurable user commands before suspending for notification purposes (:issue:`25`).
* Checks using network requests now support authentication (:issue:`32`).
* Checks using network requests now support ``file://`` URIs (:issue:`36`).
New activity checks
-------------------
* :ref:`check-active-calendar-event`: Uses an `iCalendar`_ file (via network request) to prevent suspending in case an event in the calendar is currently active (:issue:`24`).
* :ref:`check-kodi-idle-time`: Checks the idle time of `Kodi`_ to prevent suspending in case the menu is used (:issue:`33`).
New wakeup checks
-----------------
* :ref:`wakeup-calendar`: Wake up the system at the next event in an `iCalendar`_ file (requested via network, :issue:`30`).
* :ref:`wakeup-command`: Call an external command to determine the next wake up time (:issue:`26`).
* :ref:`wakeup-file`: Read the next wake up time from a file (:issue:`9`).
* :ref:`wakeup-periodic`: Wake up at a defined interval, for instance, to refresh calendars for the :ref:`wakeup-calendar` check (:issue:`34`).
* :ref:`wakeup-xpath` and :ref:`wakeup-xpath-delta`: Request an XML document and use `XPath`_ to extract the next wakeup time.
Fixed bugs
==========
* `XPath`_ checks now support responses with explicit encodings (:issue:`29`).
Notable changes
===============
* The namespace of the logging systems has been rearranged (:issue:`38`).
  Existing logging configurations might require changes.
* The default configuration file has been reduced to explain the syntax and semantics.
  For a list of all available checks, refer the manual instead (:issue:`39`).
For a complete list of all addressed issues and new features, please refer to the respective `Github milestone `_.
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 autosuspend-9.0.0/doc/source/options.rst                                                            0000664 0000000 0000000 00000003140 15043744726 0020376 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        Command line options
####################
General syntax:
    |project_bold| [*options*] **daemon|presuspend|version** [*subcommand options*]
General options
***************
.. program:: autosuspend
.. option:: -h, --help
   Displays an online help.
.. option:: -c FILE, --config FILE
   Specifies an alternate config file to use instead of the default on at :file:`/etc/autosuspend.conf`.
.. option:: -l FILE, --logging FILE
   Configure the logging system with the provided logging file.
   This file needs to follow the conventions for :ref:`Python logging files `.
.. option:: -d
   Configure full debug logging in the command line.
   Mutually exclusive to :option:`autosuspend -l`.
Subcommand ``daemon``
*********************
Starts the continuously running daemon.
.. program:: autosuspend daemon
.. option:: -a, --allchecks
   Usually, |project_program| stops checks in each iteration as soon as the first matching check indicates system activity.
   If this flag is set, all subsequent checks are still executed.
   Useful mostly for debugging purposes.
.. option:: -r SECONDS, --runfor SECONDS
   If specified, do not run endlessly.
   Instead, operate only for the specified amount of seconds, then exit.
   Useful mostly for debugging purposes.
Subcommand ``presuspend``
*************************
Should be called by the system before suspending.
.. program:: autosuspend presuspend
No options
Subcommand ``version``
*************************
Outputs the currently installed version of |project_program| to stdout.
.. program:: autosuspend version
No options
                                                                                                                                                                                                                                                                                                                                                                                                                                autosuspend-9.0.0/doc/source/support.rst                                                            0000664 0000000 0000000 00000000637 15043744726 0020427 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        Support requests
################
For questions, please first consult the issue tracker at the `Github project `_ for existing issues and questions.
Questions are marked with the `question` tag.
If your question is not answered, open a new issue with the question.
In case you have found a bug or you want to request a new feature, please also open an issue at the `Github project `_.
                                                                                                 autosuspend-9.0.0/doc/source/systemd_integration.rst                                                0000664 0000000 0000000 00000002222 15043744726 0022776 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        .. _systemd-integration:
systemd integration
###################
Even though it is possible to run |project_program| manually (cf. :ref:`the manpage `), in production use cases, the daemon will usually be run from `systemd`_.
For this purpose, the package ships with `service definition files `_ for `systemd`_, so that you should be able to manage |project_program| via `systemd`_.
These files need to be installed in the appropriate locations for such service files, which depend on the Linux distribution.
Some common locations are:
* :file:`/usr/lib/systemd/system` (e.g. Archlinux packaged service files)
* :file:`/lib/systemd/system` (e.g. Debian packaged service files)
* :file:`/etc/systemd/system` (e.g. Archlinux manually added service files)
Binary installation packages for Linux distributions should have installed the service files at the appropriate locations already.
To start |project_program| via `systemd`_, execute:
.. code-block:: bash
   systemctl enable autosuspend.service
To start |project_program| automatically at system start, execute:
.. code-block:: bash
   systemctl start autosuspend.service
                                                                                                                                                                                                                                                                                                                                                                              autosuspend-9.0.0/package-lock.json                                                                 0000664 0000000 0000000 00001510411 15043744726 0017325 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        {
  "name": "autosuspend",
  "lockfileVersion": 2,
  "requires": true,
  "packages": {
    "": {
      "devDependencies": {
        "@commitlint/cli": "19.8.1",
        "@commitlint/config-conventional": "19.8.1",
        "@semantic-release/changelog": "6.0.3",
        "@semantic-release/exec": "7.1.0",
        "@semantic-release/git": "10.0.1",
        "semantic-release": "24.2.7"
      }
    },
    "node_modules/@babel/code-frame": {
      "version": "7.27.1",
      "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
      "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==",
      "dev": true,
      "dependencies": {
        "@babel/helper-validator-identifier": "^7.27.1",
        "js-tokens": "^4.0.0",
        "picocolors": "^1.1.1"
      },
      "engines": {
        "node": ">=6.9.0"
      }
    },
    "node_modules/@babel/helper-validator-identifier": {
      "version": "7.27.1",
      "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz",
      "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==",
      "dev": true,
      "engines": {
        "node": ">=6.9.0"
      }
    },
    "node_modules/@colors/colors": {
      "version": "1.5.0",
      "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz",
      "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==",
      "dev": true,
      "optional": true,
      "engines": {
        "node": ">=0.1.90"
      }
    },
    "node_modules/@commitlint/cli": {
      "version": "19.8.1",
      "resolved": "https://registry.npmjs.org/@commitlint/cli/-/cli-19.8.1.tgz",
      "integrity": "sha512-LXUdNIkspyxrlV6VDHWBmCZRtkEVRpBKxi2Gtw3J54cGWhLCTouVD/Q6ZSaSvd2YaDObWK8mDjrz3TIKtaQMAA==",
      "dev": true,
      "dependencies": {
        "@commitlint/format": "^19.8.1",
        "@commitlint/lint": "^19.8.1",
        "@commitlint/load": "^19.8.1",
        "@commitlint/read": "^19.8.1",
        "@commitlint/types": "^19.8.1",
        "tinyexec": "^1.0.0",
        "yargs": "^17.0.0"
      },
      "bin": {
        "commitlint": "cli.js"
      },
      "engines": {
        "node": ">=v18"
      }
    },
    "node_modules/@commitlint/config-conventional": {
      "version": "19.8.1",
      "resolved": "https://registry.npmjs.org/@commitlint/config-conventional/-/config-conventional-19.8.1.tgz",
      "integrity": "sha512-/AZHJL6F6B/G959CsMAzrPKKZjeEiAVifRyEwXxcT6qtqbPwGw+iQxmNS+Bu+i09OCtdNRW6pNpBvgPrtMr9EQ==",
      "dev": true,
      "dependencies": {
        "@commitlint/types": "^19.8.1",
        "conventional-changelog-conventionalcommits": "^7.0.2"
      },
      "engines": {
        "node": ">=v18"
      }
    },
    "node_modules/@commitlint/config-validator": {
      "version": "19.8.1",
      "resolved": "https://registry.npmjs.org/@commitlint/config-validator/-/config-validator-19.8.1.tgz",
      "integrity": "sha512-0jvJ4u+eqGPBIzzSdqKNX1rvdbSU1lPNYlfQQRIFnBgLy26BtC0cFnr7c/AyuzExMxWsMOte6MkTi9I3SQ3iGQ==",
      "dev": true,
      "dependencies": {
        "@commitlint/types": "^19.8.1",
        "ajv": "^8.11.0"
      },
      "engines": {
        "node": ">=v18"
      }
    },
    "node_modules/@commitlint/ensure": {
      "version": "19.8.1",
      "resolved": "https://registry.npmjs.org/@commitlint/ensure/-/ensure-19.8.1.tgz",
      "integrity": "sha512-mXDnlJdvDzSObafjYrOSvZBwkD01cqB4gbnnFuVyNpGUM5ijwU/r/6uqUmBXAAOKRfyEjpkGVZxaDsCVnHAgyw==",
      "dev": true,
      "dependencies": {
        "@commitlint/types": "^19.8.1",
        "lodash.camelcase": "^4.3.0",
        "lodash.kebabcase": "^4.1.1",
        "lodash.snakecase": "^4.1.1",
        "lodash.startcase": "^4.4.0",
        "lodash.upperfirst": "^4.3.1"
      },
      "engines": {
        "node": ">=v18"
      }
    },
    "node_modules/@commitlint/execute-rule": {
      "version": "19.8.1",
      "resolved": "https://registry.npmjs.org/@commitlint/execute-rule/-/execute-rule-19.8.1.tgz",
      "integrity": "sha512-YfJyIqIKWI64Mgvn/sE7FXvVMQER/Cd+s3hZke6cI1xgNT/f6ZAz5heND0QtffH+KbcqAwXDEE1/5niYayYaQA==",
      "dev": true,
      "engines": {
        "node": ">=v18"
      }
    },
    "node_modules/@commitlint/format": {
      "version": "19.8.1",
      "resolved": "https://registry.npmjs.org/@commitlint/format/-/format-19.8.1.tgz",
      "integrity": "sha512-kSJj34Rp10ItP+Eh9oCItiuN/HwGQMXBnIRk69jdOwEW9llW9FlyqcWYbHPSGofmjsqeoxa38UaEA5tsbm2JWw==",
      "dev": true,
      "dependencies": {
        "@commitlint/types": "^19.8.1",
        "chalk": "^5.3.0"
      },
      "engines": {
        "node": ">=v18"
      }
    },
    "node_modules/@commitlint/format/node_modules/chalk": {
      "version": "5.3.0",
      "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz",
      "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==",
      "dev": true,
      "engines": {
        "node": "^12.17.0 || ^14.13 || >=16.0.0"
      },
      "funding": {
        "url": "https://github.com/chalk/chalk?sponsor=1"
      }
    },
    "node_modules/@commitlint/is-ignored": {
      "version": "19.8.1",
      "resolved": "https://registry.npmjs.org/@commitlint/is-ignored/-/is-ignored-19.8.1.tgz",
      "integrity": "sha512-AceOhEhekBUQ5dzrVhDDsbMaY5LqtN8s1mqSnT2Kz1ERvVZkNihrs3Sfk1Je/rxRNbXYFzKZSHaPsEJJDJV8dg==",
      "dev": true,
      "dependencies": {
        "@commitlint/types": "^19.8.1",
        "semver": "^7.6.0"
      },
      "engines": {
        "node": ">=v18"
      }
    },
    "node_modules/@commitlint/lint": {
      "version": "19.8.1",
      "resolved": "https://registry.npmjs.org/@commitlint/lint/-/lint-19.8.1.tgz",
      "integrity": "sha512-52PFbsl+1EvMuokZXLRlOsdcLHf10isTPlWwoY1FQIidTsTvjKXVXYb7AvtpWkDzRO2ZsqIgPK7bI98x8LRUEw==",
      "dev": true,
      "dependencies": {
        "@commitlint/is-ignored": "^19.8.1",
        "@commitlint/parse": "^19.8.1",
        "@commitlint/rules": "^19.8.1",
        "@commitlint/types": "^19.8.1"
      },
      "engines": {
        "node": ">=v18"
      }
    },
    "node_modules/@commitlint/load": {
      "version": "19.8.1",
      "resolved": "https://registry.npmjs.org/@commitlint/load/-/load-19.8.1.tgz",
      "integrity": "sha512-9V99EKG3u7z+FEoe4ikgq7YGRCSukAcvmKQuTtUyiYPnOd9a2/H9Ak1J9nJA1HChRQp9OA/sIKPugGS+FK/k1A==",
      "dev": true,
      "dependencies": {
        "@commitlint/config-validator": "^19.8.1",
        "@commitlint/execute-rule": "^19.8.1",
        "@commitlint/resolve-extends": "^19.8.1",
        "@commitlint/types": "^19.8.1",
        "chalk": "^5.3.0",
        "cosmiconfig": "^9.0.0",
        "cosmiconfig-typescript-loader": "^6.1.0",
        "lodash.isplainobject": "^4.0.6",
        "lodash.merge": "^4.6.2",
        "lodash.uniq": "^4.5.0"
      },
      "engines": {
        "node": ">=v18"
      }
    },
    "node_modules/@commitlint/load/node_modules/chalk": {
      "version": "5.3.0",
      "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz",
      "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==",
      "dev": true,
      "engines": {
        "node": "^12.17.0 || ^14.13 || >=16.0.0"
      },
      "funding": {
        "url": "https://github.com/chalk/chalk?sponsor=1"
      }
    },
    "node_modules/@commitlint/message": {
      "version": "19.8.1",
      "resolved": "https://registry.npmjs.org/@commitlint/message/-/message-19.8.1.tgz",
      "integrity": "sha512-+PMLQvjRXiU+Ae0Wc+p99EoGEutzSXFVwQfa3jRNUZLNW5odZAyseb92OSBTKCu+9gGZiJASt76Cj3dLTtcTdg==",
      "dev": true,
      "engines": {
        "node": ">=v18"
      }
    },
    "node_modules/@commitlint/parse": {
      "version": "19.8.1",
      "resolved": "https://registry.npmjs.org/@commitlint/parse/-/parse-19.8.1.tgz",
      "integrity": "sha512-mmAHYcMBmAgJDKWdkjIGq50X4yB0pSGpxyOODwYmoexxxiUCy5JJT99t1+PEMK7KtsCtzuWYIAXYAiKR+k+/Jw==",
      "dev": true,
      "dependencies": {
        "@commitlint/types": "^19.8.1",
        "conventional-changelog-angular": "^7.0.0",
        "conventional-commits-parser": "^5.0.0"
      },
      "engines": {
        "node": ">=v18"
      }
    },
    "node_modules/@commitlint/read": {
      "version": "19.8.1",
      "resolved": "https://registry.npmjs.org/@commitlint/read/-/read-19.8.1.tgz",
      "integrity": "sha512-03Jbjb1MqluaVXKHKRuGhcKWtSgh3Jizqy2lJCRbRrnWpcM06MYm8th59Xcns8EqBYvo0Xqb+2DoZFlga97uXQ==",
      "dev": true,
      "dependencies": {
        "@commitlint/top-level": "^19.8.1",
        "@commitlint/types": "^19.8.1",
        "git-raw-commits": "^4.0.0",
        "minimist": "^1.2.8",
        "tinyexec": "^1.0.0"
      },
      "engines": {
        "node": ">=v18"
      }
    },
    "node_modules/@commitlint/resolve-extends": {
      "version": "19.8.1",
      "resolved": "https://registry.npmjs.org/@commitlint/resolve-extends/-/resolve-extends-19.8.1.tgz",
      "integrity": "sha512-GM0mAhFk49I+T/5UCYns5ayGStkTt4XFFrjjf0L4S26xoMTSkdCf9ZRO8en1kuopC4isDFuEm7ZOm/WRVeElVg==",
      "dev": true,
      "dependencies": {
        "@commitlint/config-validator": "^19.8.1",
        "@commitlint/types": "^19.8.1",
        "global-directory": "^4.0.1",
        "import-meta-resolve": "^4.0.0",
        "lodash.mergewith": "^4.6.2",
        "resolve-from": "^5.0.0"
      },
      "engines": {
        "node": ">=v18"
      }
    },
    "node_modules/@commitlint/rules": {
      "version": "19.8.1",
      "resolved": "https://registry.npmjs.org/@commitlint/rules/-/rules-19.8.1.tgz",
      "integrity": "sha512-Hnlhd9DyvGiGwjfjfToMi1dsnw1EXKGJNLTcsuGORHz6SS9swRgkBsou33MQ2n51/boIDrbsg4tIBbRpEWK2kw==",
      "dev": true,
      "dependencies": {
        "@commitlint/ensure": "^19.8.1",
        "@commitlint/message": "^19.8.1",
        "@commitlint/to-lines": "^19.8.1",
        "@commitlint/types": "^19.8.1"
      },
      "engines": {
        "node": ">=v18"
      }
    },
    "node_modules/@commitlint/to-lines": {
      "version": "19.8.1",
      "resolved": "https://registry.npmjs.org/@commitlint/to-lines/-/to-lines-19.8.1.tgz",
      "integrity": "sha512-98Mm5inzbWTKuZQr2aW4SReY6WUukdWXuZhrqf1QdKPZBCCsXuG87c+iP0bwtD6DBnmVVQjgp4whoHRVixyPBg==",
      "dev": true,
      "engines": {
        "node": ">=v18"
      }
    },
    "node_modules/@commitlint/top-level": {
      "version": "19.8.1",
      "resolved": "https://registry.npmjs.org/@commitlint/top-level/-/top-level-19.8.1.tgz",
      "integrity": "sha512-Ph8IN1IOHPSDhURCSXBz44+CIu+60duFwRsg6HqaISFHQHbmBtxVw4ZrFNIYUzEP7WwrNPxa2/5qJ//NK1FGcw==",
      "dev": true,
      "dependencies": {
        "find-up": "^7.0.0"
      },
      "engines": {
        "node": ">=v18"
      }
    },
    "node_modules/@commitlint/types": {
      "version": "19.8.1",
      "resolved": "https://registry.npmjs.org/@commitlint/types/-/types-19.8.1.tgz",
      "integrity": "sha512-/yCrWGCoA1SVKOks25EGadP9Pnj0oAIHGpl2wH2M2Y46dPM2ueb8wyCVOD7O3WCTkaJ0IkKvzhl1JY7+uCT2Dw==",
      "dev": true,
      "dependencies": {
        "@types/conventional-commits-parser": "^5.0.0",
        "chalk": "^5.3.0"
      },
      "engines": {
        "node": ">=v18"
      }
    },
    "node_modules/@commitlint/types/node_modules/chalk": {
      "version": "5.4.1",
      "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz",
      "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==",
      "dev": true,
      "engines": {
        "node": "^12.17.0 || ^14.13 || >=16.0.0"
      },
      "funding": {
        "url": "https://github.com/chalk/chalk?sponsor=1"
      }
    },
    "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,
      "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,
      "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,
      "dependencies": {
        "@nodelib/fs.scandir": "2.1.5",
        "fastq": "^1.6.0"
      },
      "engines": {
        "node": ">= 8"
      }
    },
    "node_modules/@octokit/auth-token": {
      "version": "5.0.1",
      "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-5.0.1.tgz",
      "integrity": "sha512-RTmWsLfig8SBoiSdgvCht4BXl1CHU89Co5xiQ5JF19my/sIRDFCQ1RPrmK0exgqUZuNm39C/bV8+/83+MJEjGg==",
      "dev": true,
      "engines": {
        "node": ">= 18"
      }
    },
    "node_modules/@octokit/core": {
      "version": "6.0.1",
      "resolved": "https://registry.npmjs.org/@octokit/core/-/core-6.0.1.tgz",
      "integrity": "sha512-MIpPQXu8Y8GjHwXM81JLveiV+DHJZtLMcB5nKekBGOl3iAtk0HT3i12Xl8Biybu+bCS1+k4qbuKEq5d0RxNRnQ==",
      "dev": true,
      "dependencies": {
        "@octokit/auth-token": "^5.0.0",
        "@octokit/graphql": "^8.0.0",
        "@octokit/request": "^9.0.0",
        "@octokit/request-error": "^6.0.1",
        "@octokit/types": "^12.0.0",
        "before-after-hook": "^3.0.2",
        "universal-user-agent": "^7.0.0"
      },
      "engines": {
        "node": ">= 18"
      }
    },
    "node_modules/@octokit/endpoint": {
      "version": "10.0.0",
      "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-10.0.0.tgz",
      "integrity": "sha512-emBcNDxBdC1y3+knJonS5zhUB/CG6TihubxM2U1/pG/Z1y3a4oV0Gzz3lmkCvWWQI6h3tqBAX9MgCBFp+M68Jw==",
      "dev": true,
      "dependencies": {
        "@octokit/types": "^12.0.0",
        "universal-user-agent": "^7.0.2"
      },
      "engines": {
        "node": ">= 18"
      }
    },
    "node_modules/@octokit/graphql": {
      "version": "8.0.1",
      "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-8.0.1.tgz",
      "integrity": "sha512-lLDb6LhC1gBj2CxEDa5Xk10+H/boonhs+3Mi6jpRyetskDKNHe6crMeKmUE2efoLofMP8ruannLlCUgpTFmVzQ==",
      "dev": true,
      "dependencies": {
        "@octokit/request": "^9.0.0",
        "@octokit/types": "^12.0.0",
        "universal-user-agent": "^7.0.0"
      },
      "engines": {
        "node": ">= 18"
      }
    },
    "node_modules/@octokit/openapi-types": {
      "version": "20.0.0",
      "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-20.0.0.tgz",
      "integrity": "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA==",
      "dev": true
    },
    "node_modules/@octokit/plugin-paginate-rest": {
      "version": "11.3.5",
      "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-11.3.5.tgz",
      "integrity": "sha512-cgwIRtKrpwhLoBi0CUNuY83DPGRMaWVjqVI/bGKsLJ4PzyWZNaEmhHroI2xlrVXkk6nFv0IsZpOp+ZWSWUS2AQ==",
      "dev": true,
      "dependencies": {
        "@octokit/types": "^13.6.0"
      },
      "engines": {
        "node": ">= 18"
      },
      "peerDependencies": {
        "@octokit/core": ">=6"
      }
    },
    "node_modules/@octokit/plugin-paginate-rest/node_modules/@octokit/openapi-types": {
      "version": "22.2.0",
      "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-22.2.0.tgz",
      "integrity": "sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg==",
      "dev": true
    },
    "node_modules/@octokit/plugin-paginate-rest/node_modules/@octokit/types": {
      "version": "13.6.1",
      "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.6.1.tgz",
      "integrity": "sha512-PHZE9Z+kWXb23Ndik8MKPirBPziOc0D2/3KH1P+6jK5nGWe96kadZuE4jev2/Jq7FvIfTlT2Ltg8Fv2x1v0a5g==",
      "dev": true,
      "dependencies": {
        "@octokit/openapi-types": "^22.2.0"
      }
    },
    "node_modules/@octokit/plugin-retry": {
      "version": "7.0.3",
      "resolved": "https://registry.npmjs.org/@octokit/plugin-retry/-/plugin-retry-7.0.3.tgz",
      "integrity": "sha512-T9l5Z7XnDZ7dkyNmhJPSUq0YjbqUT/xn4yQbhcSuv4WGC/LqM73/mKwkl68VDPoLw20e8oz4L7qQopWt9v6sow==",
      "dev": true,
      "dependencies": {
        "@octokit/request-error": "^6.0.0",
        "@octokit/types": "^12.0.0",
        "bottleneck": "^2.15.3"
      },
      "engines": {
        "node": ">= 18"
      },
      "peerDependencies": {
        "@octokit/core": ">=6"
      }
    },
    "node_modules/@octokit/plugin-throttling": {
      "version": "9.0.3",
      "resolved": "https://registry.npmjs.org/@octokit/plugin-throttling/-/plugin-throttling-9.0.3.tgz",
      "integrity": "sha512-DReKamrLBJOzld73dmmxV2H137QKJfsxszAczEZXeAJQ/Po6bzQacKajPdodA6T1jfmP9+waImus+d/R2j+R7Q==",
      "dev": true,
      "dependencies": {
        "@octokit/types": "^12.6.0",
        "bottleneck": "^2.15.3"
      },
      "engines": {
        "node": ">= 18"
      },
      "peerDependencies": {
        "@octokit/core": "^6.0.0"
      }
    },
    "node_modules/@octokit/request": {
      "version": "9.0.1",
      "resolved": "https://registry.npmjs.org/@octokit/request/-/request-9.0.1.tgz",
      "integrity": "sha512-kL+cAcbSl3dctYLuJmLfx6Iku2MXXy0jszhaEIjQNaCp4zjHXrhVAHeuaRdNvJjW9qjl3u1MJ72+OuBP0YW/pg==",
      "dev": true,
      "dependencies": {
        "@octokit/endpoint": "^10.0.0",
        "@octokit/request-error": "^6.0.1",
        "@octokit/types": "^12.0.0",
        "universal-user-agent": "^7.0.2"
      },
      "engines": {
        "node": ">= 18"
      }
    },
    "node_modules/@octokit/request-error": {
      "version": "6.0.2",
      "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-6.0.2.tgz",
      "integrity": "sha512-WtRVpoHcNXs84+s9s/wqfHaxM68NGMg8Av7h59B50OVO0PwwMx+2GgQ/OliUd0iQBSNWgR6N8afi/KjSHbXHWw==",
      "dev": true,
      "dependencies": {
        "@octokit/types": "^12.0.0"
      },
      "engines": {
        "node": ">= 18"
      }
    },
    "node_modules/@octokit/types": {
      "version": "12.6.0",
      "resolved": "https://registry.npmjs.org/@octokit/types/-/types-12.6.0.tgz",
      "integrity": "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw==",
      "dev": true,
      "dependencies": {
        "@octokit/openapi-types": "^20.0.0"
      }
    },
    "node_modules/@pnpm/network.ca-file": {
      "version": "1.0.2",
      "resolved": "https://registry.npmjs.org/@pnpm/network.ca-file/-/network.ca-file-1.0.2.tgz",
      "integrity": "sha512-YcPQ8a0jwYU9bTdJDpXjMi7Brhkr1mXsXrUJvjqM2mQDgkRiz8jFaQGOdaLxgjtUfQgZhKy/O3cG/YwmgKaxLA==",
      "dev": true,
      "dependencies": {
        "graceful-fs": "4.2.10"
      },
      "engines": {
        "node": ">=12.22.0"
      }
    },
    "node_modules/@pnpm/npm-conf": {
      "version": "1.0.5",
      "resolved": "https://registry.npmjs.org/@pnpm/npm-conf/-/npm-conf-1.0.5.tgz",
      "integrity": "sha512-hD8ml183638O3R6/Txrh0L8VzGOrFXgRtRDG4qQC4tONdZ5Z1M+tlUUDUvrjYdmK6G+JTBTeaCLMna11cXzi8A==",
      "dev": true,
      "dependencies": {
        "@pnpm/network.ca-file": "^1.0.1",
        "config-chain": "^1.1.11"
      },
      "engines": {
        "node": ">=12"
      }
    },
    "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
    },
    "node_modules/@semantic-release/changelog": {
      "version": "6.0.3",
      "resolved": "https://registry.npmjs.org/@semantic-release/changelog/-/changelog-6.0.3.tgz",
      "integrity": "sha512-dZuR5qByyfe3Y03TpmCvAxCyTnp7r5XwtHRf/8vD9EAn4ZWbavUX8adMtXYzE86EVh0gyLA7lm5yW4IV30XUag==",
      "dev": true,
      "dependencies": {
        "@semantic-release/error": "^3.0.0",
        "aggregate-error": "^3.0.0",
        "fs-extra": "^11.0.0",
        "lodash": "^4.17.4"
      },
      "engines": {
        "node": ">=14.17"
      },
      "peerDependencies": {
        "semantic-release": ">=18.0.0"
      }
    },
    "node_modules/@semantic-release/commit-analyzer": {
      "version": "13.0.0",
      "resolved": "https://registry.npmjs.org/@semantic-release/commit-analyzer/-/commit-analyzer-13.0.0.tgz",
      "integrity": "sha512-KtXWczvTAB1ZFZ6B4O+w8HkfYm/OgQb1dUGNFZtDgQ0csggrmkq8sTxhd+lwGF8kMb59/RnG9o4Tn7M/I8dQ9Q==",
      "dev": true,
      "dependencies": {
        "conventional-changelog-angular": "^8.0.0",
        "conventional-changelog-writer": "^8.0.0",
        "conventional-commits-filter": "^5.0.0",
        "conventional-commits-parser": "^6.0.0",
        "debug": "^4.0.0",
        "import-from-esm": "^1.0.3",
        "lodash-es": "^4.17.21",
        "micromatch": "^4.0.2"
      },
      "engines": {
        "node": ">=20.8.1"
      },
      "peerDependencies": {
        "semantic-release": ">=20.1.0"
      }
    },
    "node_modules/@semantic-release/commit-analyzer/node_modules/conventional-changelog-angular": {
      "version": "8.0.0",
      "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-8.0.0.tgz",
      "integrity": "sha512-CLf+zr6St0wIxos4bmaKHRXWAcsCXrJU6F4VdNDrGRK3B8LDLKoX3zuMV5GhtbGkVR/LohZ6MT6im43vZLSjmA==",
      "dev": true,
      "dependencies": {
        "compare-func": "^2.0.0"
      },
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@semantic-release/commit-analyzer/node_modules/conventional-commits-parser": {
      "version": "6.0.0",
      "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-6.0.0.tgz",
      "integrity": "sha512-TbsINLp48XeMXR8EvGjTnKGsZqBemisPoyWESlpRyR8lif0lcwzqz+NMtYSj1ooF/WYjSuu7wX0CtdeeMEQAmA==",
      "dev": true,
      "dependencies": {
        "meow": "^13.0.0"
      },
      "bin": {
        "conventional-commits-parser": "dist/cli/index.js"
      },
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@semantic-release/commit-analyzer/node_modules/meow": {
      "version": "13.2.0",
      "resolved": "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz",
      "integrity": "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==",
      "dev": true,
      "engines": {
        "node": ">=18"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/@semantic-release/error": {
      "version": "3.0.0",
      "resolved": "https://registry.npmjs.org/@semantic-release/error/-/error-3.0.0.tgz",
      "integrity": "sha512-5hiM4Un+tpl4cKw3lV4UgzJj+SmfNIDCLLw0TepzQxz9ZGV5ixnqkzIVF+3tp0ZHgcMKE+VNGHJjEeyFG2dcSw==",
      "dev": true,
      "engines": {
        "node": ">=14.17"
      }
    },
    "node_modules/@semantic-release/exec": {
      "version": "7.1.0",
      "resolved": "https://registry.npmjs.org/@semantic-release/exec/-/exec-7.1.0.tgz",
      "integrity": "sha512-4ycZ2atgEUutspPZ2hxO6z8JoQt4+y/kkHvfZ1cZxgl9WKJId1xPj+UadwInj+gMn2Gsv+fLnbrZ4s+6tK2TFQ==",
      "dev": true,
      "dependencies": {
        "@semantic-release/error": "^4.0.0",
        "aggregate-error": "^3.0.0",
        "debug": "^4.0.0",
        "execa": "^9.0.0",
        "lodash-es": "^4.17.21",
        "parse-json": "^8.0.0"
      },
      "engines": {
        "node": ">=20.8.1"
      },
      "peerDependencies": {
        "semantic-release": ">=24.1.0"
      }
    },
    "node_modules/@semantic-release/exec/node_modules/@semantic-release/error": {
      "version": "4.0.0",
      "resolved": "https://registry.npmjs.org/@semantic-release/error/-/error-4.0.0.tgz",
      "integrity": "sha512-mgdxrHTLOjOddRVYIYDo0fR3/v61GNN1YGkfbrjuIKg/uMgCd+Qzo3UAXJ+woLQQpos4pl5Esuw5A7AoNlzjUQ==",
      "dev": true,
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@semantic-release/exec/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,
      "engines": {
        "node": ">=18"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/@semantic-release/exec/node_modules/execa": {
      "version": "9.5.3",
      "resolved": "https://registry.npmjs.org/execa/-/execa-9.5.3.tgz",
      "integrity": "sha512-QFNnTvU3UjgWFy8Ef9iDHvIdcgZ344ebkwYx4/KLbR+CKQA4xBaHzv+iRpp86QfMHP8faFQLh8iOc57215y4Rg==",
      "dev": true,
      "dependencies": {
        "@sindresorhus/merge-streams": "^4.0.0",
        "cross-spawn": "^7.0.3",
        "figures": "^6.1.0",
        "get-stream": "^9.0.0",
        "human-signals": "^8.0.0",
        "is-plain-obj": "^4.1.0",
        "is-stream": "^4.0.1",
        "npm-run-path": "^6.0.0",
        "pretty-ms": "^9.0.0",
        "signal-exit": "^4.1.0",
        "strip-final-newline": "^4.0.0",
        "yoctocolors": "^2.0.0"
      },
      "engines": {
        "node": "^18.19.0 || >=20.5.0"
      },
      "funding": {
        "url": "https://github.com/sindresorhus/execa?sponsor=1"
      }
    },
    "node_modules/@semantic-release/exec/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,
      "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/@semantic-release/exec/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,
      "engines": {
        "node": ">=18.18.0"
      }
    },
    "node_modules/@semantic-release/exec/node_modules/index-to-position": {
      "version": "1.1.0",
      "resolved": "https://registry.npmjs.org/index-to-position/-/index-to-position-1.1.0.tgz",
      "integrity": "sha512-XPdx9Dq4t9Qk1mTMbWONJqU7boCoumEH7fRET37HX5+khDUl3J2W6PdALxhILYlIYx2amlwYcRPp28p0tSiojg==",
      "dev": true,
      "engines": {
        "node": ">=18"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/@semantic-release/exec/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,
      "engines": {
        "node": ">=18"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/@semantic-release/exec/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,
      "dependencies": {
        "path-key": "^4.0.0",
        "unicorn-magic": "^0.3.0"
      },
      "engines": {
        "node": ">=18"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/@semantic-release/exec/node_modules/parse-json": {
      "version": "8.3.0",
      "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-8.3.0.tgz",
      "integrity": "sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==",
      "dev": true,
      "dependencies": {
        "@babel/code-frame": "^7.26.2",
        "index-to-position": "^1.1.0",
        "type-fest": "^4.39.1"
      },
      "engines": {
        "node": ">=18"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/@semantic-release/exec/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,
      "engines": {
        "node": ">=12"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/@semantic-release/exec/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,
      "engines": {
        "node": ">=14"
      },
      "funding": {
        "url": "https://github.com/sponsors/isaacs"
      }
    },
    "node_modules/@semantic-release/exec/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,
      "engines": {
        "node": ">=18"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/@semantic-release/exec/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,
      "engines": {
        "node": ">=18"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/@semantic-release/git": {
      "version": "10.0.1",
      "resolved": "https://registry.npmjs.org/@semantic-release/git/-/git-10.0.1.tgz",
      "integrity": "sha512-eWrx5KguUcU2wUPaO6sfvZI0wPafUKAMNC18aXY4EnNcrZL86dEmpNVnC9uMpGZkmZJ9EfCVJBQx4pV4EMGT1w==",
      "dev": true,
      "dependencies": {
        "@semantic-release/error": "^3.0.0",
        "aggregate-error": "^3.0.0",
        "debug": "^4.0.0",
        "dir-glob": "^3.0.0",
        "execa": "^5.0.0",
        "lodash": "^4.17.4",
        "micromatch": "^4.0.0",
        "p-reduce": "^2.0.0"
      },
      "engines": {
        "node": ">=14.17"
      },
      "peerDependencies": {
        "semantic-release": ">=18.0.0"
      }
    },
    "node_modules/@semantic-release/github": {
      "version": "11.0.0",
      "resolved": "https://registry.npmjs.org/@semantic-release/github/-/github-11.0.0.tgz",
      "integrity": "sha512-Uon6G6gJD8U1JNvPm7X0j46yxNRJ8Ui6SgK4Zw5Ktu8RgjEft3BGn+l/RX1TTzhhO3/uUcKuqM+/9/ETFxWS/Q==",
      "dev": true,
      "dependencies": {
        "@octokit/core": "^6.0.0",
        "@octokit/plugin-paginate-rest": "^11.0.0",
        "@octokit/plugin-retry": "^7.0.0",
        "@octokit/plugin-throttling": "^9.0.0",
        "@semantic-release/error": "^4.0.0",
        "aggregate-error": "^5.0.0",
        "debug": "^4.3.4",
        "dir-glob": "^3.0.1",
        "globby": "^14.0.0",
        "http-proxy-agent": "^7.0.0",
        "https-proxy-agent": "^7.0.0",
        "issue-parser": "^7.0.0",
        "lodash-es": "^4.17.21",
        "mime": "^4.0.0",
        "p-filter": "^4.0.0",
        "url-join": "^5.0.0"
      },
      "engines": {
        "node": ">=20.8.1"
      },
      "peerDependencies": {
        "semantic-release": ">=24.1.0"
      }
    },
    "node_modules/@semantic-release/github/node_modules/@semantic-release/error": {
      "version": "4.0.0",
      "resolved": "https://registry.npmjs.org/@semantic-release/error/-/error-4.0.0.tgz",
      "integrity": "sha512-mgdxrHTLOjOddRVYIYDo0fR3/v61GNN1YGkfbrjuIKg/uMgCd+Qzo3UAXJ+woLQQpos4pl5Esuw5A7AoNlzjUQ==",
      "dev": true,
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@semantic-release/github/node_modules/aggregate-error": {
      "version": "5.0.0",
      "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-5.0.0.tgz",
      "integrity": "sha512-gOsf2YwSlleG6IjRYG2A7k0HmBMEo6qVNk9Bp/EaLgAJT5ngH6PXbqa4ItvnEwCm/velL5jAnQgsHsWnjhGmvw==",
      "dev": true,
      "dependencies": {
        "clean-stack": "^5.2.0",
        "indent-string": "^5.0.0"
      },
      "engines": {
        "node": ">=18"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/@semantic-release/github/node_modules/clean-stack": {
      "version": "5.2.0",
      "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-5.2.0.tgz",
      "integrity": "sha512-TyUIUJgdFnCISzG5zu3291TAsE77ddchd0bepon1VVQrKLGKFED4iXFEDQ24mIPdPBbyE16PK3F8MYE1CmcBEQ==",
      "dev": true,
      "dependencies": {
        "escape-string-regexp": "5.0.0"
      },
      "engines": {
        "node": ">=14.16"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/@semantic-release/github/node_modules/indent-string": {
      "version": "5.0.0",
      "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz",
      "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==",
      "dev": true,
      "engines": {
        "node": ">=12"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/@semantic-release/npm": {
      "version": "12.0.2",
      "resolved": "https://registry.npmjs.org/@semantic-release/npm/-/npm-12.0.2.tgz",
      "integrity": "sha512-+M9/Lb35IgnlUO6OSJ40Ie+hUsZLuph2fqXC/qrKn0fMvUU/jiCjpoL6zEm69vzcmaZJ8yNKtMBEKHWN49WBbQ==",
      "dev": true,
      "dependencies": {
        "@semantic-release/error": "^4.0.0",
        "aggregate-error": "^5.0.0",
        "execa": "^9.0.0",
        "fs-extra": "^11.0.0",
        "lodash-es": "^4.17.21",
        "nerf-dart": "^1.0.0",
        "normalize-url": "^8.0.0",
        "npm": "^10.9.3",
        "rc": "^1.2.8",
        "read-pkg": "^9.0.0",
        "registry-auth-token": "^5.0.0",
        "semver": "^7.1.2",
        "tempy": "^3.0.0"
      },
      "engines": {
        "node": ">=20.8.1"
      },
      "peerDependencies": {
        "semantic-release": ">=20.1.0"
      }
    },
    "node_modules/@semantic-release/npm/node_modules/@semantic-release/error": {
      "version": "4.0.0",
      "resolved": "https://registry.npmjs.org/@semantic-release/error/-/error-4.0.0.tgz",
      "integrity": "sha512-mgdxrHTLOjOddRVYIYDo0fR3/v61GNN1YGkfbrjuIKg/uMgCd+Qzo3UAXJ+woLQQpos4pl5Esuw5A7AoNlzjUQ==",
      "dev": true,
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@semantic-release/npm/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,
      "engines": {
        "node": ">=18"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/@semantic-release/npm/node_modules/aggregate-error": {
      "version": "5.0.0",
      "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-5.0.0.tgz",
      "integrity": "sha512-gOsf2YwSlleG6IjRYG2A7k0HmBMEo6qVNk9Bp/EaLgAJT5ngH6PXbqa4ItvnEwCm/velL5jAnQgsHsWnjhGmvw==",
      "dev": true,
      "dependencies": {
        "clean-stack": "^5.2.0",
        "indent-string": "^5.0.0"
      },
      "engines": {
        "node": ">=18"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/@semantic-release/npm/node_modules/clean-stack": {
      "version": "5.2.0",
      "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-5.2.0.tgz",
      "integrity": "sha512-TyUIUJgdFnCISzG5zu3291TAsE77ddchd0bepon1VVQrKLGKFED4iXFEDQ24mIPdPBbyE16PK3F8MYE1CmcBEQ==",
      "dev": true,
      "dependencies": {
        "escape-string-regexp": "5.0.0"
      },
      "engines": {
        "node": ">=14.16"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/@semantic-release/npm/node_modules/execa": {
      "version": "9.6.0",
      "resolved": "https://registry.npmjs.org/execa/-/execa-9.6.0.tgz",
      "integrity": "sha512-jpWzZ1ZhwUmeWRhS7Qv3mhpOhLfwI+uAX4e5fOcXqwMR7EcJ0pj2kV1CVzHVMX/LphnKWD3LObjZCoJ71lKpHw==",
      "dev": true,
      "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/@semantic-release/npm/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,
      "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/@semantic-release/npm/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,
      "engines": {
        "node": ">=18.18.0"
      }
    },
    "node_modules/@semantic-release/npm/node_modules/indent-string": {
      "version": "5.0.0",
      "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz",
      "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==",
      "dev": true,
      "engines": {
        "node": ">=12"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/@semantic-release/npm/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,
      "engines": {
        "node": ">=18"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/@semantic-release/npm/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,
      "dependencies": {
        "path-key": "^4.0.0",
        "unicorn-magic": "^0.3.0"
      },
      "engines": {
        "node": ">=18"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/@semantic-release/npm/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,
      "engines": {
        "node": ">=12"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/@semantic-release/npm/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,
      "engines": {
        "node": ">=14"
      },
      "funding": {
        "url": "https://github.com/sponsors/isaacs"
      }
    },
    "node_modules/@semantic-release/npm/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,
      "engines": {
        "node": ">=18"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/@semantic-release/npm/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,
      "engines": {
        "node": ">=18"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/@semantic-release/release-notes-generator": {
      "version": "14.0.0",
      "resolved": "https://registry.npmjs.org/@semantic-release/release-notes-generator/-/release-notes-generator-14.0.0.tgz",
      "integrity": "sha512-XRxwr4e46yUMaXT8KGFBlRJlp5+NOMaufdq8qaEWlcJ7cT4Pn/iRmDGglZ2TgDe6GVP+u1boXFEnSs7N8Yzhng==",
      "dev": true,
      "dependencies": {
        "conventional-changelog-angular": "^8.0.0",
        "conventional-changelog-writer": "^8.0.0",
        "conventional-commits-filter": "^5.0.0",
        "conventional-commits-parser": "^6.0.0",
        "debug": "^4.0.0",
        "get-stream": "^7.0.0",
        "import-from-esm": "^1.0.3",
        "into-stream": "^7.0.0",
        "lodash-es": "^4.17.21",
        "read-pkg-up": "^11.0.0"
      },
      "engines": {
        "node": ">=20.8.1"
      },
      "peerDependencies": {
        "semantic-release": ">=20.1.0"
      }
    },
    "node_modules/@semantic-release/release-notes-generator/node_modules/conventional-changelog-angular": {
      "version": "8.0.0",
      "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-8.0.0.tgz",
      "integrity": "sha512-CLf+zr6St0wIxos4bmaKHRXWAcsCXrJU6F4VdNDrGRK3B8LDLKoX3zuMV5GhtbGkVR/LohZ6MT6im43vZLSjmA==",
      "dev": true,
      "dependencies": {
        "compare-func": "^2.0.0"
      },
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@semantic-release/release-notes-generator/node_modules/conventional-commits-parser": {
      "version": "6.0.0",
      "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-6.0.0.tgz",
      "integrity": "sha512-TbsINLp48XeMXR8EvGjTnKGsZqBemisPoyWESlpRyR8lif0lcwzqz+NMtYSj1ooF/WYjSuu7wX0CtdeeMEQAmA==",
      "dev": true,
      "dependencies": {
        "meow": "^13.0.0"
      },
      "bin": {
        "conventional-commits-parser": "dist/cli/index.js"
      },
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@semantic-release/release-notes-generator/node_modules/get-stream": {
      "version": "7.0.1",
      "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-7.0.1.tgz",
      "integrity": "sha512-3M8C1EOFN6r8AMUhwUAACIoXZJEOufDU5+0gFFN5uNs6XYOralD2Pqkl7m046va6x77FwposWXbAhPPIOus7mQ==",
      "dev": true,
      "engines": {
        "node": ">=16"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/@semantic-release/release-notes-generator/node_modules/meow": {
      "version": "13.2.0",
      "resolved": "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz",
      "integrity": "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==",
      "dev": true,
      "engines": {
        "node": ">=18"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/@sindresorhus/is": {
      "version": "4.6.0",
      "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz",
      "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==",
      "dev": true,
      "engines": {
        "node": ">=10"
      },
      "funding": {
        "url": "https://github.com/sindresorhus/is?sponsor=1"
      }
    },
    "node_modules/@sindresorhus/merge-streams": {
      "version": "2.3.0",
      "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz",
      "integrity": "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==",
      "dev": true,
      "engines": {
        "node": ">=18"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/@types/conventional-commits-parser": {
      "version": "5.0.1",
      "resolved": "https://registry.npmjs.org/@types/conventional-commits-parser/-/conventional-commits-parser-5.0.1.tgz",
      "integrity": "sha512-7uz5EHdzz2TqoMfV7ee61Egf5y6NkcO4FB/1iCCQnbeiI1F3xzv3vK5dBCXUCLQgGYS+mUeigK1iKQzvED+QnQ==",
      "dev": true,
      "dependencies": {
        "@types/node": "*"
      }
    },
    "node_modules/@types/node": {
      "version": "18.18.7",
      "resolved": "https://registry.npmjs.org/@types/node/-/node-18.18.7.tgz",
      "integrity": "sha512-bw+lEsxis6eqJYW8Ql6+yTqkE6RuFtsQPSe5JxXbqYRFQEER5aJA9a5UH9igqDWm3X4iLHIKOHlnAXLM4mi7uQ==",
      "dev": true,
      "dependencies": {
        "undici-types": "~5.26.4"
      }
    },
    "node_modules/@types/normalize-package-data": {
      "version": "2.4.3",
      "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.3.tgz",
      "integrity": "sha512-ehPtgRgaULsFG8x0NeYJvmyH1hmlfsNLujHe9dQEia/7MAJYdzMSi19JtchUHjmBA6XC/75dK55mzZH+RyieSg==",
      "dev": true
    },
    "node_modules/@types/semver": {
      "version": "7.5.8",
      "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz",
      "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==",
      "dev": true
    },
    "node_modules/agent-base": {
      "version": "7.1.0",
      "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz",
      "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==",
      "dev": true,
      "dependencies": {
        "debug": "^4.3.4"
      },
      "engines": {
        "node": ">= 14"
      }
    },
    "node_modules/aggregate-error": {
      "version": "3.1.0",
      "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz",
      "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==",
      "dev": true,
      "dependencies": {
        "clean-stack": "^2.0.0",
        "indent-string": "^4.0.0"
      },
      "engines": {
        "node": ">=8"
      }
    },
    "node_modules/ajv": {
      "version": "8.17.1",
      "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
      "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
      "dev": true,
      "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/ansi-escapes": {
      "version": "7.0.0",
      "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.0.0.tgz",
      "integrity": "sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw==",
      "dev": true,
      "dependencies": {
        "environment": "^1.0.0"
      },
      "engines": {
        "node": ">=18"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "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,
      "engines": {
        "node": ">=8"
      }
    },
    "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,
      "dependencies": {
        "color-convert": "^2.0.1"
      },
      "engines": {
        "node": ">=8"
      },
      "funding": {
        "url": "https://github.com/chalk/ansi-styles?sponsor=1"
      }
    },
    "node_modules/any-promise": {
      "version": "1.3.0",
      "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
      "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==",
      "dev": true
    },
    "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
    },
    "node_modules/argv-formatter": {
      "version": "1.0.0",
      "resolved": "https://registry.npmjs.org/argv-formatter/-/argv-formatter-1.0.0.tgz",
      "integrity": "sha512-F2+Hkm9xFaRg+GkaNnbwXNDV5O6pnCFEmqyhvfC/Ic5LbgOWjJh3L+mN/s91rxVL3znE7DYVpW0GJFT+4YBgWw==",
      "dev": true
    },
    "node_modules/array-ify": {
      "version": "1.0.0",
      "resolved": "https://registry.npmjs.org/array-ify/-/array-ify-1.0.0.tgz",
      "integrity": "sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==",
      "dev": true
    },
    "node_modules/before-after-hook": {
      "version": "3.0.2",
      "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-3.0.2.tgz",
      "integrity": "sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A==",
      "dev": true
    },
    "node_modules/bottleneck": {
      "version": "2.19.5",
      "resolved": "https://registry.npmjs.org/bottleneck/-/bottleneck-2.19.5.tgz",
      "integrity": "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==",
      "dev": true
    },
    "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,
      "dependencies": {
        "fill-range": "^7.1.1"
      },
      "engines": {
        "node": ">=8"
      }
    },
    "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,
      "engines": {
        "node": ">=6"
      }
    },
    "node_modules/chalk": {
      "version": "4.1.2",
      "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
      "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
      "dev": true,
      "dependencies": {
        "ansi-styles": "^4.1.0",
        "supports-color": "^7.1.0"
      },
      "engines": {
        "node": ">=10"
      },
      "funding": {
        "url": "https://github.com/chalk/chalk?sponsor=1"
      }
    },
    "node_modules/char-regex": {
      "version": "1.0.2",
      "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz",
      "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==",
      "dev": true,
      "engines": {
        "node": ">=10"
      }
    },
    "node_modules/clean-stack": {
      "version": "2.2.0",
      "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz",
      "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==",
      "dev": true,
      "engines": {
        "node": ">=6"
      }
    },
    "node_modules/cli-highlight": {
      "version": "2.1.11",
      "resolved": "https://registry.npmjs.org/cli-highlight/-/cli-highlight-2.1.11.tgz",
      "integrity": "sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg==",
      "dev": true,
      "dependencies": {
        "chalk": "^4.0.0",
        "highlight.js": "^10.7.1",
        "mz": "^2.4.0",
        "parse5": "^5.1.1",
        "parse5-htmlparser2-tree-adapter": "^6.0.0",
        "yargs": "^16.0.0"
      },
      "bin": {
        "highlight": "bin/highlight"
      },
      "engines": {
        "node": ">=8.0.0",
        "npm": ">=5.0.0"
      }
    },
    "node_modules/cli-highlight/node_modules/cliui": {
      "version": "7.0.4",
      "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
      "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==",
      "dev": true,
      "dependencies": {
        "string-width": "^4.2.0",
        "strip-ansi": "^6.0.0",
        "wrap-ansi": "^7.0.0"
      }
    },
    "node_modules/cli-highlight/node_modules/yargs": {
      "version": "16.2.0",
      "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz",
      "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==",
      "dev": true,
      "dependencies": {
        "cliui": "^7.0.2",
        "escalade": "^3.1.1",
        "get-caller-file": "^2.0.5",
        "require-directory": "^2.1.1",
        "string-width": "^4.2.0",
        "y18n": "^5.0.5",
        "yargs-parser": "^20.2.2"
      },
      "engines": {
        "node": ">=10"
      }
    },
    "node_modules/cli-table3": {
      "version": "0.6.5",
      "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz",
      "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==",
      "dev": true,
      "dependencies": {
        "string-width": "^4.2.0"
      },
      "engines": {
        "node": "10.* || >= 12.*"
      },
      "optionalDependencies": {
        "@colors/colors": "1.5.0"
      }
    },
    "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,
      "dependencies": {
        "string-width": "^4.2.0",
        "strip-ansi": "^6.0.1",
        "wrap-ansi": "^7.0.0"
      },
      "engines": {
        "node": ">=12"
      }
    },
    "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,
      "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
    },
    "node_modules/compare-func": {
      "version": "2.0.0",
      "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz",
      "integrity": "sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==",
      "dev": true,
      "dependencies": {
        "array-ify": "^1.0.0",
        "dot-prop": "^5.1.0"
      }
    },
    "node_modules/config-chain": {
      "version": "1.1.13",
      "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz",
      "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==",
      "dev": true,
      "dependencies": {
        "ini": "^1.3.4",
        "proto-list": "~1.2.1"
      }
    },
    "node_modules/conventional-changelog-angular": {
      "version": "7.0.0",
      "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-7.0.0.tgz",
      "integrity": "sha512-ROjNchA9LgfNMTTFSIWPzebCwOGFdgkEq45EnvvrmSLvCtAw0HSmrCs7/ty+wAeYUZyNay0YMUNYFTRL72PkBQ==",
      "dev": true,
      "dependencies": {
        "compare-func": "^2.0.0"
      },
      "engines": {
        "node": ">=16"
      }
    },
    "node_modules/conventional-changelog-conventionalcommits": {
      "version": "7.0.2",
      "resolved": "https://registry.npmjs.org/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-7.0.2.tgz",
      "integrity": "sha512-NKXYmMR/Hr1DevQegFB4MwfM5Vv0m4UIxKZTTYuD98lpTknaZlSRrDOG4X7wIXpGkfsYxZTghUN+Qq+T0YQI7w==",
      "dev": true,
      "dependencies": {
        "compare-func": "^2.0.0"
      },
      "engines": {
        "node": ">=16"
      }
    },
    "node_modules/conventional-changelog-writer": {
      "version": "8.0.0",
      "resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-8.0.0.tgz",
      "integrity": "sha512-TQcoYGRatlAnT2qEWDON/XSfnVG38JzA7E0wcGScu7RElQBkg9WWgZd1peCWFcWDh1xfb2CfsrcvOn1bbSzztA==",
      "dev": true,
      "dependencies": {
        "@types/semver": "^7.5.5",
        "conventional-commits-filter": "^5.0.0",
        "handlebars": "^4.7.7",
        "meow": "^13.0.0",
        "semver": "^7.5.2"
      },
      "bin": {
        "conventional-changelog-writer": "dist/cli/index.js"
      },
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/conventional-changelog-writer/node_modules/meow": {
      "version": "13.2.0",
      "resolved": "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz",
      "integrity": "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==",
      "dev": true,
      "engines": {
        "node": ">=18"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/conventional-commits-filter": {
      "version": "5.0.0",
      "resolved": "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-5.0.0.tgz",
      "integrity": "sha512-tQMagCOC59EVgNZcC5zl7XqO30Wki9i9J3acbUvkaosCT6JX3EeFwJD7Qqp4MCikRnzS18WXV3BLIQ66ytu6+Q==",
      "dev": true,
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/conventional-commits-parser": {
      "version": "5.0.0",
      "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-5.0.0.tgz",
      "integrity": "sha512-ZPMl0ZJbw74iS9LuX9YIAiW8pfM5p3yh2o/NbXHbkFuZzY5jvdi5jFycEOkmBW5H5I7nA+D6f3UcsCLP2vvSEA==",
      "dev": true,
      "dependencies": {
        "is-text-path": "^2.0.0",
        "JSONStream": "^1.3.5",
        "meow": "^12.0.1",
        "split2": "^4.0.0"
      },
      "bin": {
        "conventional-commits-parser": "cli.mjs"
      },
      "engines": {
        "node": ">=16"
      }
    },
    "node_modules/convert-hrtime": {
      "version": "5.0.0",
      "resolved": "https://registry.npmjs.org/convert-hrtime/-/convert-hrtime-5.0.0.tgz",
      "integrity": "sha512-lOETlkIeYSJWcbbcvjRKGxVMXJR+8+OQb/mTPbA4ObPMytYIsUbuOE0Jzy60hjARYszq1id0j8KgVhC+WGZVTg==",
      "dev": true,
      "engines": {
        "node": ">=12"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/core-util-is": {
      "version": "1.0.3",
      "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
      "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==",
      "dev": true
    },
    "node_modules/cosmiconfig": {
      "version": "9.0.0",
      "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz",
      "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==",
      "dev": true,
      "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/cosmiconfig-typescript-loader": {
      "version": "6.1.0",
      "resolved": "https://registry.npmjs.org/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-6.1.0.tgz",
      "integrity": "sha512-tJ1w35ZRUiM5FeTzT7DtYWAFFv37ZLqSRkGi2oeCK1gPhvaWjkAtfXvLmvE1pRfxxp9aQo6ba/Pvg1dKj05D4g==",
      "dev": true,
      "dependencies": {
        "jiti": "^2.4.1"
      },
      "engines": {
        "node": ">=v18"
      },
      "peerDependencies": {
        "@types/node": "*",
        "cosmiconfig": ">=9",
        "typescript": ">=5"
      }
    },
    "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,
      "dependencies": {
        "path-key": "^3.1.0",
        "shebang-command": "^2.0.0",
        "which": "^2.0.1"
      },
      "engines": {
        "node": ">= 8"
      }
    },
    "node_modules/crypto-random-string": {
      "version": "4.0.0",
      "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-4.0.0.tgz",
      "integrity": "sha512-x8dy3RnvYdlUcPOjkEHqozhiwzKNSq7GcPuXFbnyMOCHxX8V3OgIg/pYuabl2sbUPfIJaeAQB7PMOK8DFIdoRA==",
      "dev": true,
      "dependencies": {
        "type-fest": "^1.0.1"
      },
      "engines": {
        "node": ">=12"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/crypto-random-string/node_modules/type-fest": {
      "version": "1.4.0",
      "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz",
      "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==",
      "dev": true,
      "engines": {
        "node": ">=10"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/dargs": {
      "version": "8.1.0",
      "resolved": "https://registry.npmjs.org/dargs/-/dargs-8.1.0.tgz",
      "integrity": "sha512-wAV9QHOsNbwnWdNW2FYvE1P56wtgSbM+3SZcdGiWQILwVjACCXDCI3Ai8QlCjMDB8YK5zySiXZYBiwGmNY3lnw==",
      "dev": true,
      "engines": {
        "node": ">=12"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/debug": {
      "version": "4.3.4",
      "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
      "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
      "dev": true,
      "dependencies": {
        "ms": "2.1.2"
      },
      "engines": {
        "node": ">=6.0"
      },
      "peerDependenciesMeta": {
        "supports-color": {
          "optional": true
        }
      }
    },
    "node_modules/deep-extend": {
      "version": "0.6.0",
      "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
      "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
      "dev": true,
      "engines": {
        "node": ">=4.0.0"
      }
    },
    "node_modules/dir-glob": {
      "version": "3.0.1",
      "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
      "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==",
      "dev": true,
      "dependencies": {
        "path-type": "^4.0.0"
      },
      "engines": {
        "node": ">=8"
      }
    },
    "node_modules/dot-prop": {
      "version": "5.3.0",
      "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz",
      "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==",
      "dev": true,
      "dependencies": {
        "is-obj": "^2.0.0"
      },
      "engines": {
        "node": ">=8"
      }
    },
    "node_modules/duplexer2": {
      "version": "0.1.4",
      "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz",
      "integrity": "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==",
      "dev": true,
      "dependencies": {
        "readable-stream": "^2.0.2"
      }
    },
    "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
    },
    "node_modules/emojilib": {
      "version": "2.4.0",
      "resolved": "https://registry.npmjs.org/emojilib/-/emojilib-2.4.0.tgz",
      "integrity": "sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw==",
      "dev": true
    },
    "node_modules/env-ci": {
      "version": "11.0.0",
      "resolved": "https://registry.npmjs.org/env-ci/-/env-ci-11.0.0.tgz",
      "integrity": "sha512-apikxMgkipkgTvMdRT9MNqWx5VLOci79F4VBd7Op/7OPjjoanjdAvn6fglMCCEf/1bAh8eOiuEVCUs4V3qP3nQ==",
      "dev": true,
      "dependencies": {
        "execa": "^8.0.0",
        "java-properties": "^1.0.2"
      },
      "engines": {
        "node": "^18.17 || >=20.6.1"
      }
    },
    "node_modules/env-ci/node_modules/execa": {
      "version": "8.0.1",
      "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz",
      "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==",
      "dev": true,
      "dependencies": {
        "cross-spawn": "^7.0.3",
        "get-stream": "^8.0.1",
        "human-signals": "^5.0.0",
        "is-stream": "^3.0.0",
        "merge-stream": "^2.0.0",
        "npm-run-path": "^5.1.0",
        "onetime": "^6.0.0",
        "signal-exit": "^4.1.0",
        "strip-final-newline": "^3.0.0"
      },
      "engines": {
        "node": ">=16.17"
      },
      "funding": {
        "url": "https://github.com/sindresorhus/execa?sponsor=1"
      }
    },
    "node_modules/env-ci/node_modules/get-stream": {
      "version": "8.0.1",
      "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz",
      "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==",
      "dev": true,
      "engines": {
        "node": ">=16"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/env-ci/node_modules/human-signals": {
      "version": "5.0.0",
      "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz",
      "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==",
      "dev": true,
      "engines": {
        "node": ">=16.17.0"
      }
    },
    "node_modules/env-ci/node_modules/is-stream": {
      "version": "3.0.0",
      "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz",
      "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==",
      "dev": true,
      "engines": {
        "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/env-ci/node_modules/mimic-fn": {
      "version": "4.0.0",
      "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz",
      "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==",
      "dev": true,
      "engines": {
        "node": ">=12"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/env-ci/node_modules/npm-run-path": {
      "version": "5.1.0",
      "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.1.0.tgz",
      "integrity": "sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==",
      "dev": true,
      "dependencies": {
        "path-key": "^4.0.0"
      },
      "engines": {
        "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/env-ci/node_modules/onetime": {
      "version": "6.0.0",
      "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz",
      "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==",
      "dev": true,
      "dependencies": {
        "mimic-fn": "^4.0.0"
      },
      "engines": {
        "node": ">=12"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/env-ci/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,
      "engines": {
        "node": ">=12"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/env-ci/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,
      "engines": {
        "node": ">=14"
      },
      "funding": {
        "url": "https://github.com/sponsors/isaacs"
      }
    },
    "node_modules/env-ci/node_modules/strip-final-newline": {
      "version": "3.0.0",
      "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz",
      "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==",
      "dev": true,
      "engines": {
        "node": ">=12"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "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,
      "engines": {
        "node": ">=6"
      }
    },
    "node_modules/environment": {
      "version": "1.1.0",
      "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz",
      "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==",
      "dev": true,
      "engines": {
        "node": ">=18"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/error-ex": {
      "version": "1.3.2",
      "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
      "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
      "dev": true,
      "dependencies": {
        "is-arrayish": "^0.2.1"
      }
    },
    "node_modules/escalade": {
      "version": "3.1.1",
      "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
      "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==",
      "dev": true,
      "engines": {
        "node": ">=6"
      }
    },
    "node_modules/escape-string-regexp": {
      "version": "5.0.0",
      "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz",
      "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==",
      "dev": true,
      "engines": {
        "node": ">=12"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/execa": {
      "version": "5.1.1",
      "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz",
      "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==",
      "dev": true,
      "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/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
    },
    "node_modules/fast-glob": {
      "version": "3.3.2",
      "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz",
      "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==",
      "dev": true,
      "dependencies": {
        "@nodelib/fs.stat": "^2.0.2",
        "@nodelib/fs.walk": "^1.2.3",
        "glob-parent": "^5.1.2",
        "merge2": "^1.3.0",
        "micromatch": "^4.0.4"
      },
      "engines": {
        "node": ">=8.6.0"
      }
    },
    "node_modules/fast-uri": {
      "version": "3.0.6",
      "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz",
      "integrity": "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==",
      "dev": true,
      "funding": [
        {
          "type": "github",
          "url": "https://github.com/sponsors/fastify"
        },
        {
          "type": "opencollective",
          "url": "https://opencollective.com/fastify"
        }
      ]
    },
    "node_modules/fastq": {
      "version": "1.17.1",
      "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz",
      "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==",
      "dev": true,
      "dependencies": {
        "reusify": "^1.0.4"
      }
    },
    "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,
      "dependencies": {
        "is-unicode-supported": "^2.0.0"
      },
      "engines": {
        "node": ">=18"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "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,
      "dependencies": {
        "to-regex-range": "^5.0.1"
      },
      "engines": {
        "node": ">=8"
      }
    },
    "node_modules/find-up": {
      "version": "7.0.0",
      "resolved": "https://registry.npmjs.org/find-up/-/find-up-7.0.0.tgz",
      "integrity": "sha512-YyZM99iHrqLKjmt4LJDj58KI+fYyufRLBSYcqycxf//KpBk9FoewoGX0450m9nB44qrZnovzC2oeP5hUibxc/g==",
      "dev": true,
      "dependencies": {
        "locate-path": "^7.2.0",
        "path-exists": "^5.0.0",
        "unicorn-magic": "^0.1.0"
      },
      "engines": {
        "node": ">=18"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/find-up-simple": {
      "version": "1.0.0",
      "resolved": "https://registry.npmjs.org/find-up-simple/-/find-up-simple-1.0.0.tgz",
      "integrity": "sha512-q7Us7kcjj2VMePAa02hDAF6d+MzsdsAWEwYyOpwUtlerRBkOEPBCRZrAV4XfcSN8fHAgaD0hP7miwoay6DCprw==",
      "dev": true,
      "engines": {
        "node": ">=18"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/find-versions": {
      "version": "6.0.0",
      "resolved": "https://registry.npmjs.org/find-versions/-/find-versions-6.0.0.tgz",
      "integrity": "sha512-2kCCtc+JvcZ86IGAz3Z2Y0A1baIz9fL31pH/0S1IqZr9Iwnjq8izfPtrCyQKO6TLMPELLsQMre7VDqeIKCsHkA==",
      "dev": true,
      "dependencies": {
        "semver-regex": "^4.0.5",
        "super-regex": "^1.0.0"
      },
      "engines": {
        "node": ">=18"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/from2": {
      "version": "2.3.0",
      "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz",
      "integrity": "sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g==",
      "dev": true,
      "dependencies": {
        "inherits": "^2.0.1",
        "readable-stream": "^2.0.0"
      }
    },
    "node_modules/fs-extra": {
      "version": "11.1.0",
      "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.1.0.tgz",
      "integrity": "sha512-0rcTq621PD5jM/e0a3EJoGC/1TC5ZBCERW82LQuwfGnCa1V8w7dpYH1yNu+SLb6E5dkeCBzKEyLGlFrnr+dUyw==",
      "dev": true,
      "dependencies": {
        "graceful-fs": "^4.2.0",
        "jsonfile": "^6.0.1",
        "universalify": "^2.0.0"
      },
      "engines": {
        "node": ">=14.14"
      }
    },
    "node_modules/function-bind": {
      "version": "1.1.1",
      "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
      "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
      "dev": true
    },
    "node_modules/function-timeout": {
      "version": "1.0.1",
      "resolved": "https://registry.npmjs.org/function-timeout/-/function-timeout-1.0.1.tgz",
      "integrity": "sha512-6yPMImFFuaMPNaTMTBuolA8EanHJWF5Vju0NHpObRURT105J6x1Mf2a7J4P7Sqk2xDxv24N5L0RatEhTBhNmdA==",
      "dev": true,
      "engines": {
        "node": ">=18"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "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,
      "engines": {
        "node": "6.* || 8.* || >= 10.*"
      }
    },
    "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,
      "engines": {
        "node": ">=10"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/git-log-parser": {
      "version": "1.2.0",
      "resolved": "https://registry.npmjs.org/git-log-parser/-/git-log-parser-1.2.0.tgz",
      "integrity": "sha512-rnCVNfkTL8tdNryFuaY0fYiBWEBcgF748O6ZI61rslBvr2o7U65c2/6npCRqH40vuAhtgtDiqLTJjBVdrejCzA==",
      "dev": true,
      "dependencies": {
        "argv-formatter": "~1.0.0",
        "spawn-error-forwarder": "~1.0.0",
        "split2": "~1.0.0",
        "stream-combiner2": "~1.1.1",
        "through2": "~2.0.0",
        "traverse": "~0.6.6"
      }
    },
    "node_modules/git-log-parser/node_modules/split2": {
      "version": "1.0.0",
      "resolved": "https://registry.npmjs.org/split2/-/split2-1.0.0.tgz",
      "integrity": "sha512-NKywug4u4pX/AZBB1FCPzZ6/7O+Xhz1qMVbzTvvKvikjO99oPN87SkK08mEY9P63/5lWjK+wgOOgApnTg5r6qg==",
      "dev": true,
      "dependencies": {
        "through2": "~2.0.0"
      }
    },
    "node_modules/git-log-parser/node_modules/through2": {
      "version": "2.0.5",
      "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz",
      "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==",
      "dev": true,
      "dependencies": {
        "readable-stream": "~2.3.6",
        "xtend": "~4.0.1"
      }
    },
    "node_modules/git-raw-commits": {
      "version": "4.0.0",
      "resolved": "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-4.0.0.tgz",
      "integrity": "sha512-ICsMM1Wk8xSGMowkOmPrzo2Fgmfo4bMHLNX6ytHjajRJUqvHOw/TFapQ+QG75c3X/tTDDhOSRPGC52dDbNM8FQ==",
      "dev": true,
      "dependencies": {
        "dargs": "^8.0.0",
        "meow": "^12.0.1",
        "split2": "^4.0.0"
      },
      "bin": {
        "git-raw-commits": "cli.mjs"
      },
      "engines": {
        "node": ">=16"
      }
    },
    "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,
      "dependencies": {
        "is-glob": "^4.0.1"
      },
      "engines": {
        "node": ">= 6"
      }
    },
    "node_modules/global-directory": {
      "version": "4.0.1",
      "resolved": "https://registry.npmjs.org/global-directory/-/global-directory-4.0.1.tgz",
      "integrity": "sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q==",
      "dev": true,
      "dependencies": {
        "ini": "4.1.1"
      },
      "engines": {
        "node": ">=18"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/global-directory/node_modules/ini": {
      "version": "4.1.1",
      "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.1.tgz",
      "integrity": "sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==",
      "dev": true,
      "engines": {
        "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
      }
    },
    "node_modules/globby": {
      "version": "14.0.1",
      "resolved": "https://registry.npmjs.org/globby/-/globby-14.0.1.tgz",
      "integrity": "sha512-jOMLD2Z7MAhyG8aJpNOpmziMOP4rPLcc95oQPKXBazW82z+CEgPFBQvEpRUa1KeIMUJo4Wsm+q6uzO/Q/4BksQ==",
      "dev": true,
      "dependencies": {
        "@sindresorhus/merge-streams": "^2.1.0",
        "fast-glob": "^3.3.2",
        "ignore": "^5.2.4",
        "path-type": "^5.0.0",
        "slash": "^5.1.0",
        "unicorn-magic": "^0.1.0"
      },
      "engines": {
        "node": ">=18"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/globby/node_modules/path-type": {
      "version": "5.0.0",
      "resolved": "https://registry.npmjs.org/path-type/-/path-type-5.0.0.tgz",
      "integrity": "sha512-5HviZNaZcfqP95rwpv+1HDgUamezbqdSYTyzjTvwtJSnIH+3vnbmWsItli8OFEndS984VT55M3jduxZbX351gg==",
      "dev": true,
      "engines": {
        "node": ">=12"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/graceful-fs": {
      "version": "4.2.10",
      "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz",
      "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==",
      "dev": true
    },
    "node_modules/handlebars": {
      "version": "4.7.8",
      "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz",
      "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==",
      "dev": true,
      "dependencies": {
        "minimist": "^1.2.5",
        "neo-async": "^2.6.2",
        "source-map": "^0.6.1",
        "wordwrap": "^1.0.0"
      },
      "bin": {
        "handlebars": "bin/handlebars"
      },
      "engines": {
        "node": ">=0.4.7"
      },
      "optionalDependencies": {
        "uglify-js": "^3.1.4"
      }
    },
    "node_modules/has": {
      "version": "1.0.3",
      "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
      "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
      "dev": true,
      "dependencies": {
        "function-bind": "^1.1.1"
      },
      "engines": {
        "node": ">= 0.4.0"
      }
    },
    "node_modules/has-flag": {
      "version": "4.0.0",
      "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
      "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
      "dev": true,
      "engines": {
        "node": ">=8"
      }
    },
    "node_modules/highlight.js": {
      "version": "10.7.3",
      "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz",
      "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==",
      "dev": true,
      "engines": {
        "node": "*"
      }
    },
    "node_modules/hook-std": {
      "version": "3.0.0",
      "resolved": "https://registry.npmjs.org/hook-std/-/hook-std-3.0.0.tgz",
      "integrity": "sha512-jHRQzjSDzMtFy34AGj1DN+vq54WVuhSvKgrHf0OMiFQTwDD4L/qqofVEWjLOBMTn5+lCD3fPg32W9yOfnEJTTw==",
      "dev": true,
      "engines": {
        "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/hosted-git-info": {
      "version": "7.0.1",
      "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.1.tgz",
      "integrity": "sha512-+K84LB1DYwMHoHSgaOY/Jfhw3ucPmSET5v98Ke/HdNSw4a0UktWzyW1mjhjpuxxTqOOsfWT/7iVshHmVZ4IpOA==",
      "dev": true,
      "dependencies": {
        "lru-cache": "^10.0.1"
      },
      "engines": {
        "node": "^16.14.0 || >=18.0.0"
      }
    },
    "node_modules/hosted-git-info/node_modules/lru-cache": {
      "version": "10.2.0",
      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz",
      "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==",
      "dev": true,
      "engines": {
        "node": "14 || >=16.14"
      }
    },
    "node_modules/http-proxy-agent": {
      "version": "7.0.0",
      "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.0.tgz",
      "integrity": "sha512-+ZT+iBxVUQ1asugqnD6oWoRiS25AkjNfG085dKJGtGxkdwLQrMKU5wJr2bOOFAXzKcTuqq+7fZlTMgG3SRfIYQ==",
      "dev": true,
      "dependencies": {
        "agent-base": "^7.1.0",
        "debug": "^4.3.4"
      },
      "engines": {
        "node": ">= 14"
      }
    },
    "node_modules/https-proxy-agent": {
      "version": "7.0.0",
      "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.0.tgz",
      "integrity": "sha512-0euwPCRyAPSgGdzD1IVN9nJYHtBhJwb6XPfbpQcYbPCwrBidX6GzxmchnaF4sfF/jPb74Ojx5g4yTg3sixlyPw==",
      "dev": true,
      "dependencies": {
        "agent-base": "^7.0.2",
        "debug": "4"
      },
      "engines": {
        "node": ">= 14"
      }
    },
    "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,
      "engines": {
        "node": ">=10.17.0"
      }
    },
    "node_modules/ignore": {
      "version": "5.3.1",
      "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz",
      "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==",
      "dev": true,
      "engines": {
        "node": ">= 4"
      }
    },
    "node_modules/import-fresh": {
      "version": "3.3.0",
      "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
      "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
      "dev": true,
      "dependencies": {
        "parent-module": "^1.0.0",
        "resolve-from": "^4.0.0"
      },
      "engines": {
        "node": ">=6"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/import-fresh/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,
      "engines": {
        "node": ">=4"
      }
    },
    "node_modules/import-from-esm": {
      "version": "1.3.3",
      "resolved": "https://registry.npmjs.org/import-from-esm/-/import-from-esm-1.3.3.tgz",
      "integrity": "sha512-U3Qt/CyfFpTUv6LOP2jRTLYjphH6zg3okMfHbyqRa/W2w6hr8OsJWVggNlR4jxuojQy81TgTJTxgSkyoteRGMQ==",
      "dev": true,
      "dependencies": {
        "debug": "^4.3.4",
        "import-meta-resolve": "^4.0.0"
      },
      "engines": {
        "node": ">=16.20"
      }
    },
    "node_modules/import-meta-resolve": {
      "version": "4.0.0",
      "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.0.0.tgz",
      "integrity": "sha512-okYUR7ZQPH+efeuMJGlq4f8ubUgO50kByRPyt/Cy1Io4PSRsPjxME+YlVaCOx+NIToW7hCsZNFJyTPFFKepRSA==",
      "dev": true,
      "funding": {
        "type": "github",
        "url": "https://github.com/sponsors/wooorm"
      }
    },
    "node_modules/indent-string": {
      "version": "4.0.0",
      "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz",
      "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==",
      "dev": true,
      "engines": {
        "node": ">=8"
      }
    },
    "node_modules/index-to-position": {
      "version": "0.1.2",
      "resolved": "https://registry.npmjs.org/index-to-position/-/index-to-position-0.1.2.tgz",
      "integrity": "sha512-MWDKS3AS1bGCHLBA2VLImJz42f7bJh8wQsTGCzI3j519/CASStoDONUBVz2I/VID0MpiX3SGSnbOD2xUalbE5g==",
      "dev": true,
      "engines": {
        "node": ">=18"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "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
    },
    "node_modules/ini": {
      "version": "1.3.8",
      "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
      "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
      "dev": true
    },
    "node_modules/into-stream": {
      "version": "7.0.0",
      "resolved": "https://registry.npmjs.org/into-stream/-/into-stream-7.0.0.tgz",
      "integrity": "sha512-2dYz766i9HprMBasCMvHMuazJ7u4WzhJwo5kb3iPSiW/iRYV6uPari3zHoqZlnuaR7V1bEiNMxikhp37rdBXbw==",
      "dev": true,
      "dependencies": {
        "from2": "^2.3.0",
        "p-is-promise": "^3.0.0"
      },
      "engines": {
        "node": ">=12"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "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
    },
    "node_modules/is-core-module": {
      "version": "2.11.0",
      "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz",
      "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==",
      "dev": true,
      "dependencies": {
        "has": "^1.0.3"
      },
      "funding": {
        "url": "https://github.com/sponsors/ljharb"
      }
    },
    "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,
      "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,
      "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,
      "dependencies": {
        "is-extglob": "^2.1.1"
      },
      "engines": {
        "node": ">=0.10.0"
      }
    },
    "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,
      "engines": {
        "node": ">=0.12.0"
      }
    },
    "node_modules/is-obj": {
      "version": "2.0.0",
      "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz",
      "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==",
      "dev": true,
      "engines": {
        "node": ">=8"
      }
    },
    "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,
      "engines": {
        "node": ">=12"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "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,
      "engines": {
        "node": ">=8"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/is-text-path": {
      "version": "2.0.0",
      "resolved": "https://registry.npmjs.org/is-text-path/-/is-text-path-2.0.0.tgz",
      "integrity": "sha512-+oDTluR6WEjdXEJMnC2z6A4FRwFoYuvShVVEGsS7ewc0UTi2QtAKMDJuL4BDEVt+5T7MjFo12RP8ghOM75oKJw==",
      "dev": true,
      "dependencies": {
        "text-extensions": "^2.0.0"
      },
      "engines": {
        "node": ">=8"
      }
    },
    "node_modules/is-unicode-supported": {
      "version": "2.0.0",
      "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.0.0.tgz",
      "integrity": "sha512-FRdAyx5lusK1iHG0TWpVtk9+1i+GjrzRffhDg4ovQ7mcidMQ6mj+MhKPmvh7Xwyv5gIS06ns49CA7Sqg7lC22Q==",
      "dev": true,
      "engines": {
        "node": ">=18"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/isarray": {
      "version": "1.0.0",
      "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
      "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
      "dev": true
    },
    "node_modules/isexe": {
      "version": "2.0.0",
      "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
      "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
      "dev": true
    },
    "node_modules/issue-parser": {
      "version": "7.0.0",
      "resolved": "https://registry.npmjs.org/issue-parser/-/issue-parser-7.0.0.tgz",
      "integrity": "sha512-jgAw78HO3gs9UrKqJNQvfDj9Ouy8Mhu40fbEJ8yXff4MW8+/Fcn9iFjyWUQ6SKbX8ipPk3X5A3AyfYHRu6uVLw==",
      "dev": true,
      "dependencies": {
        "lodash.capitalize": "^4.2.1",
        "lodash.escaperegexp": "^4.1.2",
        "lodash.isplainobject": "^4.0.6",
        "lodash.isstring": "^4.0.1",
        "lodash.uniqby": "^4.7.0"
      },
      "engines": {
        "node": "^18.17 || >=20.6.1"
      }
    },
    "node_modules/java-properties": {
      "version": "1.0.2",
      "resolved": "https://registry.npmjs.org/java-properties/-/java-properties-1.0.2.tgz",
      "integrity": "sha512-qjdpeo2yKlYTH7nFdK0vbZWuTCesk4o63v5iVOlhMQPfuIZQfW/HI35SjfhA+4qpg36rnFSvUK5b1m+ckIblQQ==",
      "dev": true,
      "engines": {
        "node": ">= 0.6.0"
      }
    },
    "node_modules/jiti": {
      "version": "2.4.1",
      "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.1.tgz",
      "integrity": "sha512-yPBThwecp1wS9DmoA4x4KR2h3QoslacnDR8ypuFM962kI4/456Iy1oHx2RAgh4jfZNdn0bctsdadceiBUgpU1g==",
      "dev": true,
      "bin": {
        "jiti": "lib/jiti-cli.mjs"
      }
    },
    "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
    },
    "node_modules/js-yaml": {
      "version": "4.1.0",
      "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
      "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
      "dev": true,
      "dependencies": {
        "argparse": "^2.0.1"
      },
      "bin": {
        "js-yaml": "bin/js-yaml.js"
      }
    },
    "node_modules/json-parse-better-errors": {
      "version": "1.0.2",
      "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz",
      "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==",
      "dev": true
    },
    "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
    },
    "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
    },
    "node_modules/jsonfile": {
      "version": "6.1.0",
      "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
      "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
      "dev": true,
      "dependencies": {
        "universalify": "^2.0.0"
      },
      "optionalDependencies": {
        "graceful-fs": "^4.1.6"
      }
    },
    "node_modules/jsonparse": {
      "version": "1.3.1",
      "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz",
      "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==",
      "dev": true,
      "engines": [
        "node >= 0.2.0"
      ]
    },
    "node_modules/JSONStream": {
      "version": "1.3.5",
      "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz",
      "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==",
      "dev": true,
      "dependencies": {
        "jsonparse": "^1.2.0",
        "through": ">=2.2.7 <3"
      },
      "bin": {
        "JSONStream": "bin.js"
      },
      "engines": {
        "node": "*"
      }
    },
    "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
    },
    "node_modules/load-json-file": {
      "version": "4.0.0",
      "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz",
      "integrity": "sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==",
      "dev": true,
      "dependencies": {
        "graceful-fs": "^4.1.2",
        "parse-json": "^4.0.0",
        "pify": "^3.0.0",
        "strip-bom": "^3.0.0"
      },
      "engines": {
        "node": ">=4"
      }
    },
    "node_modules/load-json-file/node_modules/parse-json": {
      "version": "4.0.0",
      "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz",
      "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==",
      "dev": true,
      "dependencies": {
        "error-ex": "^1.3.1",
        "json-parse-better-errors": "^1.0.1"
      },
      "engines": {
        "node": ">=4"
      }
    },
    "node_modules/locate-path": {
      "version": "7.2.0",
      "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz",
      "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==",
      "dev": true,
      "dependencies": {
        "p-locate": "^6.0.0"
      },
      "engines": {
        "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/lodash": {
      "version": "4.17.21",
      "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
      "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
      "dev": true
    },
    "node_modules/lodash-es": {
      "version": "4.17.21",
      "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
      "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==",
      "dev": true
    },
    "node_modules/lodash.camelcase": {
      "version": "4.3.0",
      "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
      "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==",
      "dev": true
    },
    "node_modules/lodash.capitalize": {
      "version": "4.2.1",
      "resolved": "https://registry.npmjs.org/lodash.capitalize/-/lodash.capitalize-4.2.1.tgz",
      "integrity": "sha512-kZzYOKspf8XVX5AvmQF94gQW0lejFVgb80G85bU4ZWzoJ6C03PQg3coYAUpSTpQWelrZELd3XWgHzw4Ck5kaIw==",
      "dev": true
    },
    "node_modules/lodash.escaperegexp": {
      "version": "4.1.2",
      "resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz",
      "integrity": "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==",
      "dev": true
    },
    "node_modules/lodash.isplainobject": {
      "version": "4.0.6",
      "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
      "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
      "dev": true
    },
    "node_modules/lodash.isstring": {
      "version": "4.0.1",
      "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
      "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==",
      "dev": true
    },
    "node_modules/lodash.kebabcase": {
      "version": "4.1.1",
      "resolved": "https://registry.npmjs.org/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz",
      "integrity": "sha512-N8XRTIMMqqDgSy4VLKPnJ/+hpGZN+PHQiJnSenYqPaVV/NCqEogTnAdZLQiGKhxX+JCs8waWq2t1XHWKOmlY8g==",
      "dev": true
    },
    "node_modules/lodash.merge": {
      "version": "4.6.2",
      "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
      "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
      "dev": true
    },
    "node_modules/lodash.mergewith": {
      "version": "4.6.2",
      "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz",
      "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==",
      "dev": true
    },
    "node_modules/lodash.snakecase": {
      "version": "4.1.1",
      "resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz",
      "integrity": "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==",
      "dev": true
    },
    "node_modules/lodash.startcase": {
      "version": "4.4.0",
      "resolved": "https://registry.npmjs.org/lodash.startcase/-/lodash.startcase-4.4.0.tgz",
      "integrity": "sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==",
      "dev": true
    },
    "node_modules/lodash.uniq": {
      "version": "4.5.0",
      "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz",
      "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==",
      "dev": true
    },
    "node_modules/lodash.uniqby": {
      "version": "4.7.0",
      "resolved": "https://registry.npmjs.org/lodash.uniqby/-/lodash.uniqby-4.7.0.tgz",
      "integrity": "sha512-e/zcLx6CSbmaEgFHCA7BnoQKyCtKMxnuWrJygbwPs/AIn+IMKl66L8/s+wBUn5LRw2pZx3bUHibiV1b6aTWIww==",
      "dev": true
    },
    "node_modules/lodash.upperfirst": {
      "version": "4.3.1",
      "resolved": "https://registry.npmjs.org/lodash.upperfirst/-/lodash.upperfirst-4.3.1.tgz",
      "integrity": "sha512-sReKOYJIJf74dhJONhU4e0/shzi1trVbSWDOhKYE5XV2O+H7Sb2Dihwuc7xWxVl+DgFPyTqIN3zMfT9cq5iWDg==",
      "dev": true
    },
    "node_modules/lru-cache": {
      "version": "6.0.0",
      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
      "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
      "dev": true,
      "dependencies": {
        "yallist": "^4.0.0"
      },
      "engines": {
        "node": ">=10"
      }
    },
    "node_modules/marked": {
      "version": "15.0.12",
      "resolved": "https://registry.npmjs.org/marked/-/marked-15.0.12.tgz",
      "integrity": "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==",
      "dev": true,
      "bin": {
        "marked": "bin/marked.js"
      },
      "engines": {
        "node": ">= 18"
      }
    },
    "node_modules/marked-terminal": {
      "version": "7.3.0",
      "resolved": "https://registry.npmjs.org/marked-terminal/-/marked-terminal-7.3.0.tgz",
      "integrity": "sha512-t4rBvPsHc57uE/2nJOLmMbZCQ4tgAccAED3ngXQqW6g+TxA488JzJ+FK3lQkzBQOI1mRV/r/Kq+1ZlJ4D0owQw==",
      "dev": true,
      "dependencies": {
        "ansi-escapes": "^7.0.0",
        "ansi-regex": "^6.1.0",
        "chalk": "^5.4.1",
        "cli-highlight": "^2.1.11",
        "cli-table3": "^0.6.5",
        "node-emoji": "^2.2.0",
        "supports-hyperlinks": "^3.1.0"
      },
      "engines": {
        "node": ">=16.0.0"
      },
      "peerDependencies": {
        "marked": ">=1 <16"
      }
    },
    "node_modules/marked-terminal/node_modules/ansi-regex": {
      "version": "6.1.0",
      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
      "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
      "dev": true,
      "engines": {
        "node": ">=12"
      },
      "funding": {
        "url": "https://github.com/chalk/ansi-regex?sponsor=1"
      }
    },
    "node_modules/marked-terminal/node_modules/chalk": {
      "version": "5.4.1",
      "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz",
      "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==",
      "dev": true,
      "engines": {
        "node": "^12.17.0 || ^14.13 || >=16.0.0"
      },
      "funding": {
        "url": "https://github.com/chalk/chalk?sponsor=1"
      }
    },
    "node_modules/meow": {
      "version": "12.1.1",
      "resolved": "https://registry.npmjs.org/meow/-/meow-12.1.1.tgz",
      "integrity": "sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw==",
      "dev": true,
      "engines": {
        "node": ">=16.10"
      },
      "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
    },
    "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,
      "engines": {
        "node": ">= 8"
      }
    },
    "node_modules/micromatch": {
      "version": "4.0.5",
      "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz",
      "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==",
      "dev": true,
      "dependencies": {
        "braces": "^3.0.2",
        "picomatch": "^2.3.1"
      },
      "engines": {
        "node": ">=8.6"
      }
    },
    "node_modules/mime": {
      "version": "4.0.1",
      "resolved": "https://registry.npmjs.org/mime/-/mime-4.0.1.tgz",
      "integrity": "sha512-5lZ5tyrIfliMXzFtkYyekWbtRXObT9OWa8IwQ5uxTBDHucNNwniRqo0yInflj+iYi5CBa6qxadGzGarDfuEOxA==",
      "dev": true,
      "funding": [
        "https://github.com/sponsors/broofa"
      ],
      "bin": {
        "mime": "bin/cli.js"
      },
      "engines": {
        "node": ">=16"
      }
    },
    "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,
      "engines": {
        "node": ">=6"
      }
    },
    "node_modules/minimist": {
      "version": "1.2.8",
      "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
      "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
      "dev": true,
      "funding": {
        "url": "https://github.com/sponsors/ljharb"
      }
    },
    "node_modules/ms": {
      "version": "2.1.2",
      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
      "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
      "dev": true
    },
    "node_modules/mz": {
      "version": "2.7.0",
      "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
      "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==",
      "dev": true,
      "dependencies": {
        "any-promise": "^1.0.0",
        "object-assign": "^4.0.1",
        "thenify-all": "^1.0.0"
      }
    },
    "node_modules/neo-async": {
      "version": "2.6.2",
      "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz",
      "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
      "dev": true
    },
    "node_modules/nerf-dart": {
      "version": "1.0.0",
      "resolved": "https://registry.npmjs.org/nerf-dart/-/nerf-dart-1.0.0.tgz",
      "integrity": "sha512-EZSPZB70jiVsivaBLYDCyntd5eH8NTSMOn3rB+HxwdmKThGELLdYv8qVIMWvZEFy9w8ZZpW9h9OB32l1rGtj7g==",
      "dev": true
    },
    "node_modules/node-emoji": {
      "version": "2.2.0",
      "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-2.2.0.tgz",
      "integrity": "sha512-Z3lTE9pLaJF47NyMhd4ww1yFTAP8YhYI8SleJiHzM46Fgpm5cnNzSl9XfzFNqbaz+VlJrIj3fXQ4DeN1Rjm6cw==",
      "dev": true,
      "dependencies": {
        "@sindresorhus/is": "^4.6.0",
        "char-regex": "^1.0.2",
        "emojilib": "^2.4.0",
        "skin-tone": "^2.0.0"
      },
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/normalize-package-data": {
      "version": "6.0.0",
      "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.0.tgz",
      "integrity": "sha512-UL7ELRVxYBHBgYEtZCXjxuD5vPxnmvMGq0jp/dGPKKrN7tfsBh2IY7TlJ15WWwdjRWD3RJbnsygUurTK3xkPkg==",
      "dev": true,
      "dependencies": {
        "hosted-git-info": "^7.0.0",
        "is-core-module": "^2.8.1",
        "semver": "^7.3.5",
        "validate-npm-package-license": "^3.0.4"
      },
      "engines": {
        "node": "^16.14.0 || >=18.0.0"
      }
    },
    "node_modules/normalize-url": {
      "version": "8.0.0",
      "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.0.0.tgz",
      "integrity": "sha512-uVFpKhj5MheNBJRTiMZ9pE/7hD1QTeEvugSJW/OmLzAp78PB5O6adfMNTvmfKhXBkvCzC+rqifWcVYpGFwTjnw==",
      "dev": true,
      "engines": {
        "node": ">=14.16"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/npm": {
      "version": "10.9.3",
      "resolved": "https://registry.npmjs.org/npm/-/npm-10.9.3.tgz",
      "integrity": "sha512-6Eh1u5Q+kIVXeA8e7l2c/HpnFFcwrkt37xDMujD5be1gloWa9p6j3Fsv3mByXXmqJHy+2cElRMML8opNT7xIJQ==",
      "bundleDependencies": [
        "@isaacs/string-locale-compare",
        "@npmcli/arborist",
        "@npmcli/config",
        "@npmcli/fs",
        "@npmcli/map-workspaces",
        "@npmcli/package-json",
        "@npmcli/promise-spawn",
        "@npmcli/redact",
        "@npmcli/run-script",
        "@sigstore/tuf",
        "abbrev",
        "archy",
        "cacache",
        "chalk",
        "ci-info",
        "cli-columns",
        "fastest-levenshtein",
        "fs-minipass",
        "glob",
        "graceful-fs",
        "hosted-git-info",
        "ini",
        "init-package-json",
        "is-cidr",
        "json-parse-even-better-errors",
        "libnpmaccess",
        "libnpmdiff",
        "libnpmexec",
        "libnpmfund",
        "libnpmhook",
        "libnpmorg",
        "libnpmpack",
        "libnpmpublish",
        "libnpmsearch",
        "libnpmteam",
        "libnpmversion",
        "make-fetch-happen",
        "minimatch",
        "minipass",
        "minipass-pipeline",
        "ms",
        "node-gyp",
        "nopt",
        "normalize-package-data",
        "npm-audit-report",
        "npm-install-checks",
        "npm-package-arg",
        "npm-pick-manifest",
        "npm-profile",
        "npm-registry-fetch",
        "npm-user-validate",
        "p-map",
        "pacote",
        "parse-conflict-json",
        "proc-log",
        "qrcode-terminal",
        "read",
        "semver",
        "spdx-expression-parse",
        "ssri",
        "supports-color",
        "tar",
        "text-table",
        "tiny-relative-date",
        "treeverse",
        "validate-npm-package-name",
        "which",
        "write-file-atomic"
      ],
      "dev": true,
      "dependencies": {
        "@isaacs/string-locale-compare": "^1.1.0",
        "@npmcli/arborist": "^8.0.1",
        "@npmcli/config": "^9.0.0",
        "@npmcli/fs": "^4.0.0",
        "@npmcli/map-workspaces": "^4.0.2",
        "@npmcli/package-json": "^6.2.0",
        "@npmcli/promise-spawn": "^8.0.2",
        "@npmcli/redact": "^3.2.2",
        "@npmcli/run-script": "^9.1.0",
        "@sigstore/tuf": "^3.1.1",
        "abbrev": "^3.0.1",
        "archy": "~1.0.0",
        "cacache": "^19.0.1",
        "chalk": "^5.4.1",
        "ci-info": "^4.2.0",
        "cli-columns": "^4.0.0",
        "fastest-levenshtein": "^1.0.16",
        "fs-minipass": "^3.0.3",
        "glob": "^10.4.5",
        "graceful-fs": "^4.2.11",
        "hosted-git-info": "^8.1.0",
        "ini": "^5.0.0",
        "init-package-json": "^7.0.2",
        "is-cidr": "^5.1.1",
        "json-parse-even-better-errors": "^4.0.0",
        "libnpmaccess": "^9.0.0",
        "libnpmdiff": "^7.0.1",
        "libnpmexec": "^9.0.1",
        "libnpmfund": "^6.0.1",
        "libnpmhook": "^11.0.0",
        "libnpmorg": "^7.0.0",
        "libnpmpack": "^8.0.1",
        "libnpmpublish": "^10.0.1",
        "libnpmsearch": "^8.0.0",
        "libnpmteam": "^7.0.0",
        "libnpmversion": "^7.0.0",
        "make-fetch-happen": "^14.0.3",
        "minimatch": "^9.0.5",
        "minipass": "^7.1.1",
        "minipass-pipeline": "^1.2.4",
        "ms": "^2.1.2",
        "node-gyp": "^11.2.0",
        "nopt": "^8.1.0",
        "normalize-package-data": "^7.0.0",
        "npm-audit-report": "^6.0.0",
        "npm-install-checks": "^7.1.1",
        "npm-package-arg": "^12.0.2",
        "npm-pick-manifest": "^10.0.0",
        "npm-profile": "^11.0.1",
        "npm-registry-fetch": "^18.0.2",
        "npm-user-validate": "^3.0.0",
        "p-map": "^7.0.3",
        "pacote": "^19.0.1",
        "parse-conflict-json": "^4.0.0",
        "proc-log": "^5.0.0",
        "qrcode-terminal": "^0.12.0",
        "read": "^4.1.0",
        "semver": "^7.7.2",
        "spdx-expression-parse": "^4.0.0",
        "ssri": "^12.0.0",
        "supports-color": "^9.4.0",
        "tar": "^6.2.1",
        "text-table": "~0.2.0",
        "tiny-relative-date": "^1.3.0",
        "treeverse": "^3.0.0",
        "validate-npm-package-name": "^6.0.1",
        "which": "^5.0.0",
        "write-file-atomic": "^6.0.0"
      },
      "bin": {
        "npm": "bin/npm-cli.js",
        "npx": "bin/npx-cli.js"
      },
      "engines": {
        "node": "^18.17.0 || >=20.5.0"
      }
    },
    "node_modules/npm-run-path": {
      "version": "4.0.1",
      "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz",
      "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==",
      "dev": true,
      "dependencies": {
        "path-key": "^3.0.0"
      },
      "engines": {
        "node": ">=8"
      }
    },
    "node_modules/npm/node_modules/@isaacs/cliui": {
      "version": "8.0.2",
      "dev": true,
      "inBundle": true,
      "license": "ISC",
      "dependencies": {
        "string-width": "^5.1.2",
        "string-width-cjs": "npm:string-width@^4.2.0",
        "strip-ansi": "^7.0.1",
        "strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
        "wrap-ansi": "^8.1.0",
        "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
      },
      "engines": {
        "node": ">=12"
      }
    },
    "node_modules/npm/node_modules/@isaacs/cliui/node_modules/ansi-regex": {
      "version": "6.1.0",
      "dev": true,
      "inBundle": true,
      "license": "MIT",
      "engines": {
        "node": ">=12"
      },
      "funding": {
        "url": "https://github.com/chalk/ansi-regex?sponsor=1"
      }
    },
    "node_modules/npm/node_modules/@isaacs/cliui/node_modules/emoji-regex": {
      "version": "9.2.2",
      "dev": true,
      "inBundle": true,
      "license": "MIT"
    },
    "node_modules/npm/node_modules/@isaacs/cliui/node_modules/string-width": {
      "version": "5.1.2",
      "dev": true,
      "inBundle": true,
      "license": "MIT",
      "dependencies": {
        "eastasianwidth": "^0.2.0",
        "emoji-regex": "^9.2.2",
        "strip-ansi": "^7.0.1"
      },
      "engines": {
        "node": ">=12"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/npm/node_modules/@isaacs/cliui/node_modules/strip-ansi": {
      "version": "7.1.0",
      "dev": true,
      "inBundle": true,
      "license": "MIT",
      "dependencies": {
        "ansi-regex": "^6.0.1"
      },
      "engines": {
        "node": ">=12"
      },
      "funding": {
        "url": "https://github.com/chalk/strip-ansi?sponsor=1"
      }
    },
    "node_modules/npm/node_modules/@isaacs/fs-minipass": {
      "version": "4.0.1",
      "dev": true,
      "inBundle": true,
      "license": "ISC",
      "dependencies": {
        "minipass": "^7.0.4"
      },
      "engines": {
        "node": ">=18.0.0"
      }
    },
    "node_modules/npm/node_modules/@isaacs/string-locale-compare": {
      "version": "1.1.0",
      "dev": true,
      "inBundle": true,
      "license": "ISC"
    },
    "node_modules/npm/node_modules/@npmcli/agent": {
      "version": "3.0.0",
      "dev": true,
      "inBundle": true,
      "license": "ISC",
      "dependencies": {
        "agent-base": "^7.1.0",
        "http-proxy-agent": "^7.0.0",
        "https-proxy-agent": "^7.0.1",
        "lru-cache": "^10.0.1",
        "socks-proxy-agent": "^8.0.3"
      },
      "engines": {
        "node": "^18.17.0 || >=20.5.0"
      }
    },
    "node_modules/npm/node_modules/@npmcli/arborist": {
      "version": "8.0.1",
      "dev": true,
      "inBundle": true,
      "license": "ISC",
      "dependencies": {
        "@isaacs/string-locale-compare": "^1.1.0",
        "@npmcli/fs": "^4.0.0",
        "@npmcli/installed-package-contents": "^3.0.0",
        "@npmcli/map-workspaces": "^4.0.1",
        "@npmcli/metavuln-calculator": "^8.0.0",
        "@npmcli/name-from-folder": "^3.0.0",
        "@npmcli/node-gyp": "^4.0.0",
        "@npmcli/package-json": "^6.0.1",
        "@npmcli/query": "^4.0.0",
        "@npmcli/redact": "^3.0.0",
        "@npmcli/run-script": "^9.0.1",
        "bin-links": "^5.0.0",
        "cacache": "^19.0.1",
        "common-ancestor-path": "^1.0.1",
        "hosted-git-info": "^8.0.0",
        "json-parse-even-better-errors": "^4.0.0",
        "json-stringify-nice": "^1.1.4",
        "lru-cache": "^10.2.2",
        "minimatch": "^9.0.4",
        "nopt": "^8.0.0",
        "npm-install-checks": "^7.1.0",
        "npm-package-arg": "^12.0.0",
        "npm-pick-manifest": "^10.0.0",
        "npm-registry-fetch": "^18.0.1",
        "pacote": "^19.0.0",
        "parse-conflict-json": "^4.0.0",
        "proc-log": "^5.0.0",
        "proggy": "^3.0.0",
        "promise-all-reject-late": "^1.0.0",
        "promise-call-limit": "^3.0.1",
        "read-package-json-fast": "^4.0.0",
        "semver": "^7.3.7",
        "ssri": "^12.0.0",
        "treeverse": "^3.0.0",
        "walk-up-path": "^3.0.1"
      },
      "bin": {
        "arborist": "bin/index.js"
      },
      "engines": {
        "node": "^18.17.0 || >=20.5.0"
      }
    },
    "node_modules/npm/node_modules/@npmcli/config": {
      "version": "9.0.0",
      "dev": true,
      "inBundle": true,
      "license": "ISC",
      "dependencies": {
        "@npmcli/map-workspaces": "^4.0.1",
        "@npmcli/package-json": "^6.0.1",
        "ci-info": "^4.0.0",
        "ini": "^5.0.0",
        "nopt": "^8.0.0",
        "proc-log": "^5.0.0",
        "semver": "^7.3.5",
        "walk-up-path": "^3.0.1"
      },
      "engines": {
        "node": "^18.17.0 || >=20.5.0"
      }
    },
    "node_modules/npm/node_modules/@npmcli/fs": {
      "version": "4.0.0",
      "dev": true,
      "inBundle": true,
      "license": "ISC",
      "dependencies": {
        "semver": "^7.3.5"
      },
      "engines": {
        "node": "^18.17.0 || >=20.5.0"
      }
    },
    "node_modules/npm/node_modules/@npmcli/git": {
      "version": "6.0.3",
      "dev": true,
      "inBundle": true,
      "license": "ISC",
      "dependencies": {
        "@npmcli/promise-spawn": "^8.0.0",
        "ini": "^5.0.0",
        "lru-cache": "^10.0.1",
        "npm-pick-manifest": "^10.0.0",
        "proc-log": "^5.0.0",
        "promise-retry": "^2.0.1",
        "semver": "^7.3.5",
        "which": "^5.0.0"
      },
      "engines": {
        "node": "^18.17.0 || >=20.5.0"
      }
    },
    "node_modules/npm/node_modules/@npmcli/installed-package-contents": {
      "version": "3.0.0",
      "dev": true,
      "inBundle": true,
      "license": "ISC",
      "dependencies": {
        "npm-bundled": "^4.0.0",
        "npm-normalize-package-bin": "^4.0.0"
      },
      "bin": {
        "installed-package-contents": "bin/index.js"
      },
      "engines": {
        "node": "^18.17.0 || >=20.5.0"
      }
    },
    "node_modules/npm/node_modules/@npmcli/map-workspaces": {
      "version": "4.0.2",
      "dev": true,
      "inBundle": true,
      "license": "ISC",
      "dependencies": {
        "@npmcli/name-from-folder": "^3.0.0",
        "@npmcli/package-json": "^6.0.0",
        "glob": "^10.2.2",
        "minimatch": "^9.0.0"
      },
      "engines": {
        "node": "^18.17.0 || >=20.5.0"
      }
    },
    "node_modules/npm/node_modules/@npmcli/metavuln-calculator": {
      "version": "8.0.1",
      "dev": true,
      "inBundle": true,
      "license": "ISC",
      "dependencies": {
        "cacache": "^19.0.0",
        "json-parse-even-better-errors": "^4.0.0",
        "pacote": "^20.0.0",
        "proc-log": "^5.0.0",
        "semver": "^7.3.5"
      },
      "engines": {
        "node": "^18.17.0 || >=20.5.0"
      }
    },
    "node_modules/npm/node_modules/@npmcli/metavuln-calculator/node_modules/pacote": {
      "version": "20.0.0",
      "dev": true,
      "inBundle": true,
      "license": "ISC",
      "dependencies": {
        "@npmcli/git": "^6.0.0",
        "@npmcli/installed-package-contents": "^3.0.0",
        "@npmcli/package-json": "^6.0.0",
        "@npmcli/promise-spawn": "^8.0.0",
        "@npmcli/run-script": "^9.0.0",
        "cacache": "^19.0.0",
        "fs-minipass": "^3.0.0",
        "minipass": "^7.0.2",
        "npm-package-arg": "^12.0.0",
        "npm-packlist": "^9.0.0",
        "npm-pick-manifest": "^10.0.0",
        "npm-registry-fetch": "^18.0.0",
        "proc-log": "^5.0.0",
        "promise-retry": "^2.0.1",
        "sigstore": "^3.0.0",
        "ssri": "^12.0.0",
        "tar": "^6.1.11"
      },
      "bin": {
        "pacote": "bin/index.js"
      },
      "engines": {
        "node": "^18.17.0 || >=20.5.0"
      }
    },
    "node_modules/npm/node_modules/@npmcli/name-from-folder": {
      "version": "3.0.0",
      "dev": true,
      "inBundle": true,
      "license": "ISC",
      "engines": {
        "node": "^18.17.0 || >=20.5.0"
      }
    },
    "node_modules/npm/node_modules/@npmcli/node-gyp": {
      "version": "4.0.0",
      "dev": true,
      "inBundle": true,
      "license": "ISC",
      "engines": {
        "node": "^18.17.0 || >=20.5.0"
      }
    },
    "node_modules/npm/node_modules/@npmcli/package-json": {
      "version": "6.2.0",
      "dev": true,
      "inBundle": true,
      "license": "ISC",
      "dependencies": {
        "@npmcli/git": "^6.0.0",
        "glob": "^10.2.2",
        "hosted-git-info": "^8.0.0",
        "json-parse-even-better-errors": "^4.0.0",
        "proc-log": "^5.0.0",
        "semver": "^7.5.3",
        "validate-npm-package-license": "^3.0.4"
      },
      "engines": {
        "node": "^18.17.0 || >=20.5.0"
      }
    },
    "node_modules/npm/node_modules/@npmcli/promise-spawn": {
      "version": "8.0.2",
      "dev": true,
      "inBundle": true,
      "license": "ISC",
      "dependencies": {
        "which": "^5.0.0"
      },
      "engines": {
        "node": "^18.17.0 || >=20.5.0"
      }
    },
    "node_modules/npm/node_modules/@npmcli/query": {
      "version": "4.0.1",
      "dev": true,
      "inBundle": true,
      "license": "ISC",
      "dependencies": {
        "postcss-selector-parser": "^7.0.0"
      },
      "engines": {
        "node": "^18.17.0 || >=20.5.0"
      }
    },
    "node_modules/npm/node_modules/@npmcli/redact": {
      "version": "3.2.2",
      "dev": true,
      "inBundle": true,
      "license": "ISC",
      "engines": {
        "node": "^18.17.0 || >=20.5.0"
      }
    },
    "node_modules/npm/node_modules/@npmcli/run-script": {
      "version": "9.1.0",
      "dev": true,
      "inBundle": true,
      "license": "ISC",
      "dependencies": {
        "@npmcli/node-gyp": "^4.0.0",
        "@npmcli/package-json": "^6.0.0",
        "@npmcli/promise-spawn": "^8.0.0",
        "node-gyp": "^11.0.0",
        "proc-log": "^5.0.0",
        "which": "^5.0.0"
      },
      "engines": {
        "node": "^18.17.0 || >=20.5.0"
      }
    },
    "node_modules/npm/node_modules/@pkgjs/parseargs": {
      "version": "0.11.0",
      "dev": true,
      "inBundle": true,
      "license": "MIT",
      "optional": true,
      "engines": {
        "node": ">=14"
      }
    },
    "node_modules/npm/node_modules/@sigstore/protobuf-specs": {
      "version": "0.4.3",
      "dev": true,
      "inBundle": true,
      "license": "Apache-2.0",
      "engines": {
        "node": "^18.17.0 || >=20.5.0"
      }
    },
    "node_modules/npm/node_modules/@sigstore/tuf": {
      "version": "3.1.1",
      "dev": true,
      "inBundle": true,
      "license": "Apache-2.0",
      "dependencies": {
        "@sigstore/protobuf-specs": "^0.4.1",
        "tuf-js": "^3.0.1"
      },
      "engines": {
        "node": "^18.17.0 || >=20.5.0"
      }
    },
    "node_modules/npm/node_modules/@tufjs/canonical-json": {
      "version": "2.0.0",
      "dev": true,
      "inBundle": true,
      "license": "MIT",
      "engines": {
        "node": "^16.14.0 || >=18.0.0"
      }
    },
    "node_modules/npm/node_modules/abbrev": {
      "version": "3.0.1",
      "dev": true,
      "inBundle": true,
      "license": "ISC",
      "engines": {
        "node": "^18.17.0 || >=20.5.0"
      }
    },
    "node_modules/npm/node_modules/agent-base": {
      "version": "7.1.3",
      "dev": true,
      "inBundle": true,
      "license": "MIT",
      "engines": {
        "node": ">= 14"
      }
    },
    "node_modules/npm/node_modules/ansi-regex": {
      "version": "5.0.1",
      "dev": true,
      "inBundle": true,
      "license": "MIT",
      "engines": {
        "node": ">=8"
      }
    },
    "node_modules/npm/node_modules/ansi-styles": {
      "version": "6.2.1",
      "dev": true,
      "inBundle": true,
      "license": "MIT",
      "engines": {
        "node": ">=12"
      },
      "funding": {
        "url": "https://github.com/chalk/ansi-styles?sponsor=1"
      }
    },
    "node_modules/npm/node_modules/aproba": {
      "version": "2.0.0",
      "dev": true,
      "inBundle": true,
      "license": "ISC"
    },
    "node_modules/npm/node_modules/archy": {
      "version": "1.0.0",
      "dev": true,
      "inBundle": true,
      "license": "MIT"
    },
    "node_modules/npm/node_modules/balanced-match": {
      "version": "1.0.2",
      "dev": true,
      "inBundle": true,
      "license": "MIT"
    },
    "node_modules/npm/node_modules/bin-links": {
      "version": "5.0.0",
      "dev": true,
      "inBundle": true,
      "license": "ISC",
      "dependencies": {
        "cmd-shim": "^7.0.0",
        "npm-normalize-package-bin": "^4.0.0",
        "proc-log": "^5.0.0",
        "read-cmd-shim": "^5.0.0",
        "write-file-atomic": "^6.0.0"
      },
      "engines": {
        "node": "^18.17.0 || >=20.5.0"
      }
    },
    "node_modules/npm/node_modules/binary-extensions": {
      "version": "2.3.0",
      "dev": true,
      "inBundle": true,
      "license": "MIT",
      "engines": {
        "node": ">=8"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/npm/node_modules/brace-expansion": {
      "version": "2.0.2",
      "dev": true,
      "inBundle": true,
      "license": "MIT",
      "dependencies": {
        "balanced-match": "^1.0.0"
      }
    },
    "node_modules/npm/node_modules/cacache": {
      "version": "19.0.1",
      "dev": true,
      "inBundle": true,
      "license": "ISC",
      "dependencies": {
        "@npmcli/fs": "^4.0.0",
        "fs-minipass": "^3.0.0",
        "glob": "^10.2.2",
        "lru-cache": "^10.0.1",
        "minipass": "^7.0.3",
        "minipass-collect": "^2.0.1",
        "minipass-flush": "^1.0.5",
        "minipass-pipeline": "^1.2.4",
        "p-map": "^7.0.2",
        "ssri": "^12.0.0",
        "tar": "^7.4.3",
        "unique-filename": "^4.0.0"
      },
      "engines": {
        "node": "^18.17.0 || >=20.5.0"
      }
    },
    "node_modules/npm/node_modules/cacache/node_modules/chownr": {
      "version": "3.0.0",
      "dev": true,
      "inBundle": true,
      "license": "BlueOak-1.0.0",
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/npm/node_modules/cacache/node_modules/mkdirp": {
      "version": "3.0.1",
      "dev": true,
      "inBundle": true,
      "license": "MIT",
      "bin": {
        "mkdirp": "dist/cjs/src/bin.js"
      },
      "engines": {
        "node": ">=10"
      },
      "funding": {
        "url": "https://github.com/sponsors/isaacs"
      }
    },
    "node_modules/npm/node_modules/cacache/node_modules/tar": {
      "version": "7.4.3",
      "dev": true,
      "inBundle": true,
      "license": "ISC",
      "dependencies": {
        "@isaacs/fs-minipass": "^4.0.0",
        "chownr": "^3.0.0",
        "minipass": "^7.1.2",
        "minizlib": "^3.0.1",
        "mkdirp": "^3.0.1",
        "yallist": "^5.0.0"
      },
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/npm/node_modules/cacache/node_modules/yallist": {
      "version": "5.0.0",
      "dev": true,
      "inBundle": true,
      "license": "BlueOak-1.0.0",
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/npm/node_modules/chalk": {
      "version": "5.4.1",
      "dev": true,
      "inBundle": true,
      "license": "MIT",
      "engines": {
        "node": "^12.17.0 || ^14.13 || >=16.0.0"
      },
      "funding": {
        "url": "https://github.com/chalk/chalk?sponsor=1"
      }
    },
    "node_modules/npm/node_modules/chownr": {
      "version": "2.0.0",
      "dev": true,
      "inBundle": true,
      "license": "ISC",
      "engines": {
        "node": ">=10"
      }
    },
    "node_modules/npm/node_modules/ci-info": {
      "version": "4.2.0",
      "dev": true,
      "funding": [
        {
          "type": "github",
          "url": "https://github.com/sponsors/sibiraj-s"
        }
      ],
      "inBundle": true,
      "license": "MIT",
      "engines": {
        "node": ">=8"
      }
    },
    "node_modules/npm/node_modules/cidr-regex": {
      "version": "4.1.3",
      "dev": true,
      "inBundle": true,
      "license": "BSD-2-Clause",
      "dependencies": {
        "ip-regex": "^5.0.0"
      },
      "engines": {
        "node": ">=14"
      }
    },
    "node_modules/npm/node_modules/cli-columns": {
      "version": "4.0.0",
      "dev": true,
      "inBundle": true,
      "license": "MIT",
      "dependencies": {
        "string-width": "^4.2.3",
        "strip-ansi": "^6.0.1"
      },
      "engines": {
        "node": ">= 10"
      }
    },
    "node_modules/npm/node_modules/cmd-shim": {
      "version": "7.0.0",
      "dev": true,
      "inBundle": true,
      "license": "ISC",
      "engines": {
        "node": "^18.17.0 || >=20.5.0"
      }
    },
    "node_modules/npm/node_modules/color-convert": {
      "version": "2.0.1",
      "dev": true,
      "inBundle": true,
      "license": "MIT",
      "dependencies": {
        "color-name": "~1.1.4"
      },
      "engines": {
        "node": ">=7.0.0"
      }
    },
    "node_modules/npm/node_modules/color-name": {
      "version": "1.1.4",
      "dev": true,
      "inBundle": true,
      "license": "MIT"
    },
    "node_modules/npm/node_modules/common-ancestor-path": {
      "version": "1.0.1",
      "dev": true,
      "inBundle": true,
      "license": "ISC"
    },
    "node_modules/npm/node_modules/cross-spawn": {
      "version": "7.0.6",
      "dev": true,
      "inBundle": true,
      "license": "MIT",
      "dependencies": {
        "path-key": "^3.1.0",
        "shebang-command": "^2.0.0",
        "which": "^2.0.1"
      },
      "engines": {
        "node": ">= 8"
      }
    },
    "node_modules/npm/node_modules/cross-spawn/node_modules/which": {
      "version": "2.0.2",
      "dev": true,
      "inBundle": true,
      "license": "ISC",
      "dependencies": {
        "isexe": "^2.0.0"
      },
      "bin": {
        "node-which": "bin/node-which"
      },
      "engines": {
        "node": ">= 8"
      }
    },
    "node_modules/npm/node_modules/cssesc": {
      "version": "3.0.0",
      "dev": true,
      "inBundle": true,
      "license": "MIT",
      "bin": {
        "cssesc": "bin/cssesc"
      },
      "engines": {
        "node": ">=4"
      }
    },
    "node_modules/npm/node_modules/debug": {
      "version": "4.4.1",
      "dev": true,
      "inBundle": true,
      "license": "MIT",
      "dependencies": {
        "ms": "^2.1.3"
      },
      "engines": {
        "node": ">=6.0"
      },
      "peerDependenciesMeta": {
        "supports-color": {
          "optional": true
        }
      }
    },
    "node_modules/npm/node_modules/diff": {
      "version": "5.2.0",
      "dev": true,
      "inBundle": true,
      "license": "BSD-3-Clause",
      "engines": {
        "node": ">=0.3.1"
      }
    },
    "node_modules/npm/node_modules/eastasianwidth": {
      "version": "0.2.0",
      "dev": true,
      "inBundle": true,
      "license": "MIT"
    },
    "node_modules/npm/node_modules/emoji-regex": {
      "version": "8.0.0",
      "dev": true,
      "inBundle": true,
      "license": "MIT"
    },
    "node_modules/npm/node_modules/encoding": {
      "version": "0.1.13",
      "dev": true,
      "inBundle": true,
      "license": "MIT",
      "optional": true,
      "dependencies": {
        "iconv-lite": "^0.6.2"
      }
    },
    "node_modules/npm/node_modules/env-paths": {
      "version": "2.2.1",
      "dev": true,
      "inBundle": true,
      "license": "MIT",
      "engines": {
        "node": ">=6"
      }
    },
    "node_modules/npm/node_modules/err-code": {
      "version": "2.0.3",
      "dev": true,
      "inBundle": true,
      "license": "MIT"
    },
    "node_modules/npm/node_modules/exponential-backoff": {
      "version": "3.1.2",
      "dev": true,
      "inBundle": true,
      "license": "Apache-2.0"
    },
    "node_modules/npm/node_modules/fastest-levenshtein": {
      "version": "1.0.16",
      "dev": true,
      "inBundle": true,
      "license": "MIT",
      "engines": {
        "node": ">= 4.9.1"
      }
    },
    "node_modules/npm/node_modules/foreground-child": {
      "version": "3.3.1",
      "dev": true,
      "inBundle": true,
      "license": "ISC",
      "dependencies": {
        "cross-spawn": "^7.0.6",
        "signal-exit": "^4.0.1"
      },
      "engines": {
        "node": ">=14"
      },
      "funding": {
        "url": "https://github.com/sponsors/isaacs"
      }
    },
    "node_modules/npm/node_modules/fs-minipass": {
      "version": "3.0.3",
      "dev": true,
      "inBundle": true,
      "license": "ISC",
      "dependencies": {
        "minipass": "^7.0.3"
      },
      "engines": {
        "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
      }
    },
    "node_modules/npm/node_modules/glob": {
      "version": "10.4.5",
      "dev": true,
      "inBundle": true,
      "license": "ISC",
      "dependencies": {
        "foreground-child": "^3.1.0",
        "jackspeak": "^3.1.2",
        "minimatch": "^9.0.4",
        "minipass": "^7.1.2",
        "package-json-from-dist": "^1.0.0",
        "path-scurry": "^1.11.1"
      },
      "bin": {
        "glob": "dist/esm/bin.mjs"
      },
      "funding": {
        "url": "https://github.com/sponsors/isaacs"
      }
    },
    "node_modules/npm/node_modules/graceful-fs": {
      "version": "4.2.11",
      "dev": true,
      "inBundle": true,
      "license": "ISC"
    },
    "node_modules/npm/node_modules/hosted-git-info": {
      "version": "8.1.0",
      "dev": true,
      "inBundle": true,
      "license": "ISC",
      "dependencies": {
        "lru-cache": "^10.0.1"
      },
      "engines": {
        "node": "^18.17.0 || >=20.5.0"
      }
    },
    "node_modules/npm/node_modules/http-cache-semantics": {
      "version": "4.2.0",
      "dev": true,
      "inBundle": true,
      "license": "BSD-2-Clause"
    },
    "node_modules/npm/node_modules/http-proxy-agent": {
      "version": "7.0.2",
      "dev": true,
      "inBundle": true,
      "license": "MIT",
      "dependencies": {
        "agent-base": "^7.1.0",
        "debug": "^4.3.4"
      },
      "engines": {
        "node": ">= 14"
      }
    },
    "node_modules/npm/node_modules/https-proxy-agent": {
      "version": "7.0.6",
      "dev": true,
      "inBundle": true,
      "license": "MIT",
      "dependencies": {
        "agent-base": "^7.1.2",
        "debug": "4"
      },
      "engines": {
        "node": ">= 14"
      }
    },
    "node_modules/npm/node_modules/iconv-lite": {
      "version": "0.6.3",
      "dev": true,
      "inBundle": true,
      "license": "MIT",
      "optional": true,
      "dependencies": {
        "safer-buffer": ">= 2.1.2 < 3.0.0"
      },
      "engines": {
        "node": ">=0.10.0"
      }
    },
    "node_modules/npm/node_modules/ignore-walk": {
      "version": "7.0.0",
      "dev": true,
      "inBundle": true,
      "license": "ISC",
      "dependencies": {
        "minimatch": "^9.0.0"
      },
      "engines": {
        "node": "^18.17.0 || >=20.5.0"
      }
    },
    "node_modules/npm/node_modules/imurmurhash": {
      "version": "0.1.4",
      "dev": true,
      "inBundle": true,
      "license": "MIT",
      "engines": {
        "node": ">=0.8.19"
      }
    },
    "node_modules/npm/node_modules/ini": {
      "version": "5.0.0",
      "dev": true,
      "inBundle": true,
      "license": "ISC",
      "engines": {
        "node": "^18.17.0 || >=20.5.0"
      }
    },
    "node_modules/npm/node_modules/init-package-json": {
      "version": "7.0.2",
      "dev": true,
      "inBundle": true,
      "license": "ISC",
      "dependencies": {
        "@npmcli/package-json": "^6.0.0",
        "npm-package-arg": "^12.0.0",
        "promzard": "^2.0.0",
        "read": "^4.0.0",
        "semver": "^7.3.5",
        "validate-npm-package-license": "^3.0.4",
        "validate-npm-package-name": "^6.0.0"
      },
      "engines": {
        "node": "^18.17.0 || >=20.5.0"
      }
    },
    "node_modules/npm/node_modules/ip-address": {
      "version": "9.0.5",
      "dev": true,
      "inBundle": true,
      "license": "MIT",
      "dependencies": {
        "jsbn": "1.1.0",
        "sprintf-js": "^1.1.3"
      },
      "engines": {
        "node": ">= 12"
      }
    },
    "node_modules/npm/node_modules/ip-regex": {
      "version": "5.0.0",
      "dev": true,
      "inBundle": true,
      "license": "MIT",
      "engines": {
        "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/npm/node_modules/is-cidr": {
      "version": "5.1.1",
      "dev": true,
      "inBundle": true,
      "license": "BSD-2-Clause",
      "dependencies": {
        "cidr-regex": "^4.1.1"
      },
      "engines": {
        "node": ">=14"
      }
    },
    "node_modules/npm/node_modules/is-fullwidth-code-point": {
      "version": "3.0.0",
      "dev": true,
      "inBundle": true,
      "license": "MIT",
      "engines": {
        "node": ">=8"
      }
    },
    "node_modules/npm/node_modules/isexe": {
      "version": "2.0.0",
      "dev": true,
      "inBundle": true,
      "license": "ISC"
    },
    "node_modules/npm/node_modules/jackspeak": {
      "version": "3.4.3",
      "dev": true,
      "inBundle": true,
      "license": "BlueOak-1.0.0",
      "dependencies": {
        "@isaacs/cliui": "^8.0.2"
      },
      "funding": {
        "url": "https://github.com/sponsors/isaacs"
      },
      "optionalDependencies": {
        "@pkgjs/parseargs": "^0.11.0"
      }
    },
    "node_modules/npm/node_modules/jsbn": {
      "version": "1.1.0",
      "dev": true,
      "inBundle": true,
      "license": "MIT"
    },
    "node_modules/npm/node_modules/json-parse-even-better-errors": {
      "version": "4.0.0",
      "dev": true,
      "inBundle": true,
      "license": "MIT",
      "engines": {
        "node": "^18.17.0 || >=20.5.0"
      }
    },
    "node_modules/npm/node_modules/json-stringify-nice": {
      "version": "1.1.4",
      "dev": true,
      "inBundle": true,
      "license": "ISC",
      "funding": {
        "url": "https://github.com/sponsors/isaacs"
      }
    },
    "node_modules/npm/node_modules/jsonparse": {
      "version": "1.3.1",
      "dev": true,
      "engines": [
        "node >= 0.2.0"
      ],
      "inBundle": true,
      "license": "MIT"
    },
    "node_modules/npm/node_modules/just-diff": {
      "version": "6.0.2",
      "dev": true,
      "inBundle": true,
      "license": "MIT"
    },
    "node_modules/npm/node_modules/just-diff-apply": {
      "version": "5.5.0",
      "dev": true,
      "inBundle": true,
      "license": "MIT"
    },
    "node_modules/npm/node_modules/libnpmaccess": {
      "version": "9.0.0",
      "dev": true,
      "inBundle": true,
      "license": "ISC",
      "dependencies": {
        "npm-package-arg": "^12.0.0",
        "npm-registry-fetch": "^18.0.1"
      },
      "engines": {
        "node": "^18.17.0 || >=20.5.0"
      }
    },
    "node_modules/npm/node_modules/libnpmdiff": {
      "version": "7.0.1",
      "dev": true,
      "inBundle": true,
      "license": "ISC",
      "dependencies": {
        "@npmcli/arborist": "^8.0.1",
        "@npmcli/installed-package-contents": "^3.0.0",
        "binary-extensions": "^2.3.0",
        "diff": "^5.1.0",
        "minimatch": "^9.0.4",
        "npm-package-arg": "^12.0.0",
        "pacote": "^19.0.0",
        "tar": "^6.2.1"
      },
      "engines": {
        "node": "^18.17.0 || >=20.5.0"
      }
    },
    "node_modules/npm/node_modules/libnpmexec": {
      "version": "9.0.1",
      "dev": true,
      "inBundle": true,
      "license": "ISC",
      "dependencies": {
        "@npmcli/arborist": "^8.0.1",
        "@npmcli/run-script": "^9.0.1",
        "ci-info": "^4.0.0",
        "npm-package-arg": "^12.0.0",
        "pacote": "^19.0.0",
        "proc-log": "^5.0.0",
        "read": "^4.0.0",
        "read-package-json-fast": "^4.0.0",
        "semver": "^7.3.7",
        "walk-up-path": "^3.0.1"
      },
      "engines": {
        "node": "^18.17.0 || >=20.5.0"
      }
    },
    "node_modules/npm/node_modules/libnpmfund": {
      "version": "6.0.1",
      "dev": true,
      "inBundle": true,
      "license": "ISC",
      "dependencies": {
        "@npmcli/arborist": "^8.0.1"
      },
      "engines": {
        "node": "^18.17.0 || >=20.5.0"
      }
    },
    "node_modules/npm/node_modules/libnpmhook": {
      "version": "11.0.0",
      "dev": true,
      "inBundle": true,
      "license": "ISC",
      "dependencies": {
        "aproba": "^2.0.0",
        "npm-registry-fetch": "^18.0.1"
      },
      "engines": {
        "node": "^18.17.0 || >=20.5.0"
      }
    },
    "node_modules/npm/node_modules/libnpmorg": {
      "version": "7.0.0",
      "dev": true,
      "inBundle": true,
      "license": "ISC",
      "dependencies": {
        "aproba": "^2.0.0",
        "npm-registry-fetch": "^18.0.1"
      },
      "engines": {
        "node": "^18.17.0 || >=20.5.0"
      }
    },
    "node_modules/npm/node_modules/libnpmpack": {
      "version": "8.0.1",
      "dev": true,
      "inBundle": true,
      "license": "ISC",
      "dependencies": {
        "@npmcli/arborist": "^8.0.1",
        "@npmcli/run-script": "^9.0.1",
        "npm-package-arg": "^12.0.0",
        "pacote": "^19.0.0"
      },
      "engines": {
        "node": "^18.17.0 || >=20.5.0"
      }
    },
    "node_modules/npm/node_modules/libnpmpublish": {
      "version": "10.0.1",
      "dev": true,
      "inBundle": true,
      "license": "ISC",
      "dependencies": {
        "ci-info": "^4.0.0",
        "normalize-package-data": "^7.0.0",
        "npm-package-arg": "^12.0.0",
        "npm-registry-fetch": "^18.0.1",
        "proc-log": "^5.0.0",
        "semver": "^7.3.7",
        "sigstore": "^3.0.0",
        "ssri": "^12.0.0"
      },
      "engines": {
        "node": "^18.17.0 || >=20.5.0"
      }
    },
    "node_modules/npm/node_modules/libnpmsearch": {
      "version": "8.0.0",
      "dev": true,
      "inBundle": true,
      "license": "ISC",
      "dependencies": {
        "npm-registry-fetch": "^18.0.1"
      },
      "engines": {
        "node": "^18.17.0 || >=20.5.0"
      }
    },
    "node_modules/npm/node_modules/libnpmteam": {
      "version": "7.0.0",
      "dev": true,
      "inBundle": true,
      "license": "ISC",
      "dependencies": {
        "aproba": "^2.0.0",
        "npm-registry-fetch": "^18.0.1"
      },
      "engines": {
        "node": "^18.17.0 || >=20.5.0"
      }
    },
    "node_modules/npm/node_modules/libnpmversion": {
      "version": "7.0.0",
      "dev": true,
      "inBundle": true,
      "license": "ISC",
      "dependencies": {
        "@npmcli/git": "^6.0.1",
        "@npmcli/run-script": "^9.0.1",
        "json-parse-even-better-errors": "^4.0.0",
        "proc-log": "^5.0.0",
        "semver": "^7.3.7"
      },
      "engines": {
        "node": "^18.17.0 || >=20.5.0"
      }
    },
    "node_modules/npm/node_modules/lru-cache": {
      "version": "10.4.3",
      "dev": true,
      "inBundle": true,
      "license": "ISC"
    },
    "node_modules/npm/node_modules/make-fetch-happen": {
      "version": "14.0.3",
      "dev": true,
      "inBundle": true,
      "license": "ISC",
      "dependencies": {
        "@npmcli/agent": "^3.0.0",
        "cacache": "^19.0.1",
        "http-cache-semantics": "^4.1.1",
        "minipass": "^7.0.2",
        "minipass-fetch": "^4.0.0",
        "minipass-flush": "^1.0.5",
        "minipass-pipeline": "^1.2.4",
        "negotiator": "^1.0.0",
        "proc-log": "^5.0.0",
        "promise-retry": "^2.0.1",
        "ssri": "^12.0.0"
      },
      "engines": {
        "node": "^18.17.0 || >=20.5.0"
      }
    },
    "node_modules/npm/node_modules/make-fetch-happen/node_modules/negotiator": {
      "version": "1.0.0",
      "dev": true,
      "inBundle": true,
      "license": "MIT",
      "engines": {
        "node": ">= 0.6"
      }
    },
    "node_modules/npm/node_modules/minimatch": {
      "version": "9.0.5",
      "dev": true,
      "inBundle": true,
      "license": "ISC",
      "dependencies": {
        "brace-expansion": "^2.0.1"
      },
      "engines": {
        "node": ">=16 || 14 >=14.17"
      },
      "funding": {
        "url": "https://github.com/sponsors/isaacs"
      }
    },
    "node_modules/npm/node_modules/minipass": {
      "version": "7.1.2",
      "dev": true,
      "inBundle": true,
      "license": "ISC",
      "engines": {
        "node": ">=16 || 14 >=14.17"
      }
    },
    "node_modules/npm/node_modules/minipass-collect": {
      "version": "2.0.1",
      "dev": true,
      "inBundle": true,
      "license": "ISC",
      "dependencies": {
        "minipass": "^7.0.3"
      },
      "engines": {
        "node": ">=16 || 14 >=14.17"
      }
    },
    "node_modules/npm/node_modules/minipass-fetch": {
      "version": "4.0.1",
      "dev": true,
      "inBundle": true,
      "license": "MIT",
      "dependencies": {
        "minipass": "^7.0.3",
        "minipass-sized": "^1.0.3",
        "minizlib": "^3.0.1"
      },
      "engines": {
        "node": "^18.17.0 || >=20.5.0"
      },
      "optionalDependencies": {
        "encoding": "^0.1.13"
      }
    },
    "node_modules/npm/node_modules/minipass-flush": {
      "version": "1.0.5",
      "dev": true,
      "inBundle": true,
      "license": "ISC",
      "dependencies": {
        "minipass": "^3.0.0"
      },
      "engines": {
        "node": ">= 8"
      }
    },
    "node_modules/npm/node_modules/minipass-flush/node_modules/minipass": {
      "version": "3.3.6",
      "dev": true,
      "inBundle": true,
      "license": "ISC",
      "dependencies": {
        "yallist": "^4.0.0"
      },
      "engines": {
        "node": ">=8"
      }
    },
    "node_modules/npm/node_modules/minipass-pipeline": {
      "version": "1.2.4",
      "dev": true,
      "inBundle": true,
      "license": "ISC",
      "dependencies": {
        "minipass": "^3.0.0"
      },
      "engines": {
        "node": ">=8"
      }
    },
    "node_modules/npm/node_modules/minipass-pipeline/node_modules/minipass": {
      "version": "3.3.6",
      "dev": true,
      "inBundle": true,
      "license": "ISC",
      "dependencies": {
        "yallist": "^4.0.0"
      },
      "engines": {
        "node": ">=8"
      }
    },
    "node_modules/npm/node_modules/minipass-sized": {
      "version": "1.0.3",
      "dev": true,
      "inBundle": true,
      "license": "ISC",
      "dependencies": {
        "minipass": "^3.0.0"
      },
      "engines": {
        "node": ">=8"
      }
    },
    "node_modules/npm/node_modules/minipass-sized/node_modules/minipass": {
      "version": "3.3.6",
      "dev": true,
      "inBundle": true,
      "license": "ISC",
      "dependencies": {
        "yallist": "^4.0.0"
      },
      "engines": {
        "node": ">=8"
      }
    },
    "node_modules/npm/node_modules/minizlib": {
      "version": "3.0.2",
      "dev": true,
      "inBundle": true,
      "license": "MIT",
      "dependencies": {
        "minipass": "^7.1.2"
      },
      "engines": {
        "node": ">= 18"
      }
    },
    "node_modules/npm/node_modules/mkdirp": {
      "version": "1.0.4",
      "dev": true,
      "inBundle": true,
      "license": "MIT",
      "bin": {
        "mkdirp": "bin/cmd.js"
      },
      "engines": {
        "node": ">=10"
      }
    },
    "node_modules/npm/node_modules/ms": {
      "version": "2.1.3",
      "dev": true,
      "inBundle": true,
      "license": "MIT"
    },
    "node_modules/npm/node_modules/mute-stream": {
      "version": "2.0.0",
      "dev": true,
      "inBundle": true,
      "license": "ISC",
      "engines": {
        "node": "^18.17.0 || >=20.5.0"
      }
    },
    "node_modules/npm/node_modules/node-gyp": {
      "version": "11.2.0",
      "dev": true,
      "inBundle": true,
      "license": "MIT",
      "dependencies": {
        "env-paths": "^2.2.0",
        "exponential-backoff": "^3.1.1",
        "graceful-fs": "^4.2.6",
        "make-fetch-happen": "^14.0.3",
        "nopt": "^8.0.0",
        "proc-log": "^5.0.0",
        "semver": "^7.3.5",
        "tar": "^7.4.3",
        "tinyglobby": "^0.2.12",
        "which": "^5.0.0"
      },
      "bin": {
        "node-gyp": "bin/node-gyp.js"
      },
      "engines": {
        "node": "^18.17.0 || >=20.5.0"
      }
    },
    "node_modules/npm/node_modules/node-gyp/node_modules/chownr": {
      "version": "3.0.0",
      "dev": true,
      "inBundle": true,
      "license": "BlueOak-1.0.0",
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/npm/node_modules/node-gyp/node_modules/mkdirp": {
      "version": "3.0.1",
      "dev": true,
      "inBundle": true,
      "license": "MIT",
      "bin": {
        "mkdirp": "dist/cjs/src/bin.js"
      },
      "engines": {
        "node": ">=10"
      },
      "funding": {
        "url": "https://github.com/sponsors/isaacs"
      }
    },
    "node_modules/npm/node_modules/node-gyp/node_modules/tar": {
      "version": "7.4.3",
      "dev": true,
      "inBundle": true,
      "license": "ISC",
      "dependencies": {
        "@isaacs/fs-minipass": "^4.0.0",
        "chownr": "^3.0.0",
        "minipass": "^7.1.2",
        "minizlib": "^3.0.1",
        "mkdirp": "^3.0.1",
        "yallist": "^5.0.0"
      },
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/npm/node_modules/node-gyp/node_modules/yallist": {
      "version": "5.0.0",
      "dev": true,
      "inBundle": true,
      "license": "BlueOak-1.0.0",
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/npm/node_modules/nopt": {
      "version": "8.1.0",
      "dev": true,
      "inBundle": true,
      "license": "ISC",
      "dependencies": {
        "abbrev": "^3.0.0"
      },
      "bin": {
        "nopt": "bin/nopt.js"
      },
      "engines": {
        "node": "^18.17.0 || >=20.5.0"
      }
    },
    "node_modules/npm/node_modules/normalize-package-data": {
      "version": "7.0.0",
      "dev": true,
      "inBundle": true,
      "license": "BSD-2-Clause",
      "dependencies": {
        "hosted-git-info": "^8.0.0",
        "semver": "^7.3.5",
        "validate-npm-package-license": "^3.0.4"
      },
      "engines": {
        "node": "^18.17.0 || >=20.5.0"
      }
    },
    "node_modules/npm/node_modules/npm-audit-report": {
      "version": "6.0.0",
      "dev": true,
      "inBundle": true,
      "license": "ISC",
      "engines": {
        "node": "^18.17.0 || >=20.5.0"
      }
    },
    "node_modules/npm/node_modules/npm-bundled": {
      "version": "4.0.0",
      "dev": true,
      "inBundle": true,
      "license": "ISC",
      "dependencies": {
        "npm-normalize-package-bin": "^4.0.0"
      },
      "engines": {
        "node": "^18.17.0 || >=20.5.0"
      }
    },
    "node_modules/npm/node_modules/npm-install-checks": {
      "version": "7.1.1",
      "dev": true,
      "inBundle": true,
      "license": "BSD-2-Clause",
      "dependencies": {
        "semver": "^7.1.1"
      },
      "engines": {
        "node": "^18.17.0 || >=20.5.0"
      }
    },
    "node_modules/npm/node_modules/npm-normalize-package-bin": {
      "version": "4.0.0",
      "dev": true,
      "inBundle": true,
      "license": "ISC",
      "engines": {
        "node": "^18.17.0 || >=20.5.0"
      }
    },
    "node_modules/npm/node_modules/npm-package-arg": {
      "version": "12.0.2",
      "dev": true,
      "inBundle": true,
      "license": "ISC",
      "dependencies": {
        "hosted-git-info": "^8.0.0",
        "proc-log": "^5.0.0",
        "semver": "^7.3.5",
        "validate-npm-package-name": "^6.0.0"
      },
      "engines": {
        "node": "^18.17.0 || >=20.5.0"
      }
    },
    "node_modules/npm/node_modules/npm-packlist": {
      "version": "9.0.0",
      "dev": true,
      "inBundle": true,
      "license": "ISC",
      "dependencies": {
        "ignore-walk": "^7.0.0"
      },
      "engines": {
        "node": "^18.17.0 || >=20.5.0"
      }
    },
    "node_modules/npm/node_modules/npm-pick-manifest": {
      "version": "10.0.0",
      "dev": true,
      "inBundle": true,
      "license": "ISC",
      "dependencies": {
        "npm-install-checks": "^7.1.0",
        "npm-normalize-package-bin": "^4.0.0",
        "npm-package-arg": "^12.0.0",
        "semver": "^7.3.5"
      },
      "engines": {
        "node": "^18.17.0 || >=20.5.0"
      }
    },
    "node_modules/npm/node_modules/npm-profile": {
      "version": "11.0.1",
      "dev": true,
      "inBundle": true,
      "license": "ISC",
      "dependencies": {
        "npm-registry-fetch": "^18.0.0",
        "proc-log": "^5.0.0"
      },
      "engines": {
        "node": "^18.17.0 || >=20.5.0"
      }
    },
    "node_modules/npm/node_modules/npm-registry-fetch": {
      "version": "18.0.2",
      "dev": true,
      "inBundle": true,
      "license": "ISC",
      "dependencies": {
        "@npmcli/redact": "^3.0.0",
        "jsonparse": "^1.3.1",
        "make-fetch-happen": "^14.0.0",
        "minipass": "^7.0.2",
        "minipass-fetch": "^4.0.0",
        "minizlib": "^3.0.1",
        "npm-package-arg": "^12.0.0",
        "proc-log": "^5.0.0"
      },
      "engines": {
        "node": "^18.17.0 || >=20.5.0"
      }
    },
    "node_modules/npm/node_modules/npm-user-validate": {
      "version": "3.0.0",
      "dev": true,
      "inBundle": true,
      "license": "BSD-2-Clause",
      "engines": {
        "node": "^18.17.0 || >=20.5.0"
      }
    },
    "node_modules/npm/node_modules/p-map": {
      "version": "7.0.3",
      "dev": true,
      "inBundle": true,
      "license": "MIT",
      "engines": {
        "node": ">=18"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/npm/node_modules/package-json-from-dist": {
      "version": "1.0.1",
      "dev": true,
      "inBundle": true,
      "license": "BlueOak-1.0.0"
    },
    "node_modules/npm/node_modules/pacote": {
      "version": "19.0.1",
      "dev": true,
      "inBundle": true,
      "license": "ISC",
      "dependencies": {
        "@npmcli/git": "^6.0.0",
        "@npmcli/installed-package-contents": "^3.0.0",
        "@npmcli/package-json": "^6.0.0",
        "@npmcli/promise-spawn": "^8.0.0",
        "@npmcli/run-script": "^9.0.0",
        "cacache": "^19.0.0",
        "fs-minipass": "^3.0.0",
        "minipass": "^7.0.2",
        "npm-package-arg": "^12.0.0",
        "npm-packlist": "^9.0.0",
        "npm-pick-manifest": "^10.0.0",
        "npm-registry-fetch": "^18.0.0",
        "proc-log": "^5.0.0",
        "promise-retry": "^2.0.1",
        "sigstore": "^3.0.0",
        "ssri": "^12.0.0",
        "tar": "^6.1.11"
      },
      "bin": {
        "pacote": "bin/index.js"
      },
      "engines": {
        "node": "^18.17.0 || >=20.5.0"
      }
    },
    "node_modules/npm/node_modules/parse-conflict-json": {
      "version": "4.0.0",
      "dev": true,
      "inBundle": true,
      "license": "ISC",
      "dependencies": {
        "json-parse-even-better-errors": "^4.0.0",
        "just-diff": "^6.0.0",
        "just-diff-apply": "^5.2.0"
      },
      "engines": {
        "node": "^18.17.0 || >=20.5.0"
      }
    },
    "node_modules/npm/node_modules/path-key": {
      "version": "3.1.1",
      "dev": true,
      "inBundle": true,
      "license": "MIT",
      "engines": {
        "node": ">=8"
      }
    },
    "node_modules/npm/node_modules/path-scurry": {
      "version": "1.11.1",
      "dev": true,
      "inBundle": true,
      "license": "BlueOak-1.0.0",
      "dependencies": {
        "lru-cache": "^10.2.0",
        "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
      },
      "engines": {
        "node": ">=16 || 14 >=14.18"
      },
      "funding": {
        "url": "https://github.com/sponsors/isaacs"
      }
    },
    "node_modules/npm/node_modules/postcss-selector-parser": {
      "version": "7.1.0",
      "dev": true,
      "inBundle": true,
      "license": "MIT",
      "dependencies": {
        "cssesc": "^3.0.0",
        "util-deprecate": "^1.0.2"
      },
      "engines": {
        "node": ">=4"
      }
    },
    "node_modules/npm/node_modules/proc-log": {
      "version": "5.0.0",
      "dev": true,
      "inBundle": true,
      "license": "ISC",
      "engines": {
        "node": "^18.17.0 || >=20.5.0"
      }
    },
    "node_modules/npm/node_modules/proggy": {
      "version": "3.0.0",
      "dev": true,
      "inBundle": true,
      "license": "ISC",
      "engines": {
        "node": "^18.17.0 || >=20.5.0"
      }
    },
    "node_modules/npm/node_modules/promise-all-reject-late": {
      "version": "1.0.1",
      "dev": true,
      "inBundle": true,
      "license": "ISC",
      "funding": {
        "url": "https://github.com/sponsors/isaacs"
      }
    },
    "node_modules/npm/node_modules/promise-call-limit": {
      "version": "3.0.2",
      "dev": true,
      "inBundle": true,
      "license": "ISC",
      "funding": {
        "url": "https://github.com/sponsors/isaacs"
      }
    },
    "node_modules/npm/node_modules/promise-retry": {
      "version": "2.0.1",
      "dev": true,
      "inBundle": true,
      "license": "MIT",
      "dependencies": {
        "err-code": "^2.0.2",
        "retry": "^0.12.0"
      },
      "engines": {
        "node": ">=10"
      }
    },
    "node_modules/npm/node_modules/promzard": {
      "version": "2.0.0",
      "dev": true,
      "inBundle": true,
      "license": "ISC",
      "dependencies": {
        "read": "^4.0.0"
      },
      "engines": {
        "node": "^18.17.0 || >=20.5.0"
      }
    },
    "node_modules/npm/node_modules/qrcode-terminal": {
      "version": "0.12.0",
      "dev": true,
      "inBundle": true,
      "bin": {
        "qrcode-terminal": "bin/qrcode-terminal.js"
      }
    },
    "node_modules/npm/node_modules/read": {
      "version": "4.1.0",
      "dev": true,
      "inBundle": true,
      "license": "ISC",
      "dependencies": {
        "mute-stream": "^2.0.0"
      },
      "engines": {
        "node": "^18.17.0 || >=20.5.0"
      }
    },
    "node_modules/npm/node_modules/read-cmd-shim": {
      "version": "5.0.0",
      "dev": true,
      "inBundle": true,
      "license": "ISC",
      "engines": {
        "node": "^18.17.0 || >=20.5.0"
      }
    },
    "node_modules/npm/node_modules/read-package-json-fast": {
      "version": "4.0.0",
      "dev": true,
      "inBundle": true,
      "license": "ISC",
      "dependencies": {
        "json-parse-even-better-errors": "^4.0.0",
        "npm-normalize-package-bin": "^4.0.0"
      },
      "engines": {
        "node": "^18.17.0 || >=20.5.0"
      }
    },
    "node_modules/npm/node_modules/retry": {
      "version": "0.12.0",
      "dev": true,
      "inBundle": true,
      "license": "MIT",
      "engines": {
        "node": ">= 4"
      }
    },
    "node_modules/npm/node_modules/safer-buffer": {
      "version": "2.1.2",
      "dev": true,
      "inBundle": true,
      "license": "MIT",
      "optional": true
    },
    "node_modules/npm/node_modules/semver": {
      "version": "7.7.2",
      "dev": true,
      "inBundle": true,
      "license": "ISC",
      "bin": {
        "semver": "bin/semver.js"
      },
      "engines": {
        "node": ">=10"
      }
    },
    "node_modules/npm/node_modules/shebang-command": {
      "version": "2.0.0",
      "dev": true,
      "inBundle": true,
      "license": "MIT",
      "dependencies": {
        "shebang-regex": "^3.0.0"
      },
      "engines": {
        "node": ">=8"
      }
    },
    "node_modules/npm/node_modules/shebang-regex": {
      "version": "3.0.0",
      "dev": true,
      "inBundle": true,
      "license": "MIT",
      "engines": {
        "node": ">=8"
      }
    },
    "node_modules/npm/node_modules/signal-exit": {
      "version": "4.1.0",
      "dev": true,
      "inBundle": true,
      "license": "ISC",
      "engines": {
        "node": ">=14"
      },
      "funding": {
        "url": "https://github.com/sponsors/isaacs"
      }
    },
    "node_modules/npm/node_modules/sigstore": {
      "version": "3.1.0",
      "dev": true,
      "inBundle": true,
      "license": "Apache-2.0",
      "dependencies": {
        "@sigstore/bundle": "^3.1.0",
        "@sigstore/core": "^2.0.0",
        "@sigstore/protobuf-specs": "^0.4.0",
        "@sigstore/sign": "^3.1.0",
        "@sigstore/tuf": "^3.1.0",
        "@sigstore/verify": "^2.1.0"
      },
      "engines": {
        "node": "^18.17.0 || >=20.5.0"
      }
    },
    "node_modules/npm/node_modules/sigstore/node_modules/@sigstore/bundle": {
      "version": "3.1.0",
      "dev": true,
      "inBundle": true,
      "license": "Apache-2.0",
      "dependencies": {
        "@sigstore/protobuf-specs": "^0.4.0"
      },
      "engines": {
        "node": "^18.17.0 || >=20.5.0"
      }
    },
    "node_modules/npm/node_modules/sigstore/node_modules/@sigstore/core": {
      "version": "2.0.0",
      "dev": true,
      "inBundle": true,
      "license": "Apache-2.0",
      "engines": {
        "node": "^18.17.0 || >=20.5.0"
      }
    },
    "node_modules/npm/node_modules/sigstore/node_modules/@sigstore/sign": {
      "version": "3.1.0",
      "dev": true,
      "inBundle": true,
      "license": "Apache-2.0",
      "dependencies": {
        "@sigstore/bundle": "^3.1.0",
        "@sigstore/core": "^2.0.0",
        "@sigstore/protobuf-specs": "^0.4.0",
        "make-fetch-happen": "^14.0.2",
        "proc-log": "^5.0.0",
        "promise-retry": "^2.0.1"
      },
      "engines": {
        "node": "^18.17.0 || >=20.5.0"
      }
    },
    "node_modules/npm/node_modules/sigstore/node_modules/@sigstore/verify": {
      "version": "2.1.1",
      "dev": true,
      "inBundle": true,
      "license": "Apache-2.0",
      "dependencies": {
        "@sigstore/bundle": "^3.1.0",
        "@sigstore/core": "^2.0.0",
        "@sigstore/protobuf-specs": "^0.4.1"
      },
      "engines": {
        "node": "^18.17.0 || >=20.5.0"
      }
    },
    "node_modules/npm/node_modules/smart-buffer": {
      "version": "4.2.0",
      "dev": true,
      "inBundle": true,
      "license": "MIT",
      "engines": {
        "node": ">= 6.0.0",
        "npm": ">= 3.0.0"
      }
    },
    "node_modules/npm/node_modules/socks": {
      "version": "2.8.5",
      "dev": true,
      "inBundle": true,
      "license": "MIT",
      "dependencies": {
        "ip-address": "^9.0.5",
        "smart-buffer": "^4.2.0"
      },
      "engines": {
        "node": ">= 10.0.0",
        "npm": ">= 3.0.0"
      }
    },
    "node_modules/npm/node_modules/socks-proxy-agent": {
      "version": "8.0.5",
      "dev": true,
      "inBundle": true,
      "license": "MIT",
      "dependencies": {
        "agent-base": "^7.1.2",
        "debug": "^4.3.4",
        "socks": "^2.8.3"
      },
      "engines": {
        "node": ">= 14"
      }
    },
    "node_modules/npm/node_modules/spdx-correct": {
      "version": "3.2.0",
      "dev": true,
      "inBundle": true,
      "license": "Apache-2.0",
      "dependencies": {
        "spdx-expression-parse": "^3.0.0",
        "spdx-license-ids": "^3.0.0"
      }
    },
    "node_modules/npm/node_modules/spdx-correct/node_modules/spdx-expression-parse": {
      "version": "3.0.1",
      "dev": true,
      "inBundle": true,
      "license": "MIT",
      "dependencies": {
        "spdx-exceptions": "^2.1.0",
        "spdx-license-ids": "^3.0.0"
      }
    },
    "node_modules/npm/node_modules/spdx-exceptions": {
      "version": "2.5.0",
      "dev": true,
      "inBundle": true,
      "license": "CC-BY-3.0"
    },
    "node_modules/npm/node_modules/spdx-expression-parse": {
      "version": "4.0.0",
      "dev": true,
      "inBundle": true,
      "license": "MIT",
      "dependencies": {
        "spdx-exceptions": "^2.1.0",
        "spdx-license-ids": "^3.0.0"
      }
    },
    "node_modules/npm/node_modules/spdx-license-ids": {
      "version": "3.0.21",
      "dev": true,
      "inBundle": true,
      "license": "CC0-1.0"
    },
    "node_modules/npm/node_modules/sprintf-js": {
      "version": "1.1.3",
      "dev": true,
      "inBundle": true,
      "license": "BSD-3-Clause"
    },
    "node_modules/npm/node_modules/ssri": {
      "version": "12.0.0",
      "dev": true,
      "inBundle": true,
      "license": "ISC",
      "dependencies": {
        "minipass": "^7.0.3"
      },
      "engines": {
        "node": "^18.17.0 || >=20.5.0"
      }
    },
    "node_modules/npm/node_modules/string-width": {
      "version": "4.2.3",
      "dev": true,
      "inBundle": 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/npm/node_modules/string-width-cjs": {
      "name": "string-width",
      "version": "4.2.3",
      "dev": true,
      "inBundle": 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/npm/node_modules/strip-ansi": {
      "version": "6.0.1",
      "dev": true,
      "inBundle": true,
      "license": "MIT",
      "dependencies": {
        "ansi-regex": "^5.0.1"
      },
      "engines": {
        "node": ">=8"
      }
    },
    "node_modules/npm/node_modules/strip-ansi-cjs": {
      "name": "strip-ansi",
      "version": "6.0.1",
      "dev": true,
      "inBundle": true,
      "license": "MIT",
      "dependencies": {
        "ansi-regex": "^5.0.1"
      },
      "engines": {
        "node": ">=8"
      }
    },
    "node_modules/npm/node_modules/supports-color": {
      "version": "9.4.0",
      "dev": true,
      "inBundle": true,
      "license": "MIT",
      "engines": {
        "node": ">=12"
      },
      "funding": {
        "url": "https://github.com/chalk/supports-color?sponsor=1"
      }
    },
    "node_modules/npm/node_modules/tar": {
      "version": "6.2.1",
      "dev": true,
      "inBundle": true,
      "license": "ISC",
      "dependencies": {
        "chownr": "^2.0.0",
        "fs-minipass": "^2.0.0",
        "minipass": "^5.0.0",
        "minizlib": "^2.1.1",
        "mkdirp": "^1.0.3",
        "yallist": "^4.0.0"
      },
      "engines": {
        "node": ">=10"
      }
    },
    "node_modules/npm/node_modules/tar/node_modules/fs-minipass": {
      "version": "2.1.0",
      "dev": true,
      "inBundle": true,
      "license": "ISC",
      "dependencies": {
        "minipass": "^3.0.0"
      },
      "engines": {
        "node": ">= 8"
      }
    },
    "node_modules/npm/node_modules/tar/node_modules/fs-minipass/node_modules/minipass": {
      "version": "3.3.6",
      "dev": true,
      "inBundle": true,
      "license": "ISC",
      "dependencies": {
        "yallist": "^4.0.0"
      },
      "engines": {
        "node": ">=8"
      }
    },
    "node_modules/npm/node_modules/tar/node_modules/minipass": {
      "version": "5.0.0",
      "dev": true,
      "inBundle": true,
      "license": "ISC",
      "engines": {
        "node": ">=8"
      }
    },
    "node_modules/npm/node_modules/tar/node_modules/minizlib": {
      "version": "2.1.2",
      "dev": true,
      "inBundle": true,
      "license": "MIT",
      "dependencies": {
        "minipass": "^3.0.0",
        "yallist": "^4.0.0"
      },
      "engines": {
        "node": ">= 8"
      }
    },
    "node_modules/npm/node_modules/tar/node_modules/minizlib/node_modules/minipass": {
      "version": "3.3.6",
      "dev": true,
      "inBundle": true,
      "license": "ISC",
      "dependencies": {
        "yallist": "^4.0.0"
      },
      "engines": {
        "node": ">=8"
      }
    },
    "node_modules/npm/node_modules/text-table": {
      "version": "0.2.0",
      "dev": true,
      "inBundle": true,
      "license": "MIT"
    },
    "node_modules/npm/node_modules/tiny-relative-date": {
      "version": "1.3.0",
      "dev": true,
      "inBundle": true,
      "license": "MIT"
    },
    "node_modules/npm/node_modules/tinyglobby": {
      "version": "0.2.14",
      "dev": true,
      "inBundle": true,
      "license": "MIT",
      "dependencies": {
        "fdir": "^6.4.4",
        "picomatch": "^4.0.2"
      },
      "engines": {
        "node": ">=12.0.0"
      },
      "funding": {
        "url": "https://github.com/sponsors/SuperchupuDev"
      }
    },
    "node_modules/npm/node_modules/tinyglobby/node_modules/fdir": {
      "version": "6.4.6",
      "dev": true,
      "inBundle": true,
      "license": "MIT",
      "peerDependencies": {
        "picomatch": "^3 || ^4"
      },
      "peerDependenciesMeta": {
        "picomatch": {
          "optional": true
        }
      }
    },
    "node_modules/npm/node_modules/tinyglobby/node_modules/picomatch": {
      "version": "4.0.2",
      "dev": true,
      "inBundle": true,
      "license": "MIT",
      "engines": {
        "node": ">=12"
      },
      "funding": {
        "url": "https://github.com/sponsors/jonschlinkert"
      }
    },
    "node_modules/npm/node_modules/treeverse": {
      "version": "3.0.0",
      "dev": true,
      "inBundle": true,
      "license": "ISC",
      "engines": {
        "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
      }
    },
    "node_modules/npm/node_modules/tuf-js": {
      "version": "3.0.1",
      "dev": true,
      "inBundle": true,
      "license": "MIT",
      "dependencies": {
        "@tufjs/models": "3.0.1",
        "debug": "^4.3.6",
        "make-fetch-happen": "^14.0.1"
      },
      "engines": {
        "node": "^18.17.0 || >=20.5.0"
      }
    },
    "node_modules/npm/node_modules/tuf-js/node_modules/@tufjs/models": {
      "version": "3.0.1",
      "dev": true,
      "inBundle": true,
      "license": "MIT",
      "dependencies": {
        "@tufjs/canonical-json": "2.0.0",
        "minimatch": "^9.0.5"
      },
      "engines": {
        "node": "^18.17.0 || >=20.5.0"
      }
    },
    "node_modules/npm/node_modules/unique-filename": {
      "version": "4.0.0",
      "dev": true,
      "inBundle": true,
      "license": "ISC",
      "dependencies": {
        "unique-slug": "^5.0.0"
      },
      "engines": {
        "node": "^18.17.0 || >=20.5.0"
      }
    },
    "node_modules/npm/node_modules/unique-slug": {
      "version": "5.0.0",
      "dev": true,
      "inBundle": true,
      "license": "ISC",
      "dependencies": {
        "imurmurhash": "^0.1.4"
      },
      "engines": {
        "node": "^18.17.0 || >=20.5.0"
      }
    },
    "node_modules/npm/node_modules/util-deprecate": {
      "version": "1.0.2",
      "dev": true,
      "inBundle": true,
      "license": "MIT"
    },
    "node_modules/npm/node_modules/validate-npm-package-license": {
      "version": "3.0.4",
      "dev": true,
      "inBundle": true,
      "license": "Apache-2.0",
      "dependencies": {
        "spdx-correct": "^3.0.0",
        "spdx-expression-parse": "^3.0.0"
      }
    },
    "node_modules/npm/node_modules/validate-npm-package-license/node_modules/spdx-expression-parse": {
      "version": "3.0.1",
      "dev": true,
      "inBundle": true,
      "license": "MIT",
      "dependencies": {
        "spdx-exceptions": "^2.1.0",
        "spdx-license-ids": "^3.0.0"
      }
    },
    "node_modules/npm/node_modules/validate-npm-package-name": {
      "version": "6.0.1",
      "dev": true,
      "inBundle": true,
      "license": "ISC",
      "engines": {
        "node": "^18.17.0 || >=20.5.0"
      }
    },
    "node_modules/npm/node_modules/walk-up-path": {
      "version": "3.0.1",
      "dev": true,
      "inBundle": true,
      "license": "ISC"
    },
    "node_modules/npm/node_modules/which": {
      "version": "5.0.0",
      "dev": true,
      "inBundle": true,
      "license": "ISC",
      "dependencies": {
        "isexe": "^3.1.1"
      },
      "bin": {
        "node-which": "bin/which.js"
      },
      "engines": {
        "node": "^18.17.0 || >=20.5.0"
      }
    },
    "node_modules/npm/node_modules/which/node_modules/isexe": {
      "version": "3.1.1",
      "dev": true,
      "inBundle": true,
      "license": "ISC",
      "engines": {
        "node": ">=16"
      }
    },
    "node_modules/npm/node_modules/wrap-ansi": {
      "version": "8.1.0",
      "dev": true,
      "inBundle": true,
      "license": "MIT",
      "dependencies": {
        "ansi-styles": "^6.1.0",
        "string-width": "^5.0.1",
        "strip-ansi": "^7.0.1"
      },
      "engines": {
        "node": ">=12"
      },
      "funding": {
        "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
      }
    },
    "node_modules/npm/node_modules/wrap-ansi-cjs": {
      "name": "wrap-ansi",
      "version": "7.0.0",
      "dev": true,
      "inBundle": 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/npm/node_modules/wrap-ansi-cjs/node_modules/ansi-styles": {
      "version": "4.3.0",
      "dev": true,
      "inBundle": true,
      "license": "MIT",
      "dependencies": {
        "color-convert": "^2.0.1"
      },
      "engines": {
        "node": ">=8"
      },
      "funding": {
        "url": "https://github.com/chalk/ansi-styles?sponsor=1"
      }
    },
    "node_modules/npm/node_modules/wrap-ansi/node_modules/ansi-regex": {
      "version": "6.1.0",
      "dev": true,
      "inBundle": true,
      "license": "MIT",
      "engines": {
        "node": ">=12"
      },
      "funding": {
        "url": "https://github.com/chalk/ansi-regex?sponsor=1"
      }
    },
    "node_modules/npm/node_modules/wrap-ansi/node_modules/emoji-regex": {
      "version": "9.2.2",
      "dev": true,
      "inBundle": true,
      "license": "MIT"
    },
    "node_modules/npm/node_modules/wrap-ansi/node_modules/string-width": {
      "version": "5.1.2",
      "dev": true,
      "inBundle": true,
      "license": "MIT",
      "dependencies": {
        "eastasianwidth": "^0.2.0",
        "emoji-regex": "^9.2.2",
        "strip-ansi": "^7.0.1"
      },
      "engines": {
        "node": ">=12"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/npm/node_modules/wrap-ansi/node_modules/strip-ansi": {
      "version": "7.1.0",
      "dev": true,
      "inBundle": true,
      "license": "MIT",
      "dependencies": {
        "ansi-regex": "^6.0.1"
      },
      "engines": {
        "node": ">=12"
      },
      "funding": {
        "url": "https://github.com/chalk/strip-ansi?sponsor=1"
      }
    },
    "node_modules/npm/node_modules/write-file-atomic": {
      "version": "6.0.0",
      "dev": true,
      "inBundle": true,
      "license": "ISC",
      "dependencies": {
        "imurmurhash": "^0.1.4",
        "signal-exit": "^4.0.1"
      },
      "engines": {
        "node": "^18.17.0 || >=20.5.0"
      }
    },
    "node_modules/npm/node_modules/yallist": {
      "version": "4.0.0",
      "dev": true,
      "inBundle": true,
      "license": "ISC"
    },
    "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,
      "engines": {
        "node": ">=0.10.0"
      }
    },
    "node_modules/onetime": {
      "version": "5.1.2",
      "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
      "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
      "dev": true,
      "dependencies": {
        "mimic-fn": "^2.1.0"
      },
      "engines": {
        "node": ">=6"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/p-each-series": {
      "version": "3.0.0",
      "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-3.0.0.tgz",
      "integrity": "sha512-lastgtAdoH9YaLyDa5i5z64q+kzOcQHsQ5SsZJD3q0VEyI8mq872S3geuNbRUQLVAE9siMfgKrpj7MloKFHruw==",
      "dev": true,
      "engines": {
        "node": ">=12"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/p-filter": {
      "version": "4.1.0",
      "resolved": "https://registry.npmjs.org/p-filter/-/p-filter-4.1.0.tgz",
      "integrity": "sha512-37/tPdZ3oJwHaS3gNJdenCDB3Tz26i9sjhnguBtvN0vYlRIiDNnvTWkuh+0hETV9rLPdJ3rlL3yVOYPIAnM8rw==",
      "dev": true,
      "dependencies": {
        "p-map": "^7.0.1"
      },
      "engines": {
        "node": ">=18"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/p-is-promise": {
      "version": "3.0.0",
      "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-3.0.0.tgz",
      "integrity": "sha512-Wo8VsW4IRQSKVXsJCn7TomUaVtyfjVDn3nUP7kE967BQk0CwFpdbZs0X0uk5sW9mkBa9eNM7hCMaG93WUAwxYQ==",
      "dev": true,
      "engines": {
        "node": ">=8"
      }
    },
    "node_modules/p-limit": {
      "version": "4.0.0",
      "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz",
      "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==",
      "dev": true,
      "dependencies": {
        "yocto-queue": "^1.0.0"
      },
      "engines": {
        "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/p-locate": {
      "version": "6.0.0",
      "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz",
      "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==",
      "dev": true,
      "dependencies": {
        "p-limit": "^4.0.0"
      },
      "engines": {
        "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/p-map": {
      "version": "7.0.1",
      "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.1.tgz",
      "integrity": "sha512-2wnaR0XL/FDOj+TgpDuRb2KTjLnu3Fma6b1ZUwGY7LcqenMcvP/YFpjpbPKY6WVGsbuJZRuoUz8iPrt8ORnAFw==",
      "dev": true,
      "engines": {
        "node": ">=18"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/p-reduce": {
      "version": "2.1.0",
      "resolved": "https://registry.npmjs.org/p-reduce/-/p-reduce-2.1.0.tgz",
      "integrity": "sha512-2USApvnsutq8uoxZBGbbWM0JIYLiEMJ9RlaN7fAzVNb9OZN0SHjjTTfIcb667XynS5Y1VhwDJVDa72TnPzAYWw==",
      "dev": true,
      "engines": {
        "node": ">=8"
      }
    },
    "node_modules/p-try": {
      "version": "1.0.0",
      "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz",
      "integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==",
      "dev": true,
      "engines": {
        "node": ">=4"
      }
    },
    "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,
      "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,
      "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,
      "engines": {
        "node": ">=18"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/parse5": {
      "version": "5.1.1",
      "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz",
      "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==",
      "dev": true
    },
    "node_modules/parse5-htmlparser2-tree-adapter": {
      "version": "6.0.1",
      "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz",
      "integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==",
      "dev": true,
      "dependencies": {
        "parse5": "^6.0.1"
      }
    },
    "node_modules/parse5-htmlparser2-tree-adapter/node_modules/parse5": {
      "version": "6.0.1",
      "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz",
      "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==",
      "dev": true
    },
    "node_modules/path-exists": {
      "version": "5.0.0",
      "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz",
      "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==",
      "dev": true,
      "engines": {
        "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
      }
    },
    "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,
      "engines": {
        "node": ">=8"
      }
    },
    "node_modules/path-type": {
      "version": "4.0.0",
      "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
      "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
      "dev": true,
      "engines": {
        "node": ">=8"
      }
    },
    "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
    },
    "node_modules/picomatch": {
      "version": "2.3.1",
      "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
      "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
      "dev": true,
      "engines": {
        "node": ">=8.6"
      },
      "funding": {
        "url": "https://github.com/sponsors/jonschlinkert"
      }
    },
    "node_modules/pify": {
      "version": "3.0.0",
      "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
      "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==",
      "dev": true,
      "engines": {
        "node": ">=4"
      }
    },
    "node_modules/pkg-conf": {
      "version": "2.1.0",
      "resolved": "https://registry.npmjs.org/pkg-conf/-/pkg-conf-2.1.0.tgz",
      "integrity": "sha512-C+VUP+8jis7EsQZIhDYmS5qlNtjv2yP4SNtjXK9AP1ZcTRlnSfuumaTnRfYZnYgUUYVIKqL0fRvmUGDV2fmp6g==",
      "dev": true,
      "dependencies": {
        "find-up": "^2.0.0",
        "load-json-file": "^4.0.0"
      },
      "engines": {
        "node": ">=4"
      }
    },
    "node_modules/pkg-conf/node_modules/find-up": {
      "version": "2.1.0",
      "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz",
      "integrity": "sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==",
      "dev": true,
      "dependencies": {
        "locate-path": "^2.0.0"
      },
      "engines": {
        "node": ">=4"
      }
    },
    "node_modules/pkg-conf/node_modules/locate-path": {
      "version": "2.0.0",
      "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz",
      "integrity": "sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==",
      "dev": true,
      "dependencies": {
        "p-locate": "^2.0.0",
        "path-exists": "^3.0.0"
      },
      "engines": {
        "node": ">=4"
      }
    },
    "node_modules/pkg-conf/node_modules/p-limit": {
      "version": "1.3.0",
      "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz",
      "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==",
      "dev": true,
      "dependencies": {
        "p-try": "^1.0.0"
      },
      "engines": {
        "node": ">=4"
      }
    },
    "node_modules/pkg-conf/node_modules/p-locate": {
      "version": "2.0.0",
      "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz",
      "integrity": "sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==",
      "dev": true,
      "dependencies": {
        "p-limit": "^1.1.0"
      },
      "engines": {
        "node": ">=4"
      }
    },
    "node_modules/pkg-conf/node_modules/path-exists": {
      "version": "3.0.0",
      "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
      "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==",
      "dev": true,
      "engines": {
        "node": ">=4"
      }
    },
    "node_modules/pretty-ms": {
      "version": "9.2.0",
      "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.2.0.tgz",
      "integrity": "sha512-4yf0QO/sllf/1zbZWYnvWw3NxCQwLXKzIj0G849LSufP15BXKM0rbD2Z3wVnkMfjdn/CB0Dpp444gYAACdsplg==",
      "dev": true,
      "dependencies": {
        "parse-ms": "^4.0.0"
      },
      "engines": {
        "node": ">=18"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/process-nextick-args": {
      "version": "2.0.1",
      "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
      "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
      "dev": true
    },
    "node_modules/proto-list": {
      "version": "1.2.4",
      "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz",
      "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==",
      "dev": true
    },
    "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"
        }
      ]
    },
    "node_modules/rc": {
      "version": "1.2.8",
      "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
      "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
      "dev": true,
      "dependencies": {
        "deep-extend": "^0.6.0",
        "ini": "~1.3.0",
        "minimist": "^1.2.0",
        "strip-json-comments": "~2.0.1"
      },
      "bin": {
        "rc": "cli.js"
      }
    },
    "node_modules/read-package-up": {
      "version": "11.0.0",
      "resolved": "https://registry.npmjs.org/read-package-up/-/read-package-up-11.0.0.tgz",
      "integrity": "sha512-MbgfoNPANMdb4oRBNg5eqLbB2t2r+o5Ua1pNt8BqGp4I0FJZhuVSOj3PaBPni4azWuSzEdNn2evevzVmEk1ohQ==",
      "dev": true,
      "dependencies": {
        "find-up-simple": "^1.0.0",
        "read-pkg": "^9.0.0",
        "type-fest": "^4.6.0"
      },
      "engines": {
        "node": ">=18"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/read-pkg": {
      "version": "9.0.1",
      "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-9.0.1.tgz",
      "integrity": "sha512-9viLL4/n1BJUCT1NXVTdS1jtm80yDEgR5T4yCelII49Mbj0v1rZdKqj7zCiYdbB0CuCgdrvHcNogAKTFPBocFA==",
      "dev": true,
      "dependencies": {
        "@types/normalize-package-data": "^2.4.3",
        "normalize-package-data": "^6.0.0",
        "parse-json": "^8.0.0",
        "type-fest": "^4.6.0",
        "unicorn-magic": "^0.1.0"
      },
      "engines": {
        "node": ">=18"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/read-pkg-up": {
      "version": "11.0.0",
      "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-11.0.0.tgz",
      "integrity": "sha512-LOVbvF1Q0SZdjClSefZ0Nz5z8u+tIE7mV5NibzmE9VYmDe9CaBbAVtz1veOSZbofrdsilxuDAYnFenukZVp8/Q==",
      "deprecated": "Renamed to read-package-up",
      "dev": true,
      "dependencies": {
        "find-up-simple": "^1.0.0",
        "read-pkg": "^9.0.0",
        "type-fest": "^4.6.0"
      },
      "engines": {
        "node": ">=18"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/read-pkg/node_modules/parse-json": {
      "version": "8.1.0",
      "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-8.1.0.tgz",
      "integrity": "sha512-rum1bPifK5SSar35Z6EKZuYPJx85pkNaFrxBK3mwdfSJ1/WKbYrjoW/zTPSjRRamfmVX1ACBIdFAO0VRErW/EA==",
      "dev": true,
      "dependencies": {
        "@babel/code-frame": "^7.22.13",
        "index-to-position": "^0.1.2",
        "type-fest": "^4.7.1"
      },
      "engines": {
        "node": ">=18"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/readable-stream": {
      "version": "2.3.8",
      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
      "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
      "dev": true,
      "dependencies": {
        "core-util-is": "~1.0.0",
        "inherits": "~2.0.3",
        "isarray": "~1.0.0",
        "process-nextick-args": "~2.0.0",
        "safe-buffer": "~5.1.1",
        "string_decoder": "~1.1.1",
        "util-deprecate": "~1.0.1"
      }
    },
    "node_modules/registry-auth-token": {
      "version": "5.0.1",
      "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-5.0.1.tgz",
      "integrity": "sha512-UfxVOj8seK1yaIOiieV4FIP01vfBDLsY0H9sQzi9EbbUdJiuuBjJgLa1DpImXMNPnVkBD4eVxTEXcrZA6kfpJA==",
      "dev": true,
      "dependencies": {
        "@pnpm/npm-conf": "^1.0.4"
      },
      "engines": {
        "node": ">=14"
      }
    },
    "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,
      "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,
      "engines": {
        "node": ">=0.10.0"
      }
    },
    "node_modules/resolve-from": {
      "version": "5.0.0",
      "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",
      "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==",
      "dev": true,
      "engines": {
        "node": ">=8"
      }
    },
    "node_modules/reusify": {
      "version": "1.0.4",
      "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
      "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
      "dev": true,
      "engines": {
        "iojs": ">=1.0.0",
        "node": ">=0.10.0"
      }
    },
    "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"
        }
      ],
      "dependencies": {
        "queue-microtask": "^1.2.2"
      }
    },
    "node_modules/safe-buffer": {
      "version": "5.1.2",
      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
      "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
      "dev": true
    },
    "node_modules/semantic-release": {
      "version": "24.2.7",
      "resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-24.2.7.tgz",
      "integrity": "sha512-g7RssbTAbir1k/S7uSwSVZFfFXwpomUB9Oas0+xi9KStSCmeDXcA7rNhiskjLqvUe/Evhx8fVCT16OSa34eM5g==",
      "dev": true,
      "dependencies": {
        "@semantic-release/commit-analyzer": "^13.0.0-beta.1",
        "@semantic-release/error": "^4.0.0",
        "@semantic-release/github": "^11.0.0",
        "@semantic-release/npm": "^12.0.2",
        "@semantic-release/release-notes-generator": "^14.0.0-beta.1",
        "aggregate-error": "^5.0.0",
        "cosmiconfig": "^9.0.0",
        "debug": "^4.0.0",
        "env-ci": "^11.0.0",
        "execa": "^9.0.0",
        "figures": "^6.0.0",
        "find-versions": "^6.0.0",
        "get-stream": "^6.0.0",
        "git-log-parser": "^1.2.0",
        "hook-std": "^3.0.0",
        "hosted-git-info": "^8.0.0",
        "import-from-esm": "^2.0.0",
        "lodash-es": "^4.17.21",
        "marked": "^15.0.0",
        "marked-terminal": "^7.3.0",
        "micromatch": "^4.0.2",
        "p-each-series": "^3.0.0",
        "p-reduce": "^3.0.0",
        "read-package-up": "^11.0.0",
        "resolve-from": "^5.0.0",
        "semver": "^7.3.2",
        "semver-diff": "^4.0.0",
        "signale": "^1.2.1",
        "yargs": "^17.5.1"
      },
      "bin": {
        "semantic-release": "bin/semantic-release.js"
      },
      "engines": {
        "node": ">=20.8.1"
      }
    },
    "node_modules/semantic-release/node_modules/@semantic-release/error": {
      "version": "4.0.0",
      "resolved": "https://registry.npmjs.org/@semantic-release/error/-/error-4.0.0.tgz",
      "integrity": "sha512-mgdxrHTLOjOddRVYIYDo0fR3/v61GNN1YGkfbrjuIKg/uMgCd+Qzo3UAXJ+woLQQpos4pl5Esuw5A7AoNlzjUQ==",
      "dev": true,
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/semantic-release/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,
      "engines": {
        "node": ">=18"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/semantic-release/node_modules/aggregate-error": {
      "version": "5.0.0",
      "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-5.0.0.tgz",
      "integrity": "sha512-gOsf2YwSlleG6IjRYG2A7k0HmBMEo6qVNk9Bp/EaLgAJT5ngH6PXbqa4ItvnEwCm/velL5jAnQgsHsWnjhGmvw==",
      "dev": true,
      "dependencies": {
        "clean-stack": "^5.2.0",
        "indent-string": "^5.0.0"
      },
      "engines": {
        "node": ">=18"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/semantic-release/node_modules/clean-stack": {
      "version": "5.2.0",
      "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-5.2.0.tgz",
      "integrity": "sha512-TyUIUJgdFnCISzG5zu3291TAsE77ddchd0bepon1VVQrKLGKFED4iXFEDQ24mIPdPBbyE16PK3F8MYE1CmcBEQ==",
      "dev": true,
      "dependencies": {
        "escape-string-regexp": "5.0.0"
      },
      "engines": {
        "node": ">=14.16"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/semantic-release/node_modules/execa": {
      "version": "9.6.0",
      "resolved": "https://registry.npmjs.org/execa/-/execa-9.6.0.tgz",
      "integrity": "sha512-jpWzZ1ZhwUmeWRhS7Qv3mhpOhLfwI+uAX4e5fOcXqwMR7EcJ0pj2kV1CVzHVMX/LphnKWD3LObjZCoJ71lKpHw==",
      "dev": true,
      "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/semantic-release/node_modules/execa/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,
      "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/semantic-release/node_modules/hosted-git-info": {
      "version": "8.1.0",
      "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-8.1.0.tgz",
      "integrity": "sha512-Rw/B2DNQaPBICNXEm8balFz9a6WpZrkCGpcWFpy7nCj+NyhSdqXipmfvtmWt9xGfp0wZnBxB+iVpLmQMYt47Tw==",
      "dev": true,
      "dependencies": {
        "lru-cache": "^10.0.1"
      },
      "engines": {
        "node": "^18.17.0 || >=20.5.0"
      }
    },
    "node_modules/semantic-release/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,
      "engines": {
        "node": ">=18.18.0"
      }
    },
    "node_modules/semantic-release/node_modules/import-from-esm": {
      "version": "2.0.0",
      "resolved": "https://registry.npmjs.org/import-from-esm/-/import-from-esm-2.0.0.tgz",
      "integrity": "sha512-YVt14UZCgsX1vZQ3gKjkWVdBdHQ6eu3MPU1TBgL1H5orXe2+jWD006WCPPtOuwlQm10NuzOW5WawiF1Q9veW8g==",
      "dev": true,
      "dependencies": {
        "debug": "^4.3.4",
        "import-meta-resolve": "^4.0.0"
      },
      "engines": {
        "node": ">=18.20"
      }
    },
    "node_modules/semantic-release/node_modules/indent-string": {
      "version": "5.0.0",
      "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz",
      "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==",
      "dev": true,
      "engines": {
        "node": ">=12"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/semantic-release/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,
      "engines": {
        "node": ">=18"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/semantic-release/node_modules/lru-cache": {
      "version": "10.4.3",
      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
      "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
      "dev": true
    },
    "node_modules/semantic-release/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,
      "dependencies": {
        "path-key": "^4.0.0",
        "unicorn-magic": "^0.3.0"
      },
      "engines": {
        "node": ">=18"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/semantic-release/node_modules/p-reduce": {
      "version": "3.0.0",
      "resolved": "https://registry.npmjs.org/p-reduce/-/p-reduce-3.0.0.tgz",
      "integrity": "sha512-xsrIUgI0Kn6iyDYm9StOpOeK29XM1aboGji26+QEortiFST1hGZaUQOLhtEbqHErPpGW/aSz6allwK2qcptp0Q==",
      "dev": true,
      "engines": {
        "node": ">=12"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/semantic-release/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,
      "engines": {
        "node": ">=12"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/semantic-release/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,
      "engines": {
        "node": ">=14"
      },
      "funding": {
        "url": "https://github.com/sponsors/isaacs"
      }
    },
    "node_modules/semantic-release/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,
      "engines": {
        "node": ">=18"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/semantic-release/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,
      "engines": {
        "node": ">=18"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/semver": {
      "version": "7.6.0",
      "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",
      "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==",
      "dev": true,
      "dependencies": {
        "lru-cache": "^6.0.0"
      },
      "bin": {
        "semver": "bin/semver.js"
      },
      "engines": {
        "node": ">=10"
      }
    },
    "node_modules/semver-diff": {
      "version": "4.0.0",
      "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-4.0.0.tgz",
      "integrity": "sha512-0Ju4+6A8iOnpL/Thra7dZsSlOHYAHIeMxfhWQRI1/VLcT3WDBZKKtQt/QkBOsiIN9ZpuvHE6cGZ0x4glCMmfiA==",
      "dev": true,
      "dependencies": {
        "semver": "^7.3.5"
      },
      "engines": {
        "node": ">=12"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/semver-regex": {
      "version": "4.0.5",
      "resolved": "https://registry.npmjs.org/semver-regex/-/semver-regex-4.0.5.tgz",
      "integrity": "sha512-hunMQrEy1T6Jr2uEVjrAIqjwWcQTgOAcIM52C8MY1EZSD3DDNft04XzvYKPqjED65bNVVko0YI38nYeEHCX3yw==",
      "dev": true,
      "engines": {
        "node": ">=12"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "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,
      "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,
      "engines": {
        "node": ">=8"
      }
    },
    "node_modules/signal-exit": {
      "version": "3.0.7",
      "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
      "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
      "dev": true
    },
    "node_modules/signale": {
      "version": "1.4.0",
      "resolved": "https://registry.npmjs.org/signale/-/signale-1.4.0.tgz",
      "integrity": "sha512-iuh+gPf28RkltuJC7W5MRi6XAjTDCAPC/prJUpQoG4vIP3MJZ+GTydVnodXA7pwvTKb2cA0m9OFZW/cdWy/I/w==",
      "dev": true,
      "dependencies": {
        "chalk": "^2.3.2",
        "figures": "^2.0.0",
        "pkg-conf": "^2.1.0"
      },
      "engines": {
        "node": ">=6"
      }
    },
    "node_modules/signale/node_modules/ansi-styles": {
      "version": "3.2.1",
      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
      "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
      "dev": true,
      "dependencies": {
        "color-convert": "^1.9.0"
      },
      "engines": {
        "node": ">=4"
      }
    },
    "node_modules/signale/node_modules/chalk": {
      "version": "2.4.2",
      "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
      "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
      "dev": true,
      "dependencies": {
        "ansi-styles": "^3.2.1",
        "escape-string-regexp": "^1.0.5",
        "supports-color": "^5.3.0"
      },
      "engines": {
        "node": ">=4"
      }
    },
    "node_modules/signale/node_modules/color-convert": {
      "version": "1.9.3",
      "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
      "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
      "dev": true,
      "dependencies": {
        "color-name": "1.1.3"
      }
    },
    "node_modules/signale/node_modules/color-name": {
      "version": "1.1.3",
      "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
      "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
      "dev": true
    },
    "node_modules/signale/node_modules/escape-string-regexp": {
      "version": "1.0.5",
      "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
      "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
      "dev": true,
      "engines": {
        "node": ">=0.8.0"
      }
    },
    "node_modules/signale/node_modules/figures": {
      "version": "2.0.0",
      "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz",
      "integrity": "sha512-Oa2M9atig69ZkfwiApY8F2Yy+tzMbazyvqv21R0NsSC8floSOC09BbT1ITWAdoMGQvJ/aZnR1KMwdx9tvHnTNA==",
      "dev": true,
      "dependencies": {
        "escape-string-regexp": "^1.0.5"
      },
      "engines": {
        "node": ">=4"
      }
    },
    "node_modules/signale/node_modules/has-flag": {
      "version": "3.0.0",
      "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
      "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
      "dev": true,
      "engines": {
        "node": ">=4"
      }
    },
    "node_modules/signale/node_modules/supports-color": {
      "version": "5.5.0",
      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
      "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
      "dev": true,
      "dependencies": {
        "has-flag": "^3.0.0"
      },
      "engines": {
        "node": ">=4"
      }
    },
    "node_modules/skin-tone": {
      "version": "2.0.0",
      "resolved": "https://registry.npmjs.org/skin-tone/-/skin-tone-2.0.0.tgz",
      "integrity": "sha512-kUMbT1oBJCpgrnKoSr0o6wPtvRWT9W9UKvGLwfJYO2WuahZRHOpEyL1ckyMGgMWh0UdpmaoFqKKD29WTomNEGA==",
      "dev": true,
      "dependencies": {
        "unicode-emoji-modifier-base": "^1.0.0"
      },
      "engines": {
        "node": ">=8"
      }
    },
    "node_modules/slash": {
      "version": "5.1.0",
      "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz",
      "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==",
      "dev": true,
      "engines": {
        "node": ">=14.16"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "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,
      "engines": {
        "node": ">=0.10.0"
      }
    },
    "node_modules/spawn-error-forwarder": {
      "version": "1.0.0",
      "resolved": "https://registry.npmjs.org/spawn-error-forwarder/-/spawn-error-forwarder-1.0.0.tgz",
      "integrity": "sha512-gRjMgK5uFjbCvdibeGJuy3I5OYz6VLoVdsOJdA6wV0WlfQVLFueoqMxwwYD9RODdgb6oUIvlRlsyFSiQkMKu0g==",
      "dev": true
    },
    "node_modules/spdx-correct": {
      "version": "3.1.1",
      "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz",
      "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==",
      "dev": true,
      "dependencies": {
        "spdx-expression-parse": "^3.0.0",
        "spdx-license-ids": "^3.0.0"
      }
    },
    "node_modules/spdx-exceptions": {
      "version": "2.3.0",
      "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz",
      "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==",
      "dev": true
    },
    "node_modules/spdx-expression-parse": {
      "version": "3.0.1",
      "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz",
      "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==",
      "dev": true,
      "dependencies": {
        "spdx-exceptions": "^2.1.0",
        "spdx-license-ids": "^3.0.0"
      }
    },
    "node_modules/spdx-license-ids": {
      "version": "3.0.12",
      "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.12.tgz",
      "integrity": "sha512-rr+VVSXtRhO4OHbXUiAF7xW3Bo9DuuF6C5jH+q/x15j2jniycgKbxU09Hr0WqlSLUs4i4ltHGXqTe7VHclYWyA==",
      "dev": true
    },
    "node_modules/split2": {
      "version": "4.2.0",
      "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
      "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==",
      "dev": true,
      "engines": {
        "node": ">= 10.x"
      }
    },
    "node_modules/stream-combiner2": {
      "version": "1.1.1",
      "resolved": "https://registry.npmjs.org/stream-combiner2/-/stream-combiner2-1.1.1.tgz",
      "integrity": "sha512-3PnJbYgS56AeWgtKF5jtJRT6uFJe56Z0Hc5Ngg/6sI6rIt8iiMBTa9cvdyFfpMQjaVHr8dusbNeFGIIonxOvKw==",
      "dev": true,
      "dependencies": {
        "duplexer2": "~0.1.0",
        "readable-stream": "^2.0.2"
      }
    },
    "node_modules/string_decoder": {
      "version": "1.1.1",
      "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
      "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
      "dev": true,
      "dependencies": {
        "safe-buffer": "~5.1.0"
      }
    },
    "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,
      "dependencies": {
        "emoji-regex": "^8.0.0",
        "is-fullwidth-code-point": "^3.0.0",
        "strip-ansi": "^6.0.1"
      },
      "engines": {
        "node": ">=8"
      }
    },
    "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,
      "dependencies": {
        "ansi-regex": "^5.0.1"
      },
      "engines": {
        "node": ">=8"
      }
    },
    "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,
      "engines": {
        "node": ">=4"
      }
    },
    "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,
      "engines": {
        "node": ">=6"
      }
    },
    "node_modules/strip-json-comments": {
      "version": "2.0.1",
      "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
      "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==",
      "dev": true,
      "engines": {
        "node": ">=0.10.0"
      }
    },
    "node_modules/super-regex": {
      "version": "1.0.0",
      "resolved": "https://registry.npmjs.org/super-regex/-/super-regex-1.0.0.tgz",
      "integrity": "sha512-CY8u7DtbvucKuquCmOFEKhr9Besln7n9uN8eFbwcoGYWXOMW07u2o8njWaiXt11ylS3qoGF55pILjRmPlbodyg==",
      "dev": true,
      "dependencies": {
        "function-timeout": "^1.0.1",
        "time-span": "^5.1.0"
      },
      "engines": {
        "node": ">=18"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/supports-color": {
      "version": "7.2.0",
      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
      "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
      "dev": true,
      "dependencies": {
        "has-flag": "^4.0.0"
      },
      "engines": {
        "node": ">=8"
      }
    },
    "node_modules/supports-hyperlinks": {
      "version": "3.2.0",
      "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-3.2.0.tgz",
      "integrity": "sha512-zFObLMyZeEwzAoKCyu1B91U79K2t7ApXuQfo8OuxwXLDgcKxuwM+YvcbIhm6QWqz7mHUH1TVytR1PwVVjEuMig==",
      "dev": true,
      "dependencies": {
        "has-flag": "^4.0.0",
        "supports-color": "^7.0.0"
      },
      "engines": {
        "node": ">=14.18"
      },
      "funding": {
        "url": "https://github.com/chalk/supports-hyperlinks?sponsor=1"
      }
    },
    "node_modules/temp-dir": {
      "version": "2.0.0",
      "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz",
      "integrity": "sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==",
      "dev": true,
      "engines": {
        "node": ">=8"
      }
    },
    "node_modules/tempy": {
      "version": "3.0.0",
      "resolved": "https://registry.npmjs.org/tempy/-/tempy-3.0.0.tgz",
      "integrity": "sha512-B2I9X7+o2wOaW4r/CWMkpOO9mdiTRCxXNgob6iGvPmfPWgH/KyUD6Uy5crtWBxIBe3YrNZKR2lSzv1JJKWD4vA==",
      "dev": true,
      "dependencies": {
        "is-stream": "^3.0.0",
        "temp-dir": "^2.0.0",
        "type-fest": "^2.12.2",
        "unique-string": "^3.0.0"
      },
      "engines": {
        "node": ">=14.16"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/tempy/node_modules/is-stream": {
      "version": "3.0.0",
      "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz",
      "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==",
      "dev": true,
      "engines": {
        "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/tempy/node_modules/type-fest": {
      "version": "2.19.0",
      "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz",
      "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==",
      "dev": true,
      "engines": {
        "node": ">=12.20"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/text-extensions": {
      "version": "2.4.0",
      "resolved": "https://registry.npmjs.org/text-extensions/-/text-extensions-2.4.0.tgz",
      "integrity": "sha512-te/NtwBwfiNRLf9Ijqx3T0nlqZiQ2XrrtBvu+cLL8ZRrGkO0NHTug8MYFKyoSrv/sHTaSKfilUkizV6XhxMJ3g==",
      "dev": true,
      "engines": {
        "node": ">=8"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/thenify": {
      "version": "3.3.1",
      "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
      "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==",
      "dev": true,
      "dependencies": {
        "any-promise": "^1.0.0"
      }
    },
    "node_modules/thenify-all": {
      "version": "1.6.0",
      "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz",
      "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==",
      "dev": true,
      "dependencies": {
        "thenify": ">= 3.1.0 < 4"
      },
      "engines": {
        "node": ">=0.8"
      }
    },
    "node_modules/through": {
      "version": "2.3.8",
      "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
      "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==",
      "dev": true
    },
    "node_modules/time-span": {
      "version": "5.1.0",
      "resolved": "https://registry.npmjs.org/time-span/-/time-span-5.1.0.tgz",
      "integrity": "sha512-75voc/9G4rDIJleOo4jPvN4/YC4GRZrY8yy1uU4lwrB3XEQbWve8zXoO5No4eFrGcTAMYyoY67p8jRQdtA1HbA==",
      "dev": true,
      "dependencies": {
        "convert-hrtime": "^5.0.0"
      },
      "engines": {
        "node": ">=12"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/tinyexec": {
      "version": "1.0.1",
      "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.1.tgz",
      "integrity": "sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==",
      "dev": true
    },
    "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,
      "dependencies": {
        "is-number": "^7.0.0"
      },
      "engines": {
        "node": ">=8.0"
      }
    },
    "node_modules/traverse": {
      "version": "0.6.7",
      "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.7.tgz",
      "integrity": "sha512-/y956gpUo9ZNCb99YjxG7OaslxZWHfCHAUUfshwqOXmxUIvqLjVO581BT+gM59+QV9tFe6/CGG53tsA1Y7RSdg==",
      "dev": true,
      "funding": {
        "url": "https://github.com/sponsors/ljharb"
      }
    },
    "node_modules/type-fest": {
      "version": "4.41.0",
      "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz",
      "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==",
      "dev": true,
      "engines": {
        "node": ">=16"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/typescript": {
      "version": "5.2.2",
      "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz",
      "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==",
      "dev": true,
      "peer": true,
      "bin": {
        "tsc": "bin/tsc",
        "tsserver": "bin/tsserver"
      },
      "engines": {
        "node": ">=14.17"
      }
    },
    "node_modules/uglify-js": {
      "version": "3.17.4",
      "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz",
      "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==",
      "dev": true,
      "optional": true,
      "bin": {
        "uglifyjs": "bin/uglifyjs"
      },
      "engines": {
        "node": ">=0.8.0"
      }
    },
    "node_modules/undici-types": {
      "version": "5.26.5",
      "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
      "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
      "dev": true
    },
    "node_modules/unicode-emoji-modifier-base": {
      "version": "1.0.0",
      "resolved": "https://registry.npmjs.org/unicode-emoji-modifier-base/-/unicode-emoji-modifier-base-1.0.0.tgz",
      "integrity": "sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g==",
      "dev": true,
      "engines": {
        "node": ">=4"
      }
    },
    "node_modules/unicorn-magic": {
      "version": "0.1.0",
      "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz",
      "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==",
      "dev": true,
      "engines": {
        "node": ">=18"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/unique-string": {
      "version": "3.0.0",
      "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-3.0.0.tgz",
      "integrity": "sha512-VGXBUVwxKMBUznyffQweQABPRRW1vHZAbadFZud4pLFAqRGvv/96vafgjWFqzourzr8YonlQiPgH0YCJfawoGQ==",
      "dev": true,
      "dependencies": {
        "crypto-random-string": "^4.0.0"
      },
      "engines": {
        "node": ">=12"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/universal-user-agent": {
      "version": "7.0.2",
      "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.2.tgz",
      "integrity": "sha512-0JCqzSKnStlRRQfCdowvqy3cy0Dvtlb8xecj/H8JFZuCze4rwjPZQOgvFvn0Ws/usCHQFGpyr+pB9adaGwXn4Q==",
      "dev": true
    },
    "node_modules/universalify": {
      "version": "2.0.0",
      "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
      "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==",
      "dev": true,
      "engines": {
        "node": ">= 10.0.0"
      }
    },
    "node_modules/url-join": {
      "version": "5.0.0",
      "resolved": "https://registry.npmjs.org/url-join/-/url-join-5.0.0.tgz",
      "integrity": "sha512-n2huDr9h9yzd6exQVnH/jU5mr+Pfx08LRXXZhkLLetAMESRj+anQsTAh940iMrIetKAmry9coFuZQ2jY8/p3WA==",
      "dev": true,
      "engines": {
        "node": "^12.20.0 || ^14.13.1 || >=16.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
    },
    "node_modules/validate-npm-package-license": {
      "version": "3.0.4",
      "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",
      "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==",
      "dev": true,
      "dependencies": {
        "spdx-correct": "^3.0.0",
        "spdx-expression-parse": "^3.0.0"
      }
    },
    "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,
      "dependencies": {
        "isexe": "^2.0.0"
      },
      "bin": {
        "node-which": "bin/node-which"
      },
      "engines": {
        "node": ">= 8"
      }
    },
    "node_modules/wordwrap": {
      "version": "1.0.0",
      "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz",
      "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==",
      "dev": true
    },
    "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,
      "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/xtend": {
      "version": "4.0.2",
      "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
      "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
      "dev": true,
      "engines": {
        "node": ">=0.4"
      }
    },
    "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,
      "engines": {
        "node": ">=10"
      }
    },
    "node_modules/yallist": {
      "version": "4.0.0",
      "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
      "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
      "dev": true
    },
    "node_modules/yargs": {
      "version": "17.7.1",
      "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz",
      "integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==",
      "dev": true,
      "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": "20.2.9",
      "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz",
      "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==",
      "dev": true,
      "engines": {
        "node": ">=10"
      }
    },
    "node_modules/yargs/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,
      "engines": {
        "node": ">=12"
      }
    },
    "node_modules/yocto-queue": {
      "version": "1.2.1",
      "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.1.tgz",
      "integrity": "sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg==",
      "dev": true,
      "engines": {
        "node": ">=12.20"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/yoctocolors": {
      "version": "2.1.1",
      "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.1.tgz",
      "integrity": "sha512-GQHQqAopRhwU8Kt1DDM8NjibDXHC8eoh1erhGAJPEyveY9qqVeXvVikNKrDz69sHowPMorbPUrH/mx8c50eiBQ==",
      "dev": true,
      "engines": {
        "node": ">=18"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    }
  },
  "dependencies": {
    "@babel/code-frame": {
      "version": "7.27.1",
      "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
      "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==",
      "dev": true,
      "requires": {
        "@babel/helper-validator-identifier": "^7.27.1",
        "js-tokens": "^4.0.0",
        "picocolors": "^1.1.1"
      }
    },
    "@babel/helper-validator-identifier": {
      "version": "7.27.1",
      "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz",
      "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==",
      "dev": true
    },
    "@colors/colors": {
      "version": "1.5.0",
      "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz",
      "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==",
      "dev": true,
      "optional": true
    },
    "@commitlint/cli": {
      "version": "19.8.1",
      "resolved": "https://registry.npmjs.org/@commitlint/cli/-/cli-19.8.1.tgz",
      "integrity": "sha512-LXUdNIkspyxrlV6VDHWBmCZRtkEVRpBKxi2Gtw3J54cGWhLCTouVD/Q6ZSaSvd2YaDObWK8mDjrz3TIKtaQMAA==",
      "dev": true,
      "requires": {
        "@commitlint/format": "^19.8.1",
        "@commitlint/lint": "^19.8.1",
        "@commitlint/load": "^19.8.1",
        "@commitlint/read": "^19.8.1",
        "@commitlint/types": "^19.8.1",
        "tinyexec": "^1.0.0",
        "yargs": "^17.0.0"
      }
    },
    "@commitlint/config-conventional": {
      "version": "19.8.1",
      "resolved": "https://registry.npmjs.org/@commitlint/config-conventional/-/config-conventional-19.8.1.tgz",
      "integrity": "sha512-/AZHJL6F6B/G959CsMAzrPKKZjeEiAVifRyEwXxcT6qtqbPwGw+iQxmNS+Bu+i09OCtdNRW6pNpBvgPrtMr9EQ==",
      "dev": true,
      "requires": {
        "@commitlint/types": "^19.8.1",
        "conventional-changelog-conventionalcommits": "^7.0.2"
      }
    },
    "@commitlint/config-validator": {
      "version": "19.8.1",
      "resolved": "https://registry.npmjs.org/@commitlint/config-validator/-/config-validator-19.8.1.tgz",
      "integrity": "sha512-0jvJ4u+eqGPBIzzSdqKNX1rvdbSU1lPNYlfQQRIFnBgLy26BtC0cFnr7c/AyuzExMxWsMOte6MkTi9I3SQ3iGQ==",
      "dev": true,
      "requires": {
        "@commitlint/types": "^19.8.1",
        "ajv": "^8.11.0"
      }
    },
    "@commitlint/ensure": {
      "version": "19.8.1",
      "resolved": "https://registry.npmjs.org/@commitlint/ensure/-/ensure-19.8.1.tgz",
      "integrity": "sha512-mXDnlJdvDzSObafjYrOSvZBwkD01cqB4gbnnFuVyNpGUM5ijwU/r/6uqUmBXAAOKRfyEjpkGVZxaDsCVnHAgyw==",
      "dev": true,
      "requires": {
        "@commitlint/types": "^19.8.1",
        "lodash.camelcase": "^4.3.0",
        "lodash.kebabcase": "^4.1.1",
        "lodash.snakecase": "^4.1.1",
        "lodash.startcase": "^4.4.0",
        "lodash.upperfirst": "^4.3.1"
      }
    },
    "@commitlint/execute-rule": {
      "version": "19.8.1",
      "resolved": "https://registry.npmjs.org/@commitlint/execute-rule/-/execute-rule-19.8.1.tgz",
      "integrity": "sha512-YfJyIqIKWI64Mgvn/sE7FXvVMQER/Cd+s3hZke6cI1xgNT/f6ZAz5heND0QtffH+KbcqAwXDEE1/5niYayYaQA==",
      "dev": true
    },
    "@commitlint/format": {
      "version": "19.8.1",
      "resolved": "https://registry.npmjs.org/@commitlint/format/-/format-19.8.1.tgz",
      "integrity": "sha512-kSJj34Rp10ItP+Eh9oCItiuN/HwGQMXBnIRk69jdOwEW9llW9FlyqcWYbHPSGofmjsqeoxa38UaEA5tsbm2JWw==",
      "dev": true,
      "requires": {
        "@commitlint/types": "^19.8.1",
        "chalk": "^5.3.0"
      },
      "dependencies": {
        "chalk": {
          "version": "5.3.0",
          "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz",
          "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==",
          "dev": true
        }
      }
    },
    "@commitlint/is-ignored": {
      "version": "19.8.1",
      "resolved": "https://registry.npmjs.org/@commitlint/is-ignored/-/is-ignored-19.8.1.tgz",
      "integrity": "sha512-AceOhEhekBUQ5dzrVhDDsbMaY5LqtN8s1mqSnT2Kz1ERvVZkNihrs3Sfk1Je/rxRNbXYFzKZSHaPsEJJDJV8dg==",
      "dev": true,
      "requires": {
        "@commitlint/types": "^19.8.1",
        "semver": "^7.6.0"
      }
    },
    "@commitlint/lint": {
      "version": "19.8.1",
      "resolved": "https://registry.npmjs.org/@commitlint/lint/-/lint-19.8.1.tgz",
      "integrity": "sha512-52PFbsl+1EvMuokZXLRlOsdcLHf10isTPlWwoY1FQIidTsTvjKXVXYb7AvtpWkDzRO2ZsqIgPK7bI98x8LRUEw==",
      "dev": true,
      "requires": {
        "@commitlint/is-ignored": "^19.8.1",
        "@commitlint/parse": "^19.8.1",
        "@commitlint/rules": "^19.8.1",
        "@commitlint/types": "^19.8.1"
      }
    },
    "@commitlint/load": {
      "version": "19.8.1",
      "resolved": "https://registry.npmjs.org/@commitlint/load/-/load-19.8.1.tgz",
      "integrity": "sha512-9V99EKG3u7z+FEoe4ikgq7YGRCSukAcvmKQuTtUyiYPnOd9a2/H9Ak1J9nJA1HChRQp9OA/sIKPugGS+FK/k1A==",
      "dev": true,
      "requires": {
        "@commitlint/config-validator": "^19.8.1",
        "@commitlint/execute-rule": "^19.8.1",
        "@commitlint/resolve-extends": "^19.8.1",
        "@commitlint/types": "^19.8.1",
        "chalk": "^5.3.0",
        "cosmiconfig": "^9.0.0",
        "cosmiconfig-typescript-loader": "^6.1.0",
        "lodash.isplainobject": "^4.0.6",
        "lodash.merge": "^4.6.2",
        "lodash.uniq": "^4.5.0"
      },
      "dependencies": {
        "chalk": {
          "version": "5.3.0",
          "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz",
          "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==",
          "dev": true
        }
      }
    },
    "@commitlint/message": {
      "version": "19.8.1",
      "resolved": "https://registry.npmjs.org/@commitlint/message/-/message-19.8.1.tgz",
      "integrity": "sha512-+PMLQvjRXiU+Ae0Wc+p99EoGEutzSXFVwQfa3jRNUZLNW5odZAyseb92OSBTKCu+9gGZiJASt76Cj3dLTtcTdg==",
      "dev": true
    },
    "@commitlint/parse": {
      "version": "19.8.1",
      "resolved": "https://registry.npmjs.org/@commitlint/parse/-/parse-19.8.1.tgz",
      "integrity": "sha512-mmAHYcMBmAgJDKWdkjIGq50X4yB0pSGpxyOODwYmoexxxiUCy5JJT99t1+PEMK7KtsCtzuWYIAXYAiKR+k+/Jw==",
      "dev": true,
      "requires": {
        "@commitlint/types": "^19.8.1",
        "conventional-changelog-angular": "^7.0.0",
        "conventional-commits-parser": "^5.0.0"
      }
    },
    "@commitlint/read": {
      "version": "19.8.1",
      "resolved": "https://registry.npmjs.org/@commitlint/read/-/read-19.8.1.tgz",
      "integrity": "sha512-03Jbjb1MqluaVXKHKRuGhcKWtSgh3Jizqy2lJCRbRrnWpcM06MYm8th59Xcns8EqBYvo0Xqb+2DoZFlga97uXQ==",
      "dev": true,
      "requires": {
        "@commitlint/top-level": "^19.8.1",
        "@commitlint/types": "^19.8.1",
        "git-raw-commits": "^4.0.0",
        "minimist": "^1.2.8",
        "tinyexec": "^1.0.0"
      }
    },
    "@commitlint/resolve-extends": {
      "version": "19.8.1",
      "resolved": "https://registry.npmjs.org/@commitlint/resolve-extends/-/resolve-extends-19.8.1.tgz",
      "integrity": "sha512-GM0mAhFk49I+T/5UCYns5ayGStkTt4XFFrjjf0L4S26xoMTSkdCf9ZRO8en1kuopC4isDFuEm7ZOm/WRVeElVg==",
      "dev": true,
      "requires": {
        "@commitlint/config-validator": "^19.8.1",
        "@commitlint/types": "^19.8.1",
        "global-directory": "^4.0.1",
        "import-meta-resolve": "^4.0.0",
        "lodash.mergewith": "^4.6.2",
        "resolve-from": "^5.0.0"
      }
    },
    "@commitlint/rules": {
      "version": "19.8.1",
      "resolved": "https://registry.npmjs.org/@commitlint/rules/-/rules-19.8.1.tgz",
      "integrity": "sha512-Hnlhd9DyvGiGwjfjfToMi1dsnw1EXKGJNLTcsuGORHz6SS9swRgkBsou33MQ2n51/boIDrbsg4tIBbRpEWK2kw==",
      "dev": true,
      "requires": {
        "@commitlint/ensure": "^19.8.1",
        "@commitlint/message": "^19.8.1",
        "@commitlint/to-lines": "^19.8.1",
        "@commitlint/types": "^19.8.1"
      }
    },
    "@commitlint/to-lines": {
      "version": "19.8.1",
      "resolved": "https://registry.npmjs.org/@commitlint/to-lines/-/to-lines-19.8.1.tgz",
      "integrity": "sha512-98Mm5inzbWTKuZQr2aW4SReY6WUukdWXuZhrqf1QdKPZBCCsXuG87c+iP0bwtD6DBnmVVQjgp4whoHRVixyPBg==",
      "dev": true
    },
    "@commitlint/top-level": {
      "version": "19.8.1",
      "resolved": "https://registry.npmjs.org/@commitlint/top-level/-/top-level-19.8.1.tgz",
      "integrity": "sha512-Ph8IN1IOHPSDhURCSXBz44+CIu+60duFwRsg6HqaISFHQHbmBtxVw4ZrFNIYUzEP7WwrNPxa2/5qJ//NK1FGcw==",
      "dev": true,
      "requires": {
        "find-up": "^7.0.0"
      }
    },
    "@commitlint/types": {
      "version": "19.8.1",
      "resolved": "https://registry.npmjs.org/@commitlint/types/-/types-19.8.1.tgz",
      "integrity": "sha512-/yCrWGCoA1SVKOks25EGadP9Pnj0oAIHGpl2wH2M2Y46dPM2ueb8wyCVOD7O3WCTkaJ0IkKvzhl1JY7+uCT2Dw==",
      "dev": true,
      "requires": {
        "@types/conventional-commits-parser": "^5.0.0",
        "chalk": "^5.3.0"
      },
      "dependencies": {
        "chalk": {
          "version": "5.4.1",
          "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz",
          "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==",
          "dev": true
        }
      }
    },
    "@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,
      "requires": {
        "@nodelib/fs.stat": "2.0.5",
        "run-parallel": "^1.1.9"
      }
    },
    "@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
    },
    "@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,
      "requires": {
        "@nodelib/fs.scandir": "2.1.5",
        "fastq": "^1.6.0"
      }
    },
    "@octokit/auth-token": {
      "version": "5.0.1",
      "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-5.0.1.tgz",
      "integrity": "sha512-RTmWsLfig8SBoiSdgvCht4BXl1CHU89Co5xiQ5JF19my/sIRDFCQ1RPrmK0exgqUZuNm39C/bV8+/83+MJEjGg==",
      "dev": true
    },
    "@octokit/core": {
      "version": "6.0.1",
      "resolved": "https://registry.npmjs.org/@octokit/core/-/core-6.0.1.tgz",
      "integrity": "sha512-MIpPQXu8Y8GjHwXM81JLveiV+DHJZtLMcB5nKekBGOl3iAtk0HT3i12Xl8Biybu+bCS1+k4qbuKEq5d0RxNRnQ==",
      "dev": true,
      "requires": {
        "@octokit/auth-token": "^5.0.0",
        "@octokit/graphql": "^8.0.0",
        "@octokit/request": "^9.0.0",
        "@octokit/request-error": "^6.0.1",
        "@octokit/types": "^12.0.0",
        "before-after-hook": "^3.0.2",
        "universal-user-agent": "^7.0.0"
      }
    },
    "@octokit/endpoint": {
      "version": "10.0.0",
      "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-10.0.0.tgz",
      "integrity": "sha512-emBcNDxBdC1y3+knJonS5zhUB/CG6TihubxM2U1/pG/Z1y3a4oV0Gzz3lmkCvWWQI6h3tqBAX9MgCBFp+M68Jw==",
      "dev": true,
      "requires": {
        "@octokit/types": "^12.0.0",
        "universal-user-agent": "^7.0.2"
      }
    },
    "@octokit/graphql": {
      "version": "8.0.1",
      "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-8.0.1.tgz",
      "integrity": "sha512-lLDb6LhC1gBj2CxEDa5Xk10+H/boonhs+3Mi6jpRyetskDKNHe6crMeKmUE2efoLofMP8ruannLlCUgpTFmVzQ==",
      "dev": true,
      "requires": {
        "@octokit/request": "^9.0.0",
        "@octokit/types": "^12.0.0",
        "universal-user-agent": "^7.0.0"
      }
    },
    "@octokit/openapi-types": {
      "version": "20.0.0",
      "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-20.0.0.tgz",
      "integrity": "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA==",
      "dev": true
    },
    "@octokit/plugin-paginate-rest": {
      "version": "11.3.5",
      "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-11.3.5.tgz",
      "integrity": "sha512-cgwIRtKrpwhLoBi0CUNuY83DPGRMaWVjqVI/bGKsLJ4PzyWZNaEmhHroI2xlrVXkk6nFv0IsZpOp+ZWSWUS2AQ==",
      "dev": true,
      "requires": {
        "@octokit/types": "^13.6.0"
      },
      "dependencies": {
        "@octokit/openapi-types": {
          "version": "22.2.0",
          "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-22.2.0.tgz",
          "integrity": "sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg==",
          "dev": true
        },
        "@octokit/types": {
          "version": "13.6.1",
          "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.6.1.tgz",
          "integrity": "sha512-PHZE9Z+kWXb23Ndik8MKPirBPziOc0D2/3KH1P+6jK5nGWe96kadZuE4jev2/Jq7FvIfTlT2Ltg8Fv2x1v0a5g==",
          "dev": true,
          "requires": {
            "@octokit/openapi-types": "^22.2.0"
          }
        }
      }
    },
    "@octokit/plugin-retry": {
      "version": "7.0.3",
      "resolved": "https://registry.npmjs.org/@octokit/plugin-retry/-/plugin-retry-7.0.3.tgz",
      "integrity": "sha512-T9l5Z7XnDZ7dkyNmhJPSUq0YjbqUT/xn4yQbhcSuv4WGC/LqM73/mKwkl68VDPoLw20e8oz4L7qQopWt9v6sow==",
      "dev": true,
      "requires": {
        "@octokit/request-error": "^6.0.0",
        "@octokit/types": "^12.0.0",
        "bottleneck": "^2.15.3"
      }
    },
    "@octokit/plugin-throttling": {
      "version": "9.0.3",
      "resolved": "https://registry.npmjs.org/@octokit/plugin-throttling/-/plugin-throttling-9.0.3.tgz",
      "integrity": "sha512-DReKamrLBJOzld73dmmxV2H137QKJfsxszAczEZXeAJQ/Po6bzQacKajPdodA6T1jfmP9+waImus+d/R2j+R7Q==",
      "dev": true,
      "requires": {
        "@octokit/types": "^12.6.0",
        "bottleneck": "^2.15.3"
      }
    },
    "@octokit/request": {
      "version": "9.0.1",
      "resolved": "https://registry.npmjs.org/@octokit/request/-/request-9.0.1.tgz",
      "integrity": "sha512-kL+cAcbSl3dctYLuJmLfx6Iku2MXXy0jszhaEIjQNaCp4zjHXrhVAHeuaRdNvJjW9qjl3u1MJ72+OuBP0YW/pg==",
      "dev": true,
      "requires": {
        "@octokit/endpoint": "^10.0.0",
        "@octokit/request-error": "^6.0.1",
        "@octokit/types": "^12.0.0",
        "universal-user-agent": "^7.0.2"
      }
    },
    "@octokit/request-error": {
      "version": "6.0.2",
      "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-6.0.2.tgz",
      "integrity": "sha512-WtRVpoHcNXs84+s9s/wqfHaxM68NGMg8Av7h59B50OVO0PwwMx+2GgQ/OliUd0iQBSNWgR6N8afi/KjSHbXHWw==",
      "dev": true,
      "requires": {
        "@octokit/types": "^12.0.0"
      }
    },
    "@octokit/types": {
      "version": "12.6.0",
      "resolved": "https://registry.npmjs.org/@octokit/types/-/types-12.6.0.tgz",
      "integrity": "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw==",
      "dev": true,
      "requires": {
        "@octokit/openapi-types": "^20.0.0"
      }
    },
    "@pnpm/network.ca-file": {
      "version": "1.0.2",
      "resolved": "https://registry.npmjs.org/@pnpm/network.ca-file/-/network.ca-file-1.0.2.tgz",
      "integrity": "sha512-YcPQ8a0jwYU9bTdJDpXjMi7Brhkr1mXsXrUJvjqM2mQDgkRiz8jFaQGOdaLxgjtUfQgZhKy/O3cG/YwmgKaxLA==",
      "dev": true,
      "requires": {
        "graceful-fs": "4.2.10"
      }
    },
    "@pnpm/npm-conf": {
      "version": "1.0.5",
      "resolved": "https://registry.npmjs.org/@pnpm/npm-conf/-/npm-conf-1.0.5.tgz",
      "integrity": "sha512-hD8ml183638O3R6/Txrh0L8VzGOrFXgRtRDG4qQC4tONdZ5Z1M+tlUUDUvrjYdmK6G+JTBTeaCLMna11cXzi8A==",
      "dev": true,
      "requires": {
        "@pnpm/network.ca-file": "^1.0.1",
        "config-chain": "^1.1.11"
      }
    },
    "@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
    },
    "@semantic-release/changelog": {
      "version": "6.0.3",
      "resolved": "https://registry.npmjs.org/@semantic-release/changelog/-/changelog-6.0.3.tgz",
      "integrity": "sha512-dZuR5qByyfe3Y03TpmCvAxCyTnp7r5XwtHRf/8vD9EAn4ZWbavUX8adMtXYzE86EVh0gyLA7lm5yW4IV30XUag==",
      "dev": true,
      "requires": {
        "@semantic-release/error": "^3.0.0",
        "aggregate-error": "^3.0.0",
        "fs-extra": "^11.0.0",
        "lodash": "^4.17.4"
      }
    },
    "@semantic-release/commit-analyzer": {
      "version": "13.0.0",
      "resolved": "https://registry.npmjs.org/@semantic-release/commit-analyzer/-/commit-analyzer-13.0.0.tgz",
      "integrity": "sha512-KtXWczvTAB1ZFZ6B4O+w8HkfYm/OgQb1dUGNFZtDgQ0csggrmkq8sTxhd+lwGF8kMb59/RnG9o4Tn7M/I8dQ9Q==",
      "dev": true,
      "requires": {
        "conventional-changelog-angular": "^8.0.0",
        "conventional-changelog-writer": "^8.0.0",
        "conventional-commits-filter": "^5.0.0",
        "conventional-commits-parser": "^6.0.0",
        "debug": "^4.0.0",
        "import-from-esm": "^1.0.3",
        "lodash-es": "^4.17.21",
        "micromatch": "^4.0.2"
      },
      "dependencies": {
        "conventional-changelog-angular": {
          "version": "8.0.0",
          "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-8.0.0.tgz",
          "integrity": "sha512-CLf+zr6St0wIxos4bmaKHRXWAcsCXrJU6F4VdNDrGRK3B8LDLKoX3zuMV5GhtbGkVR/LohZ6MT6im43vZLSjmA==",
          "dev": true,
          "requires": {
            "compare-func": "^2.0.0"
          }
        },
        "conventional-commits-parser": {
          "version": "6.0.0",
          "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-6.0.0.tgz",
          "integrity": "sha512-TbsINLp48XeMXR8EvGjTnKGsZqBemisPoyWESlpRyR8lif0lcwzqz+NMtYSj1ooF/WYjSuu7wX0CtdeeMEQAmA==",
          "dev": true,
          "requires": {
            "meow": "^13.0.0"
          }
        },
        "meow": {
          "version": "13.2.0",
          "resolved": "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz",
          "integrity": "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==",
          "dev": true
        }
      }
    },
    "@semantic-release/error": {
      "version": "3.0.0",
      "resolved": "https://registry.npmjs.org/@semantic-release/error/-/error-3.0.0.tgz",
      "integrity": "sha512-5hiM4Un+tpl4cKw3lV4UgzJj+SmfNIDCLLw0TepzQxz9ZGV5ixnqkzIVF+3tp0ZHgcMKE+VNGHJjEeyFG2dcSw==",
      "dev": true
    },
    "@semantic-release/exec": {
      "version": "7.1.0",
      "resolved": "https://registry.npmjs.org/@semantic-release/exec/-/exec-7.1.0.tgz",
      "integrity": "sha512-4ycZ2atgEUutspPZ2hxO6z8JoQt4+y/kkHvfZ1cZxgl9WKJId1xPj+UadwInj+gMn2Gsv+fLnbrZ4s+6tK2TFQ==",
      "dev": true,
      "requires": {
        "@semantic-release/error": "^4.0.0",
        "aggregate-error": "^3.0.0",
        "debug": "^4.0.0",
        "execa": "^9.0.0",
        "lodash-es": "^4.17.21",
        "parse-json": "^8.0.0"
      },
      "dependencies": {
        "@semantic-release/error": {
          "version": "4.0.0",
          "resolved": "https://registry.npmjs.org/@semantic-release/error/-/error-4.0.0.tgz",
          "integrity": "sha512-mgdxrHTLOjOddRVYIYDo0fR3/v61GNN1YGkfbrjuIKg/uMgCd+Qzo3UAXJ+woLQQpos4pl5Esuw5A7AoNlzjUQ==",
          "dev": true
        },
        "@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
        },
        "execa": {
          "version": "9.5.3",
          "resolved": "https://registry.npmjs.org/execa/-/execa-9.5.3.tgz",
          "integrity": "sha512-QFNnTvU3UjgWFy8Ef9iDHvIdcgZ344ebkwYx4/KLbR+CKQA4xBaHzv+iRpp86QfMHP8faFQLh8iOc57215y4Rg==",
          "dev": true,
          "requires": {
            "@sindresorhus/merge-streams": "^4.0.0",
            "cross-spawn": "^7.0.3",
            "figures": "^6.1.0",
            "get-stream": "^9.0.0",
            "human-signals": "^8.0.0",
            "is-plain-obj": "^4.1.0",
            "is-stream": "^4.0.1",
            "npm-run-path": "^6.0.0",
            "pretty-ms": "^9.0.0",
            "signal-exit": "^4.1.0",
            "strip-final-newline": "^4.0.0",
            "yoctocolors": "^2.0.0"
          }
        },
        "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,
          "requires": {
            "@sec-ant/readable-stream": "^0.4.1",
            "is-stream": "^4.0.1"
          }
        },
        "human-signals": {
          "version": "8.0.1",
          "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.1.tgz",
          "integrity": "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==",
          "dev": true
        },
        "index-to-position": {
          "version": "1.1.0",
          "resolved": "https://registry.npmjs.org/index-to-position/-/index-to-position-1.1.0.tgz",
          "integrity": "sha512-XPdx9Dq4t9Qk1mTMbWONJqU7boCoumEH7fRET37HX5+khDUl3J2W6PdALxhILYlIYx2amlwYcRPp28p0tSiojg==",
          "dev": true
        },
        "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
        },
        "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,
          "requires": {
            "path-key": "^4.0.0",
            "unicorn-magic": "^0.3.0"
          }
        },
        "parse-json": {
          "version": "8.3.0",
          "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-8.3.0.tgz",
          "integrity": "sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==",
          "dev": true,
          "requires": {
            "@babel/code-frame": "^7.26.2",
            "index-to-position": "^1.1.0",
            "type-fest": "^4.39.1"
          }
        },
        "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
        },
        "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
        },
        "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
        },
        "unicorn-magic": {
          "version": "0.3.0",
          "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz",
          "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==",
          "dev": true
        }
      }
    },
    "@semantic-release/git": {
      "version": "10.0.1",
      "resolved": "https://registry.npmjs.org/@semantic-release/git/-/git-10.0.1.tgz",
      "integrity": "sha512-eWrx5KguUcU2wUPaO6sfvZI0wPafUKAMNC18aXY4EnNcrZL86dEmpNVnC9uMpGZkmZJ9EfCVJBQx4pV4EMGT1w==",
      "dev": true,
      "requires": {
        "@semantic-release/error": "^3.0.0",
        "aggregate-error": "^3.0.0",
        "debug": "^4.0.0",
        "dir-glob": "^3.0.0",
        "execa": "^5.0.0",
        "lodash": "^4.17.4",
        "micromatch": "^4.0.0",
        "p-reduce": "^2.0.0"
      }
    },
    "@semantic-release/github": {
      "version": "11.0.0",
      "resolved": "https://registry.npmjs.org/@semantic-release/github/-/github-11.0.0.tgz",
      "integrity": "sha512-Uon6G6gJD8U1JNvPm7X0j46yxNRJ8Ui6SgK4Zw5Ktu8RgjEft3BGn+l/RX1TTzhhO3/uUcKuqM+/9/ETFxWS/Q==",
      "dev": true,
      "requires": {
        "@octokit/core": "^6.0.0",
        "@octokit/plugin-paginate-rest": "^11.0.0",
        "@octokit/plugin-retry": "^7.0.0",
        "@octokit/plugin-throttling": "^9.0.0",
        "@semantic-release/error": "^4.0.0",
        "aggregate-error": "^5.0.0",
        "debug": "^4.3.4",
        "dir-glob": "^3.0.1",
        "globby": "^14.0.0",
        "http-proxy-agent": "^7.0.0",
        "https-proxy-agent": "^7.0.0",
        "issue-parser": "^7.0.0",
        "lodash-es": "^4.17.21",
        "mime": "^4.0.0",
        "p-filter": "^4.0.0",
        "url-join": "^5.0.0"
      },
      "dependencies": {
        "@semantic-release/error": {
          "version": "4.0.0",
          "resolved": "https://registry.npmjs.org/@semantic-release/error/-/error-4.0.0.tgz",
          "integrity": "sha512-mgdxrHTLOjOddRVYIYDo0fR3/v61GNN1YGkfbrjuIKg/uMgCd+Qzo3UAXJ+woLQQpos4pl5Esuw5A7AoNlzjUQ==",
          "dev": true
        },
        "aggregate-error": {
          "version": "5.0.0",
          "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-5.0.0.tgz",
          "integrity": "sha512-gOsf2YwSlleG6IjRYG2A7k0HmBMEo6qVNk9Bp/EaLgAJT5ngH6PXbqa4ItvnEwCm/velL5jAnQgsHsWnjhGmvw==",
          "dev": true,
          "requires": {
            "clean-stack": "^5.2.0",
            "indent-string": "^5.0.0"
          }
        },
        "clean-stack": {
          "version": "5.2.0",
          "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-5.2.0.tgz",
          "integrity": "sha512-TyUIUJgdFnCISzG5zu3291TAsE77ddchd0bepon1VVQrKLGKFED4iXFEDQ24mIPdPBbyE16PK3F8MYE1CmcBEQ==",
          "dev": true,
          "requires": {
            "escape-string-regexp": "5.0.0"
          }
        },
        "indent-string": {
          "version": "5.0.0",
          "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz",
          "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==",
          "dev": true
        }
      }
    },
    "@semantic-release/npm": {
      "version": "12.0.2",
      "resolved": "https://registry.npmjs.org/@semantic-release/npm/-/npm-12.0.2.tgz",
      "integrity": "sha512-+M9/Lb35IgnlUO6OSJ40Ie+hUsZLuph2fqXC/qrKn0fMvUU/jiCjpoL6zEm69vzcmaZJ8yNKtMBEKHWN49WBbQ==",
      "dev": true,
      "requires": {
        "@semantic-release/error": "^4.0.0",
        "aggregate-error": "^5.0.0",
        "execa": "^9.0.0",
        "fs-extra": "^11.0.0",
        "lodash-es": "^4.17.21",
        "nerf-dart": "^1.0.0",
        "normalize-url": "^8.0.0",
        "npm": "^10.9.3",
        "rc": "^1.2.8",
        "read-pkg": "^9.0.0",
        "registry-auth-token": "^5.0.0",
        "semver": "^7.1.2",
        "tempy": "^3.0.0"
      },
      "dependencies": {
        "@semantic-release/error": {
          "version": "4.0.0",
          "resolved": "https://registry.npmjs.org/@semantic-release/error/-/error-4.0.0.tgz",
          "integrity": "sha512-mgdxrHTLOjOddRVYIYDo0fR3/v61GNN1YGkfbrjuIKg/uMgCd+Qzo3UAXJ+woLQQpos4pl5Esuw5A7AoNlzjUQ==",
          "dev": true
        },
        "@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
        },
        "aggregate-error": {
          "version": "5.0.0",
          "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-5.0.0.tgz",
          "integrity": "sha512-gOsf2YwSlleG6IjRYG2A7k0HmBMEo6qVNk9Bp/EaLgAJT5ngH6PXbqa4ItvnEwCm/velL5jAnQgsHsWnjhGmvw==",
          "dev": true,
          "requires": {
            "clean-stack": "^5.2.0",
            "indent-string": "^5.0.0"
          }
        },
        "clean-stack": {
          "version": "5.2.0",
          "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-5.2.0.tgz",
          "integrity": "sha512-TyUIUJgdFnCISzG5zu3291TAsE77ddchd0bepon1VVQrKLGKFED4iXFEDQ24mIPdPBbyE16PK3F8MYE1CmcBEQ==",
          "dev": true,
          "requires": {
            "escape-string-regexp": "5.0.0"
          }
        },
        "execa": {
          "version": "9.6.0",
          "resolved": "https://registry.npmjs.org/execa/-/execa-9.6.0.tgz",
          "integrity": "sha512-jpWzZ1ZhwUmeWRhS7Qv3mhpOhLfwI+uAX4e5fOcXqwMR7EcJ0pj2kV1CVzHVMX/LphnKWD3LObjZCoJ71lKpHw==",
          "dev": true,
          "requires": {
            "@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"
          }
        },
        "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,
          "requires": {
            "@sec-ant/readable-stream": "^0.4.1",
            "is-stream": "^4.0.1"
          }
        },
        "human-signals": {
          "version": "8.0.1",
          "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.1.tgz",
          "integrity": "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==",
          "dev": true
        },
        "indent-string": {
          "version": "5.0.0",
          "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz",
          "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==",
          "dev": true
        },
        "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
        },
        "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,
          "requires": {
            "path-key": "^4.0.0",
            "unicorn-magic": "^0.3.0"
          }
        },
        "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
        },
        "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
        },
        "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
        },
        "unicorn-magic": {
          "version": "0.3.0",
          "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz",
          "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==",
          "dev": true
        }
      }
    },
    "@semantic-release/release-notes-generator": {
      "version": "14.0.0",
      "resolved": "https://registry.npmjs.org/@semantic-release/release-notes-generator/-/release-notes-generator-14.0.0.tgz",
      "integrity": "sha512-XRxwr4e46yUMaXT8KGFBlRJlp5+NOMaufdq8qaEWlcJ7cT4Pn/iRmDGglZ2TgDe6GVP+u1boXFEnSs7N8Yzhng==",
      "dev": true,
      "requires": {
        "conventional-changelog-angular": "^8.0.0",
        "conventional-changelog-writer": "^8.0.0",
        "conventional-commits-filter": "^5.0.0",
        "conventional-commits-parser": "^6.0.0",
        "debug": "^4.0.0",
        "get-stream": "^7.0.0",
        "import-from-esm": "^1.0.3",
        "into-stream": "^7.0.0",
        "lodash-es": "^4.17.21",
        "read-pkg-up": "^11.0.0"
      },
      "dependencies": {
        "conventional-changelog-angular": {
          "version": "8.0.0",
          "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-8.0.0.tgz",
          "integrity": "sha512-CLf+zr6St0wIxos4bmaKHRXWAcsCXrJU6F4VdNDrGRK3B8LDLKoX3zuMV5GhtbGkVR/LohZ6MT6im43vZLSjmA==",
          "dev": true,
          "requires": {
            "compare-func": "^2.0.0"
          }
        },
        "conventional-commits-parser": {
          "version": "6.0.0",
          "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-6.0.0.tgz",
          "integrity": "sha512-TbsINLp48XeMXR8EvGjTnKGsZqBemisPoyWESlpRyR8lif0lcwzqz+NMtYSj1ooF/WYjSuu7wX0CtdeeMEQAmA==",
          "dev": true,
          "requires": {
            "meow": "^13.0.0"
          }
        },
        "get-stream": {
          "version": "7.0.1",
          "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-7.0.1.tgz",
          "integrity": "sha512-3M8C1EOFN6r8AMUhwUAACIoXZJEOufDU5+0gFFN5uNs6XYOralD2Pqkl7m046va6x77FwposWXbAhPPIOus7mQ==",
          "dev": true
        },
        "meow": {
          "version": "13.2.0",
          "resolved": "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz",
          "integrity": "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==",
          "dev": true
        }
      }
    },
    "@sindresorhus/is": {
      "version": "4.6.0",
      "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz",
      "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==",
      "dev": true
    },
    "@sindresorhus/merge-streams": {
      "version": "2.3.0",
      "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz",
      "integrity": "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==",
      "dev": true
    },
    "@types/conventional-commits-parser": {
      "version": "5.0.1",
      "resolved": "https://registry.npmjs.org/@types/conventional-commits-parser/-/conventional-commits-parser-5.0.1.tgz",
      "integrity": "sha512-7uz5EHdzz2TqoMfV7ee61Egf5y6NkcO4FB/1iCCQnbeiI1F3xzv3vK5dBCXUCLQgGYS+mUeigK1iKQzvED+QnQ==",
      "dev": true,
      "requires": {
        "@types/node": "*"
      }
    },
    "@types/node": {
      "version": "18.18.7",
      "resolved": "https://registry.npmjs.org/@types/node/-/node-18.18.7.tgz",
      "integrity": "sha512-bw+lEsxis6eqJYW8Ql6+yTqkE6RuFtsQPSe5JxXbqYRFQEER5aJA9a5UH9igqDWm3X4iLHIKOHlnAXLM4mi7uQ==",
      "dev": true,
      "requires": {
        "undici-types": "~5.26.4"
      }
    },
    "@types/normalize-package-data": {
      "version": "2.4.3",
      "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.3.tgz",
      "integrity": "sha512-ehPtgRgaULsFG8x0NeYJvmyH1hmlfsNLujHe9dQEia/7MAJYdzMSi19JtchUHjmBA6XC/75dK55mzZH+RyieSg==",
      "dev": true
    },
    "@types/semver": {
      "version": "7.5.8",
      "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz",
      "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==",
      "dev": true
    },
    "agent-base": {
      "version": "7.1.0",
      "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz",
      "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==",
      "dev": true,
      "requires": {
        "debug": "^4.3.4"
      }
    },
    "aggregate-error": {
      "version": "3.1.0",
      "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz",
      "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==",
      "dev": true,
      "requires": {
        "clean-stack": "^2.0.0",
        "indent-string": "^4.0.0"
      }
    },
    "ajv": {
      "version": "8.17.1",
      "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
      "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
      "dev": true,
      "requires": {
        "fast-deep-equal": "^3.1.3",
        "fast-uri": "^3.0.1",
        "json-schema-traverse": "^1.0.0",
        "require-from-string": "^2.0.2"
      }
    },
    "ansi-escapes": {
      "version": "7.0.0",
      "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.0.0.tgz",
      "integrity": "sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw==",
      "dev": true,
      "requires": {
        "environment": "^1.0.0"
      }
    },
    "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
    },
    "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,
      "requires": {
        "color-convert": "^2.0.1"
      }
    },
    "any-promise": {
      "version": "1.3.0",
      "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
      "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==",
      "dev": true
    },
    "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
    },
    "argv-formatter": {
      "version": "1.0.0",
      "resolved": "https://registry.npmjs.org/argv-formatter/-/argv-formatter-1.0.0.tgz",
      "integrity": "sha512-F2+Hkm9xFaRg+GkaNnbwXNDV5O6pnCFEmqyhvfC/Ic5LbgOWjJh3L+mN/s91rxVL3znE7DYVpW0GJFT+4YBgWw==",
      "dev": true
    },
    "array-ify": {
      "version": "1.0.0",
      "resolved": "https://registry.npmjs.org/array-ify/-/array-ify-1.0.0.tgz",
      "integrity": "sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==",
      "dev": true
    },
    "before-after-hook": {
      "version": "3.0.2",
      "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-3.0.2.tgz",
      "integrity": "sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A==",
      "dev": true
    },
    "bottleneck": {
      "version": "2.19.5",
      "resolved": "https://registry.npmjs.org/bottleneck/-/bottleneck-2.19.5.tgz",
      "integrity": "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==",
      "dev": true
    },
    "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,
      "requires": {
        "fill-range": "^7.1.1"
      }
    },
    "callsites": {
      "version": "3.1.0",
      "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
      "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
      "dev": true
    },
    "chalk": {
      "version": "4.1.2",
      "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
      "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
      "dev": true,
      "requires": {
        "ansi-styles": "^4.1.0",
        "supports-color": "^7.1.0"
      }
    },
    "char-regex": {
      "version": "1.0.2",
      "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz",
      "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==",
      "dev": true
    },
    "clean-stack": {
      "version": "2.2.0",
      "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz",
      "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==",
      "dev": true
    },
    "cli-highlight": {
      "version": "2.1.11",
      "resolved": "https://registry.npmjs.org/cli-highlight/-/cli-highlight-2.1.11.tgz",
      "integrity": "sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg==",
      "dev": true,
      "requires": {
        "chalk": "^4.0.0",
        "highlight.js": "^10.7.1",
        "mz": "^2.4.0",
        "parse5": "^5.1.1",
        "parse5-htmlparser2-tree-adapter": "^6.0.0",
        "yargs": "^16.0.0"
      },
      "dependencies": {
        "cliui": {
          "version": "7.0.4",
          "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
          "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==",
          "dev": true,
          "requires": {
            "string-width": "^4.2.0",
            "strip-ansi": "^6.0.0",
            "wrap-ansi": "^7.0.0"
          }
        },
        "yargs": {
          "version": "16.2.0",
          "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz",
          "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==",
          "dev": true,
          "requires": {
            "cliui": "^7.0.2",
            "escalade": "^3.1.1",
            "get-caller-file": "^2.0.5",
            "require-directory": "^2.1.1",
            "string-width": "^4.2.0",
            "y18n": "^5.0.5",
            "yargs-parser": "^20.2.2"
          }
        }
      }
    },
    "cli-table3": {
      "version": "0.6.5",
      "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz",
      "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==",
      "dev": true,
      "requires": {
        "@colors/colors": "1.5.0",
        "string-width": "^4.2.0"
      }
    },
    "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,
      "requires": {
        "string-width": "^4.2.0",
        "strip-ansi": "^6.0.1",
        "wrap-ansi": "^7.0.0"
      }
    },
    "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,
      "requires": {
        "color-name": "~1.1.4"
      }
    },
    "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
    },
    "compare-func": {
      "version": "2.0.0",
      "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz",
      "integrity": "sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==",
      "dev": true,
      "requires": {
        "array-ify": "^1.0.0",
        "dot-prop": "^5.1.0"
      }
    },
    "config-chain": {
      "version": "1.1.13",
      "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz",
      "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==",
      "dev": true,
      "requires": {
        "ini": "^1.3.4",
        "proto-list": "~1.2.1"
      }
    },
    "conventional-changelog-angular": {
      "version": "7.0.0",
      "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-7.0.0.tgz",
      "integrity": "sha512-ROjNchA9LgfNMTTFSIWPzebCwOGFdgkEq45EnvvrmSLvCtAw0HSmrCs7/ty+wAeYUZyNay0YMUNYFTRL72PkBQ==",
      "dev": true,
      "requires": {
        "compare-func": "^2.0.0"
      }
    },
    "conventional-changelog-conventionalcommits": {
      "version": "7.0.2",
      "resolved": "https://registry.npmjs.org/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-7.0.2.tgz",
      "integrity": "sha512-NKXYmMR/Hr1DevQegFB4MwfM5Vv0m4UIxKZTTYuD98lpTknaZlSRrDOG4X7wIXpGkfsYxZTghUN+Qq+T0YQI7w==",
      "dev": true,
      "requires": {
        "compare-func": "^2.0.0"
      }
    },
    "conventional-changelog-writer": {
      "version": "8.0.0",
      "resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-8.0.0.tgz",
      "integrity": "sha512-TQcoYGRatlAnT2qEWDON/XSfnVG38JzA7E0wcGScu7RElQBkg9WWgZd1peCWFcWDh1xfb2CfsrcvOn1bbSzztA==",
      "dev": true,
      "requires": {
        "@types/semver": "^7.5.5",
        "conventional-commits-filter": "^5.0.0",
        "handlebars": "^4.7.7",
        "meow": "^13.0.0",
        "semver": "^7.5.2"
      },
      "dependencies": {
        "meow": {
          "version": "13.2.0",
          "resolved": "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz",
          "integrity": "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==",
          "dev": true
        }
      }
    },
    "conventional-commits-filter": {
      "version": "5.0.0",
      "resolved": "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-5.0.0.tgz",
      "integrity": "sha512-tQMagCOC59EVgNZcC5zl7XqO30Wki9i9J3acbUvkaosCT6JX3EeFwJD7Qqp4MCikRnzS18WXV3BLIQ66ytu6+Q==",
      "dev": true
    },
    "conventional-commits-parser": {
      "version": "5.0.0",
      "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-5.0.0.tgz",
      "integrity": "sha512-ZPMl0ZJbw74iS9LuX9YIAiW8pfM5p3yh2o/NbXHbkFuZzY5jvdi5jFycEOkmBW5H5I7nA+D6f3UcsCLP2vvSEA==",
      "dev": true,
      "requires": {
        "is-text-path": "^2.0.0",
        "JSONStream": "^1.3.5",
        "meow": "^12.0.1",
        "split2": "^4.0.0"
      }
    },
    "convert-hrtime": {
      "version": "5.0.0",
      "resolved": "https://registry.npmjs.org/convert-hrtime/-/convert-hrtime-5.0.0.tgz",
      "integrity": "sha512-lOETlkIeYSJWcbbcvjRKGxVMXJR+8+OQb/mTPbA4ObPMytYIsUbuOE0Jzy60hjARYszq1id0j8KgVhC+WGZVTg==",
      "dev": true
    },
    "core-util-is": {
      "version": "1.0.3",
      "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
      "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==",
      "dev": true
    },
    "cosmiconfig": {
      "version": "9.0.0",
      "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz",
      "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==",
      "dev": true,
      "requires": {
        "env-paths": "^2.2.1",
        "import-fresh": "^3.3.0",
        "js-yaml": "^4.1.0",
        "parse-json": "^5.2.0"
      }
    },
    "cosmiconfig-typescript-loader": {
      "version": "6.1.0",
      "resolved": "https://registry.npmjs.org/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-6.1.0.tgz",
      "integrity": "sha512-tJ1w35ZRUiM5FeTzT7DtYWAFFv37ZLqSRkGi2oeCK1gPhvaWjkAtfXvLmvE1pRfxxp9aQo6ba/Pvg1dKj05D4g==",
      "dev": true,
      "requires": {
        "jiti": "^2.4.1"
      }
    },
    "cross-spawn": {
      "version": "7.0.6",
      "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
      "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
      "dev": true,
      "requires": {
        "path-key": "^3.1.0",
        "shebang-command": "^2.0.0",
        "which": "^2.0.1"
      }
    },
    "crypto-random-string": {
      "version": "4.0.0",
      "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-4.0.0.tgz",
      "integrity": "sha512-x8dy3RnvYdlUcPOjkEHqozhiwzKNSq7GcPuXFbnyMOCHxX8V3OgIg/pYuabl2sbUPfIJaeAQB7PMOK8DFIdoRA==",
      "dev": true,
      "requires": {
        "type-fest": "^1.0.1"
      },
      "dependencies": {
        "type-fest": {
          "version": "1.4.0",
          "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz",
          "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==",
          "dev": true
        }
      }
    },
    "dargs": {
      "version": "8.1.0",
      "resolved": "https://registry.npmjs.org/dargs/-/dargs-8.1.0.tgz",
      "integrity": "sha512-wAV9QHOsNbwnWdNW2FYvE1P56wtgSbM+3SZcdGiWQILwVjACCXDCI3Ai8QlCjMDB8YK5zySiXZYBiwGmNY3lnw==",
      "dev": true
    },
    "debug": {
      "version": "4.3.4",
      "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
      "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
      "dev": true,
      "requires": {
        "ms": "2.1.2"
      }
    },
    "deep-extend": {
      "version": "0.6.0",
      "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
      "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
      "dev": true
    },
    "dir-glob": {
      "version": "3.0.1",
      "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
      "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==",
      "dev": true,
      "requires": {
        "path-type": "^4.0.0"
      }
    },
    "dot-prop": {
      "version": "5.3.0",
      "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz",
      "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==",
      "dev": true,
      "requires": {
        "is-obj": "^2.0.0"
      }
    },
    "duplexer2": {
      "version": "0.1.4",
      "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz",
      "integrity": "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==",
      "dev": true,
      "requires": {
        "readable-stream": "^2.0.2"
      }
    },
    "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
    },
    "emojilib": {
      "version": "2.4.0",
      "resolved": "https://registry.npmjs.org/emojilib/-/emojilib-2.4.0.tgz",
      "integrity": "sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw==",
      "dev": true
    },
    "env-ci": {
      "version": "11.0.0",
      "resolved": "https://registry.npmjs.org/env-ci/-/env-ci-11.0.0.tgz",
      "integrity": "sha512-apikxMgkipkgTvMdRT9MNqWx5VLOci79F4VBd7Op/7OPjjoanjdAvn6fglMCCEf/1bAh8eOiuEVCUs4V3qP3nQ==",
      "dev": true,
      "requires": {
        "execa": "^8.0.0",
        "java-properties": "^1.0.2"
      },
      "dependencies": {
        "execa": {
          "version": "8.0.1",
          "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz",
          "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==",
          "dev": true,
          "requires": {
            "cross-spawn": "^7.0.3",
            "get-stream": "^8.0.1",
            "human-signals": "^5.0.0",
            "is-stream": "^3.0.0",
            "merge-stream": "^2.0.0",
            "npm-run-path": "^5.1.0",
            "onetime": "^6.0.0",
            "signal-exit": "^4.1.0",
            "strip-final-newline": "^3.0.0"
          }
        },
        "get-stream": {
          "version": "8.0.1",
          "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz",
          "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==",
          "dev": true
        },
        "human-signals": {
          "version": "5.0.0",
          "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz",
          "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==",
          "dev": true
        },
        "is-stream": {
          "version": "3.0.0",
          "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz",
          "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==",
          "dev": true
        },
        "mimic-fn": {
          "version": "4.0.0",
          "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz",
          "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==",
          "dev": true
        },
        "npm-run-path": {
          "version": "5.1.0",
          "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.1.0.tgz",
          "integrity": "sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==",
          "dev": true,
          "requires": {
            "path-key": "^4.0.0"
          }
        },
        "onetime": {
          "version": "6.0.0",
          "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz",
          "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==",
          "dev": true,
          "requires": {
            "mimic-fn": "^4.0.0"
          }
        },
        "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
        },
        "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
        },
        "strip-final-newline": {
          "version": "3.0.0",
          "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz",
          "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==",
          "dev": true
        }
      }
    },
    "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
    },
    "environment": {
      "version": "1.1.0",
      "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz",
      "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==",
      "dev": true
    },
    "error-ex": {
      "version": "1.3.2",
      "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
      "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
      "dev": true,
      "requires": {
        "is-arrayish": "^0.2.1"
      }
    },
    "escalade": {
      "version": "3.1.1",
      "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
      "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==",
      "dev": true
    },
    "escape-string-regexp": {
      "version": "5.0.0",
      "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz",
      "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==",
      "dev": true
    },
    "execa": {
      "version": "5.1.1",
      "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz",
      "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==",
      "dev": true,
      "requires": {
        "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"
      }
    },
    "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
    },
    "fast-glob": {
      "version": "3.3.2",
      "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz",
      "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==",
      "dev": true,
      "requires": {
        "@nodelib/fs.stat": "^2.0.2",
        "@nodelib/fs.walk": "^1.2.3",
        "glob-parent": "^5.1.2",
        "merge2": "^1.3.0",
        "micromatch": "^4.0.4"
      }
    },
    "fast-uri": {
      "version": "3.0.6",
      "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz",
      "integrity": "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==",
      "dev": true
    },
    "fastq": {
      "version": "1.17.1",
      "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz",
      "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==",
      "dev": true,
      "requires": {
        "reusify": "^1.0.4"
      }
    },
    "figures": {
      "version": "6.1.0",
      "resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz",
      "integrity": "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==",
      "dev": true,
      "requires": {
        "is-unicode-supported": "^2.0.0"
      }
    },
    "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,
      "requires": {
        "to-regex-range": "^5.0.1"
      }
    },
    "find-up": {
      "version": "7.0.0",
      "resolved": "https://registry.npmjs.org/find-up/-/find-up-7.0.0.tgz",
      "integrity": "sha512-YyZM99iHrqLKjmt4LJDj58KI+fYyufRLBSYcqycxf//KpBk9FoewoGX0450m9nB44qrZnovzC2oeP5hUibxc/g==",
      "dev": true,
      "requires": {
        "locate-path": "^7.2.0",
        "path-exists": "^5.0.0",
        "unicorn-magic": "^0.1.0"
      }
    },
    "find-up-simple": {
      "version": "1.0.0",
      "resolved": "https://registry.npmjs.org/find-up-simple/-/find-up-simple-1.0.0.tgz",
      "integrity": "sha512-q7Us7kcjj2VMePAa02hDAF6d+MzsdsAWEwYyOpwUtlerRBkOEPBCRZrAV4XfcSN8fHAgaD0hP7miwoay6DCprw==",
      "dev": true
    },
    "find-versions": {
      "version": "6.0.0",
      "resolved": "https://registry.npmjs.org/find-versions/-/find-versions-6.0.0.tgz",
      "integrity": "sha512-2kCCtc+JvcZ86IGAz3Z2Y0A1baIz9fL31pH/0S1IqZr9Iwnjq8izfPtrCyQKO6TLMPELLsQMre7VDqeIKCsHkA==",
      "dev": true,
      "requires": {
        "semver-regex": "^4.0.5",
        "super-regex": "^1.0.0"
      }
    },
    "from2": {
      "version": "2.3.0",
      "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz",
      "integrity": "sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g==",
      "dev": true,
      "requires": {
        "inherits": "^2.0.1",
        "readable-stream": "^2.0.0"
      }
    },
    "fs-extra": {
      "version": "11.1.0",
      "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.1.0.tgz",
      "integrity": "sha512-0rcTq621PD5jM/e0a3EJoGC/1TC5ZBCERW82LQuwfGnCa1V8w7dpYH1yNu+SLb6E5dkeCBzKEyLGlFrnr+dUyw==",
      "dev": true,
      "requires": {
        "graceful-fs": "^4.2.0",
        "jsonfile": "^6.0.1",
        "universalify": "^2.0.0"
      }
    },
    "function-bind": {
      "version": "1.1.1",
      "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
      "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
      "dev": true
    },
    "function-timeout": {
      "version": "1.0.1",
      "resolved": "https://registry.npmjs.org/function-timeout/-/function-timeout-1.0.1.tgz",
      "integrity": "sha512-6yPMImFFuaMPNaTMTBuolA8EanHJWF5Vju0NHpObRURT105J6x1Mf2a7J4P7Sqk2xDxv24N5L0RatEhTBhNmdA==",
      "dev": true
    },
    "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
    },
    "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
    },
    "git-log-parser": {
      "version": "1.2.0",
      "resolved": "https://registry.npmjs.org/git-log-parser/-/git-log-parser-1.2.0.tgz",
      "integrity": "sha512-rnCVNfkTL8tdNryFuaY0fYiBWEBcgF748O6ZI61rslBvr2o7U65c2/6npCRqH40vuAhtgtDiqLTJjBVdrejCzA==",
      "dev": true,
      "requires": {
        "argv-formatter": "~1.0.0",
        "spawn-error-forwarder": "~1.0.0",
        "split2": "~1.0.0",
        "stream-combiner2": "~1.1.1",
        "through2": "~2.0.0",
        "traverse": "~0.6.6"
      },
      "dependencies": {
        "split2": {
          "version": "1.0.0",
          "resolved": "https://registry.npmjs.org/split2/-/split2-1.0.0.tgz",
          "integrity": "sha512-NKywug4u4pX/AZBB1FCPzZ6/7O+Xhz1qMVbzTvvKvikjO99oPN87SkK08mEY9P63/5lWjK+wgOOgApnTg5r6qg==",
          "dev": true,
          "requires": {
            "through2": "~2.0.0"
          }
        },
        "through2": {
          "version": "2.0.5",
          "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz",
          "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==",
          "dev": true,
          "requires": {
            "readable-stream": "~2.3.6",
            "xtend": "~4.0.1"
          }
        }
      }
    },
    "git-raw-commits": {
      "version": "4.0.0",
      "resolved": "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-4.0.0.tgz",
      "integrity": "sha512-ICsMM1Wk8xSGMowkOmPrzo2Fgmfo4bMHLNX6ytHjajRJUqvHOw/TFapQ+QG75c3X/tTDDhOSRPGC52dDbNM8FQ==",
      "dev": true,
      "requires": {
        "dargs": "^8.0.0",
        "meow": "^12.0.1",
        "split2": "^4.0.0"
      }
    },
    "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,
      "requires": {
        "is-glob": "^4.0.1"
      }
    },
    "global-directory": {
      "version": "4.0.1",
      "resolved": "https://registry.npmjs.org/global-directory/-/global-directory-4.0.1.tgz",
      "integrity": "sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q==",
      "dev": true,
      "requires": {
        "ini": "4.1.1"
      },
      "dependencies": {
        "ini": {
          "version": "4.1.1",
          "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.1.tgz",
          "integrity": "sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==",
          "dev": true
        }
      }
    },
    "globby": {
      "version": "14.0.1",
      "resolved": "https://registry.npmjs.org/globby/-/globby-14.0.1.tgz",
      "integrity": "sha512-jOMLD2Z7MAhyG8aJpNOpmziMOP4rPLcc95oQPKXBazW82z+CEgPFBQvEpRUa1KeIMUJo4Wsm+q6uzO/Q/4BksQ==",
      "dev": true,
      "requires": {
        "@sindresorhus/merge-streams": "^2.1.0",
        "fast-glob": "^3.3.2",
        "ignore": "^5.2.4",
        "path-type": "^5.0.0",
        "slash": "^5.1.0",
        "unicorn-magic": "^0.1.0"
      },
      "dependencies": {
        "path-type": {
          "version": "5.0.0",
          "resolved": "https://registry.npmjs.org/path-type/-/path-type-5.0.0.tgz",
          "integrity": "sha512-5HviZNaZcfqP95rwpv+1HDgUamezbqdSYTyzjTvwtJSnIH+3vnbmWsItli8OFEndS984VT55M3jduxZbX351gg==",
          "dev": true
        }
      }
    },
    "graceful-fs": {
      "version": "4.2.10",
      "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz",
      "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==",
      "dev": true
    },
    "handlebars": {
      "version": "4.7.8",
      "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz",
      "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==",
      "dev": true,
      "requires": {
        "minimist": "^1.2.5",
        "neo-async": "^2.6.2",
        "source-map": "^0.6.1",
        "uglify-js": "^3.1.4",
        "wordwrap": "^1.0.0"
      }
    },
    "has": {
      "version": "1.0.3",
      "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
      "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
      "dev": true,
      "requires": {
        "function-bind": "^1.1.1"
      }
    },
    "has-flag": {
      "version": "4.0.0",
      "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
      "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
      "dev": true
    },
    "highlight.js": {
      "version": "10.7.3",
      "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz",
      "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==",
      "dev": true
    },
    "hook-std": {
      "version": "3.0.0",
      "resolved": "https://registry.npmjs.org/hook-std/-/hook-std-3.0.0.tgz",
      "integrity": "sha512-jHRQzjSDzMtFy34AGj1DN+vq54WVuhSvKgrHf0OMiFQTwDD4L/qqofVEWjLOBMTn5+lCD3fPg32W9yOfnEJTTw==",
      "dev": true
    },
    "hosted-git-info": {
      "version": "7.0.1",
      "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.1.tgz",
      "integrity": "sha512-+K84LB1DYwMHoHSgaOY/Jfhw3ucPmSET5v98Ke/HdNSw4a0UktWzyW1mjhjpuxxTqOOsfWT/7iVshHmVZ4IpOA==",
      "dev": true,
      "requires": {
        "lru-cache": "^10.0.1"
      },
      "dependencies": {
        "lru-cache": {
          "version": "10.2.0",
          "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz",
          "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==",
          "dev": true
        }
      }
    },
    "http-proxy-agent": {
      "version": "7.0.0",
      "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.0.tgz",
      "integrity": "sha512-+ZT+iBxVUQ1asugqnD6oWoRiS25AkjNfG085dKJGtGxkdwLQrMKU5wJr2bOOFAXzKcTuqq+7fZlTMgG3SRfIYQ==",
      "dev": true,
      "requires": {
        "agent-base": "^7.1.0",
        "debug": "^4.3.4"
      }
    },
    "https-proxy-agent": {
      "version": "7.0.0",
      "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.0.tgz",
      "integrity": "sha512-0euwPCRyAPSgGdzD1IVN9nJYHtBhJwb6XPfbpQcYbPCwrBidX6GzxmchnaF4sfF/jPb74Ojx5g4yTg3sixlyPw==",
      "dev": true,
      "requires": {
        "agent-base": "^7.0.2",
        "debug": "4"
      }
    },
    "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
    },
    "ignore": {
      "version": "5.3.1",
      "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz",
      "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==",
      "dev": true
    },
    "import-fresh": {
      "version": "3.3.0",
      "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
      "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
      "dev": true,
      "requires": {
        "parent-module": "^1.0.0",
        "resolve-from": "^4.0.0"
      },
      "dependencies": {
        "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
        }
      }
    },
    "import-from-esm": {
      "version": "1.3.3",
      "resolved": "https://registry.npmjs.org/import-from-esm/-/import-from-esm-1.3.3.tgz",
      "integrity": "sha512-U3Qt/CyfFpTUv6LOP2jRTLYjphH6zg3okMfHbyqRa/W2w6hr8OsJWVggNlR4jxuojQy81TgTJTxgSkyoteRGMQ==",
      "dev": true,
      "requires": {
        "debug": "^4.3.4",
        "import-meta-resolve": "^4.0.0"
      }
    },
    "import-meta-resolve": {
      "version": "4.0.0",
      "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.0.0.tgz",
      "integrity": "sha512-okYUR7ZQPH+efeuMJGlq4f8ubUgO50kByRPyt/Cy1Io4PSRsPjxME+YlVaCOx+NIToW7hCsZNFJyTPFFKepRSA==",
      "dev": true
    },
    "indent-string": {
      "version": "4.0.0",
      "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz",
      "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==",
      "dev": true
    },
    "index-to-position": {
      "version": "0.1.2",
      "resolved": "https://registry.npmjs.org/index-to-position/-/index-to-position-0.1.2.tgz",
      "integrity": "sha512-MWDKS3AS1bGCHLBA2VLImJz42f7bJh8wQsTGCzI3j519/CASStoDONUBVz2I/VID0MpiX3SGSnbOD2xUalbE5g==",
      "dev": true
    },
    "inherits": {
      "version": "2.0.4",
      "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
      "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
      "dev": true
    },
    "ini": {
      "version": "1.3.8",
      "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
      "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
      "dev": true
    },
    "into-stream": {
      "version": "7.0.0",
      "resolved": "https://registry.npmjs.org/into-stream/-/into-stream-7.0.0.tgz",
      "integrity": "sha512-2dYz766i9HprMBasCMvHMuazJ7u4WzhJwo5kb3iPSiW/iRYV6uPari3zHoqZlnuaR7V1bEiNMxikhp37rdBXbw==",
      "dev": true,
      "requires": {
        "from2": "^2.3.0",
        "p-is-promise": "^3.0.0"
      }
    },
    "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
    },
    "is-core-module": {
      "version": "2.11.0",
      "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz",
      "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==",
      "dev": true,
      "requires": {
        "has": "^1.0.3"
      }
    },
    "is-extglob": {
      "version": "2.1.1",
      "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
      "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
      "dev": true
    },
    "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
    },
    "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,
      "requires": {
        "is-extglob": "^2.1.1"
      }
    },
    "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
    },
    "is-obj": {
      "version": "2.0.0",
      "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz",
      "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==",
      "dev": true
    },
    "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
    },
    "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
    },
    "is-text-path": {
      "version": "2.0.0",
      "resolved": "https://registry.npmjs.org/is-text-path/-/is-text-path-2.0.0.tgz",
      "integrity": "sha512-+oDTluR6WEjdXEJMnC2z6A4FRwFoYuvShVVEGsS7ewc0UTi2QtAKMDJuL4BDEVt+5T7MjFo12RP8ghOM75oKJw==",
      "dev": true,
      "requires": {
        "text-extensions": "^2.0.0"
      }
    },
    "is-unicode-supported": {
      "version": "2.0.0",
      "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.0.0.tgz",
      "integrity": "sha512-FRdAyx5lusK1iHG0TWpVtk9+1i+GjrzRffhDg4ovQ7mcidMQ6mj+MhKPmvh7Xwyv5gIS06ns49CA7Sqg7lC22Q==",
      "dev": true
    },
    "isarray": {
      "version": "1.0.0",
      "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
      "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
      "dev": true
    },
    "isexe": {
      "version": "2.0.0",
      "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
      "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
      "dev": true
    },
    "issue-parser": {
      "version": "7.0.0",
      "resolved": "https://registry.npmjs.org/issue-parser/-/issue-parser-7.0.0.tgz",
      "integrity": "sha512-jgAw78HO3gs9UrKqJNQvfDj9Ouy8Mhu40fbEJ8yXff4MW8+/Fcn9iFjyWUQ6SKbX8ipPk3X5A3AyfYHRu6uVLw==",
      "dev": true,
      "requires": {
        "lodash.capitalize": "^4.2.1",
        "lodash.escaperegexp": "^4.1.2",
        "lodash.isplainobject": "^4.0.6",
        "lodash.isstring": "^4.0.1",
        "lodash.uniqby": "^4.7.0"
      }
    },
    "java-properties": {
      "version": "1.0.2",
      "resolved": "https://registry.npmjs.org/java-properties/-/java-properties-1.0.2.tgz",
      "integrity": "sha512-qjdpeo2yKlYTH7nFdK0vbZWuTCesk4o63v5iVOlhMQPfuIZQfW/HI35SjfhA+4qpg36rnFSvUK5b1m+ckIblQQ==",
      "dev": true
    },
    "jiti": {
      "version": "2.4.1",
      "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.1.tgz",
      "integrity": "sha512-yPBThwecp1wS9DmoA4x4KR2h3QoslacnDR8ypuFM962kI4/456Iy1oHx2RAgh4jfZNdn0bctsdadceiBUgpU1g==",
      "dev": true
    },
    "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
    },
    "js-yaml": {
      "version": "4.1.0",
      "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
      "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
      "dev": true,
      "requires": {
        "argparse": "^2.0.1"
      }
    },
    "json-parse-better-errors": {
      "version": "1.0.2",
      "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz",
      "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==",
      "dev": true
    },
    "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
    },
    "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
    },
    "jsonfile": {
      "version": "6.1.0",
      "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
      "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
      "dev": true,
      "requires": {
        "graceful-fs": "^4.1.6",
        "universalify": "^2.0.0"
      }
    },
    "jsonparse": {
      "version": "1.3.1",
      "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz",
      "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==",
      "dev": true
    },
    "JSONStream": {
      "version": "1.3.5",
      "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz",
      "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==",
      "dev": true,
      "requires": {
        "jsonparse": "^1.2.0",
        "through": ">=2.2.7 <3"
      }
    },
    "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
    },
    "load-json-file": {
      "version": "4.0.0",
      "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz",
      "integrity": "sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==",
      "dev": true,
      "requires": {
        "graceful-fs": "^4.1.2",
        "parse-json": "^4.0.0",
        "pify": "^3.0.0",
        "strip-bom": "^3.0.0"
      },
      "dependencies": {
        "parse-json": {
          "version": "4.0.0",
          "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz",
          "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==",
          "dev": true,
          "requires": {
            "error-ex": "^1.3.1",
            "json-parse-better-errors": "^1.0.1"
          }
        }
      }
    },
    "locate-path": {
      "version": "7.2.0",
      "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz",
      "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==",
      "dev": true,
      "requires": {
        "p-locate": "^6.0.0"
      }
    },
    "lodash": {
      "version": "4.17.21",
      "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
      "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
      "dev": true
    },
    "lodash-es": {
      "version": "4.17.21",
      "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
      "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==",
      "dev": true
    },
    "lodash.camelcase": {
      "version": "4.3.0",
      "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
      "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==",
      "dev": true
    },
    "lodash.capitalize": {
      "version": "4.2.1",
      "resolved": "https://registry.npmjs.org/lodash.capitalize/-/lodash.capitalize-4.2.1.tgz",
      "integrity": "sha512-kZzYOKspf8XVX5AvmQF94gQW0lejFVgb80G85bU4ZWzoJ6C03PQg3coYAUpSTpQWelrZELd3XWgHzw4Ck5kaIw==",
      "dev": true
    },
    "lodash.escaperegexp": {
      "version": "4.1.2",
      "resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz",
      "integrity": "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==",
      "dev": true
    },
    "lodash.isplainobject": {
      "version": "4.0.6",
      "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
      "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
      "dev": true
    },
    "lodash.isstring": {
      "version": "4.0.1",
      "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
      "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==",
      "dev": true
    },
    "lodash.kebabcase": {
      "version": "4.1.1",
      "resolved": "https://registry.npmjs.org/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz",
      "integrity": "sha512-N8XRTIMMqqDgSy4VLKPnJ/+hpGZN+PHQiJnSenYqPaVV/NCqEogTnAdZLQiGKhxX+JCs8waWq2t1XHWKOmlY8g==",
      "dev": true
    },
    "lodash.merge": {
      "version": "4.6.2",
      "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
      "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
      "dev": true
    },
    "lodash.mergewith": {
      "version": "4.6.2",
      "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz",
      "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==",
      "dev": true
    },
    "lodash.snakecase": {
      "version": "4.1.1",
      "resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz",
      "integrity": "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==",
      "dev": true
    },
    "lodash.startcase": {
      "version": "4.4.0",
      "resolved": "https://registry.npmjs.org/lodash.startcase/-/lodash.startcase-4.4.0.tgz",
      "integrity": "sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==",
      "dev": true
    },
    "lodash.uniq": {
      "version": "4.5.0",
      "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz",
      "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==",
      "dev": true
    },
    "lodash.uniqby": {
      "version": "4.7.0",
      "resolved": "https://registry.npmjs.org/lodash.uniqby/-/lodash.uniqby-4.7.0.tgz",
      "integrity": "sha512-e/zcLx6CSbmaEgFHCA7BnoQKyCtKMxnuWrJygbwPs/AIn+IMKl66L8/s+wBUn5LRw2pZx3bUHibiV1b6aTWIww==",
      "dev": true
    },
    "lodash.upperfirst": {
      "version": "4.3.1",
      "resolved": "https://registry.npmjs.org/lodash.upperfirst/-/lodash.upperfirst-4.3.1.tgz",
      "integrity": "sha512-sReKOYJIJf74dhJONhU4e0/shzi1trVbSWDOhKYE5XV2O+H7Sb2Dihwuc7xWxVl+DgFPyTqIN3zMfT9cq5iWDg==",
      "dev": true
    },
    "lru-cache": {
      "version": "6.0.0",
      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
      "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
      "dev": true,
      "requires": {
        "yallist": "^4.0.0"
      }
    },
    "marked": {
      "version": "15.0.12",
      "resolved": "https://registry.npmjs.org/marked/-/marked-15.0.12.tgz",
      "integrity": "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==",
      "dev": true
    },
    "marked-terminal": {
      "version": "7.3.0",
      "resolved": "https://registry.npmjs.org/marked-terminal/-/marked-terminal-7.3.0.tgz",
      "integrity": "sha512-t4rBvPsHc57uE/2nJOLmMbZCQ4tgAccAED3ngXQqW6g+TxA488JzJ+FK3lQkzBQOI1mRV/r/Kq+1ZlJ4D0owQw==",
      "dev": true,
      "requires": {
        "ansi-escapes": "^7.0.0",
        "ansi-regex": "^6.1.0",
        "chalk": "^5.4.1",
        "cli-highlight": "^2.1.11",
        "cli-table3": "^0.6.5",
        "node-emoji": "^2.2.0",
        "supports-hyperlinks": "^3.1.0"
      },
      "dependencies": {
        "ansi-regex": {
          "version": "6.1.0",
          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
          "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
          "dev": true
        },
        "chalk": {
          "version": "5.4.1",
          "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz",
          "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==",
          "dev": true
        }
      }
    },
    "meow": {
      "version": "12.1.1",
      "resolved": "https://registry.npmjs.org/meow/-/meow-12.1.1.tgz",
      "integrity": "sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw==",
      "dev": true
    },
    "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
    },
    "merge2": {
      "version": "1.4.1",
      "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
      "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
      "dev": true
    },
    "micromatch": {
      "version": "4.0.5",
      "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz",
      "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==",
      "dev": true,
      "requires": {
        "braces": "^3.0.2",
        "picomatch": "^2.3.1"
      }
    },
    "mime": {
      "version": "4.0.1",
      "resolved": "https://registry.npmjs.org/mime/-/mime-4.0.1.tgz",
      "integrity": "sha512-5lZ5tyrIfliMXzFtkYyekWbtRXObT9OWa8IwQ5uxTBDHucNNwniRqo0yInflj+iYi5CBa6qxadGzGarDfuEOxA==",
      "dev": true
    },
    "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
    },
    "minimist": {
      "version": "1.2.8",
      "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
      "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
      "dev": true
    },
    "ms": {
      "version": "2.1.2",
      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
      "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
      "dev": true
    },
    "mz": {
      "version": "2.7.0",
      "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
      "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==",
      "dev": true,
      "requires": {
        "any-promise": "^1.0.0",
        "object-assign": "^4.0.1",
        "thenify-all": "^1.0.0"
      }
    },
    "neo-async": {
      "version": "2.6.2",
      "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz",
      "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
      "dev": true
    },
    "nerf-dart": {
      "version": "1.0.0",
      "resolved": "https://registry.npmjs.org/nerf-dart/-/nerf-dart-1.0.0.tgz",
      "integrity": "sha512-EZSPZB70jiVsivaBLYDCyntd5eH8NTSMOn3rB+HxwdmKThGELLdYv8qVIMWvZEFy9w8ZZpW9h9OB32l1rGtj7g==",
      "dev": true
    },
    "node-emoji": {
      "version": "2.2.0",
      "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-2.2.0.tgz",
      "integrity": "sha512-Z3lTE9pLaJF47NyMhd4ww1yFTAP8YhYI8SleJiHzM46Fgpm5cnNzSl9XfzFNqbaz+VlJrIj3fXQ4DeN1Rjm6cw==",
      "dev": true,
      "requires": {
        "@sindresorhus/is": "^4.6.0",
        "char-regex": "^1.0.2",
        "emojilib": "^2.4.0",
        "skin-tone": "^2.0.0"
      }
    },
    "normalize-package-data": {
      "version": "6.0.0",
      "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.0.tgz",
      "integrity": "sha512-UL7ELRVxYBHBgYEtZCXjxuD5vPxnmvMGq0jp/dGPKKrN7tfsBh2IY7TlJ15WWwdjRWD3RJbnsygUurTK3xkPkg==",
      "dev": true,
      "requires": {
        "hosted-git-info": "^7.0.0",
        "is-core-module": "^2.8.1",
        "semver": "^7.3.5",
        "validate-npm-package-license": "^3.0.4"
      }
    },
    "normalize-url": {
      "version": "8.0.0",
      "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.0.0.tgz",
      "integrity": "sha512-uVFpKhj5MheNBJRTiMZ9pE/7hD1QTeEvugSJW/OmLzAp78PB5O6adfMNTvmfKhXBkvCzC+rqifWcVYpGFwTjnw==",
      "dev": true
    },
    "npm": {
      "version": "10.9.3",
      "resolved": "https://registry.npmjs.org/npm/-/npm-10.9.3.tgz",
      "integrity": "sha512-6Eh1u5Q+kIVXeA8e7l2c/HpnFFcwrkt37xDMujD5be1gloWa9p6j3Fsv3mByXXmqJHy+2cElRMML8opNT7xIJQ==",
      "dev": true,
      "requires": {
        "@isaacs/string-locale-compare": "^1.1.0",
        "@npmcli/arborist": "^8.0.1",
        "@npmcli/config": "^9.0.0",
        "@npmcli/fs": "^4.0.0",
        "@npmcli/map-workspaces": "^4.0.2",
        "@npmcli/package-json": "^6.2.0",
        "@npmcli/promise-spawn": "^8.0.2",
        "@npmcli/redact": "^3.2.2",
        "@npmcli/run-script": "^9.1.0",
        "@sigstore/tuf": "^3.1.1",
        "abbrev": "^3.0.1",
        "archy": "~1.0.0",
        "cacache": "^19.0.1",
        "chalk": "^5.4.1",
        "ci-info": "^4.2.0",
        "cli-columns": "^4.0.0",
        "fastest-levenshtein": "^1.0.16",
        "fs-minipass": "^3.0.3",
        "glob": "^10.4.5",
        "graceful-fs": "^4.2.11",
        "hosted-git-info": "^8.1.0",
        "ini": "^5.0.0",
        "init-package-json": "^7.0.2",
        "is-cidr": "^5.1.1",
        "json-parse-even-better-errors": "^4.0.0",
        "libnpmaccess": "^9.0.0",
        "libnpmdiff": "^7.0.1",
        "libnpmexec": "^9.0.1",
        "libnpmfund": "^6.0.1",
        "libnpmhook": "^11.0.0",
        "libnpmorg": "^7.0.0",
        "libnpmpack": "^8.0.1",
        "libnpmpublish": "^10.0.1",
        "libnpmsearch": "^8.0.0",
        "libnpmteam": "^7.0.0",
        "libnpmversion": "^7.0.0",
        "make-fetch-happen": "^14.0.3",
        "minimatch": "^9.0.5",
        "minipass": "^7.1.1",
        "minipass-pipeline": "^1.2.4",
        "ms": "^2.1.2",
        "node-gyp": "^11.2.0",
        "nopt": "^8.1.0",
        "normalize-package-data": "^7.0.0",
        "npm-audit-report": "^6.0.0",
        "npm-install-checks": "^7.1.1",
        "npm-package-arg": "^12.0.2",
        "npm-pick-manifest": "^10.0.0",
        "npm-profile": "^11.0.1",
        "npm-registry-fetch": "^18.0.2",
        "npm-user-validate": "^3.0.0",
        "p-map": "^7.0.3",
        "pacote": "^19.0.1",
        "parse-conflict-json": "^4.0.0",
        "proc-log": "^5.0.0",
        "qrcode-terminal": "^0.12.0",
        "read": "^4.1.0",
        "semver": "^7.7.2",
        "spdx-expression-parse": "^4.0.0",
        "ssri": "^12.0.0",
        "supports-color": "^9.4.0",
        "tar": "^6.2.1",
        "text-table": "~0.2.0",
        "tiny-relative-date": "^1.3.0",
        "treeverse": "^3.0.0",
        "validate-npm-package-name": "^6.0.1",
        "which": "^5.0.0",
        "write-file-atomic": "^6.0.0"
      },
      "dependencies": {
        "@isaacs/cliui": {
          "version": "8.0.2",
          "bundled": true,
          "dev": true,
          "requires": {
            "string-width": "^5.1.2",
            "string-width-cjs": "npm:string-width@^4.2.0",
            "strip-ansi": "^7.0.1",
            "strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
            "wrap-ansi": "^8.1.0",
            "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
          },
          "dependencies": {
            "ansi-regex": {
              "version": "6.1.0",
              "bundled": true,
              "dev": true
            },
            "emoji-regex": {
              "version": "9.2.2",
              "bundled": true,
              "dev": true
            },
            "string-width": {
              "version": "5.1.2",
              "bundled": true,
              "dev": true,
              "requires": {
                "eastasianwidth": "^0.2.0",
                "emoji-regex": "^9.2.2",
                "strip-ansi": "^7.0.1"
              }
            },
            "strip-ansi": {
              "version": "7.1.0",
              "bundled": true,
              "dev": true,
              "requires": {
                "ansi-regex": "^6.0.1"
              }
            }
          }
        },
        "@isaacs/fs-minipass": {
          "version": "4.0.1",
          "bundled": true,
          "dev": true,
          "requires": {
            "minipass": "^7.0.4"
          }
        },
        "@isaacs/string-locale-compare": {
          "version": "1.1.0",
          "bundled": true,
          "dev": true
        },
        "@npmcli/agent": {
          "version": "3.0.0",
          "bundled": true,
          "dev": true,
          "requires": {
            "agent-base": "^7.1.0",
            "http-proxy-agent": "^7.0.0",
            "https-proxy-agent": "^7.0.1",
            "lru-cache": "^10.0.1",
            "socks-proxy-agent": "^8.0.3"
          }
        },
        "@npmcli/arborist": {
          "version": "8.0.1",
          "bundled": true,
          "dev": true,
          "requires": {
            "@isaacs/string-locale-compare": "^1.1.0",
            "@npmcli/fs": "^4.0.0",
            "@npmcli/installed-package-contents": "^3.0.0",
            "@npmcli/map-workspaces": "^4.0.1",
            "@npmcli/metavuln-calculator": "^8.0.0",
            "@npmcli/name-from-folder": "^3.0.0",
            "@npmcli/node-gyp": "^4.0.0",
            "@npmcli/package-json": "^6.0.1",
            "@npmcli/query": "^4.0.0",
            "@npmcli/redact": "^3.0.0",
            "@npmcli/run-script": "^9.0.1",
            "bin-links": "^5.0.0",
            "cacache": "^19.0.1",
            "common-ancestor-path": "^1.0.1",
            "hosted-git-info": "^8.0.0",
            "json-parse-even-better-errors": "^4.0.0",
            "json-stringify-nice": "^1.1.4",
            "lru-cache": "^10.2.2",
            "minimatch": "^9.0.4",
            "nopt": "^8.0.0",
            "npm-install-checks": "^7.1.0",
            "npm-package-arg": "^12.0.0",
            "npm-pick-manifest": "^10.0.0",
            "npm-registry-fetch": "^18.0.1",
            "pacote": "^19.0.0",
            "parse-conflict-json": "^4.0.0",
            "proc-log": "^5.0.0",
            "proggy": "^3.0.0",
            "promise-all-reject-late": "^1.0.0",
            "promise-call-limit": "^3.0.1",
            "read-package-json-fast": "^4.0.0",
            "semver": "^7.3.7",
            "ssri": "^12.0.0",
            "treeverse": "^3.0.0",
            "walk-up-path": "^3.0.1"
          }
        },
        "@npmcli/config": {
          "version": "9.0.0",
          "bundled": true,
          "dev": true,
          "requires": {
            "@npmcli/map-workspaces": "^4.0.1",
            "@npmcli/package-json": "^6.0.1",
            "ci-info": "^4.0.0",
            "ini": "^5.0.0",
            "nopt": "^8.0.0",
            "proc-log": "^5.0.0",
            "semver": "^7.3.5",
            "walk-up-path": "^3.0.1"
          }
        },
        "@npmcli/fs": {
          "version": "4.0.0",
          "bundled": true,
          "dev": true,
          "requires": {
            "semver": "^7.3.5"
          }
        },
        "@npmcli/git": {
          "version": "6.0.3",
          "bundled": true,
          "dev": true,
          "requires": {
            "@npmcli/promise-spawn": "^8.0.0",
            "ini": "^5.0.0",
            "lru-cache": "^10.0.1",
            "npm-pick-manifest": "^10.0.0",
            "proc-log": "^5.0.0",
            "promise-retry": "^2.0.1",
            "semver": "^7.3.5",
            "which": "^5.0.0"
          }
        },
        "@npmcli/installed-package-contents": {
          "version": "3.0.0",
          "bundled": true,
          "dev": true,
          "requires": {
            "npm-bundled": "^4.0.0",
            "npm-normalize-package-bin": "^4.0.0"
          }
        },
        "@npmcli/map-workspaces": {
          "version": "4.0.2",
          "bundled": true,
          "dev": true,
          "requires": {
            "@npmcli/name-from-folder": "^3.0.0",
            "@npmcli/package-json": "^6.0.0",
            "glob": "^10.2.2",
            "minimatch": "^9.0.0"
          }
        },
        "@npmcli/metavuln-calculator": {
          "version": "8.0.1",
          "bundled": true,
          "dev": true,
          "requires": {
            "cacache": "^19.0.0",
            "json-parse-even-better-errors": "^4.0.0",
            "pacote": "^20.0.0",
            "proc-log": "^5.0.0",
            "semver": "^7.3.5"
          },
          "dependencies": {
            "pacote": {
              "version": "20.0.0",
              "bundled": true,
              "dev": true,
              "requires": {
                "@npmcli/git": "^6.0.0",
                "@npmcli/installed-package-contents": "^3.0.0",
                "@npmcli/package-json": "^6.0.0",
                "@npmcli/promise-spawn": "^8.0.0",
                "@npmcli/run-script": "^9.0.0",
                "cacache": "^19.0.0",
                "fs-minipass": "^3.0.0",
                "minipass": "^7.0.2",
                "npm-package-arg": "^12.0.0",
                "npm-packlist": "^9.0.0",
                "npm-pick-manifest": "^10.0.0",
                "npm-registry-fetch": "^18.0.0",
                "proc-log": "^5.0.0",
                "promise-retry": "^2.0.1",
                "sigstore": "^3.0.0",
                "ssri": "^12.0.0",
                "tar": "^6.1.11"
              }
            }
          }
        },
        "@npmcli/name-from-folder": {
          "version": "3.0.0",
          "bundled": true,
          "dev": true
        },
        "@npmcli/node-gyp": {
          "version": "4.0.0",
          "bundled": true,
          "dev": true
        },
        "@npmcli/package-json": {
          "version": "6.2.0",
          "bundled": true,
          "dev": true,
          "requires": {
            "@npmcli/git": "^6.0.0",
            "glob": "^10.2.2",
            "hosted-git-info": "^8.0.0",
            "json-parse-even-better-errors": "^4.0.0",
            "proc-log": "^5.0.0",
            "semver": "^7.5.3",
            "validate-npm-package-license": "^3.0.4"
          }
        },
        "@npmcli/promise-spawn": {
          "version": "8.0.2",
          "bundled": true,
          "dev": true,
          "requires": {
            "which": "^5.0.0"
          }
        },
        "@npmcli/query": {
          "version": "4.0.1",
          "bundled": true,
          "dev": true,
          "requires": {
            "postcss-selector-parser": "^7.0.0"
          }
        },
        "@npmcli/redact": {
          "version": "3.2.2",
          "bundled": true,
          "dev": true
        },
        "@npmcli/run-script": {
          "version": "9.1.0",
          "bundled": true,
          "dev": true,
          "requires": {
            "@npmcli/node-gyp": "^4.0.0",
            "@npmcli/package-json": "^6.0.0",
            "@npmcli/promise-spawn": "^8.0.0",
            "node-gyp": "^11.0.0",
            "proc-log": "^5.0.0",
            "which": "^5.0.0"
          }
        },
        "@pkgjs/parseargs": {
          "version": "0.11.0",
          "bundled": true,
          "dev": true,
          "optional": true
        },
        "@sigstore/protobuf-specs": {
          "version": "0.4.3",
          "bundled": true,
          "dev": true
        },
        "@sigstore/tuf": {
          "version": "3.1.1",
          "bundled": true,
          "dev": true,
          "requires": {
            "@sigstore/protobuf-specs": "^0.4.1",
            "tuf-js": "^3.0.1"
          }
        },
        "@tufjs/canonical-json": {
          "version": "2.0.0",
          "bundled": true,
          "dev": true
        },
        "abbrev": {
          "version": "3.0.1",
          "bundled": true,
          "dev": true
        },
        "agent-base": {
          "version": "7.1.3",
          "bundled": true,
          "dev": true
        },
        "ansi-regex": {
          "version": "5.0.1",
          "bundled": true,
          "dev": true
        },
        "ansi-styles": {
          "version": "6.2.1",
          "bundled": true,
          "dev": true
        },
        "aproba": {
          "version": "2.0.0",
          "bundled": true,
          "dev": true
        },
        "archy": {
          "version": "1.0.0",
          "bundled": true,
          "dev": true
        },
        "balanced-match": {
          "version": "1.0.2",
          "bundled": true,
          "dev": true
        },
        "bin-links": {
          "version": "5.0.0",
          "bundled": true,
          "dev": true,
          "requires": {
            "cmd-shim": "^7.0.0",
            "npm-normalize-package-bin": "^4.0.0",
            "proc-log": "^5.0.0",
            "read-cmd-shim": "^5.0.0",
            "write-file-atomic": "^6.0.0"
          }
        },
        "binary-extensions": {
          "version": "2.3.0",
          "bundled": true,
          "dev": true
        },
        "brace-expansion": {
          "version": "2.0.2",
          "bundled": true,
          "dev": true,
          "requires": {
            "balanced-match": "^1.0.0"
          }
        },
        "cacache": {
          "version": "19.0.1",
          "bundled": true,
          "dev": true,
          "requires": {
            "@npmcli/fs": "^4.0.0",
            "fs-minipass": "^3.0.0",
            "glob": "^10.2.2",
            "lru-cache": "^10.0.1",
            "minipass": "^7.0.3",
            "minipass-collect": "^2.0.1",
            "minipass-flush": "^1.0.5",
            "minipass-pipeline": "^1.2.4",
            "p-map": "^7.0.2",
            "ssri": "^12.0.0",
            "tar": "^7.4.3",
            "unique-filename": "^4.0.0"
          },
          "dependencies": {
            "chownr": {
              "version": "3.0.0",
              "bundled": true,
              "dev": true
            },
            "mkdirp": {
              "version": "3.0.1",
              "bundled": true,
              "dev": true
            },
            "tar": {
              "version": "7.4.3",
              "bundled": true,
              "dev": true,
              "requires": {
                "@isaacs/fs-minipass": "^4.0.0",
                "chownr": "^3.0.0",
                "minipass": "^7.1.2",
                "minizlib": "^3.0.1",
                "mkdirp": "^3.0.1",
                "yallist": "^5.0.0"
              }
            },
            "yallist": {
              "version": "5.0.0",
              "bundled": true,
              "dev": true
            }
          }
        },
        "chalk": {
          "version": "5.4.1",
          "bundled": true,
          "dev": true
        },
        "chownr": {
          "version": "2.0.0",
          "bundled": true,
          "dev": true
        },
        "ci-info": {
          "version": "4.2.0",
          "bundled": true,
          "dev": true
        },
        "cidr-regex": {
          "version": "4.1.3",
          "bundled": true,
          "dev": true,
          "requires": {
            "ip-regex": "^5.0.0"
          }
        },
        "cli-columns": {
          "version": "4.0.0",
          "bundled": true,
          "dev": true,
          "requires": {
            "string-width": "^4.2.3",
            "strip-ansi": "^6.0.1"
          }
        },
        "cmd-shim": {
          "version": "7.0.0",
          "bundled": true,
          "dev": true
        },
        "color-convert": {
          "version": "2.0.1",
          "bundled": true,
          "dev": true,
          "requires": {
            "color-name": "~1.1.4"
          }
        },
        "color-name": {
          "version": "1.1.4",
          "bundled": true,
          "dev": true
        },
        "common-ancestor-path": {
          "version": "1.0.1",
          "bundled": true,
          "dev": true
        },
        "cross-spawn": {
          "version": "7.0.6",
          "bundled": true,
          "dev": true,
          "requires": {
            "path-key": "^3.1.0",
            "shebang-command": "^2.0.0",
            "which": "^2.0.1"
          },
          "dependencies": {
            "which": {
              "version": "2.0.2",
              "bundled": true,
              "dev": true,
              "requires": {
                "isexe": "^2.0.0"
              }
            }
          }
        },
        "cssesc": {
          "version": "3.0.0",
          "bundled": true,
          "dev": true
        },
        "debug": {
          "version": "4.4.1",
          "bundled": true,
          "dev": true,
          "requires": {
            "ms": "^2.1.3"
          }
        },
        "diff": {
          "version": "5.2.0",
          "bundled": true,
          "dev": true
        },
        "eastasianwidth": {
          "version": "0.2.0",
          "bundled": true,
          "dev": true
        },
        "emoji-regex": {
          "version": "8.0.0",
          "bundled": true,
          "dev": true
        },
        "encoding": {
          "version": "0.1.13",
          "bundled": true,
          "dev": true,
          "optional": true,
          "requires": {
            "iconv-lite": "^0.6.2"
          }
        },
        "env-paths": {
          "version": "2.2.1",
          "bundled": true,
          "dev": true
        },
        "err-code": {
          "version": "2.0.3",
          "bundled": true,
          "dev": true
        },
        "exponential-backoff": {
          "version": "3.1.2",
          "bundled": true,
          "dev": true
        },
        "fastest-levenshtein": {
          "version": "1.0.16",
          "bundled": true,
          "dev": true
        },
        "foreground-child": {
          "version": "3.3.1",
          "bundled": true,
          "dev": true,
          "requires": {
            "cross-spawn": "^7.0.6",
            "signal-exit": "^4.0.1"
          }
        },
        "fs-minipass": {
          "version": "3.0.3",
          "bundled": true,
          "dev": true,
          "requires": {
            "minipass": "^7.0.3"
          }
        },
        "glob": {
          "version": "10.4.5",
          "bundled": true,
          "dev": true,
          "requires": {
            "foreground-child": "^3.1.0",
            "jackspeak": "^3.1.2",
            "minimatch": "^9.0.4",
            "minipass": "^7.1.2",
            "package-json-from-dist": "^1.0.0",
            "path-scurry": "^1.11.1"
          }
        },
        "graceful-fs": {
          "version": "4.2.11",
          "bundled": true,
          "dev": true
        },
        "hosted-git-info": {
          "version": "8.1.0",
          "bundled": true,
          "dev": true,
          "requires": {
            "lru-cache": "^10.0.1"
          }
        },
        "http-cache-semantics": {
          "version": "4.2.0",
          "bundled": true,
          "dev": true
        },
        "http-proxy-agent": {
          "version": "7.0.2",
          "bundled": true,
          "dev": true,
          "requires": {
            "agent-base": "^7.1.0",
            "debug": "^4.3.4"
          }
        },
        "https-proxy-agent": {
          "version": "7.0.6",
          "bundled": true,
          "dev": true,
          "requires": {
            "agent-base": "^7.1.2",
            "debug": "4"
          }
        },
        "iconv-lite": {
          "version": "0.6.3",
          "bundled": true,
          "dev": true,
          "optional": true,
          "requires": {
            "safer-buffer": ">= 2.1.2 < 3.0.0"
          }
        },
        "ignore-walk": {
          "version": "7.0.0",
          "bundled": true,
          "dev": true,
          "requires": {
            "minimatch": "^9.0.0"
          }
        },
        "imurmurhash": {
          "version": "0.1.4",
          "bundled": true,
          "dev": true
        },
        "ini": {
          "version": "5.0.0",
          "bundled": true,
          "dev": true
        },
        "init-package-json": {
          "version": "7.0.2",
          "bundled": true,
          "dev": true,
          "requires": {
            "@npmcli/package-json": "^6.0.0",
            "npm-package-arg": "^12.0.0",
            "promzard": "^2.0.0",
            "read": "^4.0.0",
            "semver": "^7.3.5",
            "validate-npm-package-license": "^3.0.4",
            "validate-npm-package-name": "^6.0.0"
          }
        },
        "ip-address": {
          "version": "9.0.5",
          "bundled": true,
          "dev": true,
          "requires": {
            "jsbn": "1.1.0",
            "sprintf-js": "^1.1.3"
          }
        },
        "ip-regex": {
          "version": "5.0.0",
          "bundled": true,
          "dev": true
        },
        "is-cidr": {
          "version": "5.1.1",
          "bundled": true,
          "dev": true,
          "requires": {
            "cidr-regex": "^4.1.1"
          }
        },
        "is-fullwidth-code-point": {
          "version": "3.0.0",
          "bundled": true,
          "dev": true
        },
        "isexe": {
          "version": "2.0.0",
          "bundled": true,
          "dev": true
        },
        "jackspeak": {
          "version": "3.4.3",
          "bundled": true,
          "dev": true,
          "requires": {
            "@isaacs/cliui": "^8.0.2",
            "@pkgjs/parseargs": "^0.11.0"
          }
        },
        "jsbn": {
          "version": "1.1.0",
          "bundled": true,
          "dev": true
        },
        "json-parse-even-better-errors": {
          "version": "4.0.0",
          "bundled": true,
          "dev": true
        },
        "json-stringify-nice": {
          "version": "1.1.4",
          "bundled": true,
          "dev": true
        },
        "jsonparse": {
          "version": "1.3.1",
          "bundled": true,
          "dev": true
        },
        "just-diff": {
          "version": "6.0.2",
          "bundled": true,
          "dev": true
        },
        "just-diff-apply": {
          "version": "5.5.0",
          "bundled": true,
          "dev": true
        },
        "libnpmaccess": {
          "version": "9.0.0",
          "bundled": true,
          "dev": true,
          "requires": {
            "npm-package-arg": "^12.0.0",
            "npm-registry-fetch": "^18.0.1"
          }
        },
        "libnpmdiff": {
          "version": "7.0.1",
          "bundled": true,
          "dev": true,
          "requires": {
            "@npmcli/arborist": "^8.0.1",
            "@npmcli/installed-package-contents": "^3.0.0",
            "binary-extensions": "^2.3.0",
            "diff": "^5.1.0",
            "minimatch": "^9.0.4",
            "npm-package-arg": "^12.0.0",
            "pacote": "^19.0.0",
            "tar": "^6.2.1"
          }
        },
        "libnpmexec": {
          "version": "9.0.1",
          "bundled": true,
          "dev": true,
          "requires": {
            "@npmcli/arborist": "^8.0.1",
            "@npmcli/run-script": "^9.0.1",
            "ci-info": "^4.0.0",
            "npm-package-arg": "^12.0.0",
            "pacote": "^19.0.0",
            "proc-log": "^5.0.0",
            "read": "^4.0.0",
            "read-package-json-fast": "^4.0.0",
            "semver": "^7.3.7",
            "walk-up-path": "^3.0.1"
          }
        },
        "libnpmfund": {
          "version": "6.0.1",
          "bundled": true,
          "dev": true,
          "requires": {
            "@npmcli/arborist": "^8.0.1"
          }
        },
        "libnpmhook": {
          "version": "11.0.0",
          "bundled": true,
          "dev": true,
          "requires": {
            "aproba": "^2.0.0",
            "npm-registry-fetch": "^18.0.1"
          }
        },
        "libnpmorg": {
          "version": "7.0.0",
          "bundled": true,
          "dev": true,
          "requires": {
            "aproba": "^2.0.0",
            "npm-registry-fetch": "^18.0.1"
          }
        },
        "libnpmpack": {
          "version": "8.0.1",
          "bundled": true,
          "dev": true,
          "requires": {
            "@npmcli/arborist": "^8.0.1",
            "@npmcli/run-script": "^9.0.1",
            "npm-package-arg": "^12.0.0",
            "pacote": "^19.0.0"
          }
        },
        "libnpmpublish": {
          "version": "10.0.1",
          "bundled": true,
          "dev": true,
          "requires": {
            "ci-info": "^4.0.0",
            "normalize-package-data": "^7.0.0",
            "npm-package-arg": "^12.0.0",
            "npm-registry-fetch": "^18.0.1",
            "proc-log": "^5.0.0",
            "semver": "^7.3.7",
            "sigstore": "^3.0.0",
            "ssri": "^12.0.0"
          }
        },
        "libnpmsearch": {
          "version": "8.0.0",
          "bundled": true,
          "dev": true,
          "requires": {
            "npm-registry-fetch": "^18.0.1"
          }
        },
        "libnpmteam": {
          "version": "7.0.0",
          "bundled": true,
          "dev": true,
          "requires": {
            "aproba": "^2.0.0",
            "npm-registry-fetch": "^18.0.1"
          }
        },
        "libnpmversion": {
          "version": "7.0.0",
          "bundled": true,
          "dev": true,
          "requires": {
            "@npmcli/git": "^6.0.1",
            "@npmcli/run-script": "^9.0.1",
            "json-parse-even-better-errors": "^4.0.0",
            "proc-log": "^5.0.0",
            "semver": "^7.3.7"
          }
        },
        "lru-cache": {
          "version": "10.4.3",
          "bundled": true,
          "dev": true
        },
        "make-fetch-happen": {
          "version": "14.0.3",
          "bundled": true,
          "dev": true,
          "requires": {
            "@npmcli/agent": "^3.0.0",
            "cacache": "^19.0.1",
            "http-cache-semantics": "^4.1.1",
            "minipass": "^7.0.2",
            "minipass-fetch": "^4.0.0",
            "minipass-flush": "^1.0.5",
            "minipass-pipeline": "^1.2.4",
            "negotiator": "^1.0.0",
            "proc-log": "^5.0.0",
            "promise-retry": "^2.0.1",
            "ssri": "^12.0.0"
          },
          "dependencies": {
            "negotiator": {
              "version": "1.0.0",
              "bundled": true,
              "dev": true
            }
          }
        },
        "minimatch": {
          "version": "9.0.5",
          "bundled": true,
          "dev": true,
          "requires": {
            "brace-expansion": "^2.0.1"
          }
        },
        "minipass": {
          "version": "7.1.2",
          "bundled": true,
          "dev": true
        },
        "minipass-collect": {
          "version": "2.0.1",
          "bundled": true,
          "dev": true,
          "requires": {
            "minipass": "^7.0.3"
          }
        },
        "minipass-fetch": {
          "version": "4.0.1",
          "bundled": true,
          "dev": true,
          "requires": {
            "encoding": "^0.1.13",
            "minipass": "^7.0.3",
            "minipass-sized": "^1.0.3",
            "minizlib": "^3.0.1"
          }
        },
        "minipass-flush": {
          "version": "1.0.5",
          "bundled": true,
          "dev": true,
          "requires": {
            "minipass": "^3.0.0"
          },
          "dependencies": {
            "minipass": {
              "version": "3.3.6",
              "bundled": true,
              "dev": true,
              "requires": {
                "yallist": "^4.0.0"
              }
            }
          }
        },
        "minipass-pipeline": {
          "version": "1.2.4",
          "bundled": true,
          "dev": true,
          "requires": {
            "minipass": "^3.0.0"
          },
          "dependencies": {
            "minipass": {
              "version": "3.3.6",
              "bundled": true,
              "dev": true,
              "requires": {
                "yallist": "^4.0.0"
              }
            }
          }
        },
        "minipass-sized": {
          "version": "1.0.3",
          "bundled": true,
          "dev": true,
          "requires": {
            "minipass": "^3.0.0"
          },
          "dependencies": {
            "minipass": {
              "version": "3.3.6",
              "bundled": true,
              "dev": true,
              "requires": {
                "yallist": "^4.0.0"
              }
            }
          }
        },
        "minizlib": {
          "version": "3.0.2",
          "bundled": true,
          "dev": true,
          "requires": {
            "minipass": "^7.1.2"
          }
        },
        "mkdirp": {
          "version": "1.0.4",
          "bundled": true,
          "dev": true
        },
        "ms": {
          "version": "2.1.3",
          "bundled": true,
          "dev": true
        },
        "mute-stream": {
          "version": "2.0.0",
          "bundled": true,
          "dev": true
        },
        "node-gyp": {
          "version": "11.2.0",
          "bundled": true,
          "dev": true,
          "requires": {
            "env-paths": "^2.2.0",
            "exponential-backoff": "^3.1.1",
            "graceful-fs": "^4.2.6",
            "make-fetch-happen": "^14.0.3",
            "nopt": "^8.0.0",
            "proc-log": "^5.0.0",
            "semver": "^7.3.5",
            "tar": "^7.4.3",
            "tinyglobby": "^0.2.12",
            "which": "^5.0.0"
          },
          "dependencies": {
            "chownr": {
              "version": "3.0.0",
              "bundled": true,
              "dev": true
            },
            "mkdirp": {
              "version": "3.0.1",
              "bundled": true,
              "dev": true
            },
            "tar": {
              "version": "7.4.3",
              "bundled": true,
              "dev": true,
              "requires": {
                "@isaacs/fs-minipass": "^4.0.0",
                "chownr": "^3.0.0",
                "minipass": "^7.1.2",
                "minizlib": "^3.0.1",
                "mkdirp": "^3.0.1",
                "yallist": "^5.0.0"
              }
            },
            "yallist": {
              "version": "5.0.0",
              "bundled": true,
              "dev": true
            }
          }
        },
        "nopt": {
          "version": "8.1.0",
          "bundled": true,
          "dev": true,
          "requires": {
            "abbrev": "^3.0.0"
          }
        },
        "normalize-package-data": {
          "version": "7.0.0",
          "bundled": true,
          "dev": true,
          "requires": {
            "hosted-git-info": "^8.0.0",
            "semver": "^7.3.5",
            "validate-npm-package-license": "^3.0.4"
          }
        },
        "npm-audit-report": {
          "version": "6.0.0",
          "bundled": true,
          "dev": true
        },
        "npm-bundled": {
          "version": "4.0.0",
          "bundled": true,
          "dev": true,
          "requires": {
            "npm-normalize-package-bin": "^4.0.0"
          }
        },
        "npm-install-checks": {
          "version": "7.1.1",
          "bundled": true,
          "dev": true,
          "requires": {
            "semver": "^7.1.1"
          }
        },
        "npm-normalize-package-bin": {
          "version": "4.0.0",
          "bundled": true,
          "dev": true
        },
        "npm-package-arg": {
          "version": "12.0.2",
          "bundled": true,
          "dev": true,
          "requires": {
            "hosted-git-info": "^8.0.0",
            "proc-log": "^5.0.0",
            "semver": "^7.3.5",
            "validate-npm-package-name": "^6.0.0"
          }
        },
        "npm-packlist": {
          "version": "9.0.0",
          "bundled": true,
          "dev": true,
          "requires": {
            "ignore-walk": "^7.0.0"
          }
        },
        "npm-pick-manifest": {
          "version": "10.0.0",
          "bundled": true,
          "dev": true,
          "requires": {
            "npm-install-checks": "^7.1.0",
            "npm-normalize-package-bin": "^4.0.0",
            "npm-package-arg": "^12.0.0",
            "semver": "^7.3.5"
          }
        },
        "npm-profile": {
          "version": "11.0.1",
          "bundled": true,
          "dev": true,
          "requires": {
            "npm-registry-fetch": "^18.0.0",
            "proc-log": "^5.0.0"
          }
        },
        "npm-registry-fetch": {
          "version": "18.0.2",
          "bundled": true,
          "dev": true,
          "requires": {
            "@npmcli/redact": "^3.0.0",
            "jsonparse": "^1.3.1",
            "make-fetch-happen": "^14.0.0",
            "minipass": "^7.0.2",
            "minipass-fetch": "^4.0.0",
            "minizlib": "^3.0.1",
            "npm-package-arg": "^12.0.0",
            "proc-log": "^5.0.0"
          }
        },
        "npm-user-validate": {
          "version": "3.0.0",
          "bundled": true,
          "dev": true
        },
        "p-map": {
          "version": "7.0.3",
          "bundled": true,
          "dev": true
        },
        "package-json-from-dist": {
          "version": "1.0.1",
          "bundled": true,
          "dev": true
        },
        "pacote": {
          "version": "19.0.1",
          "bundled": true,
          "dev": true,
          "requires": {
            "@npmcli/git": "^6.0.0",
            "@npmcli/installed-package-contents": "^3.0.0",
            "@npmcli/package-json": "^6.0.0",
            "@npmcli/promise-spawn": "^8.0.0",
            "@npmcli/run-script": "^9.0.0",
            "cacache": "^19.0.0",
            "fs-minipass": "^3.0.0",
            "minipass": "^7.0.2",
            "npm-package-arg": "^12.0.0",
            "npm-packlist": "^9.0.0",
            "npm-pick-manifest": "^10.0.0",
            "npm-registry-fetch": "^18.0.0",
            "proc-log": "^5.0.0",
            "promise-retry": "^2.0.1",
            "sigstore": "^3.0.0",
            "ssri": "^12.0.0",
            "tar": "^6.1.11"
          }
        },
        "parse-conflict-json": {
          "version": "4.0.0",
          "bundled": true,
          "dev": true,
          "requires": {
            "json-parse-even-better-errors": "^4.0.0",
            "just-diff": "^6.0.0",
            "just-diff-apply": "^5.2.0"
          }
        },
        "path-key": {
          "version": "3.1.1",
          "bundled": true,
          "dev": true
        },
        "path-scurry": {
          "version": "1.11.1",
          "bundled": true,
          "dev": true,
          "requires": {
            "lru-cache": "^10.2.0",
            "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
          }
        },
        "postcss-selector-parser": {
          "version": "7.1.0",
          "bundled": true,
          "dev": true,
          "requires": {
            "cssesc": "^3.0.0",
            "util-deprecate": "^1.0.2"
          }
        },
        "proc-log": {
          "version": "5.0.0",
          "bundled": true,
          "dev": true
        },
        "proggy": {
          "version": "3.0.0",
          "bundled": true,
          "dev": true
        },
        "promise-all-reject-late": {
          "version": "1.0.1",
          "bundled": true,
          "dev": true
        },
        "promise-call-limit": {
          "version": "3.0.2",
          "bundled": true,
          "dev": true
        },
        "promise-retry": {
          "version": "2.0.1",
          "bundled": true,
          "dev": true,
          "requires": {
            "err-code": "^2.0.2",
            "retry": "^0.12.0"
          }
        },
        "promzard": {
          "version": "2.0.0",
          "bundled": true,
          "dev": true,
          "requires": {
            "read": "^4.0.0"
          }
        },
        "qrcode-terminal": {
          "version": "0.12.0",
          "bundled": true,
          "dev": true
        },
        "read": {
          "version": "4.1.0",
          "bundled": true,
          "dev": true,
          "requires": {
            "mute-stream": "^2.0.0"
          }
        },
        "read-cmd-shim": {
          "version": "5.0.0",
          "bundled": true,
          "dev": true
        },
        "read-package-json-fast": {
          "version": "4.0.0",
          "bundled": true,
          "dev": true,
          "requires": {
            "json-parse-even-better-errors": "^4.0.0",
            "npm-normalize-package-bin": "^4.0.0"
          }
        },
        "retry": {
          "version": "0.12.0",
          "bundled": true,
          "dev": true
        },
        "safer-buffer": {
          "version": "2.1.2",
          "bundled": true,
          "dev": true,
          "optional": true
        },
        "semver": {
          "version": "7.7.2",
          "bundled": true,
          "dev": true
        },
        "shebang-command": {
          "version": "2.0.0",
          "bundled": true,
          "dev": true,
          "requires": {
            "shebang-regex": "^3.0.0"
          }
        },
        "shebang-regex": {
          "version": "3.0.0",
          "bundled": true,
          "dev": true
        },
        "signal-exit": {
          "version": "4.1.0",
          "bundled": true,
          "dev": true
        },
        "sigstore": {
          "version": "3.1.0",
          "bundled": true,
          "dev": true,
          "requires": {
            "@sigstore/bundle": "^3.1.0",
            "@sigstore/core": "^2.0.0",
            "@sigstore/protobuf-specs": "^0.4.0",
            "@sigstore/sign": "^3.1.0",
            "@sigstore/tuf": "^3.1.0",
            "@sigstore/verify": "^2.1.0"
          },
          "dependencies": {
            "@sigstore/bundle": {
              "version": "3.1.0",
              "bundled": true,
              "dev": true,
              "requires": {
                "@sigstore/protobuf-specs": "^0.4.0"
              }
            },
            "@sigstore/core": {
              "version": "2.0.0",
              "bundled": true,
              "dev": true
            },
            "@sigstore/sign": {
              "version": "3.1.0",
              "bundled": true,
              "dev": true,
              "requires": {
                "@sigstore/bundle": "^3.1.0",
                "@sigstore/core": "^2.0.0",
                "@sigstore/protobuf-specs": "^0.4.0",
                "make-fetch-happen": "^14.0.2",
                "proc-log": "^5.0.0",
                "promise-retry": "^2.0.1"
              }
            },
            "@sigstore/verify": {
              "version": "2.1.1",
              "bundled": true,
              "dev": true,
              "requires": {
                "@sigstore/bundle": "^3.1.0",
                "@sigstore/core": "^2.0.0",
                "@sigstore/protobuf-specs": "^0.4.1"
              }
            }
          }
        },
        "smart-buffer": {
          "version": "4.2.0",
          "bundled": true,
          "dev": true
        },
        "socks": {
          "version": "2.8.5",
          "bundled": true,
          "dev": true,
          "requires": {
            "ip-address": "^9.0.5",
            "smart-buffer": "^4.2.0"
          }
        },
        "socks-proxy-agent": {
          "version": "8.0.5",
          "bundled": true,
          "dev": true,
          "requires": {
            "agent-base": "^7.1.2",
            "debug": "^4.3.4",
            "socks": "^2.8.3"
          }
        },
        "spdx-correct": {
          "version": "3.2.0",
          "bundled": true,
          "dev": true,
          "requires": {
            "spdx-expression-parse": "^3.0.0",
            "spdx-license-ids": "^3.0.0"
          },
          "dependencies": {
            "spdx-expression-parse": {
              "version": "3.0.1",
              "bundled": true,
              "dev": true,
              "requires": {
                "spdx-exceptions": "^2.1.0",
                "spdx-license-ids": "^3.0.0"
              }
            }
          }
        },
        "spdx-exceptions": {
          "version": "2.5.0",
          "bundled": true,
          "dev": true
        },
        "spdx-expression-parse": {
          "version": "4.0.0",
          "bundled": true,
          "dev": true,
          "requires": {
            "spdx-exceptions": "^2.1.0",
            "spdx-license-ids": "^3.0.0"
          }
        },
        "spdx-license-ids": {
          "version": "3.0.21",
          "bundled": true,
          "dev": true
        },
        "sprintf-js": {
          "version": "1.1.3",
          "bundled": true,
          "dev": true
        },
        "ssri": {
          "version": "12.0.0",
          "bundled": true,
          "dev": true,
          "requires": {
            "minipass": "^7.0.3"
          }
        },
        "string-width": {
          "version": "4.2.3",
          "bundled": true,
          "dev": true,
          "requires": {
            "emoji-regex": "^8.0.0",
            "is-fullwidth-code-point": "^3.0.0",
            "strip-ansi": "^6.0.1"
          }
        },
        "string-width-cjs": {
          "version": "npm:string-width@4.2.3",
          "bundled": true,
          "dev": true,
          "requires": {
            "emoji-regex": "^8.0.0",
            "is-fullwidth-code-point": "^3.0.0",
            "strip-ansi": "^6.0.1"
          }
        },
        "strip-ansi": {
          "version": "6.0.1",
          "bundled": true,
          "dev": true,
          "requires": {
            "ansi-regex": "^5.0.1"
          }
        },
        "strip-ansi-cjs": {
          "version": "npm:strip-ansi@6.0.1",
          "bundled": true,
          "dev": true,
          "requires": {
            "ansi-regex": "^5.0.1"
          }
        },
        "supports-color": {
          "version": "9.4.0",
          "bundled": true,
          "dev": true
        },
        "tar": {
          "version": "6.2.1",
          "bundled": true,
          "dev": true,
          "requires": {
            "chownr": "^2.0.0",
            "fs-minipass": "^2.0.0",
            "minipass": "^5.0.0",
            "minizlib": "^2.1.1",
            "mkdirp": "^1.0.3",
            "yallist": "^4.0.0"
          },
          "dependencies": {
            "fs-minipass": {
              "version": "2.1.0",
              "bundled": true,
              "dev": true,
              "requires": {
                "minipass": "^3.0.0"
              },
              "dependencies": {
                "minipass": {
                  "version": "3.3.6",
                  "bundled": true,
                  "dev": true,
                  "requires": {
                    "yallist": "^4.0.0"
                  }
                }
              }
            },
            "minipass": {
              "version": "5.0.0",
              "bundled": true,
              "dev": true
            },
            "minizlib": {
              "version": "2.1.2",
              "bundled": true,
              "dev": true,
              "requires": {
                "minipass": "^3.0.0",
                "yallist": "^4.0.0"
              },
              "dependencies": {
                "minipass": {
                  "version": "3.3.6",
                  "bundled": true,
                  "dev": true,
                  "requires": {
                    "yallist": "^4.0.0"
                  }
                }
              }
            }
          }
        },
        "text-table": {
          "version": "0.2.0",
          "bundled": true,
          "dev": true
        },
        "tiny-relative-date": {
          "version": "1.3.0",
          "bundled": true,
          "dev": true
        },
        "tinyglobby": {
          "version": "0.2.14",
          "bundled": true,
          "dev": true,
          "requires": {
            "fdir": "^6.4.4",
            "picomatch": "^4.0.2"
          },
          "dependencies": {
            "fdir": {
              "version": "6.4.6",
              "bundled": true,
              "dev": true,
              "requires": {}
            },
            "picomatch": {
              "version": "4.0.2",
              "bundled": true,
              "dev": true
            }
          }
        },
        "treeverse": {
          "version": "3.0.0",
          "bundled": true,
          "dev": true
        },
        "tuf-js": {
          "version": "3.0.1",
          "bundled": true,
          "dev": true,
          "requires": {
            "@tufjs/models": "3.0.1",
            "debug": "^4.3.6",
            "make-fetch-happen": "^14.0.1"
          },
          "dependencies": {
            "@tufjs/models": {
              "version": "3.0.1",
              "bundled": true,
              "dev": true,
              "requires": {
                "@tufjs/canonical-json": "2.0.0",
                "minimatch": "^9.0.5"
              }
            }
          }
        },
        "unique-filename": {
          "version": "4.0.0",
          "bundled": true,
          "dev": true,
          "requires": {
            "unique-slug": "^5.0.0"
          }
        },
        "unique-slug": {
          "version": "5.0.0",
          "bundled": true,
          "dev": true,
          "requires": {
            "imurmurhash": "^0.1.4"
          }
        },
        "util-deprecate": {
          "version": "1.0.2",
          "bundled": true,
          "dev": true
        },
        "validate-npm-package-license": {
          "version": "3.0.4",
          "bundled": true,
          "dev": true,
          "requires": {
            "spdx-correct": "^3.0.0",
            "spdx-expression-parse": "^3.0.0"
          },
          "dependencies": {
            "spdx-expression-parse": {
              "version": "3.0.1",
              "bundled": true,
              "dev": true,
              "requires": {
                "spdx-exceptions": "^2.1.0",
                "spdx-license-ids": "^3.0.0"
              }
            }
          }
        },
        "validate-npm-package-name": {
          "version": "6.0.1",
          "bundled": true,
          "dev": true
        },
        "walk-up-path": {
          "version": "3.0.1",
          "bundled": true,
          "dev": true
        },
        "which": {
          "version": "5.0.0",
          "bundled": true,
          "dev": true,
          "requires": {
            "isexe": "^3.1.1"
          },
          "dependencies": {
            "isexe": {
              "version": "3.1.1",
              "bundled": true,
              "dev": true
            }
          }
        },
        "wrap-ansi": {
          "version": "8.1.0",
          "bundled": true,
          "dev": true,
          "requires": {
            "ansi-styles": "^6.1.0",
            "string-width": "^5.0.1",
            "strip-ansi": "^7.0.1"
          },
          "dependencies": {
            "ansi-regex": {
              "version": "6.1.0",
              "bundled": true,
              "dev": true
            },
            "emoji-regex": {
              "version": "9.2.2",
              "bundled": true,
              "dev": true
            },
            "string-width": {
              "version": "5.1.2",
              "bundled": true,
              "dev": true,
              "requires": {
                "eastasianwidth": "^0.2.0",
                "emoji-regex": "^9.2.2",
                "strip-ansi": "^7.0.1"
              }
            },
            "strip-ansi": {
              "version": "7.1.0",
              "bundled": true,
              "dev": true,
              "requires": {
                "ansi-regex": "^6.0.1"
              }
            }
          }
        },
        "wrap-ansi-cjs": {
          "version": "npm:wrap-ansi@7.0.0",
          "bundled": true,
          "dev": true,
          "requires": {
            "ansi-styles": "^4.0.0",
            "string-width": "^4.1.0",
            "strip-ansi": "^6.0.0"
          },
          "dependencies": {
            "ansi-styles": {
              "version": "4.3.0",
              "bundled": true,
              "dev": true,
              "requires": {
                "color-convert": "^2.0.1"
              }
            }
          }
        },
        "write-file-atomic": {
          "version": "6.0.0",
          "bundled": true,
          "dev": true,
          "requires": {
            "imurmurhash": "^0.1.4",
            "signal-exit": "^4.0.1"
          }
        },
        "yallist": {
          "version": "4.0.0",
          "bundled": true,
          "dev": true
        }
      }
    },
    "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,
      "requires": {
        "path-key": "^3.0.0"
      }
    },
    "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
    },
    "onetime": {
      "version": "5.1.2",
      "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
      "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
      "dev": true,
      "requires": {
        "mimic-fn": "^2.1.0"
      }
    },
    "p-each-series": {
      "version": "3.0.0",
      "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-3.0.0.tgz",
      "integrity": "sha512-lastgtAdoH9YaLyDa5i5z64q+kzOcQHsQ5SsZJD3q0VEyI8mq872S3geuNbRUQLVAE9siMfgKrpj7MloKFHruw==",
      "dev": true
    },
    "p-filter": {
      "version": "4.1.0",
      "resolved": "https://registry.npmjs.org/p-filter/-/p-filter-4.1.0.tgz",
      "integrity": "sha512-37/tPdZ3oJwHaS3gNJdenCDB3Tz26i9sjhnguBtvN0vYlRIiDNnvTWkuh+0hETV9rLPdJ3rlL3yVOYPIAnM8rw==",
      "dev": true,
      "requires": {
        "p-map": "^7.0.1"
      }
    },
    "p-is-promise": {
      "version": "3.0.0",
      "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-3.0.0.tgz",
      "integrity": "sha512-Wo8VsW4IRQSKVXsJCn7TomUaVtyfjVDn3nUP7kE967BQk0CwFpdbZs0X0uk5sW9mkBa9eNM7hCMaG93WUAwxYQ==",
      "dev": true
    },
    "p-limit": {
      "version": "4.0.0",
      "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz",
      "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==",
      "dev": true,
      "requires": {
        "yocto-queue": "^1.0.0"
      }
    },
    "p-locate": {
      "version": "6.0.0",
      "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz",
      "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==",
      "dev": true,
      "requires": {
        "p-limit": "^4.0.0"
      }
    },
    "p-map": {
      "version": "7.0.1",
      "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.1.tgz",
      "integrity": "sha512-2wnaR0XL/FDOj+TgpDuRb2KTjLnu3Fma6b1ZUwGY7LcqenMcvP/YFpjpbPKY6WVGsbuJZRuoUz8iPrt8ORnAFw==",
      "dev": true
    },
    "p-reduce": {
      "version": "2.1.0",
      "resolved": "https://registry.npmjs.org/p-reduce/-/p-reduce-2.1.0.tgz",
      "integrity": "sha512-2USApvnsutq8uoxZBGbbWM0JIYLiEMJ9RlaN7fAzVNb9OZN0SHjjTTfIcb667XynS5Y1VhwDJVDa72TnPzAYWw==",
      "dev": true
    },
    "p-try": {
      "version": "1.0.0",
      "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz",
      "integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==",
      "dev": true
    },
    "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,
      "requires": {
        "callsites": "^3.0.0"
      }
    },
    "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,
      "requires": {
        "@babel/code-frame": "^7.0.0",
        "error-ex": "^1.3.1",
        "json-parse-even-better-errors": "^2.3.0",
        "lines-and-columns": "^1.1.6"
      }
    },
    "parse-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
    },
    "parse5": {
      "version": "5.1.1",
      "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz",
      "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==",
      "dev": true
    },
    "parse5-htmlparser2-tree-adapter": {
      "version": "6.0.1",
      "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz",
      "integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==",
      "dev": true,
      "requires": {
        "parse5": "^6.0.1"
      },
      "dependencies": {
        "parse5": {
          "version": "6.0.1",
          "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz",
          "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==",
          "dev": true
        }
      }
    },
    "path-exists": {
      "version": "5.0.0",
      "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz",
      "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==",
      "dev": true
    },
    "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
    },
    "path-type": {
      "version": "4.0.0",
      "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
      "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
      "dev": true
    },
    "picocolors": {
      "version": "1.1.1",
      "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
      "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
      "dev": true
    },
    "picomatch": {
      "version": "2.3.1",
      "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
      "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
      "dev": true
    },
    "pify": {
      "version": "3.0.0",
      "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
      "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==",
      "dev": true
    },
    "pkg-conf": {
      "version": "2.1.0",
      "resolved": "https://registry.npmjs.org/pkg-conf/-/pkg-conf-2.1.0.tgz",
      "integrity": "sha512-C+VUP+8jis7EsQZIhDYmS5qlNtjv2yP4SNtjXK9AP1ZcTRlnSfuumaTnRfYZnYgUUYVIKqL0fRvmUGDV2fmp6g==",
      "dev": true,
      "requires": {
        "find-up": "^2.0.0",
        "load-json-file": "^4.0.0"
      },
      "dependencies": {
        "find-up": {
          "version": "2.1.0",
          "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz",
          "integrity": "sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==",
          "dev": true,
          "requires": {
            "locate-path": "^2.0.0"
          }
        },
        "locate-path": {
          "version": "2.0.0",
          "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz",
          "integrity": "sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==",
          "dev": true,
          "requires": {
            "p-locate": "^2.0.0",
            "path-exists": "^3.0.0"
          }
        },
        "p-limit": {
          "version": "1.3.0",
          "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz",
          "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==",
          "dev": true,
          "requires": {
            "p-try": "^1.0.0"
          }
        },
        "p-locate": {
          "version": "2.0.0",
          "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz",
          "integrity": "sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==",
          "dev": true,
          "requires": {
            "p-limit": "^1.1.0"
          }
        },
        "path-exists": {
          "version": "3.0.0",
          "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
          "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==",
          "dev": true
        }
      }
    },
    "pretty-ms": {
      "version": "9.2.0",
      "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.2.0.tgz",
      "integrity": "sha512-4yf0QO/sllf/1zbZWYnvWw3NxCQwLXKzIj0G849LSufP15BXKM0rbD2Z3wVnkMfjdn/CB0Dpp444gYAACdsplg==",
      "dev": true,
      "requires": {
        "parse-ms": "^4.0.0"
      }
    },
    "process-nextick-args": {
      "version": "2.0.1",
      "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
      "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
      "dev": true
    },
    "proto-list": {
      "version": "1.2.4",
      "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz",
      "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==",
      "dev": true
    },
    "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
    },
    "rc": {
      "version": "1.2.8",
      "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
      "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
      "dev": true,
      "requires": {
        "deep-extend": "^0.6.0",
        "ini": "~1.3.0",
        "minimist": "^1.2.0",
        "strip-json-comments": "~2.0.1"
      }
    },
    "read-package-up": {
      "version": "11.0.0",
      "resolved": "https://registry.npmjs.org/read-package-up/-/read-package-up-11.0.0.tgz",
      "integrity": "sha512-MbgfoNPANMdb4oRBNg5eqLbB2t2r+o5Ua1pNt8BqGp4I0FJZhuVSOj3PaBPni4azWuSzEdNn2evevzVmEk1ohQ==",
      "dev": true,
      "requires": {
        "find-up-simple": "^1.0.0",
        "read-pkg": "^9.0.0",
        "type-fest": "^4.6.0"
      }
    },
    "read-pkg": {
      "version": "9.0.1",
      "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-9.0.1.tgz",
      "integrity": "sha512-9viLL4/n1BJUCT1NXVTdS1jtm80yDEgR5T4yCelII49Mbj0v1rZdKqj7zCiYdbB0CuCgdrvHcNogAKTFPBocFA==",
      "dev": true,
      "requires": {
        "@types/normalize-package-data": "^2.4.3",
        "normalize-package-data": "^6.0.0",
        "parse-json": "^8.0.0",
        "type-fest": "^4.6.0",
        "unicorn-magic": "^0.1.0"
      },
      "dependencies": {
        "parse-json": {
          "version": "8.1.0",
          "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-8.1.0.tgz",
          "integrity": "sha512-rum1bPifK5SSar35Z6EKZuYPJx85pkNaFrxBK3mwdfSJ1/WKbYrjoW/zTPSjRRamfmVX1ACBIdFAO0VRErW/EA==",
          "dev": true,
          "requires": {
            "@babel/code-frame": "^7.22.13",
            "index-to-position": "^0.1.2",
            "type-fest": "^4.7.1"
          }
        }
      }
    },
    "read-pkg-up": {
      "version": "11.0.0",
      "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-11.0.0.tgz",
      "integrity": "sha512-LOVbvF1Q0SZdjClSefZ0Nz5z8u+tIE7mV5NibzmE9VYmDe9CaBbAVtz1veOSZbofrdsilxuDAYnFenukZVp8/Q==",
      "dev": true,
      "requires": {
        "find-up-simple": "^1.0.0",
        "read-pkg": "^9.0.0",
        "type-fest": "^4.6.0"
      }
    },
    "readable-stream": {
      "version": "2.3.8",
      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
      "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
      "dev": true,
      "requires": {
        "core-util-is": "~1.0.0",
        "inherits": "~2.0.3",
        "isarray": "~1.0.0",
        "process-nextick-args": "~2.0.0",
        "safe-buffer": "~5.1.1",
        "string_decoder": "~1.1.1",
        "util-deprecate": "~1.0.1"
      }
    },
    "registry-auth-token": {
      "version": "5.0.1",
      "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-5.0.1.tgz",
      "integrity": "sha512-UfxVOj8seK1yaIOiieV4FIP01vfBDLsY0H9sQzi9EbbUdJiuuBjJgLa1DpImXMNPnVkBD4eVxTEXcrZA6kfpJA==",
      "dev": true,
      "requires": {
        "@pnpm/npm-conf": "^1.0.4"
      }
    },
    "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
    },
    "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
    },
    "resolve-from": {
      "version": "5.0.0",
      "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",
      "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==",
      "dev": true
    },
    "reusify": {
      "version": "1.0.4",
      "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
      "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
      "dev": true
    },
    "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,
      "requires": {
        "queue-microtask": "^1.2.2"
      }
    },
    "safe-buffer": {
      "version": "5.1.2",
      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
      "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
      "dev": true
    },
    "semantic-release": {
      "version": "24.2.7",
      "resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-24.2.7.tgz",
      "integrity": "sha512-g7RssbTAbir1k/S7uSwSVZFfFXwpomUB9Oas0+xi9KStSCmeDXcA7rNhiskjLqvUe/Evhx8fVCT16OSa34eM5g==",
      "dev": true,
      "requires": {
        "@semantic-release/commit-analyzer": "^13.0.0-beta.1",
        "@semantic-release/error": "^4.0.0",
        "@semantic-release/github": "^11.0.0",
        "@semantic-release/npm": "^12.0.2",
        "@semantic-release/release-notes-generator": "^14.0.0-beta.1",
        "aggregate-error": "^5.0.0",
        "cosmiconfig": "^9.0.0",
        "debug": "^4.0.0",
        "env-ci": "^11.0.0",
        "execa": "^9.0.0",
        "figures": "^6.0.0",
        "find-versions": "^6.0.0",
        "get-stream": "^6.0.0",
        "git-log-parser": "^1.2.0",
        "hook-std": "^3.0.0",
        "hosted-git-info": "^8.0.0",
        "import-from-esm": "^2.0.0",
        "lodash-es": "^4.17.21",
        "marked": "^15.0.0",
        "marked-terminal": "^7.3.0",
        "micromatch": "^4.0.2",
        "p-each-series": "^3.0.0",
        "p-reduce": "^3.0.0",
        "read-package-up": "^11.0.0",
        "resolve-from": "^5.0.0",
        "semver": "^7.3.2",
        "semver-diff": "^4.0.0",
        "signale": "^1.2.1",
        "yargs": "^17.5.1"
      },
      "dependencies": {
        "@semantic-release/error": {
          "version": "4.0.0",
          "resolved": "https://registry.npmjs.org/@semantic-release/error/-/error-4.0.0.tgz",
          "integrity": "sha512-mgdxrHTLOjOddRVYIYDo0fR3/v61GNN1YGkfbrjuIKg/uMgCd+Qzo3UAXJ+woLQQpos4pl5Esuw5A7AoNlzjUQ==",
          "dev": true
        },
        "@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
        },
        "aggregate-error": {
          "version": "5.0.0",
          "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-5.0.0.tgz",
          "integrity": "sha512-gOsf2YwSlleG6IjRYG2A7k0HmBMEo6qVNk9Bp/EaLgAJT5ngH6PXbqa4ItvnEwCm/velL5jAnQgsHsWnjhGmvw==",
          "dev": true,
          "requires": {
            "clean-stack": "^5.2.0",
            "indent-string": "^5.0.0"
          }
        },
        "clean-stack": {
          "version": "5.2.0",
          "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-5.2.0.tgz",
          "integrity": "sha512-TyUIUJgdFnCISzG5zu3291TAsE77ddchd0bepon1VVQrKLGKFED4iXFEDQ24mIPdPBbyE16PK3F8MYE1CmcBEQ==",
          "dev": true,
          "requires": {
            "escape-string-regexp": "5.0.0"
          }
        },
        "execa": {
          "version": "9.6.0",
          "resolved": "https://registry.npmjs.org/execa/-/execa-9.6.0.tgz",
          "integrity": "sha512-jpWzZ1ZhwUmeWRhS7Qv3mhpOhLfwI+uAX4e5fOcXqwMR7EcJ0pj2kV1CVzHVMX/LphnKWD3LObjZCoJ71lKpHw==",
          "dev": true,
          "requires": {
            "@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"
          },
          "dependencies": {
            "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,
              "requires": {
                "@sec-ant/readable-stream": "^0.4.1",
                "is-stream": "^4.0.1"
              }
            }
          }
        },
        "hosted-git-info": {
          "version": "8.1.0",
          "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-8.1.0.tgz",
          "integrity": "sha512-Rw/B2DNQaPBICNXEm8balFz9a6WpZrkCGpcWFpy7nCj+NyhSdqXipmfvtmWt9xGfp0wZnBxB+iVpLmQMYt47Tw==",
          "dev": true,
          "requires": {
            "lru-cache": "^10.0.1"
          }
        },
        "human-signals": {
          "version": "8.0.1",
          "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.1.tgz",
          "integrity": "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==",
          "dev": true
        },
        "import-from-esm": {
          "version": "2.0.0",
          "resolved": "https://registry.npmjs.org/import-from-esm/-/import-from-esm-2.0.0.tgz",
          "integrity": "sha512-YVt14UZCgsX1vZQ3gKjkWVdBdHQ6eu3MPU1TBgL1H5orXe2+jWD006WCPPtOuwlQm10NuzOW5WawiF1Q9veW8g==",
          "dev": true,
          "requires": {
            "debug": "^4.3.4",
            "import-meta-resolve": "^4.0.0"
          }
        },
        "indent-string": {
          "version": "5.0.0",
          "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz",
          "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==",
          "dev": true
        },
        "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
        },
        "lru-cache": {
          "version": "10.4.3",
          "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
          "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
          "dev": true
        },
        "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,
          "requires": {
            "path-key": "^4.0.0",
            "unicorn-magic": "^0.3.0"
          }
        },
        "p-reduce": {
          "version": "3.0.0",
          "resolved": "https://registry.npmjs.org/p-reduce/-/p-reduce-3.0.0.tgz",
          "integrity": "sha512-xsrIUgI0Kn6iyDYm9StOpOeK29XM1aboGji26+QEortiFST1hGZaUQOLhtEbqHErPpGW/aSz6allwK2qcptp0Q==",
          "dev": true
        },
        "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
        },
        "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
        },
        "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
        },
        "unicorn-magic": {
          "version": "0.3.0",
          "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz",
          "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==",
          "dev": true
        }
      }
    },
    "semver": {
      "version": "7.6.0",
      "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",
      "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==",
      "dev": true,
      "requires": {
        "lru-cache": "^6.0.0"
      }
    },
    "semver-diff": {
      "version": "4.0.0",
      "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-4.0.0.tgz",
      "integrity": "sha512-0Ju4+6A8iOnpL/Thra7dZsSlOHYAHIeMxfhWQRI1/VLcT3WDBZKKtQt/QkBOsiIN9ZpuvHE6cGZ0x4glCMmfiA==",
      "dev": true,
      "requires": {
        "semver": "^7.3.5"
      }
    },
    "semver-regex": {
      "version": "4.0.5",
      "resolved": "https://registry.npmjs.org/semver-regex/-/semver-regex-4.0.5.tgz",
      "integrity": "sha512-hunMQrEy1T6Jr2uEVjrAIqjwWcQTgOAcIM52C8MY1EZSD3DDNft04XzvYKPqjED65bNVVko0YI38nYeEHCX3yw==",
      "dev": true
    },
    "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,
      "requires": {
        "shebang-regex": "^3.0.0"
      }
    },
    "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
    },
    "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
    },
    "signale": {
      "version": "1.4.0",
      "resolved": "https://registry.npmjs.org/signale/-/signale-1.4.0.tgz",
      "integrity": "sha512-iuh+gPf28RkltuJC7W5MRi6XAjTDCAPC/prJUpQoG4vIP3MJZ+GTydVnodXA7pwvTKb2cA0m9OFZW/cdWy/I/w==",
      "dev": true,
      "requires": {
        "chalk": "^2.3.2",
        "figures": "^2.0.0",
        "pkg-conf": "^2.1.0"
      },
      "dependencies": {
        "ansi-styles": {
          "version": "3.2.1",
          "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
          "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
          "dev": true,
          "requires": {
            "color-convert": "^1.9.0"
          }
        },
        "chalk": {
          "version": "2.4.2",
          "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
          "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
          "dev": true,
          "requires": {
            "ansi-styles": "^3.2.1",
            "escape-string-regexp": "^1.0.5",
            "supports-color": "^5.3.0"
          }
        },
        "color-convert": {
          "version": "1.9.3",
          "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
          "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
          "dev": true,
          "requires": {
            "color-name": "1.1.3"
          }
        },
        "color-name": {
          "version": "1.1.3",
          "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
          "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
          "dev": true
        },
        "escape-string-regexp": {
          "version": "1.0.5",
          "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
          "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
          "dev": true
        },
        "figures": {
          "version": "2.0.0",
          "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz",
          "integrity": "sha512-Oa2M9atig69ZkfwiApY8F2Yy+tzMbazyvqv21R0NsSC8floSOC09BbT1ITWAdoMGQvJ/aZnR1KMwdx9tvHnTNA==",
          "dev": true,
          "requires": {
            "escape-string-regexp": "^1.0.5"
          }
        },
        "has-flag": {
          "version": "3.0.0",
          "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
          "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
          "dev": true
        },
        "supports-color": {
          "version": "5.5.0",
          "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
          "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
          "dev": true,
          "requires": {
            "has-flag": "^3.0.0"
          }
        }
      }
    },
    "skin-tone": {
      "version": "2.0.0",
      "resolved": "https://registry.npmjs.org/skin-tone/-/skin-tone-2.0.0.tgz",
      "integrity": "sha512-kUMbT1oBJCpgrnKoSr0o6wPtvRWT9W9UKvGLwfJYO2WuahZRHOpEyL1ckyMGgMWh0UdpmaoFqKKD29WTomNEGA==",
      "dev": true,
      "requires": {
        "unicode-emoji-modifier-base": "^1.0.0"
      }
    },
    "slash": {
      "version": "5.1.0",
      "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz",
      "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==",
      "dev": true
    },
    "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
    },
    "spawn-error-forwarder": {
      "version": "1.0.0",
      "resolved": "https://registry.npmjs.org/spawn-error-forwarder/-/spawn-error-forwarder-1.0.0.tgz",
      "integrity": "sha512-gRjMgK5uFjbCvdibeGJuy3I5OYz6VLoVdsOJdA6wV0WlfQVLFueoqMxwwYD9RODdgb6oUIvlRlsyFSiQkMKu0g==",
      "dev": true
    },
    "spdx-correct": {
      "version": "3.1.1",
      "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz",
      "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==",
      "dev": true,
      "requires": {
        "spdx-expression-parse": "^3.0.0",
        "spdx-license-ids": "^3.0.0"
      }
    },
    "spdx-exceptions": {
      "version": "2.3.0",
      "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz",
      "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==",
      "dev": true
    },
    "spdx-expression-parse": {
      "version": "3.0.1",
      "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz",
      "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==",
      "dev": true,
      "requires": {
        "spdx-exceptions": "^2.1.0",
        "spdx-license-ids": "^3.0.0"
      }
    },
    "spdx-license-ids": {
      "version": "3.0.12",
      "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.12.tgz",
      "integrity": "sha512-rr+VVSXtRhO4OHbXUiAF7xW3Bo9DuuF6C5jH+q/x15j2jniycgKbxU09Hr0WqlSLUs4i4ltHGXqTe7VHclYWyA==",
      "dev": true
    },
    "split2": {
      "version": "4.2.0",
      "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
      "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==",
      "dev": true
    },
    "stream-combiner2": {
      "version": "1.1.1",
      "resolved": "https://registry.npmjs.org/stream-combiner2/-/stream-combiner2-1.1.1.tgz",
      "integrity": "sha512-3PnJbYgS56AeWgtKF5jtJRT6uFJe56Z0Hc5Ngg/6sI6rIt8iiMBTa9cvdyFfpMQjaVHr8dusbNeFGIIonxOvKw==",
      "dev": true,
      "requires": {
        "duplexer2": "~0.1.0",
        "readable-stream": "^2.0.2"
      }
    },
    "string_decoder": {
      "version": "1.1.1",
      "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
      "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
      "dev": true,
      "requires": {
        "safe-buffer": "~5.1.0"
      }
    },
    "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,
      "requires": {
        "emoji-regex": "^8.0.0",
        "is-fullwidth-code-point": "^3.0.0",
        "strip-ansi": "^6.0.1"
      }
    },
    "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,
      "requires": {
        "ansi-regex": "^5.0.1"
      }
    },
    "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
    },
    "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
    },
    "strip-json-comments": {
      "version": "2.0.1",
      "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
      "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==",
      "dev": true
    },
    "super-regex": {
      "version": "1.0.0",
      "resolved": "https://registry.npmjs.org/super-regex/-/super-regex-1.0.0.tgz",
      "integrity": "sha512-CY8u7DtbvucKuquCmOFEKhr9Besln7n9uN8eFbwcoGYWXOMW07u2o8njWaiXt11ylS3qoGF55pILjRmPlbodyg==",
      "dev": true,
      "requires": {
        "function-timeout": "^1.0.1",
        "time-span": "^5.1.0"
      }
    },
    "supports-color": {
      "version": "7.2.0",
      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
      "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
      "dev": true,
      "requires": {
        "has-flag": "^4.0.0"
      }
    },
    "supports-hyperlinks": {
      "version": "3.2.0",
      "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-3.2.0.tgz",
      "integrity": "sha512-zFObLMyZeEwzAoKCyu1B91U79K2t7ApXuQfo8OuxwXLDgcKxuwM+YvcbIhm6QWqz7mHUH1TVytR1PwVVjEuMig==",
      "dev": true,
      "requires": {
        "has-flag": "^4.0.0",
        "supports-color": "^7.0.0"
      }
    },
    "temp-dir": {
      "version": "2.0.0",
      "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz",
      "integrity": "sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==",
      "dev": true
    },
    "tempy": {
      "version": "3.0.0",
      "resolved": "https://registry.npmjs.org/tempy/-/tempy-3.0.0.tgz",
      "integrity": "sha512-B2I9X7+o2wOaW4r/CWMkpOO9mdiTRCxXNgob6iGvPmfPWgH/KyUD6Uy5crtWBxIBe3YrNZKR2lSzv1JJKWD4vA==",
      "dev": true,
      "requires": {
        "is-stream": "^3.0.0",
        "temp-dir": "^2.0.0",
        "type-fest": "^2.12.2",
        "unique-string": "^3.0.0"
      },
      "dependencies": {
        "is-stream": {
          "version": "3.0.0",
          "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz",
          "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==",
          "dev": true
        },
        "type-fest": {
          "version": "2.19.0",
          "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz",
          "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==",
          "dev": true
        }
      }
    },
    "text-extensions": {
      "version": "2.4.0",
      "resolved": "https://registry.npmjs.org/text-extensions/-/text-extensions-2.4.0.tgz",
      "integrity": "sha512-te/NtwBwfiNRLf9Ijqx3T0nlqZiQ2XrrtBvu+cLL8ZRrGkO0NHTug8MYFKyoSrv/sHTaSKfilUkizV6XhxMJ3g==",
      "dev": true
    },
    "thenify": {
      "version": "3.3.1",
      "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
      "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==",
      "dev": true,
      "requires": {
        "any-promise": "^1.0.0"
      }
    },
    "thenify-all": {
      "version": "1.6.0",
      "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz",
      "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==",
      "dev": true,
      "requires": {
        "thenify": ">= 3.1.0 < 4"
      }
    },
    "through": {
      "version": "2.3.8",
      "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
      "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==",
      "dev": true
    },
    "time-span": {
      "version": "5.1.0",
      "resolved": "https://registry.npmjs.org/time-span/-/time-span-5.1.0.tgz",
      "integrity": "sha512-75voc/9G4rDIJleOo4jPvN4/YC4GRZrY8yy1uU4lwrB3XEQbWve8zXoO5No4eFrGcTAMYyoY67p8jRQdtA1HbA==",
      "dev": true,
      "requires": {
        "convert-hrtime": "^5.0.0"
      }
    },
    "tinyexec": {
      "version": "1.0.1",
      "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.1.tgz",
      "integrity": "sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==",
      "dev": true
    },
    "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,
      "requires": {
        "is-number": "^7.0.0"
      }
    },
    "traverse": {
      "version": "0.6.7",
      "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.7.tgz",
      "integrity": "sha512-/y956gpUo9ZNCb99YjxG7OaslxZWHfCHAUUfshwqOXmxUIvqLjVO581BT+gM59+QV9tFe6/CGG53tsA1Y7RSdg==",
      "dev": true
    },
    "type-fest": {
      "version": "4.41.0",
      "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz",
      "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==",
      "dev": true
    },
    "typescript": {
      "version": "5.2.2",
      "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz",
      "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==",
      "dev": true,
      "peer": true
    },
    "uglify-js": {
      "version": "3.17.4",
      "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz",
      "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==",
      "dev": true,
      "optional": true
    },
    "undici-types": {
      "version": "5.26.5",
      "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
      "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
      "dev": true
    },
    "unicode-emoji-modifier-base": {
      "version": "1.0.0",
      "resolved": "https://registry.npmjs.org/unicode-emoji-modifier-base/-/unicode-emoji-modifier-base-1.0.0.tgz",
      "integrity": "sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g==",
      "dev": true
    },
    "unicorn-magic": {
      "version": "0.1.0",
      "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz",
      "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==",
      "dev": true
    },
    "unique-string": {
      "version": "3.0.0",
      "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-3.0.0.tgz",
      "integrity": "sha512-VGXBUVwxKMBUznyffQweQABPRRW1vHZAbadFZud4pLFAqRGvv/96vafgjWFqzourzr8YonlQiPgH0YCJfawoGQ==",
      "dev": true,
      "requires": {
        "crypto-random-string": "^4.0.0"
      }
    },
    "universal-user-agent": {
      "version": "7.0.2",
      "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.2.tgz",
      "integrity": "sha512-0JCqzSKnStlRRQfCdowvqy3cy0Dvtlb8xecj/H8JFZuCze4rwjPZQOgvFvn0Ws/usCHQFGpyr+pB9adaGwXn4Q==",
      "dev": true
    },
    "universalify": {
      "version": "2.0.0",
      "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
      "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==",
      "dev": true
    },
    "url-join": {
      "version": "5.0.0",
      "resolved": "https://registry.npmjs.org/url-join/-/url-join-5.0.0.tgz",
      "integrity": "sha512-n2huDr9h9yzd6exQVnH/jU5mr+Pfx08LRXXZhkLLetAMESRj+anQsTAh940iMrIetKAmry9coFuZQ2jY8/p3WA==",
      "dev": true
    },
    "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
    },
    "validate-npm-package-license": {
      "version": "3.0.4",
      "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",
      "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==",
      "dev": true,
      "requires": {
        "spdx-correct": "^3.0.0",
        "spdx-expression-parse": "^3.0.0"
      }
    },
    "which": {
      "version": "2.0.2",
      "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
      "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
      "dev": true,
      "requires": {
        "isexe": "^2.0.0"
      }
    },
    "wordwrap": {
      "version": "1.0.0",
      "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz",
      "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==",
      "dev": true
    },
    "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,
      "requires": {
        "ansi-styles": "^4.0.0",
        "string-width": "^4.1.0",
        "strip-ansi": "^6.0.0"
      }
    },
    "xtend": {
      "version": "4.0.2",
      "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
      "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
      "dev": true
    },
    "y18n": {
      "version": "5.0.8",
      "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
      "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
      "dev": true
    },
    "yallist": {
      "version": "4.0.0",
      "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
      "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
      "dev": true
    },
    "yargs": {
      "version": "17.7.1",
      "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz",
      "integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==",
      "dev": true,
      "requires": {
        "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"
      },
      "dependencies": {
        "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
        }
      }
    },
    "yargs-parser": {
      "version": "20.2.9",
      "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz",
      "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==",
      "dev": true
    },
    "yocto-queue": {
      "version": "1.2.1",
      "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.1.tgz",
      "integrity": "sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg==",
      "dev": true
    },
    "yoctocolors": {
      "version": "2.1.1",
      "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.1.tgz",
      "integrity": "sha512-GQHQqAopRhwU8Kt1DDM8NjibDXHC8eoh1erhGAJPEyveY9qqVeXvVikNKrDz69sHowPMorbPUrH/mx8c50eiBQ==",
      "dev": true
    }
  }
}
                                                                                                                                                                                                                                                       autosuspend-9.0.0/package.json                                                                      0000664 0000000 0000000 00000000414 15043744726 0016373 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        {
  "devDependencies": {
    "@commitlint/cli": "19.8.1",
    "@commitlint/config-conventional": "19.8.1",
    "@semantic-release/changelog": "6.0.3",
    "@semantic-release/exec": "7.1.0",
    "@semantic-release/git": "10.0.1",
    "semantic-release": "24.2.7"
  }
}
                                                                                                                                                                                                                                                    autosuspend-9.0.0/pyproject.toml                                                                    0000664 0000000 0000000 00000003362 15043744726 0017026 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        [build-system]
requires = ["setuptools", "wheel"]
[tool.ruff]
src = ["src"]
target-version = "py311"
[tool.ruff.lint]
select = [
    "E",
    "F",
    "D",
    "ANN",
    "S",
    # "BLE",
    "B",
    "A",
    "C4",
    "T10",
    "DTZ",
    "EXE",
    "ISC",
    "G",
    "PIE",
    "T20",
    "PT",
    "Q",
    "RET",
    "SLF",
    "SIM",
    "TID",
    "TCH",
    "ARG",
    "PTH",
    "ERA",
    "TRY",
    "RUF",
    "UP",
]
ignore = [
    # We do this deliberately when extending modules with functionality
    "A005",
    # Not available in all supported Python versions
    "B905",
    # Black will handle this
    "E501",
    # Do not require docstrings everywhere
    "D1",
    # No need to add type annotation to self and cls
    "ANN10",
    # Allow Any
    "ANN401",
    # We use assert only for documentation purposes and debugging.
    "S101",
    # We want this as a feature of the configuration. The user is warned.
    "S602",
    # This one is hard to get around here
    "S603",
    # Required to be location-independent
    "S607",
    # I don't like this style
    "TRY300",
    # Gives some readability sometimes, No need to prevent this style
    "RET505",
    # This is the style used in this project.
    "TID252",
    # Will be fixed lated.
    "TRY003",
    "TRY301",
]
[tool.ruff.lint.per-file-ignores]
"tests/**" = [
    # Allow hard-coded passwords in tests
    "S105",
    "S106",
    # Allow potentially insecure temp directory access
    "S108",
    # shell access in tests it ok
    "S604",
    # Sometimes needed for the current tests
    "SLF",
]
"src/autosuspend/checks/ical.py" = [
    # Terrible hack accessing internal members required to handle rrules correctly.
    "SLF001",
]
[tool.ruff.lint.pydocstyle]
convention = "google"
                                                                                                                                                                                                                                                                              autosuspend-9.0.0/renovate.json                                                                     0000664 0000000 0000000 00000001115 15043744726 0016622 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        {
  "extends": [
    "config:recommended"
  ],
  "pip_requirements": {
    "fileMatch": [
      "^requirements.*\\.txt$"
    ]
  },
  "customManagers": [
    {
      "customType": "regex",
      "fileMatch": [
        "^\\.github/.*\\.ya?ml$"
      ],
      "matchStrings": [
        "node-version: (?.*)"
      ],
      "depNameTemplate": "node-version",
      "datasourceTemplate": "node-version"
    }
  ],
  "packageRules": [
    {
      "matchUpdateTypes": [
        "minor",
        "patch",
        "pin",
        "digest"
      ],
      "automerge": true
    }
  ]
}
                                                                                                                                                                                                                                                                                                                                                                                                                                                   autosuspend-9.0.0/requirements-check.txt                                                            0000664 0000000 0000000 00000000170 15043744726 0020443 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        ruff==0.12.7
black==25.1.0
isort==6.0.1
mypy==1.17.1
types-tzlocal
types-requests
types-freezegun
types-python-dateutil
                                                                                                                                                                                                                                                                                                                                                                                                        autosuspend-9.0.0/requirements-doc.txt                                                              0000664 0000000 0000000 00000000204 15043744726 0020131 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        sphinx-issues==5.0.1
sphinx==8.2.3
furo==2025.7.19
sphinxcontrib-plantuml==0.30
sphinx-autodoc-typehints==3.2.0
recommonmark==0.7.1
                                                                                                                                                                                                                                                                                                                                                                                            autosuspend-9.0.0/setup.cfg                                                                         0000664 0000000 0000000 00000001732 15043744726 0015732 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        [build_sphinx]
source-dir = doc/source
build-dir  = doc/build
[mypy]
ignore_missing_imports = True
disallow_untyped_defs = True
check_untyped_defs = True
no_implicit_optional = True
warn_unused_configs = True
warn_unused_ignores = True
[tool:pytest]
log_level = DEBUG
markers =
    integration: longer-running integration tests
filterwarnings =
    ignore::DeprecationWarning
    default::DeprecationWarning:autosuspend
addopts =
    --cov-config=setup.cfg
[coverage:run]
branch = True
source = autosuspend
[coverage:paths]
source =
    src/
    */site-packages/
[coverage:report]
exclude_lines =
    pragma: no cover
    def __repr__
    if __name__ == "__main__":
    if TYPE_CHECKING:
    @abc.abstractmethod
[isort]
profile = google
known_local_folder = tests
case_sensitive = false
combine_as_imports = true
force_single_line = false
multi_line_output = 3
include_trailing_comma = true
lines_after_imports = 2
line_length = 88
force_grid_wrap = false
reverse_relative = true
                                      autosuspend-9.0.0/setup.py                                                                          0000664 0000000 0000000 00000003357 15043744726 0015630 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        from pathlib import Path
from setuptools import find_packages, setup
name = "autosuspend"
version_file = Path(__file__).absolute().parent / "VERSION"
lines = version_file.read_text().splitlines()
release = lines[1].strip()
extras_require = {
    "Mpd": ["python-mpd2"],
    "Kodi": ["requests"],
    "XPath": ["lxml", "requests"],
    "JSONPath": ["jsonpath-ng", "requests"],
    "Logind": ["dbus-python"],
    "ical": ["requests", "icalendar", "python-dateutil", "tzlocal"],
    "localfiles": ["requests-file"],
    "logactivity": ["python-dateutil", "tzdata"],
    "test": [
        "pytest",
        "pytest-cov",
        "pytest-mock",
        "freezegun",
        "python-dbusmock",
        "PyGObject",
        "pytest-datadir",
        "pytest-httpserver",
    ],
}
extras_require["test"].extend(
    {dep for k, v in extras_require.items() if k != "test" for dep in v}
)
extras_require["all"] = list(
    {dep for k, v in extras_require.items() if k != "test" for dep in v}
)
setup(
    name=name,
    version=release,
    description="A daemon to suspend your server in case of inactivity",
    author="Johannes Wienke",
    author_email="languitar@semipol.de",
    license="GPL2",
    zip_safe=False,
    python_requires=">=3.11",
    install_requires=[
        "psutil>=5.0",
        "portalocker",
    ],
    extras_require=extras_require,
    package_dir={"": "src"},
    packages=find_packages("src"),
    entry_points={
        "console_scripts": [
            "autosuspend = autosuspend:main",
        ],
    },
    data_files=[
        ("etc", ["data/autosuspend.conf", "data/autosuspend-logging.conf"]),
        (
            "lib/systemd/system",
            ["data/autosuspend.service", "data/autosuspend-detect-suspend.service"],
        ),
    ],
)
                                                                                                                                                                                                                                                                                 autosuspend-9.0.0/src/                                                                              0000775 0000000 0000000 00000000000 15043744726 0014675 5                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        autosuspend-9.0.0/src/autosuspend/                                                                  0000775 0000000 0000000 00000000000 15043744726 0017247 5                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        autosuspend-9.0.0/src/autosuspend/__init__.py                                                       0000775 0000000 0000000 00000062367 15043744726 0021401 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        #!/usr/bin/env python3
"""A daemon to suspend a system on inactivity."""
import argparse
from collections.abc import Callable, Iterable, Sequence
import configparser
from contextlib import suppress
from datetime import datetime, timedelta, UTC
import functools
from importlib.metadata import version
import logging
import logging.config
from pathlib import Path
import subprocess
import time
from typing import IO
import portalocker
from .checks import Activity, CheckType, ConfigurationError, TemporaryCheckError, Wakeup
from .util import logger_by_class_instance
# pylint: disable=invalid-name
_logger = logging.getLogger("autosuspend")
# pylint: enable=invalid-name
def execute_suspend(
    command: str | Sequence[str],
    wakeup_at: datetime | None,
) -> None:
    """Suspend the system by calling the specified command.
    Args:
        command:
            The command to execute, which will be executed using shell
            execution
        wakeup_at:
            potential next wakeup time. Only informative.
    """
    _logger.info(
        "Suspending using command: %s with next wake up at %s", command, wakeup_at
    )
    try:
        subprocess.check_call(command, shell=True)
    except subprocess.CalledProcessError:
        _logger.warning("Unable to execute suspend command: %s", command, exc_info=True)
def notify_suspend(
    command_wakeup_template: str | None,
    command_no_wakeup: str | None,
    wakeup_at: datetime | None,
) -> None:
    """Call a command to notify on suspending.
    Args:
        command_wakeup_template:
            A template for the command to execute in case a wakeup is
            scheduled.
            It will be executed using shell execution.
            The template is processed with string formatting to include
            information on a potentially scheduled wakeup.
            Notifications can be disable by providing ``None`` here.
        command_no_wakeup:
            Command to execute for notification in case no wake up is
            scheduled.
            Will be executed using shell execution.
        wakeup_at:
            if not ``None``, this is the time the system will wake up again
    """
    def safe_exec(command: str) -> None:
        _logger.info("Notifying using command: %s", command)
        try:
            subprocess.check_call(command, shell=True)
        except subprocess.CalledProcessError:
            _logger.warning(
                "Unable to execute notification command: %s", command, exc_info=True
            )
    if wakeup_at and command_wakeup_template:
        command = command_wakeup_template.format(
            timestamp=wakeup_at.timestamp(), iso=wakeup_at.isoformat()
        )
        safe_exec(command)
    elif not wakeup_at and command_no_wakeup:
        safe_exec(command_no_wakeup)
    else:
        _logger.info("No suitable notification command configured.")
def notify_and_suspend(
    suspend_cmd: str | Sequence[str],
    notify_cmd_wakeup_template: str | None,
    notify_cmd_no_wakeup: str | None,
    wakeup_at: datetime | None,
) -> None:
    notify_suspend(notify_cmd_wakeup_template, notify_cmd_no_wakeup, wakeup_at)
    execute_suspend(suspend_cmd, wakeup_at)
def schedule_wakeup(command_template: str, wakeup_at: datetime) -> None:
    command = command_template.format(
        timestamp=wakeup_at.timestamp(), iso=wakeup_at.isoformat()
    )
    _logger.info("Scheduling wakeup using command: %s", command)
    try:
        subprocess.check_call(command, shell=True)
    except subprocess.CalledProcessError:
        _logger.warning(
            "Unable to execute wakeup scheduling command: %s", command, exc_info=True
        )
def _safe_execute_activity(check: Activity, logger: logging.Logger) -> str | None:
    try:
        return check.check()
    except TemporaryCheckError:
        logger.warning("Check %s failed. Ignoring...", check, exc_info=True)
        return f"Check {check.name} failed temporarily"
def execute_checks(
    checks: Iterable[Activity], all_checks: bool, logger: logging.Logger
) -> bool:
    """Execute the provided checks sequentially.
    Args:
        checks:
            the checks to execute
        all_checks:
            if ``True``, execute all checks even if a previous one already
            matched.
        logger:
            the logger instance to use
    Return:
        ``True`` if a check matched
    """
    matched = False
    for check in checks:
        logger.debug("Executing check %s", check.name)
        result = _safe_execute_activity(check, logger)
        if result is not None:
            logger.info("Check %s matched. Reason: %s", check.name, result)
            matched = True
            if not all_checks:
                logger.debug("Skipping further checks")
                break
    return matched
def _safe_execute_wakeup(
    check: Wakeup, timestamp: datetime, logger: logging.Logger
) -> datetime | None:
    try:
        return check.check(timestamp)
    except TemporaryCheckError:
        logger.warning("Wakeup %s failed. Ignoring...", check, exc_info=True)
        return None
def execute_wakeups(
    wakeups: Iterable[Wakeup], timestamp: datetime, logger: logging.Logger
) -> datetime | None:
    wakeup_at = None
    for wakeup in wakeups:
        this_at = _safe_execute_wakeup(wakeup, timestamp, logger)
        # sanity checks
        if this_at is None:
            continue
        if this_at <= timestamp:
            logger.warning(
                "Wakeup %s returned a scheduled wakeup at %s, "
                "which is earlier than the current time %s. "
                "Ignoring.",
                wakeup,
                this_at,
                timestamp,
            )
            continue
        # determine the earliest wake up point in time
        wakeup_at = min(this_at, wakeup_at or this_at)
    return wakeup_at
class Processor:
    """Implements the logic for triggering suspension.
    Args:
        activities:
            the activity checks to execute
        wakeups:
            the wakeup checks to execute
        idle_time:
            the required amount of time the system has to be idle before
            suspension is triggered in seconds
        min_sleep_time:
            the minimum time the system has to sleep before it is woken up
            again in seconds.
        wakeup_delta:
            wake up this amount of seconds before the scheduled wake up time.
        sleep_fn:
            a callable that triggers suspension
        wakeup_fn:
            a callable that schedules the wakeup at the specified time in UTC
            seconds
        notify_fn:
            a callable that is called before suspending.
            One argument gives the scheduled wakeup time or ``None``.
        all_activities:
            if ``True``, execute all activity checks even if a previous one
            already matched.
    """
    def __init__(
        self,
        activities: Iterable[Activity],
        wakeups: Iterable[Wakeup],
        idle_time: float,
        min_sleep_time: float,
        wakeup_delta: float,
        sleep_fn: Callable,
        wakeup_fn: Callable[[datetime], None],
        all_activities: bool,
    ) -> None:
        self._logger = logger_by_class_instance(self)
        self._activities = activities
        self._wakeups = wakeups
        self._idle_time = idle_time
        self._min_sleep_time = min_sleep_time
        self._wakeup_delta = wakeup_delta
        self._sleep_fn = sleep_fn
        self._wakeup_fn = wakeup_fn
        self._all_activities = all_activities
        self._idle_since = None  # type: datetime | None
    def _reset_state(self, reason: str) -> None:
        self._logger.info("%s. Resetting state", reason)
        self._idle_since = None
    def _set_idle(self, since: datetime) -> datetime:
        """Set the idle since marker to the given dt if not already set earlier."""
        self._idle_since = min(since, self._idle_since or since)
        return self._idle_since
    def iteration(self, timestamp: datetime, just_woke_up: bool) -> None:
        self._logger.info("Starting new check iteration")
        # exit in case something prevents suspension
        if just_woke_up:
            self._reset_state("Just woke up from suspension.")
            return
        # determine system activity
        active = execute_checks(self._activities, self._all_activities, self._logger)
        self._logger.debug("All activity checks have been executed. Active: %s", active)
        if active:
            self._reset_state("System is active")
            return
        # set idle timestamp if required
        idle_since = self._set_idle(timestamp)
        self._logger.info("System is idle since %s", idle_since)
        # determine if systems is idle long enough
        idle_seconds = (timestamp - idle_since).total_seconds()
        self._logger.debug("Idle seconds: %s", idle_seconds)
        if idle_seconds <= self._idle_time:
            self._logger.info(
                "Desired idle time of %s s not reached yet. Currently idle since %s s",
                self._idle_time,
                idle_seconds,
            )
            return
        self._logger.info("System is idle long enough.")
        # determine potential wake ups
        wakeup_at = execute_wakeups(self._wakeups, timestamp, self._logger)
        if wakeup_at is None:
            self._logger.debug("No automatic wakeup required")
        else:
            self._logger.debug("System wakeup required at %s", wakeup_at)
            # Apply configured wakeup delta
            wakeup_at -= timedelta(seconds=self._wakeup_delta)
            self._logger.debug(
                "With delta applied, system should wake up at %s",
                wakeup_at,
            )
            wakeup_in = wakeup_at - timestamp
            if wakeup_in.total_seconds() < self._min_sleep_time:
                self._logger.info(
                    "Would wake up in %s seconds, which is "
                    "below the minimum amount of %s s. "
                    "Not suspending.",
                    wakeup_in.total_seconds(),
                    self._min_sleep_time,
                )
                return
            # schedule wakeup
            self._logger.info("Scheduling wakeup at %s", wakeup_at)
            self._wakeup_fn(wakeup_at)
        self._reset_state("Going to suspend")
        self._sleep_fn(wakeup_at)
def _continue_looping(run_for: int | None, start_time: datetime) -> bool:
    return (run_for is None) or (
        datetime.now(UTC) < (start_time + timedelta(seconds=run_for))
    )
def _do_loop_iteration(
    processor: Processor,
    woke_up_file: Path,
    lock_file: Path,
    lock_timeout: float,
) -> None:
    try:
        _logger.debug("New iteration, trying to acquire lock")
        with portalocker.Lock(lock_file, timeout=lock_timeout):
            _logger.debug("Acquired lock")
            just_woke_up = woke_up_file.is_file()
            if just_woke_up:
                _logger.debug("Removing woke up file at %s", woke_up_file)
                try:
                    woke_up_file.unlink()
                except FileNotFoundError:
                    _logger.warning("Just woke up file disappeared", exc_info=True)
            processor.iteration(datetime.now(UTC), just_woke_up)
    except portalocker.LockException:
        _logger.warning("Failed to acquire lock, skipping iteration", exc_info=True)
def loop(
    processor: Processor,
    interval: float,
    run_for: int | None,
    woke_up_file: Path,
    lock_file: Path,
    lock_timeout: float,
) -> None:
    """Run the main loop of the daemon.
    Args:
        processor:
            the processor to use for handling the suspension computations
        interval:
            the length of one iteration of the main loop in seconds
        run_for:
            if specified, run the main loop for the specified amount of seconds
            before terminating (approximately)
        woke_up_file:
            path of a file that marks that the system was sleeping since the
            last processing iterations
        lock_file:
            path of a file used for locking modifications to the `woke_up_file`
            to ensure consistency
        lock_timeout:
            time in seconds to wait for acquiring the lock file
    """
    start_time = datetime.now(UTC)
    while _continue_looping(run_for, start_time):
        _do_loop_iteration(processor, woke_up_file, lock_file, lock_timeout)
        time.sleep(interval)
def config_section_string(section: configparser.SectionProxy) -> str:
    data = {k: v if k != "password" else "" for k, v in section.items()}
    return f"{data}"
def _determine_check_class_and_module(
    class_name: str, internal_module: str
) -> tuple[str, str]:
    """Determine module and class of a check depending on whether it is internal."""
    if "." in class_name:
        # dot in class name means external class
        import_module, import_class = class_name.rsplit(".", maxsplit=1)
    else:
        # no dot means internal class
        import_module = f"autosuspend.checks.{internal_module}"
        import_class = class_name
    return import_module, import_class
def _determine_check_class_name(name: str, section: configparser.SectionProxy) -> str:
    # if there is an explicit class, use that one with higher priority
    # else, use the legacy method to determine the check name from the section header
    return section.get("class", name)
def _set_up_single_check(
    section: configparser.SectionProxy,
    prefix: str,
    internal_module: str,
    target_class: type[CheckType],
) -> CheckType:
    name = section.name[len(f"{prefix}.") :]
    class_name = _determine_check_class_name(name, section)
    # try to find the required class
    import_module, import_class = _determine_check_class_and_module(
        class_name, internal_module
    )
    _logger.info(
        "Configuring check %s with class %s from module %s "
        "using config parameters %s",
        name,
        import_class,
        import_module,
        config_section_string(section),
    )
    try:
        klass = getattr(
            __import__(import_module, fromlist=[import_class]), import_class
        )
    except AttributeError as error:
        raise ConfigurationError(
            f"Cannot create built-in check named {class_name}: Class does not exist"
        ) from error
    check = klass.create(name, section)
    if not isinstance(check, target_class):
        raise ConfigurationError(
            "Check %s is not a correct %s instance", check, target_class.__name__
        )
    _logger.debug("Created check instance %s with options %s", check, check.options())
    return check
def set_up_checks(
    config: configparser.ConfigParser,
    prefix: str,
    internal_module: str,
    target_class: type[CheckType],
    error_none: bool = False,
) -> list[CheckType]:
    """Set up :py.class:`Check` instances from a given configuration.
    Args:
        config:
            the configuration to use
        prefix:
            The prefix of sections in the configuration file to use for
            creating instances.
        internal_module:
            Name of the submodule of ``autosuspend.checks`` to use for
            discovering internal check classes.
        target_class:
            the base class to check new instance against
        error_none:
            Raise an error if nothing was configured?
    """
    configured_checks = []
    check_section = [s for s in config.sections() if s.startswith(f"{prefix}.")]
    for section_name in check_section:
        section = config[section_name]
        if not section.getboolean("enabled", fallback=False):
            _logger.debug("Skipping disabled check %s", section_name)
            continue
        configured_checks.append(
            _set_up_single_check(section, prefix, internal_module, target_class)
        )
    if not configured_checks and error_none:
        raise ConfigurationError("No checks enabled")
    return configured_checks
def parse_config(config_file: Iterable[str]) -> configparser.ConfigParser:
    """Parse the configuration file.
    Args:
        config_file:
            The file to parse
    """
    _logger.debug("Reading config file %s", config_file)
    config = configparser.ConfigParser(
        interpolation=configparser.ExtendedInterpolation()
    )
    config.read_file(config_file)
    _logger.debug("Parsed config file: %s", config)
    return config
def parse_arguments(args: Sequence[str] | None) -> argparse.Namespace:
    """Parse command line arguments.
    Args:
        args:
            if specified, use the provided arguments instead of the default
            ones determined via the :module:`sys` module.
    """
    parser = argparse.ArgumentParser(
        description="Automatically suspends a server based on several criteria",
        formatter_class=argparse.ArgumentDefaultsHelpFormatter,
    )
    default_config: IO[str] | None = None
    with suppress(FileNotFoundError, IsADirectoryError, PermissionError):
        # The open file is required after this function finishes inside the argparse
        # result. Therefore, a context manager is not easily usable here.
        default_config = Path("/etc/autosuspend.conf").open("r")  # noqa: SIM115
    parser.add_argument(
        "-c",
        "--config",
        dest="config_file",
        type=argparse.FileType("r"),
        default=default_config,
        required=default_config is None,
        metavar="FILE",
        help="The config file to use",
    )
    logging_group = parser.add_mutually_exclusive_group()
    logging_group.add_argument(
        "-l",
        "--logging",
        type=argparse.FileType("r"),
        default=None,
        metavar="FILE",
        help="Configures the python logging system from the specified "
        "configuration file.",
    )
    logging_group.add_argument(
        "-d",
        "--debug",
        action="store_true",
        default=False,
        help="Configures the logging system to provide full debug output on stdout.",
    )
    subparsers = parser.add_subparsers(title="subcommands", dest="subcommand")
    subparsers.required = True
    parser_version = subparsers.add_parser(
        "version", help="Outputs the program version"
    )
    parser_version.set_defaults(func=main_version)
    parser_daemon = subparsers.add_parser(
        "daemon", help="Execute the continuously operating daemon"
    )
    parser_daemon.set_defaults(func=main_daemon)
    parser_daemon.add_argument(
        "-a",
        "--allchecks",
        dest="all_checks",
        default=False,
        action="store_true",
        help="Execute all checks even if one has already prevented "
        "the system from going to sleep. Useful to debug individual "
        "checks.",
    )
    parser_daemon.add_argument(
        "-r",
        "--runfor",
        dest="run_for",
        type=float,
        default=None,
        metavar="SEC",
        help="If set, run for the specified amount of seconds before exiting "
        "instead of endless execution.",
    )
    parser_hook = subparsers.add_parser(
        "presuspend", help="Hook method to be called before suspending"
    )
    parser_hook.set_defaults(func=main_hook)
    result = parser.parse_args(args)
    _logger.debug("Parsed command line arguments %s", result)
    return result
def configure_logging(config_file: IO | None, debug: bool) -> None:
    """Configure the python :mod:`logging` system.
    Assumes that either a config file is provided, or debugging is enabled.
    Both together are not possible.
    Args:
        config_file:
            a configuration file pointed by a :ref:`file object
            `
        debug:
            if ``True``, enable debug logging
    """
    if config_file:
        try:
            logging.config.fileConfig(config_file)
        except Exception:  # probably ok for main-like function
            # at least configure warnings
            logging.basicConfig(level=logging.WARNING)
            _logger.warning(
                "Unable to configure logging from file %s. "
                "Falling back to warning level.",
                config_file,
                exc_info=True,
            )
    else:
        if debug:
            logging.basicConfig(level=logging.DEBUG)
        else:
            # at least configure warnings
            logging.basicConfig(level=logging.WARNING)
def get_notify_and_suspend_func(config: configparser.ConfigParser) -> Callable:
    return functools.partial(
        notify_and_suspend,
        config.get("general", "suspend_cmd"),
        config.get(
            "general",
            "notify_cmd_wakeup",
            fallback=None,
        ),
        config.get(
            "general",
            "notify_cmd_no_wakeup",
            fallback=None,
        ),
    )
def get_schedule_wakeup_func(
    config: configparser.ConfigParser,
) -> Callable[[datetime], None]:
    return functools.partial(schedule_wakeup, config.get("general", "wakeup_cmd"))
def get_woke_up_file(config: configparser.ConfigParser) -> Path:
    return Path(
        config.get(
            "general", "woke_up_file", fallback="/var/run/autosuspend-just-woke-up"
        )
    )
def get_lock_file(config: configparser.ConfigParser) -> Path:
    return Path(
        config.get("general", "lock_file", fallback="/var/lock/autosuspend.lock")
    )
def get_lock_timeout(config: configparser.ConfigParser) -> float:
    return config.getfloat("general", "lock_timeout", fallback=30.0)
def get_wakeup_delta(config: configparser.ConfigParser) -> float:
    return config.getfloat("general", "wakeup_delta", fallback=30)
def configure_processor(
    args: argparse.Namespace,
    config: configparser.ConfigParser,
    checks: Iterable[Activity],
    wakeups: Iterable[Wakeup],
) -> Processor:
    return Processor(
        checks,
        wakeups,
        config.getfloat("general", "idle_time", fallback=300),
        config.getfloat("general", "min_sleep_time", fallback=1200),
        get_wakeup_delta(config),
        get_notify_and_suspend_func(config),
        get_schedule_wakeup_func(config),
        all_activities=args.all_checks,
    )
def hook(
    wakeups: list[Wakeup],
    wakeup_delta: float,
    wakeup_fn: Callable[[datetime], None],
    woke_up_file: Path,
    lock_file: Path,
    lock_timeout: float,
) -> None:
    """Installs wake ups and notifies the daemon before suspending.
    Args:
        wakeups:
            set of wakeup checks to use for determining the wake up time
        wakeup_delta:
            The amount of time in seconds to wake up before an event
        wakeup_fn:
            function to call with the next wake up time
        woke_up_file:
            location of the file that instructs the daemon that the system just
            woke up
        lock_file:
            path of a file used for locking modifications to the `woke_up_file`
            to ensure consistency
        lock_timeout:
            time in seconds to wait for acquiring the lock file
    """
    _logger.info("Pre-suspend hook starting, trying to acquire lock")
    try:
        with portalocker.Lock(lock_file, timeout=lock_timeout):
            _logger.debug("Hook acquired lock")
            _logger.debug("Hook executing with configured wake ups: %s", wakeups)
            wakeup_at = execute_wakeups(wakeups, datetime.now(UTC), _logger)
            _logger.debug("Hook next wake up at %s", wakeup_at)
            if wakeup_at:
                wakeup_at -= timedelta(seconds=wakeup_delta)
                _logger.info("Scheduling next wake up at %s", wakeup_at)
                wakeup_fn(wakeup_at)
            else:
                _logger.info("No wake up required. Terminating")
            # create the just woke up file
            woke_up_file.touch()
    except portalocker.LockException:
        _logger.warning(
            "Hook unable to acquire lock. Not informing daemon.", exc_info=True
        )
def main_version(
    args: argparse.Namespace, config: configparser.ConfigParser  # noqa: ARG001
) -> None:
    print(version("autosuspend"))  # noqa: T201
def main_hook(
    args: argparse.Namespace, config: configparser.ConfigParser  # noqa: ARG001
) -> None:
    wakeups = set_up_checks(
        config,
        "wakeup",
        "wakeup",
        Wakeup,  # type: ignore # python/mypy#5374
    )
    hook(
        wakeups,
        get_wakeup_delta(config),
        get_schedule_wakeup_func(config),
        get_woke_up_file(config),
        get_lock_file(config),
        get_lock_timeout(config),
    )
def main_daemon(args: argparse.Namespace, config: configparser.ConfigParser) -> None:
    """Run the daemon."""
    checks = set_up_checks(
        config,
        "check",
        "activity",
        Activity,  # type: ignore
        error_none=True,
    )
    wakeups = set_up_checks(
        config,
        "wakeup",
        "wakeup",
        Wakeup,  # type: ignore
    )
    processor = configure_processor(args, config, checks, wakeups)
    loop(
        processor,
        config.getfloat("general", "interval", fallback=60),
        run_for=args.run_for,
        woke_up_file=get_woke_up_file(config),
        lock_file=get_lock_file(config),
        lock_timeout=get_lock_timeout(config),
    )
def main(argv: Sequence[str] | None = None) -> None:
    """Run the daemon."""
    args = parse_arguments(argv)
    configure_logging(args.logging, args.debug)
    config = parse_config(args.config_file)
    args.func(args, config)
if __name__ == "__main__":
    main()
                                                                                                                                                                                                                                                                         autosuspend-9.0.0/src/autosuspend/checks/                                                           0000775 0000000 0000000 00000000000 15043744726 0020507 5                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        autosuspend-9.0.0/src/autosuspend/checks/__init__.py                                                0000664 0000000 0000000 00000006625 15043744726 0022631 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        """Provides the basic types used for checks."""
import abc
from collections.abc import Mapping
import configparser
from datetime import datetime
from typing import Any, Self, TypeVar
from autosuspend.util import logger_by_class_instance
class ConfigurationError(RuntimeError):
    """Indicates an error in the configuration of a :class:`Check`."""
class TemporaryCheckError(RuntimeError):
    """Indicates a temporary error while performing a check.
    Such an error can be ignored for some time since it might recover
    automatically.
    """
class SevereCheckError(RuntimeError):
    """Indicates a sever check error that will probably not recover.
    There is no hope this situation recovers.
    """
CheckType = TypeVar("CheckType", bound="Check")
class Check(abc.ABC):
    """Base class for all kinds of checks.
    Subclasses must call this class' ``__init__`` method.
    Args:
        name (str):
            Configured name of the check
    """
    @classmethod
    @abc.abstractmethod
    def create(cls: type[Self], name: str, config: configparser.SectionProxy) -> Self:
        """Create a new check instance from the provided configuration.
        Args:
            name:
                user-defined name for the check
            config:
                config parser section with the configuration for this check
        Raises:
            ConfigurationError:
                Configuration for this check is inappropriate
        """
    def __init__(self, name: str | None = None) -> None:
        if name:
            self.name = name
        else:
            self.name = self.__class__.__name__
        self.logger = logger_by_class_instance(self, name)
    def options(self) -> Mapping[str, Any]:
        """Return the configured options as a mapping.
        This is used for debugging purposes only.
        """
        return {
            k: v for k, v in self.__dict__.items() if not callable(v) and k != "logger"
        }
    def __str__(self) -> str:
        return f"{self.name}[class={self.__class__.__name__}]"
class Activity(Check):
    """Base class for activity checks.
    Subclasses must call this class' __init__ method.
    """
    @abc.abstractmethod
    def check(self) -> str | None:
        """Determine if system activity exists that prevents suspending.
        Returns:
            A string describing which condition currently prevents sleep, else ``None``.
        Raises:
            TemporaryCheckError:
                Check execution currently fails but might recover later
            SevereCheckError:
                Check executions fails severely
        """
    def __str__(self) -> str:
        return f"{self.name}[class={self.__class__.__name__}]"
class Wakeup(Check):
    """Represents a check for potential wake up points."""
    @abc.abstractmethod
    def check(self, timestamp: datetime) -> datetime | None:
        """Indicate if a wakeup has to be scheduled for this check.
        Args:
            timestamp:
                the time at which the call to the wakeup check is made
        Returns:
            a datetime describing when the system needs to be running again or
            ``None`` if no wakeup is required. Use timezone aware datetimes.
        Raises:
            TemporaryCheckError:
                Check execution currently fails but might recover later
            SevereCheckError:
                Check executions fails severely
        """
                                                                                                           autosuspend-9.0.0/src/autosuspend/checks/activity.py                                                0000664 0000000 0000000 00000001520 15043744726 0022713 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        from contextlib import suppress
# isort: off
from .command import CommandActivity as ExternalCommand  # noqa
from .linux import (  # noqa
    ActiveConnection,
    Load,
    NetworkBandwidth,
    Ping,
    Processes,
    Users,
)
from .smb import Smb  # noqa
from .xorg import XIdleTime  # noqa
with suppress(ModuleNotFoundError):
    from .ical import ActiveCalendarEvent  # noqa
with suppress(ModuleNotFoundError):
    from .json import JsonPath  # noqa
with suppress(ModuleNotFoundError):
    from .logs import LastLogActivity  # noqa
with suppress(ModuleNotFoundError):
    from .xpath import XPathActivity as XPath  # noqa
with suppress(ModuleNotFoundError):
    from .systemd import LogindSessionsIdle  # noqa
with suppress(ModuleNotFoundError):
    from .mpd import Mpd  # noqa
from .kodi import Kodi, KodiIdleTime  # noqa
# isort: on
                                                                                                                                                                                autosuspend-9.0.0/src/autosuspend/checks/command.py                                                 0000664 0000000 0000000 00000005214 15043744726 0022501 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        import configparser
from datetime import datetime, UTC
import subprocess
from typing import Self
from . import (
    Activity,
    Check,
    ConfigurationError,
    SevereCheckError,
    TemporaryCheckError,
    Wakeup,
)
def raise_severe_if_command_not_found(error: subprocess.CalledProcessError) -> None:
    if error.returncode == 127:
        # see http://tldp.org/LDP/abs/html/exitcodes.html
        raise SevereCheckError(f"Command '{' '.join(error.cmd)}' does not exist")
class CommandMixin(Check):
    """Mixin for configuring checks based on external commands."""
    @classmethod
    def create(cls: type[Self], name: str, config: configparser.SectionProxy) -> Self:
        try:
            return cls(name, config["command"].strip())  # type: ignore
        except KeyError as error:
            raise ConfigurationError("Missing command specification") from error
    def __init__(self, command: str) -> None:
        self._command = command
class CommandActivity(CommandMixin, Activity):
    def __init__(self, name: str, command: str) -> None:
        CommandMixin.__init__(self, command)
        Activity.__init__(self, name)
    def check(self) -> str | None:
        try:
            subprocess.check_call(self._command, shell=True)
            return f"Command {self._command} succeeded"
        except subprocess.CalledProcessError as error:
            raise_severe_if_command_not_found(error)
            return None
class CommandWakeup(CommandMixin, Wakeup):
    """Determine wake up times based on an external command.
    The called command must return a timestamp in UTC or nothing in case no
    wake up is planned.
    """
    def __init__(self, name: str, command: str) -> None:
        CommandMixin.__init__(self, command)
        Wakeup.__init__(self, name)
    def check(self, timestamp: datetime) -> datetime | None:  # noqa: ARG002
        try:
            output = subprocess.check_output(
                self._command,
                shell=True,
            ).splitlines()[0]
            self.logger.debug(
                "Command %s succeeded with output %s", self._command, output
            )
            if output.strip():
                return datetime.fromtimestamp(float(output.strip()), UTC)
            else:
                return None
        except subprocess.CalledProcessError as error:
            raise_severe_if_command_not_found(error)
            raise TemporaryCheckError(
                "Unable to call the configured command"
            ) from error
        except ValueError as error:
            raise TemporaryCheckError(
                "Return value cannot be interpreted as a timestamp"
            ) from error
                                                                                                                                                                                                                                                                                                                                                                                    autosuspend-9.0.0/src/autosuspend/checks/ical.py                                                    0000664 0000000 0000000 00000024513 15043744726 0021776 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        from collections.abc import Iterable, Sequence
from contextlib import suppress
from dataclasses import dataclass
from datetime import date, datetime, timedelta, tzinfo, UTC
from io import BytesIO
from typing import Any, cast, IO, TypeVar
from zoneinfo import ZoneInfo
from dateutil.rrule import rrule, rruleset, rrulestr
import icalendar
import icalendar.cal
import tzlocal
from . import Activity, Wakeup
from .util import NetworkMixin
from ..util.datetime import is_aware, to_tz_unaware
@dataclass
class CalendarEvent:
    summary: str
    start: datetime | date
    end: datetime | date
    def __str__(self) -> str:
        return (
            f"CalendarEvent[summary={self.summary}, start={self.start}, end={self.end}]"
        )
def _expand_rrule_all_day(
    rrule: str, start: date, exclusions: Iterable, start_at: datetime, end_at: datetime
) -> Iterable[date]:
    """Expand an rrule for all-day events.
    To my mind, these events cannot have changes, just exclusions, because
    changes only affect the time, which doesn't exist for all-day events.
    """
    rules = cast(
        "rruleset", rrulestr(rrule, dtstart=start, ignoretz=True, forceset=True)
    )
    # add exclusions
    if exclusions:
        for xdate in exclusions:
            rules.exdate(datetime.combine(xdate.dts[0].dt, datetime.min.time()))
    dates = []
    # reduce start and end to datetimes without timezone that just represent a
    # date at midnight.
    for candidate in rules.between(
        datetime.combine(start_at.date(), datetime.min.time()),
        datetime.combine(end_at.date(), datetime.min.time()),
        inc=True,
    ):
        dates.append(candidate.date())
    return dates
def _prepare_rruleset_for_expanding(
    rule: str,
    start: datetime,
    exclusions: Iterable,
    changes: Iterable[icalendar.cal.Event],
    tz: tzinfo | None,
) -> rruleset:
    """Prepare an rruleset for expanding.
    Every timestamp is converted to a single timezone and then made unaware to avoid DST
    issues.
    """
    start = to_tz_unaware(start, tz)
    rules = rruleset()
    first_rule = cast(
        "rrule", rrulestr(rule, dtstart=start, ignoretz=True, forceset=False)
    )
    # apply the same timezone logic for the until part of the rule after
    # parsing it.
    if first_rule._until:  # type: ignore
        first_rule._until = to_tz_unaware(  # type: ignore
            first_rule._until.replace(tzinfo=ZoneInfo("UTC")),  # type: ignore
            tz,
        )
    rules.rrule(first_rule)
    # add exclusions
    if exclusions:
        for xdate in exclusions:
            with suppress(AttributeError):
                # also in this case, unify and strip the timezone
                rules.exdate(xdate.dts[0].dt.astimezone(tz).replace(tzinfo=None))
    # add events that were changed
    for change in changes:
        # same timezone mangling applies here
        rules.exdate(to_tz_unaware(change.get("recurrence-id").dt, tz))
    return rules
def _expand_rrule(
    rrule: str,
    start: datetime,
    instance_duration: timedelta,
    exclusions: Iterable,
    changes: Iterable[icalendar.cal.Event],
    start_at: datetime,
    end_at: datetime,
) -> Sequence[datetime]:
    # unify everything to a single timezone and then strip it to handle DST
    # changes correctly
    orig_tz = start.tzinfo
    start_at = to_tz_unaware(start_at, orig_tz)
    end_at = to_tz_unaware(end_at, orig_tz)
    rules = _prepare_rruleset_for_expanding(rrule, start, exclusions, changes, orig_tz)
    # expand the rrule
    dates = []
    for candidate in rules.between(start_at - instance_duration, end_at, inc=True):
        localized = _localize(candidate, orig_tz)
        dates.append(localized)
    return dates
ChangeMapping = dict[str, list[icalendar.cal.Event]]
def _collect_recurrence_changes(calendar: icalendar.Calendar) -> ChangeMapping:
    recurring_changes: ChangeMapping = {}
    for component in calendar.walk("VEVENT"):
        if component.get("recurrence-id"):
            if component.get("uid") not in recurring_changes:
                recurring_changes[component.get("uid")] = []
            recurring_changes[component.get("uid")].append(component)
    return recurring_changes
def _get_recurrence_exclusions_as_list(component: dict) -> list:
    exclusions = component.get("exdate")
    if exclusions and not isinstance(exclusions, list):
        exclusions = [exclusions]
    return exclusions  # type: ignore
DateType = TypeVar("DateType", date, datetime)
def _extract_events_from_recurring_component(
    component: icalendar.Event,
    component_start: DateType,
    component_end: DateType,
    start_at: datetime,
    end_at: datetime,
    recurring_changes: ChangeMapping,
) -> list[CalendarEvent]:
    summary = component.get("summary")
    rrule = component.get("rrule").to_ical().decode("utf-8")
    exclusions = _get_recurrence_exclusions_as_list(component)
    length = component_end - component_start
    changes = recurring_changes.get(component.get("uid"), [])
    events = []
    if isinstance(component_start, datetime):
        # complex processing in case of normal events
        for local_start in _expand_rrule(
            rrule, component_start, length, exclusions, changes, start_at, end_at
        ):
            events.append(
                CalendarEvent(str(summary), local_start, local_start + length)
            )
    else:
        # simplified processing for all-day events
        for local_start_date in _expand_rrule_all_day(
            rrule, component_start, exclusions, start_at, end_at
        ):
            events.append(
                CalendarEvent(
                    str(summary), local_start_date, local_start_date + timedelta(days=1)
                )
            )
    return events
def _extract_events_from_single_component(
    component: icalendar.Event,
    component_start: DateType,
    component_end: DateType,
    start_at: datetime,
    end_at: datetime,
) -> list[CalendarEvent]:
    summary = component.get("summary")
    events = []
    # distinction between usual events and all-day events
    if isinstance(component_start, datetime):
        # single events
        if component_end > start_at and component_start < end_at:
            events.append(CalendarEvent(str(summary), component_start, component_end))
    else:
        # all-day events
        if component_end > start_at.date() and component_start <= end_at.date():
            events.append(CalendarEvent(str(summary), component_start, component_end))
    return events
def _localize(dt: datetime, tz: Any) -> datetime:
    """Localizes a datetime with the provided timezone.
    This method handles the different return types of tzlocal in different versions.
    """
    return dt.replace(tzinfo=tz)
def _extract_events_from_component(
    component: icalendar.Event,
    recurring_changes: ChangeMapping,
    start_at: datetime,
    end_at: datetime,
) -> list[CalendarEvent]:
    start = component.get("dtstart").dt
    end = component.get("dtend").dt
    # Check whether dates are floating and localize with local time if so.
    # Only works in case of non-all-day events, which are dates, not
    # datetimes.
    if isinstance(start, datetime) and not is_aware(start):
        assert not is_aware(end)
        local_time = tzlocal.get_localzone()
        start = _localize(start, local_time)
        end = _localize(end, local_time)
    if component.get("rrule"):
        return _extract_events_from_recurring_component(
            component, start, end, start_at, end_at, recurring_changes
        )
    else:
        return _extract_events_from_single_component(
            component, start, end, start_at, end_at
        )
def list_calendar_events(
    data: IO[bytes], start_at: datetime, end_at: datetime
) -> Sequence[CalendarEvent]:
    """List all relevant calendar events in the provided interval.
    Args:
        data:
            A stream with icalendar data
        start_at:
            include events overlapping with this time (inclusive)
        end_at:
            do not include events that start after or exactly at this time
    """
    # some useful notes:
    # * end times and dates are non-inclusive for ical events
    # * start and end are dates for all-day events
    calendar: icalendar.Calendar = icalendar.Calendar.from_ical(data.read())
    # Do a first pass through the calendar to collect all exclusions to
    # recurring events so that they can be handled when expanding recurrences.
    recurring_changes = _collect_recurrence_changes(calendar)
    events = []
    for component in calendar.walk("VEVENT"):
        events.extend(
            _extract_events_from_component(
                component, recurring_changes, start_at, end_at
            )
        )
    return sorted(events, key=lambda e: e.start)
class ActiveCalendarEvent(NetworkMixin, Activity):
    """Determines activity by checking against events in an icalendar file."""
    def __init__(self, name: str, **kwargs: Any) -> None:
        NetworkMixin.__init__(self, **kwargs)
        Activity.__init__(self, name)
    def check(self) -> str | None:
        response = self.request()
        start = datetime.now(UTC)
        end = start + timedelta(minutes=1)
        events = list_calendar_events(BytesIO(response.content), start, end)
        self.logger.debug(
            "Listing active events between %s and %s returned %s events",
            start,
            end,
            len(events),
        )
        if events:
            return f"Calendar event {events[0]} is active"
        else:
            return None
class Calendar(NetworkMixin, Wakeup):
    """Uses an ical calendar to wake up on the next scheduled event."""
    def __init__(self, name: str, **kwargs: Any) -> None:
        NetworkMixin.__init__(self, **kwargs)
        Wakeup.__init__(self, name)
    def check(self, timestamp: datetime) -> datetime | None:
        response = self.request()
        end = timestamp + timedelta(weeks=6 * 4)
        events = list_calendar_events(BytesIO(response.content), timestamp, end)
        # Filter out currently active events. They are not our business.
        events = [e for e in events if e.start >= timestamp]
        if events:
            candidate = events[0]
            if isinstance(candidate.start, datetime):
                return candidate.start
            else:
                return datetime.combine(candidate.start, datetime.min.time())
        else:
            return None
                                                                                                                                                                                     autosuspend-9.0.0/src/autosuspend/checks/json.py                                                    0000664 0000000 0000000 00000003157 15043744726 0022040 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        import configparser
import json
from textwrap import shorten
from typing import Any
from jsonpath_ng import JSONPath
import requests
import requests.exceptions
from . import Activity, ConfigurationError, TemporaryCheckError
from .util import NetworkMixin
class JsonPath(NetworkMixin, Activity):
    """Requests a URL and evaluates whether a JSONPath expression matches."""
    @classmethod
    def collect_init_args(cls, config: configparser.SectionProxy) -> dict[str, Any]:
        from jsonpath_ng.ext import parse
        try:
            args = NetworkMixin.collect_init_args(config)
            args["jsonpath"] = parse(config["jsonpath"])
            return args
        except KeyError as error:
            raise ConfigurationError("Property jsonpath is missing") from error
        except Exception as error:
            raise ConfigurationError(f"JSONPath error {error}") from error
    def __init__(self, name: str, jsonpath: JSONPath, **kwargs: Any) -> None:
        Activity.__init__(self, name)
        NetworkMixin.__init__(self, accept="application/json", **kwargs)
        self._jsonpath = jsonpath
    def check(self) -> str | None:
        try:
            reply = self.request().json()
            matched = self._jsonpath.find(reply)
            if matched:
                # shorten to avoid excessive logging output
                return f"JSONPath {self._jsonpath} found elements " + shorten(
                    str(matched), 24
                )
            return None
        except (json.JSONDecodeError, requests.exceptions.RequestException) as error:
            raise TemporaryCheckError(error) from error
                                                                                                                                                                                                                                                                                                                                                                                                                 autosuspend-9.0.0/src/autosuspend/checks/kodi.py                                                    0000664 0000000 0000000 00000007243 15043744726 0022015 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        import configparser
import json
from typing import Any, Self
from . import Activity, ConfigurationError, TemporaryCheckError
from .util import NetworkMixin
def _add_default_kodi_url(config: configparser.SectionProxy) -> None:
    if "url" not in config:
        config["url"] = "http://localhost:8080/jsonrpc"
class Kodi(NetworkMixin, Activity):
    @classmethod
    def collect_init_args(cls, config: configparser.SectionProxy) -> dict[str, Any]:
        try:
            _add_default_kodi_url(config)
            args = NetworkMixin.collect_init_args(config)
            args["suspend_while_paused"] = config.getboolean(
                "suspend_while_paused", fallback=False
            )
            return args
        except ValueError as error:
            raise ConfigurationError(f"Configuration error {error}") from error
    @classmethod
    def create(cls, name: str, config: configparser.SectionProxy) -> Self:
        return cls(name, **cls.collect_init_args(config))
    def __init__(
        self, name: str, url: str, suspend_while_paused: bool = False, **kwargs: Any
    ) -> None:
        self._suspend_while_paused = suspend_while_paused
        if self._suspend_while_paused:
            request = url + (
                '?request={"jsonrpc": "2.0", "id": 1, '
                '"method": "XBMC.GetInfoBooleans",'
                '"params": {"booleans": ["Player.Playing"]} }'
            )
        else:
            request = url + (
                '?request={"jsonrpc": "2.0", "id": 1, '
                '"method": "Player.GetActivePlayers"}'
            )
        NetworkMixin.__init__(self, url=request, **kwargs)
        Activity.__init__(self, name)
    def _safe_request_result(self) -> dict:
        try:
            return self.request().json()["result"]
        except (KeyError, TypeError, json.JSONDecodeError) as error:
            raise TemporaryCheckError("Unable to get or parse Kodi state") from error
    def check(self) -> str | None:
        reply = self._safe_request_result()
        if self._suspend_while_paused:
            return (
                "Kodi actively playing media" if reply.get("Player.Playing") else None
            )
        else:
            return "Kodi currently playing" if reply else None
class KodiIdleTime(NetworkMixin, Activity):
    @classmethod
    def collect_init_args(cls, config: configparser.SectionProxy) -> dict[str, Any]:
        try:
            _add_default_kodi_url(config)
            args = NetworkMixin.collect_init_args(config)
            args["idle_time"] = config.getint("idle_time", fallback=120)
            return args
        except ValueError as error:
            raise ConfigurationError("Configuration error " + str(error)) from error
    @classmethod
    def create(cls, name: str, config: configparser.SectionProxy) -> Self:
        return cls(name, **cls.collect_init_args(config))
    def __init__(self, name: str, url: str, idle_time: int, **kwargs: Any) -> None:
        request = url + (
            '?request={"jsonrpc": "2.0", "id": 1, '
            '"method": "XBMC.GetInfoBooleans",'
            f'"params": {{"booleans": ["System.IdleTime({idle_time})"]}}}}'
        )
        NetworkMixin.__init__(self, url=request, **kwargs)
        Activity.__init__(self, name)
        self._idle_time = idle_time
    def check(self) -> str | None:
        try:
            reply = self.request().json()
            if not reply["result"][f"System.IdleTime({self._idle_time})"]:
                return "Someone interacts with Kodi"
            else:
                return None
        except (KeyError, TypeError, json.JSONDecodeError) as error:
            raise TemporaryCheckError("Unable to get or parse Kodi state") from error
                                                                                                                                                                                                                                                                                                                                                             autosuspend-9.0.0/src/autosuspend/checks/linux.py                                                   0000664 0000000 0000000 00000030406 15043744726 0022223 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        """Contains checks directly using the Linux operating system concepts."""
from collections.abc import Iterable
import configparser
from contextlib import suppress
from datetime import datetime, UTC
import os
from pathlib import Path
import re
from re import Pattern
import socket
import subprocess
import time
from typing import Self
import warnings
import psutil
from . import (
    Activity,
    ConfigurationError,
    SevereCheckError,
    TemporaryCheckError,
    Wakeup,
)
class ActiveConnection(Activity):
    """Checks if a client connection exists on specified ports."""
    @classmethod
    def create(
        cls: type[Self],
        name: str,
        config: configparser.SectionProxy,
    ) -> Self:
        try:
            split_ports = config["ports"].split(",")
            ports = {int(p.strip()) for p in split_ports}
            return cls(name, ports)
        except KeyError as error:
            raise ConfigurationError("Missing option ports") from error
        except ValueError as error:
            raise ConfigurationError("Ports must be integers") from error
    def __init__(self, name: str, ports: Iterable[int]) -> None:
        Activity.__init__(self, name)
        self._ports = ports
    def normalize_address(
        self, family: socket.AddressFamily, address: str
    ) -> tuple[socket.AddressFamily, str]:
        if family == socket.AF_INET6:
            # strip scope
            return family, address.split("%")[0]
        elif family == socket.AF_INET:
            # convert to IPv6 to handle cases where an IPv4 address is targeted via IPv6
            # to IPv4 mapping
            return socket.AF_INET6, f"::ffff:{address}"
        else:
            return family, address
    def check(self) -> str | None:
        # Find the addresses of the system
        own_addresses = [
            self.normalize_address(item.family, item.address)
            for sublist in psutil.net_if_addrs().values()
            for item in sublist
        ]
        # Find established connections to target ports
        connected = [
            connection.laddr[1]
            for connection in psutil.net_connections()
            if (
                self.normalize_address(connection.family, connection.laddr[0])
                in own_addresses
                and connection.status == "ESTABLISHED"
                and connection.laddr[1] in self._ports
            )
        ]
        if connected:
            return f"Ports {connected} are connected"
        else:
            return None
class Load(Activity):
    @classmethod
    def create(cls: type[Self], name: str, config: configparser.SectionProxy) -> Self:
        try:
            return cls(name, config.getfloat("threshold", fallback=2.5))
        except ValueError as error:
            raise ConfigurationError(
                f"Unable to parse threshold as float: {error}"
            ) from error
    def __init__(self, name: str, threshold: float) -> None:
        Activity.__init__(self, name)
        self._threshold = threshold
    def check(self) -> str | None:
        loadcurrent = os.getloadavg()[1]
        self.logger.debug("Load: %s", loadcurrent)
        if loadcurrent > self._threshold:
            return f"Load {loadcurrent} > threshold {self._threshold}"
        else:
            return None
class NetworkBandwidth(Activity):
    @classmethod
    def _ensure_interfaces_exist(cls, interfaces: Iterable[str]) -> None:
        host_interfaces = psutil.net_if_addrs().keys()
        for interface in interfaces:
            if interface not in host_interfaces:
                raise ConfigurationError(
                    f"Network interface {interface} does not exist"
                )
    @classmethod
    def _extract_interfaces(cls, config: configparser.SectionProxy) -> list[str]:
        interfaces = config["interfaces"].split(",")
        interfaces = [i.strip() for i in interfaces if i.strip()]
        if not interfaces:
            raise ConfigurationError("No interfaces configured")
        cls._ensure_interfaces_exist(interfaces)
        return interfaces
    @classmethod
    def create(
        cls: type[Self],
        name: str,
        config: configparser.SectionProxy,
    ) -> Self:
        try:
            interfaces = cls._extract_interfaces(config)
            threshold_send = config.getfloat("threshold_send", fallback=100)
            threshold_receive = config.getfloat("threshold_receive", fallback=100)
            return cls(name, interfaces, threshold_send, threshold_receive)
        except KeyError as error:
            raise ConfigurationError(f"Missing configuration key: {error}") from error
        except ValueError as error:
            raise ConfigurationError(f"Threshold in wrong format: {error}") from error
    def __init__(
        self,
        name: str,
        interfaces: Iterable[str],
        threshold_send: float,
        threshold_receive: float,
    ) -> None:
        Activity.__init__(self, name)
        self._interfaces = interfaces
        self._threshold_send = threshold_send
        self._threshold_receive = threshold_receive
        self._previous_values = psutil.net_io_counters(pernic=True)
        self._previous_time = time.time()
    @classmethod
    def _rate(cls, new: float, old: float, new_time: float, old_time: float) -> float:
        delta = new - old
        return delta / (new_time - old_time)
    class _InterfaceActive(RuntimeError):
        pass
    def _check_interface(
        self,
        interface: str,
        new: psutil._common.snetio,
        old: psutil._common.snetio,
        new_time: float,
        old_time: float,
    ) -> None:
        # send direction
        rate_send = self._rate(new.bytes_sent, old.bytes_sent, new_time, old_time)
        if rate_send > self._threshold_send:
            raise self._InterfaceActive(
                f"Interface {interface} sending rate {rate_send} byte/s "
                f"higher than threshold {self._threshold_send}"
            )
        # receive direction
        rate_receive = self._rate(new.bytes_recv, old.bytes_recv, new_time, old_time)
        if rate_receive > self._threshold_receive:
            raise self._InterfaceActive(
                f"Interface {interface} receive rate {rate_receive} byte/s "
                f"higher than threshold {self._threshold_receive}"
            )
    def check(self) -> str | None:
        # acquire the previous state and preserve it
        old_values = self._previous_values
        old_time = self._previous_time
        # read new values and store them for the next iteration
        new_values = psutil.net_io_counters(pernic=True)
        self._previous_values = new_values
        new_time = time.time()
        if new_time <= self._previous_time:
            raise TemporaryCheckError("Called too fast, no time between calls")
        self._previous_time = new_time
        for interface in self._interfaces:
            if interface not in new_values or interface not in self._previous_values:
                raise TemporaryCheckError(f"Interface {interface} is missing")
            try:
                self._check_interface(
                    interface,
                    new_values[interface],
                    old_values[interface],
                    new_time,
                    old_time,
                )
            except self._InterfaceActive as e:
                return str(e)
        return None
class Ping(Activity):
    """Check if one or several hosts are reachable via ping."""
    @classmethod
    def create(cls: type[Self], name: str, config: configparser.SectionProxy) -> Self:
        try:
            hosts = config["hosts"].split(",")
            hosts = [h.strip() for h in hosts]
            return cls(name, hosts)
        except KeyError as error:
            raise ConfigurationError(
                f"Unable to determine hosts to ping: {error}"
            ) from error
    def __init__(self, name: str, hosts: Iterable[str]) -> None:
        Activity.__init__(self, name)
        self._hosts = hosts
    def check(self) -> str | None:
        try:
            for host in self._hosts:
                cmd = ["ping", "-q", "-c", "1", host]
                if (
                    subprocess.call(  # we know the input from the config
                        cmd,
                        stdout=subprocess.DEVNULL,
                        stderr=subprocess.DEVNULL,
                    )
                    == 0
                ):
                    self.logger.debug("host %s appears to be up", host)
                    return f"Host {host} is up"
            return None
        except FileNotFoundError as error:
            raise SevereCheckError("Binary ping cannot be found") from error
class Processes(Activity):
    @classmethod
    def create(cls: type[Self], name: str, config: configparser.SectionProxy) -> Self:
        try:
            processes = config["processes"].split(",")
            processes = [p.strip() for p in processes]
            return cls(name, processes)
        except KeyError as error:
            raise ConfigurationError("No processes to check specified") from error
    def __init__(self, name: str, processes: Iterable[str]) -> None:
        Activity.__init__(self, name)
        self._processes = processes
    def check(self) -> str | None:
        for proc in psutil.process_iter():
            with suppress(psutil.NoSuchProcess):
                pinfo = proc.name()
                if pinfo in self._processes:
                    return f"Process {pinfo} is running"
        return None
class Users(Activity):
    @classmethod
    def create(cls: type[Self], name: str, config: configparser.SectionProxy) -> Self:
        with warnings.catch_warnings():
            warnings.simplefilter("ignore", FutureWarning)
            try:
                user_regex = re.compile(config.get("name", fallback=r".*"))
                terminal_regex = re.compile(config.get("terminal", fallback=r".*"))
                host_regex = re.compile(config.get("host", fallback=r".*"))
                return cls(name, user_regex, terminal_regex, host_regex)
            except re.error as error:
                raise ConfigurationError(
                    f"Regular expression is invalid: {error}",
                ) from error
    def __init__(
        self,
        name: str,
        user_regex: Pattern,
        terminal_regex: Pattern,
        host_regex: Pattern,
    ) -> None:
        Activity.__init__(self, name)
        self._user_regex = user_regex
        self._terminal_regex = terminal_regex
        self._host_regex = host_regex
    def check(self) -> str | None:
        for entry in psutil.users():
            if (
                self._user_regex.fullmatch(entry.name) is not None
                and self._terminal_regex.fullmatch(entry.terminal) is not None
                and self._host_regex.fullmatch(entry.host) is not None
            ):
                self.logger.debug(
                    "User %s on terminal %s from host %s matches criteria.",
                    entry.name,
                    entry.terminal,
                    entry.host,
                )
                return (
                    f"User {entry.name} is logged in on terminal {entry.terminal} "
                    f"from {entry.host} since {entry.started}"
                )
        return None
class File(Wakeup):
    """Determines scheduled wake ups from the contents of a file on disk.
    File contents are interpreted as a Unix timestamp in seconds UTC.
    """
    @classmethod
    def create(cls: type[Self], name: str, config: configparser.SectionProxy) -> Self:
        try:
            path = Path(config["path"])
            return cls(name, path)
        except KeyError as error:
            raise ConfigurationError("Missing option path") from error
    def __init__(self, name: str, path: Path) -> None:
        Wakeup.__init__(self, name)
        self._path = path
    def check(self, timestamp: datetime) -> datetime | None:  # noqa: ARG002
        try:
            first_line = self._path.read_text().splitlines()[0]
            return datetime.fromtimestamp(float(first_line.strip()), UTC)
        except FileNotFoundError:
            # this is ok
            return None
        except (OSError, ValueError) as error:
            raise TemporaryCheckError(
                "Next wakeup time cannot be read despite a file being present"
            ) from error
                                                                                                                                                                                                                                                          autosuspend-9.0.0/src/autosuspend/checks/logs.py                                                    0000664 0000000 0000000 00000007202 15043744726 0022026 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        from collections.abc import Iterable
import configparser
from datetime import datetime, timedelta, timezone, UTC
from pathlib import Path
import re
from re import Pattern
from typing import Self
from zoneinfo import ZoneInfo
from dateutil.parser import parse
from dateutil.utils import default_tzinfo
from . import Activity, ConfigurationError, TemporaryCheckError
class LastLogActivity(Activity):
    @classmethod
    def create(cls: type[Self], name: str, config: configparser.SectionProxy) -> Self:
        try:
            return cls(
                name,
                Path(config["log_file"]),
                re.compile(config["pattern"]),
                timedelta(minutes=config.getint("minutes", fallback=10)),
                config.get("encoding", "ascii"),
                ZoneInfo(config.get("timezone", "UTC")),  # type: ignore
            )
        except KeyError as error:
            raise ConfigurationError(
                f"Missing config key {error}",
            ) from error
        except re.error as error:
            raise ConfigurationError(
                f"Regular expression is invalid: {error}",
            ) from error
        except ValueError as error:
            raise ConfigurationError(
                f"Unable to parse configuration: {error}",
            ) from error
    def __init__(
        self,
        name: str,
        log_file: Path,
        pattern: Pattern,
        delta: timedelta,
        encoding: str,
        default_timezone: timezone,
    ) -> None:
        if delta.total_seconds() < 0:
            raise ValueError("Given delta must be positive")
        if pattern.groups != 1:
            raise ValueError("Given pattern must have exactly one capture group")
        super().__init__(name=name)
        self.log_file = log_file
        self.pattern = pattern
        self.delta = delta
        self.encoding = encoding
        self.default_timezone = default_timezone
    def _safe_parse_date(self, match: str, now: datetime) -> datetime:
        try:
            match_date = default_tzinfo(parse(match), self.default_timezone)
            if match_date > now:
                raise TemporaryCheckError(
                    f"Detected date {match_date} is in the future"
                )
            return match_date
        except ValueError as error:
            raise TemporaryCheckError(
                f"Detected date {match} cannot be parsed as a date"
            ) from error
        except OverflowError as error:
            raise TemporaryCheckError(
                f"Detected date {match} is out of the valid range"
            ) from error
    def _file_lines_reversed(self) -> Iterable[str]:
        try:
            # Probably not the most effective solution for large log files. Might need
            # optimizations later on.
            return reversed(
                self.log_file.read_text(encoding=self.encoding).splitlines()
            )
        except OSError as error:
            raise TemporaryCheckError(
                f"Cannot access log file {self.log_file}"
            ) from error
    def check(self) -> str | None:
        lines = self._file_lines_reversed()
        now = datetime.now(UTC)
        for line in lines:
            match = self.pattern.match(line)
            if not match:
                continue
            match_date = self._safe_parse_date(match.group(1), now)
            # Only check the first line (reverse order) that has a match, not all
            if (now - match_date) < self.delta:
                return f"Log activity in {self.log_file} at {match_date}"
            else:
                return None
        # No line matched at all
        return None
                                                                                                                                                                                                                                                                                                                                                                                              autosuspend-9.0.0/src/autosuspend/checks/mpd.py                                                     0000664 0000000 0000000 00000003014 15043744726 0021637 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        import configparser
import socket
from typing import Self
from mpd import MPDClient, MPDError
from . import Activity, Check, ConfigurationError, TemporaryCheckError
class Mpd(Activity):
    @classmethod
    def create(cls: type[Self], name: str, config: configparser.SectionProxy) -> Self:
        try:
            host = config.get("host", fallback="localhost")
            port = config.getint("port", fallback=6600)
            timeout = config.getint("timeout", fallback=5)
            return cls(name, host, port, timeout)
        except ValueError as error:
            raise ConfigurationError(
                f"Host port or timeout configuration wrong: {error}"
            ) from error
    def __init__(self, name: str, host: str, port: int, timeout: float) -> None:
        Check.__init__(self, name)
        self._host = host
        self._port = port
        self._timeout = timeout
    def _get_state(self) -> dict:
        client = MPDClient()
        client.timeout = self._timeout
        client.connect(self._host, self._port)
        state = client.status()
        client.close()
        client.disconnect()
        return state
    def check(self) -> str | None:
        try:
            state = self._get_state()
            if state["state"] == "play":
                return "MPD currently playing"
            else:
                return None
        except (TimeoutError, MPDError, ConnectionError, socket.gaierror) as error:
            raise TemporaryCheckError("Unable to get the current MPD state") from error
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    autosuspend-9.0.0/src/autosuspend/checks/smb.py                                                     0000664 0000000 0000000 00000002412 15043744726 0021641 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        import configparser
import subprocess
from typing import Self
from . import Activity, SevereCheckError, TemporaryCheckError
class Smb(Activity):
    @classmethod
    def create(
        cls: type[Self],
        name: str,
        config: configparser.SectionProxy | None,  # noqa: ARG003
    ) -> Self:
        return cls(name)
    def _safe_get_status(self) -> str:
        try:
            return subprocess.check_output(["smbstatus", "-b"]).decode("utf-8")
        except FileNotFoundError as error:
            raise SevereCheckError("smbstatus binary not found") from error
        except subprocess.CalledProcessError as error:
            raise TemporaryCheckError("Unable to execute smbstatus") from error
    def check(self) -> str | None:
        status_output = self._safe_get_status()
        self.logger.debug("Received status output:\n%s", status_output)
        connections = []
        start_seen = False
        for line in status_output.splitlines():
            if start_seen:
                connections.append(line)
            else:
                if line.startswith("----"):
                    start_seen = True
        if connections:
            return "SMB clients are connected:\n{}".format("\n".join(connections))
        else:
            return None
                                                                                                                                                                                                                                                      autosuspend-9.0.0/src/autosuspend/checks/stub.py                                                    0000664 0000000 0000000 00000001540 15043744726 0022036 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        import configparser
from datetime import datetime, timedelta
from typing import Self
from . import ConfigurationError, Wakeup
class Periodic(Wakeup):
    """Always indicates a wake up after a specified delta of time from now on.
    Use this to periodically wake up a system.
    """
    @classmethod
    def create(cls: type[Self], name: str, config: configparser.SectionProxy) -> Self:
        try:
            kwargs = {config["unit"]: float(config["value"])}
            return cls(name, timedelta(**kwargs))
        except (ValueError, KeyError, TypeError) as error:
            raise ConfigurationError(str(error)) from error
    def __init__(self, name: str, delta: timedelta) -> None:
        Wakeup.__init__(self, name)
        self._delta = delta
    def check(self, timestamp: datetime) -> datetime | None:
        return timestamp + self._delta
                                                                                                                                                                autosuspend-9.0.0/src/autosuspend/checks/systemd.py                                                 0000664 0000000 0000000 00000011574 15043744726 0022561 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        from collections.abc import Iterable
import configparser
from datetime import datetime, timedelta, UTC
import re
from re import Pattern
from typing import Any, Self
import dbus
from . import Activity, ConfigurationError, TemporaryCheckError, Wakeup
from ..util.systemd import list_logind_sessions, LogindDBusException
_UINT64_MAX = 18446744073709551615
def next_timer_executions() -> dict[str, datetime]:
    bus = dbus.SystemBus()
    systemd = bus.get_object("org.freedesktop.systemd1", "/org/freedesktop/systemd1")
    units = systemd.ListUnits(dbus_interface="org.freedesktop.systemd1.Manager")
    timers = [unit for unit in units if unit[0].endswith(".timer")]
    def get_if_set(props: dict[str, Any], key: str) -> int | None:
        # For timers running after boot, next execution time might not be available. In
        # this case, the expected keys are all set to uint64 max.
        if props[key] and props[key] != _UINT64_MAX:
            return props[key]
        else:
            return None
    result: dict[str, datetime] = {}
    for timer in timers:
        obj = bus.get_object("org.freedesktop.systemd1", timer[6])
        properties_interface = dbus.Interface(obj, "org.freedesktop.DBus.Properties")
        props = properties_interface.GetAll("org.freedesktop.systemd1.Timer")
        realtime = get_if_set(props, "NextElapseUSecRealtime")
        monotonic = get_if_set(props, "NextElapseUSecMonotonic")
        next_time: datetime | None = None
        if realtime is not None:
            next_time = datetime.fromtimestamp(
                realtime / 1000000,
                tz=UTC,
            )
        elif monotonic is not None:
            next_time = datetime.now(tz=UTC) + timedelta(seconds=monotonic / 1000000)
        if next_time:
            result[str(timer[0])] = next_time
    return result
class SystemdTimer(Wakeup):
    """Ensures that the system is active when some selected SystemD timers will run."""
    @classmethod
    def create(cls: type[Self], name: str, config: configparser.SectionProxy) -> Self:
        try:
            return cls(name, re.compile(config["match"]))
        except (re.error, ValueError, KeyError, TypeError) as error:
            raise ConfigurationError(str(error)) from error
    def __init__(self, name: str, match: Pattern) -> None:
        Wakeup.__init__(self, name)
        self._match = match
    def check(self, timestamp: datetime) -> datetime | None:  # noqa: ARG002
        executions = next_timer_executions()
        matching_executions = [
            next_run for name, next_run in executions.items() if self._match.match(name)
        ]
        try:
            return min(matching_executions)
        except ValueError:
            return None
class LogindSessionsIdle(Activity):
    """Prevents suspending in case a logind session is marked not idle.
    The decision is based on the ``IdleHint`` property of logind sessions.
    """
    @classmethod
    def create(
        cls: type[Self],
        name: str,
        config: configparser.SectionProxy,
    ) -> Self:
        types = config.get("types", fallback="tty,x11,wayland").split(",")
        types = [t.strip() for t in types]
        states = config.get("states", fallback="active,online").split(",")
        states = [t.strip() for t in states]
        classes = config.get("classes", fallback="user").split(",")
        classes = [t.strip() for t in classes]
        return cls(name, types, states, classes)
    def __init__(
        self,
        name: str,
        types: Iterable[str],
        states: Iterable[str],
        classes: Iterable[str] = ("user"),
    ) -> None:
        Activity.__init__(self, name)
        self._types = types
        self._states = states
        self._classes = classes
    @staticmethod
    def _list_logind_sessions() -> Iterable[tuple[str, dict]]:
        try:
            return list_logind_sessions()
        except LogindDBusException as error:
            raise TemporaryCheckError(error) from error
    def check(self) -> str | None:
        for session_id, properties in self._list_logind_sessions():
            self.logger.debug("Session %s properties: %s", session_id, properties)
            if properties["Type"] not in self._types:
                self.logger.debug(
                    "Ignoring session of wrong type %s", properties["Type"]
                )
                continue
            if properties["State"] not in self._states:
                self.logger.debug(
                    "Ignoring session because its state is %s", properties["State"]
                )
                continue
            if properties["Class"] not in self._classes:
                self.logger.debug(
                    "Ignoring session because its class is %s", properties["Class"]
                )
                continue
            if not properties["IdleHint"]:
                return f"Login session {session_id} is not idle"
        return None
                                                                                                                                    autosuspend-9.0.0/src/autosuspend/checks/util.py                                                    0000664 0000000 0000000 00000007352 15043744726 0022045 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        import configparser
from contextlib import suppress
from typing import Any, Self, TYPE_CHECKING
from . import Check, ConfigurationError, SevereCheckError, TemporaryCheckError
if TYPE_CHECKING:
    import requests
    import requests.models
class NetworkMixin(Check):
    @staticmethod
    def _ensure_credentials_consistent(args: dict[str, Any]) -> None:
        if (args["username"] is None) != (args["password"] is None):
            raise ConfigurationError("Username and password must be set")
    @classmethod
    def collect_init_args(
        cls,
        config: configparser.SectionProxy,
    ) -> dict[str, Any]:
        try:
            args: dict[str, Any] = {}
            args["timeout"] = config.getint("timeout", fallback=5)
            args["url"] = config["url"]
            args["username"] = config.get("username")
            args["password"] = config.get("password")
            cls._ensure_credentials_consistent(args)
            return args
        except ValueError as error:
            raise ConfigurationError("Configuration error " + str(error)) from error
        except KeyError as error:
            raise ConfigurationError("Lacks " + str(error) + " config entry") from error
    @classmethod
    def create(cls: type[Self], name: str, config: configparser.SectionProxy) -> Self:
        return cls(name, **cls.collect_init_args(config))
    def __init__(
        self,
        url: str,
        timeout: int,
        username: str | None = None,
        password: str | None = None,
        accept: str | None = None,
    ) -> None:
        self._url = url
        self._timeout = timeout
        self._username = username
        self._password = password
        self._accept = accept
    @staticmethod
    def _create_session() -> "requests.Session":
        import requests
        session = requests.Session()
        with suppress(ImportError):
            from requests_file import FileAdapter
            session.mount("file://", FileAdapter())
        return session
    def _request_headers(self) -> dict[str, str] | None:
        if self._accept:
            return {"Accept": self._accept}
        else:
            return None
    def _create_auth_from_failed_request(
        self,
        reply: "requests.models.Response",
        username: str,
        password: str,
    ) -> Any:
        from requests.auth import HTTPBasicAuth, HTTPDigestAuth
        auth_map = {
            "basic": HTTPBasicAuth,
            "digest": HTTPDigestAuth,
        }
        auth_scheme = reply.headers["WWW-Authenticate"].split(" ")[0].lower()
        if auth_scheme not in auth_map:
            raise SevereCheckError(f"Unsupported authentication scheme {auth_scheme}")
        return auth_map[auth_scheme](username, password)
    def request(self) -> "requests.models.Response":
        import requests
        import requests.exceptions
        session = self._create_session()
        try:
            reply = session.get(
                self._url, timeout=self._timeout, headers=self._request_headers()
            )
            # replace reply with an authenticated version if credentials are
            # available and the server has requested authentication
            if self._username and self._password and reply.status_code == 401:
                reply = session.get(
                    self._url,
                    timeout=self._timeout,
                    auth=self._create_auth_from_failed_request(
                        reply, self._username, self._password
                    ),
                    headers=self._request_headers(),
                )
            reply.raise_for_status()
            return reply
        except requests.exceptions.RequestException as error:
            raise TemporaryCheckError(error) from error
                                                                                                                                                                                                                                                                                      autosuspend-9.0.0/src/autosuspend/checks/wakeup.py                                                  0000664 0000000 0000000 00000000752 15043744726 0022361 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        from contextlib import suppress
# isort: off
from .command import CommandWakeup as Command  # noqa
from .linux import File  # noqa
from .stub import Periodic  # noqa
with suppress(ModuleNotFoundError):
    from .ical import Calendar  # noqa
with suppress(ModuleNotFoundError):
    from .xpath import XPathWakeup as XPath  # noqa
    from .xpath import XPathDeltaWakeup as XPathDelta  # noqa
with suppress(ModuleNotFoundError):
    from .systemd import SystemdTimer  # noqa
# isort: on
                      autosuspend-9.0.0/src/autosuspend/checks/xorg.py                                                    0000664 0000000 0000000 00000017426 15043744726 0022052 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        from collections.abc import Callable
import configparser
from contextlib import suppress
import copy
from dataclasses import dataclass
import logging
import os
from pathlib import Path
import re
from re import Pattern
import subprocess
from typing import Self
import warnings
import psutil
from . import Activity, ConfigurationError, SevereCheckError, TemporaryCheckError
from ..util.systemd import list_logind_sessions, LogindDBusException
@dataclass
class XorgSession:
    display: int
    user: str
_logger = logging.getLogger(__name__)
def list_sessions_sockets(socket_path: Path | None = None) -> list[XorgSession]:
    """List running X sessions by iterating the X sockets.
    This method assumes that X servers are run under the users using the
    server.
    """
    folder = socket_path or Path("/tmp/.X11-unix/")  # noqa: S108 expected default path
    sockets = folder.glob("X*")
    _logger.debug("Found sockets: %s", sockets)
    results = []
    for sock in sockets:
        # determine the number of the X display by stripping the X prefix
        try:
            display = int(sock.name[1:])
        except ValueError:
            _logger.warning(
                "Cannot parse display number from socket %s. Skipping.",
                sock,
                exc_info=True,
            )
            continue
        # determine the user of the display
        try:
            user = sock.owner()
        except (FileNotFoundError, KeyError):
            _logger.warning(
                "Cannot get the owning user from socket %s. Skipping.",
                sock,
                exc_info=True,
            )
            continue
        results.append(XorgSession(display, user))
    return results
def list_sessions_logind() -> list[XorgSession]:
    """List running X sessions using logind.
    This method assumes that a ``Display`` variable is set in the logind
    sessions.
    Raises:
        LogindDBusException: cannot connect or extract sessions
    """
    results = []
    for session_id, properties in list_logind_sessions():
        if "Name" not in properties or "Display" not in properties:
            _logger.debug(
                "Skipping session %s because it does not contain "
                "a user name and a display",
                session_id,
            )
            continue
        try:
            results.append(
                XorgSession(
                    int(properties["Display"].replace(":", "")),
                    str(properties["Name"]),
                )
            )
        except ValueError:
            _logger.warning(
                "Unable to parse display from session properties %s",
                properties,
                exc_info=True,
            )
    return results
class XIdleTime(Activity):
    """Check that local X display have been idle long enough."""
    @classmethod
    def create(cls: type[Self], name: str, config: configparser.SectionProxy) -> Self:
        with warnings.catch_warnings():
            warnings.simplefilter("ignore", FutureWarning)
            try:
                return cls(
                    name,
                    config.getint("timeout", fallback=600),
                    config.get("method", fallback="sockets"),
                    re.compile(config.get("ignore_if_process", fallback=r"a^")),
                    re.compile(config.get("ignore_users", fallback=r"a^")),
                )
            except re.error as error:
                raise ConfigurationError(
                    f"Regular expression is invalid: {error}",
                ) from error
            except ValueError as error:
                raise ConfigurationError(
                    f"Unable to parse configuration: {error}",
                ) from error
    @staticmethod
    def _get_session_method(method: str) -> Callable[[], list[XorgSession]]:
        if method == "sockets":
            return list_sessions_sockets
        elif method == "logind":
            return list_sessions_logind
        else:
            raise ValueError(f"Unknown session discovery method {method}")
    def __init__(
        self,
        name: str,
        timeout: float,
        method: str,
        ignore_process_re: Pattern,
        ignore_users_re: Pattern,
    ) -> None:
        Activity.__init__(self, name)
        self._timeout = timeout
        self._provide_sessions: Callable[[], list[XorgSession]]
        self._provide_sessions = self._get_session_method(method)
        self._ignore_process_re = ignore_process_re
        self._ignore_users_re = ignore_users_re
    @staticmethod
    def _get_user_processes(user: str) -> list[psutil.Process]:
        user_processes = []
        for process in psutil.process_iter():
            with suppress(
                psutil.NoSuchProcess, psutil.ZombieProcess, psutil.AccessDenied
            ):
                if process.username() == user:
                    user_processes.append(process.name())
        return user_processes
    def _is_skip_process_running(self, user: str) -> bool:
        for process in self._get_user_processes(user):
            if self._ignore_process_re.match(process) is not None:
                self.logger.debug(
                    "Process %s with pid %s matches the ignore regex '%s'."
                    " Skipping idle time check for this user.",
                    process.name(),
                    process.pid,
                    self._ignore_process_re,
                )
                return True
        return False
    def _safe_provide_sessions(self) -> list[XorgSession]:
        try:
            return self._provide_sessions()
        except LogindDBusException as error:
            raise TemporaryCheckError(error) from error
    def _get_idle_time(self, session: XorgSession) -> float:
        env = copy.deepcopy(os.environ)
        env["DISPLAY"] = f":{session.display}"
        env["XAUTHORITY"] = str(Path("~" + session.user).expanduser() / ".Xauthority")
        try:
            idle_time_output = subprocess.check_output(
                ["sudo", "-u", session.user, "xprintidle"], env=env
            )
            return float(idle_time_output.strip()) / 1000.0
        except FileNotFoundError as error:
            raise SevereCheckError("sudo executable not found") from error
        except (subprocess.CalledProcessError, ValueError) as error:
            self.logger.warning(
                "Unable to determine the idle time for display %s.",
                session.display,
                exc_info=True,
            )
            raise TemporaryCheckError("Unable to call xprintidle") from error
    def check(self) -> str | None:
        for session in self._safe_provide_sessions():
            self.logger.info("Checking session %s", session)
            # check whether this users should be ignored completely
            if self._ignore_users_re.match(session.user) is not None:
                self.logger.debug("Skipping user '%s' due to request", session.user)
                continue
            # check whether any of the running processes of this user matches
            # the ignore regular expression. In that case we skip idletime
            # checking because we assume the user has a process running that
            # inevitably tampers with the idle time.
            if self._is_skip_process_running(session.user):
                continue
            idle_time = self._get_idle_time(session)
            self.logger.debug(
                "Idle time for display %s of user %s is %s seconds.",
                session.display,
                session.user,
                idle_time,
            )
            if idle_time < self._timeout:
                return (
                    f"X session {session.display} of user {session.user} "
                    f"has idle time {idle_time} < threshold {self._timeout}"
                )
        return None
                                                                                                                                                                                                                                          autosuspend-9.0.0/src/autosuspend/checks/xpath.py                                                   0000664 0000000 0000000 00000010271 15043744726 0022206 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        from collections.abc import Sequence
import configparser
from datetime import datetime, timedelta, UTC
from typing import Any, Self
from lxml import etree  # using safe parser
from lxml.etree import XPath, XPathSyntaxError  # our input
import requests
import requests.exceptions
from . import Activity, ConfigurationError, TemporaryCheckError, Wakeup
from .util import NetworkMixin
class XPathMixin(NetworkMixin):
    @classmethod
    def collect_init_args(cls, config: configparser.SectionProxy) -> dict[str, Any]:
        try:
            args = NetworkMixin.collect_init_args(config)
            args["xpath"] = config["xpath"].strip()
            # validate the expression
            try:
                XPath(args["xpath"])
            except XPathSyntaxError as error:
                raise ConfigurationError(
                    "Invalid xpath expression: " + args["xpath"]
                ) from error
            return args
        except KeyError as error:
            raise ConfigurationError("Lacks " + str(error) + " config entry") from error
    @classmethod
    def create(cls: type[Self], name: str, config: configparser.SectionProxy) -> Self:
        return cls(name, **cls.collect_init_args(config))
    def __init__(self, xpath: str, **kwargs: Any) -> None:
        NetworkMixin.__init__(self, **kwargs)
        self._xpath = xpath
        self._parser = etree.XMLParser(resolve_entities=False)
    def evaluate(self) -> Sequence[Any]:
        try:
            reply = self.request().content
            root = etree.fromstring(reply, parser=self._parser)
            return root.xpath(self._xpath)
        except requests.exceptions.RequestException as error:
            raise TemporaryCheckError(error) from error
        except etree.XMLSyntaxError as error:
            raise TemporaryCheckError(error) from error
class XPathActivity(XPathMixin, Activity):
    def __init__(self, name: str, **kwargs: Any) -> None:
        Activity.__init__(self, name)
        XPathMixin.__init__(self, **kwargs)
    def check(self) -> str | None:
        if self.evaluate():
            return "XPath matches for url " + self._url
        else:
            return None
class XPathWakeup(XPathMixin, Wakeup):
    """Determine wake up times from a network resource using XPath expressions.
    The matched results are expected to represent timestamps in seconds UTC.
    """
    def __init__(self, name: str, **kwargs: Any) -> None:
        Wakeup.__init__(self, name)
        XPathMixin.__init__(self, **kwargs)
    def convert_result(
        self,
        result: str,
        timestamp: datetime,  # noqa: ARG002
    ) -> datetime:
        return datetime.fromtimestamp(float(result), UTC)
    def check(self, timestamp: datetime) -> datetime | None:
        matches = self.evaluate()
        try:
            if matches:
                return min(self.convert_result(m, timestamp) for m in matches)
            else:
                return None
        except TypeError as error:
            raise TemporaryCheckError(
                "XPath returned a result that is not a string: " + str(error)
            ) from None
        except ValueError as error:
            raise TemporaryCheckError(
                "Result cannot be parsed: " + str(error)
            ) from error
class XPathDeltaWakeup(XPathWakeup):
    UNITS = (
        "days",
        "seconds",
        "microseconds",
        "milliseconds",
        "minutes",
        "hours",
        "weeks",
    )
    @classmethod
    def create(cls: type[Self], name: str, config: configparser.SectionProxy) -> Self:
        try:
            args = XPathWakeup.collect_init_args(config)
            args["unit"] = config.get("unit", fallback="minutes")
            return cls(name, **args)
        except ValueError as error:
            raise ConfigurationError(str(error)) from error
    def __init__(self, name: str, unit: str, **kwargs: Any) -> None:
        if unit not in self.UNITS:
            raise ValueError("Unsupported unit")
        XPathWakeup.__init__(self, name, **kwargs)
        self._unit = unit
    def convert_result(self, result: str, timestamp: datetime) -> datetime:
        kwargs = {self._unit: float(result)}
        return timestamp + timedelta(**kwargs)
                                                                                                                                                                                                                                                                                                                                       autosuspend-9.0.0/src/autosuspend/util/                                                             0000775 0000000 0000000 00000000000 15043744726 0020224 5                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        autosuspend-9.0.0/src/autosuspend/util/__init__.py                                                  0000664 0000000 0000000 00000000737 15043744726 0022344 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        import logging
from typing import Any
def logger_by_class(klass: type, name: str | None = None) -> logging.Logger:
    return logging.getLogger(
        "{module}.{klass}{name}".format(
            module=klass.__module__,
            klass=klass.__name__,
            name=f".{name}" if name else "",
        )
    )
def logger_by_class_instance(
    instance: Any,
    name: str | None = None,
) -> logging.Logger:
    return logger_by_class(instance.__class__, name=name)
                                 autosuspend-9.0.0/src/autosuspend/util/datetime.py                                                  0000664 0000000 0000000 00000000641 15043744726 0022373 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        from datetime import datetime, tzinfo
def is_aware(dt: datetime) -> bool:
    return dt.tzinfo is not None and dt.tzinfo.utcoffset(dt) is not None
def to_tz_unaware(dt: datetime, tz: tzinfo | None) -> datetime:
    """Convert a datetime to the given timezone and return a naive datetime (no tzinfo)."""
    dt = dt.replace(tzinfo=tz) if dt.tzinfo is None else dt.astimezone(tz)
    return dt.replace(tzinfo=None)
                                                                                               autosuspend-9.0.0/src/autosuspend/util/systemd.py                                                   0000664 0000000 0000000 00000002524 15043744726 0022271 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        from collections.abc import Iterable
from typing import TYPE_CHECKING
if TYPE_CHECKING:
    import dbus
def _get_bus() -> "dbus.SystemBus":
    import dbus
    return dbus.SystemBus()
class LogindDBusException(RuntimeError):
    """Indicates an error communicating to Logind via DBus."""
def list_logind_sessions() -> Iterable[tuple[str, dict]]:
    """List running logind sessions and their properties.
    Returns:
        list of (session_id, properties dict):
            A list with tuples of sessions ids and their associated properties
            represented as dicts.
    """
    import dbus
    try:
        bus = _get_bus()
        login1 = bus.get_object("org.freedesktop.login1", "/org/freedesktop/login1")
        sessions = login1.ListSessions(dbus_interface="org.freedesktop.login1.Manager")
        results = []
        for session_id, path in [(s[0], s[4]) for s in sessions]:
            session = bus.get_object("org.freedesktop.login1", path)
            properties_interface = dbus.Interface(
                session, "org.freedesktop.DBus.Properties"
            )
            properties = properties_interface.GetAll("org.freedesktop.login1.Session")
            results.append((session_id, properties))
    except dbus.exceptions.DBusException as error:
        raise LogindDBusException(error) from error
    return results
                                                                                                                                                                            autosuspend-9.0.0/tests/                                                                            0000775 0000000 0000000 00000000000 15043744726 0015250 5                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        autosuspend-9.0.0/tests/__init__.py                                                                 0000664 0000000 0000000 00000000522 15043744726 0017360 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        import abc
from typing import Self
from autosuspend.checks import Check
class CheckTest(abc.ABC):
    @abc.abstractmethod
    def create_instance(self: Self, name: str) -> Check:
        pass
    def test_the_configured_name_is_used(self) -> None:
        name = "checktestname"
        assert self.create_instance(name).name == name
                                                                                                                                                                              autosuspend-9.0.0/tests/conftest.py                                                                 0000664 0000000 0000000 00000005510 15043744726 0017450 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        from collections.abc import Callable, Iterable
from pathlib import Path
from typing import Any
from dbus import Bus
from dbus.proxies import ProxyObject
import dbusmock
from dbusmock.pytest_fixtures import dbusmock_system, PrivateDBus  # noqa: F401
import pytest
from pytest_httpserver import HTTPServer
from werkzeug.wrappers import Request, Response
from autosuspend.util import systemd as util_systemd
@pytest.fixture
def serve_file(httpserver: HTTPServer) -> Callable[[Path], str]:
    """Serve a file via HTTP.
    Returns:
        A callable that expected the file path to server. It returns the URL to
        use for accessing the file.
    """
    def serve(the_file: Path) -> str:
        path = f"/{the_file.name}"
        httpserver.expect_request(path).respond_with_data(the_file.read_bytes())
        return httpserver.url_for(path)
    return serve
@pytest.fixture
def serve_protected(httpserver: HTTPServer) -> Callable[[Path], tuple[str, str, str]]:
    """Serve a file behind basic authentication.
    Returns:
        A callable that accepts the file path to serve. It returns as a tuple
        the URL to use for the file, valid username and password
    """
    realm = "the_realm"
    username = "the_user"
    password = "the_password"  # only for testing
    def serve(the_file: Path) -> tuple[str, str, str]:
        def handler(request: Request) -> Response:
            auth = request.authorization
            if not auth or not (
                auth.username == username and auth.password == password
            ):
                return Response(
                    "Authentication required",
                    401,
                    {"WWW-Authenticate": f"Basic realm={realm}"},
                )
            else:
                return Response(the_file.read_bytes())
        path = f"/{the_file.name}"
        httpserver.expect_request(path).respond_with_handler(handler)
        return (httpserver.url_for(path), username, password)
    return serve
@pytest.fixture
def logind(
    monkeypatch: Any,
    dbusmock_system: PrivateDBus,  # noqa
) -> Iterable[ProxyObject]:
    pytest.importorskip("dbus")
    pytest.importorskip("gi")
    with dbusmock.SpawnedMock.spawn_with_template("logind") as server:
        def get_bus() -> Bus:
            return dbusmock_system.bustype.get_connection()
        monkeypatch.setattr(util_systemd, "_get_bus", get_bus)
        yield server.obj
@pytest.fixture
def _logind_dbus_error(
    monkeypatch: Any, dbusmock_system: PrivateDBus  # noqa
) -> Iterable[None]:
    pytest.importorskip("dbus")
    pytest.importorskip("gi")
    with dbusmock.SpawnedMock.spawn_with_template("logind"):
        def get_bus() -> Bus:
            import dbus
            raise dbus.exceptions.ValidationException("Test")
        monkeypatch.setattr(util_systemd, "_get_bus", get_bus)
        yield
                                                                                                                                                                                        autosuspend-9.0.0/tests/data/                                                                       0000775 0000000 0000000 00000000000 15043744726 0016161 5                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        autosuspend-9.0.0/tests/data/mindeps-test.conf                                                      0000664 0000000 0000000 00000000441 15043744726 0021443 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        [general]
interval = 5
idle_time = 900
suspend_cmd = /usr/bin/systemctl suspend
wakeup_cmd = echo {timestamp:.0f} > /sys/class/rtc/rtc0/wakealarm
woke_up_file = /var/run/autosuspend-just-woke-up
lock_file = /tmp/autosuspend-test-mindeps.lock
[check.Ping]
enabled = true
hosts = localhost
                                                                                                                                                                                                                               autosuspend-9.0.0/tests/test_autosuspend.py                                                         0000664 0000000 0000000 00000056550 15043744726 0021246 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        import argparse
import configparser
from datetime import datetime, timedelta, timezone, UTC
import logging
import subprocess
from typing import Any
import dateutil.parser
import pytest
from pytest_mock import MockerFixture
import autosuspend
class TestExecuteSuspend:
    def test_smoke(self, mocker: MockerFixture) -> None:
        mock = mocker.patch("subprocess.check_call")
        command = ["foo", "bar"]
        autosuspend.execute_suspend(command, None)
        mock.assert_called_once_with(command, shell=True)
    def test_call_exception(self, mocker: MockerFixture) -> None:
        mock = mocker.patch("subprocess.check_call")
        command = ["foo", "bar"]
        mock.side_effect = subprocess.CalledProcessError(2, command)
        spy = mocker.spy(autosuspend._logger, "warning")
        autosuspend.execute_suspend(command, None)
        mock.assert_called_once_with(command, shell=True)
        assert spy.call_count == 1
class TestScheduleWakeup:
    def test_smoke(self, mocker: MockerFixture) -> None:
        mock = mocker.patch("subprocess.check_call")
        dt = datetime.fromtimestamp(1525270801, timezone(timedelta(hours=4)))
        autosuspend.schedule_wakeup("echo {timestamp:.0f} {iso}", dt)
        mock.assert_called_once_with(
            "echo 1525270801 2018-05-02T18:20:01+04:00", shell=True
        )
    def test_call_exception(self, mocker: MockerFixture) -> None:
        mock = mocker.patch("subprocess.check_call")
        mock.side_effect = subprocess.CalledProcessError(2, "foo")
        spy = mocker.spy(autosuspend._logger, "warning")
        autosuspend.schedule_wakeup("foo", datetime.now(UTC))
        mock.assert_called_once_with("foo", shell=True)
        assert spy.call_count == 1
class TestConfigureLogging:
    def test_debug(self, mocker: MockerFixture) -> None:
        mock = mocker.patch("logging.basicConfig")
        autosuspend.configure_logging(None, True)
        mock.assert_called_once_with(level=logging.DEBUG)
    def test_standard(self, mocker: MockerFixture) -> None:
        mock = mocker.patch("logging.basicConfig")
        autosuspend.configure_logging(None, False)
        mock.assert_called_once_with(level=logging.WARNING)
    def test_file(self, mocker: MockerFixture) -> None:
        mock = mocker.patch("logging.config.fileConfig")
        # anything that is not a boolean is treated like a file
        autosuspend.configure_logging(42, False)  # type: ignore
        mock.assert_called_once_with(42)
    def test_file_fallback(self, mocker: MockerFixture) -> None:
        mock = mocker.patch("logging.config.fileConfig", side_effect=RuntimeError())
        mock_basic = mocker.patch("logging.basicConfig")
        # anything that is not a boolean is treated like a file
        autosuspend.configure_logging(42, False)  # type: ignore
        mock.assert_called_once_with(42)
        mock_basic.assert_called_once_with(level=logging.WARNING)
class TestSetUpChecks:
    def test_smoke(self, mocker: MockerFixture) -> None:
        mock_class = mocker.patch("autosuspend.checks.activity.Mpd")
        mock_class.create.return_value = mocker.MagicMock(
            spec=autosuspend.checks.Activity
        )
        parser = configparser.ConfigParser()
        parser.read_string(
            """
            [check.Foo]
            class = Mpd
            enabled = True
            """
        )
        autosuspend.set_up_checks(
            parser, "check", "activity", autosuspend.Activity  # type: ignore
        )
        mock_class.create.assert_called_once_with("Foo", parser["check.Foo"])
    def test_external_class(self, mocker: MockerFixture) -> None:
        mock_class = mocker.patch("os.path.TestCheck", create=True)
        mock_class.create.return_value = mocker.MagicMock(
            spec=autosuspend.checks.Activity
        )
        parser = configparser.ConfigParser()
        parser.read_string(
            """
            [check.Foo]
            class = os.path.TestCheck
            enabled = True
            """
        )
        autosuspend.set_up_checks(
            parser, "check", "activity", autosuspend.Activity  # type: ignore
        )
        mock_class.create.assert_called_once_with("Foo", parser["check.Foo"])
    def test_not_enabled(self, mocker: MockerFixture) -> None:
        mock_class = mocker.patch("autosuspend.checks.activity.Mpd")
        mock_class.create.return_value = mocker.MagicMock(spec=autosuspend.Activity)
        parser = configparser.ConfigParser()
        parser.read_string(
            """
            [check.Foo]
            class = Mpd
            enabled = False
            """
        )
        assert not autosuspend.set_up_checks(
            parser,
            "check",
            "activity",
            autosuspend.Activity,  # type: ignore
        )
        with pytest.raises(autosuspend.ConfigurationError):
            autosuspend.set_up_checks(
                parser,
                "check",
                "activity",
                autosuspend.Activity,  # type: ignore
                error_none=True,
            )
    def test_not_enabled_continues_with_next(self, mocker: MockerFixture) -> None:
        mock_mpd = mocker.patch("autosuspend.checks.activity.Mpd")
        mock_mpd.create.return_value = mocker.MagicMock(spec=autosuspend.Activity)
        mock_xidletime = mocker.patch("autosuspend.checks.activity.XIdleTime")
        mock_xidletime.create.return_value = mocker.MagicMock(spec=autosuspend.Activity)
        parser = configparser.ConfigParser()
        parser.read_string(
            """
            [check.Foo]
            class = Mpd
            enabled = False
            [check.Bar]
            class = XIdleTime
            enabled = True
            """
        )
        assert (
            len(
                autosuspend.set_up_checks(
                    parser,
                    "check",
                    "activity",
                    autosuspend.Activity,  # type: ignore
                )
            )
            == 1
        )
    def test_no_such_class(self) -> None:
        parser = configparser.ConfigParser()
        parser.read_string(
            """
            [check.Foo]
            class = FooBarr
            enabled = True
            """
        )
        with pytest.raises(autosuspend.ConfigurationError):
            autosuspend.set_up_checks(
                parser, "check", "activity", autosuspend.Activity  # type: ignore
            )
    def test_not_a_check(self, mocker: MockerFixture) -> None:
        mock_class = mocker.patch("autosuspend.checks.activity.Mpd")
        mock_class.create.return_value = mocker.MagicMock()
        parser = configparser.ConfigParser()
        parser.read_string(
            """
            [check.Foo]
            class = Mpd
            enabled = True
            """
        )
        with pytest.raises(autosuspend.ConfigurationError):
            autosuspend.set_up_checks(
                parser, "check", "activity", autosuspend.Activity  # type: ignore
            )
        mock_class.create.assert_called_once_with("Foo", parser["check.Foo"])
    def test_passwords_redacted(self, mocker: MockerFixture, caplog: Any) -> None:
        mock_class = mocker.patch("autosuspend.checks.activity.Mpd")
        mock_class.create.return_value = mocker.MagicMock(
            spec=autosuspend.checks.Activity
        )
        parser = configparser.ConfigParser()
        parser.read_string(
            """
            [check.Foo]
            class = Mpd
            enabled = True
            password = THEPASS
            """
        )
        with caplog.at_level(logging.DEBUG):
            autosuspend.set_up_checks(
                parser, "check", "activity", autosuspend.Activity  # type: ignore
            )
            assert "THEPASS" not in caplog.text
class TestExecuteChecks:
    def test_no_checks(self, mocker: MockerFixture) -> None:
        assert autosuspend.execute_checks([], False, mocker.MagicMock()) is False
    def test_matches(self, mocker: MockerFixture) -> None:
        matching_check = mocker.MagicMock(spec=autosuspend.Activity)
        matching_check.name = "foo"
        matching_check.check.return_value = "matches"
        assert (
            autosuspend.execute_checks([matching_check], False, mocker.MagicMock())
            is True
        )
        matching_check.check.assert_called_once_with()
    def test_only_first_called(self, mocker: MockerFixture) -> None:
        matching_check = mocker.MagicMock(spec=autosuspend.Activity)
        matching_check.name = "foo"
        matching_check.check.return_value = "matches"
        second_check = mocker.MagicMock()
        second_check.name = "bar"
        second_check.check.return_value = "matches"
        assert (
            autosuspend.execute_checks(
                [matching_check, second_check], False, mocker.MagicMock()
            )
            is True
        )
        matching_check.check.assert_called_once_with()
        second_check.check.assert_not_called()
    def test_all_called(self, mocker: MockerFixture) -> None:
        matching_check = mocker.MagicMock(spec=autosuspend.Activity)
        matching_check.name = "foo"
        matching_check.check.return_value = "matches"
        second_check = mocker.MagicMock()
        second_check.name = "bar"
        second_check.check.return_value = "matches"
        assert (
            autosuspend.execute_checks(
                [matching_check, second_check], True, mocker.MagicMock()
            )
            is True
        )
        matching_check.check.assert_called_once_with()
        second_check.check.assert_called_once_with()
    def test_treat_temporary_errors_as_activity(self, mocker: MockerFixture) -> None:
        matching_check = mocker.MagicMock(spec=autosuspend.Activity)
        matching_check.name = "foo"
        matching_check.check.side_effect = autosuspend.TemporaryCheckError()
        assert (
            autosuspend.execute_checks([matching_check], False, mocker.MagicMock())
            is True
        )
        matching_check.check.assert_called_once_with()
class TestExecuteWakeups:
    def test_no_wakeups(self, mocker: MockerFixture) -> None:
        assert (
            autosuspend.execute_wakeups([], datetime.now(UTC), mocker.MagicMock())
            is None
        )
    def test_all_none(self, mocker: MockerFixture) -> None:
        wakeup = mocker.MagicMock(spec=autosuspend.Wakeup)
        wakeup.check.return_value = None
        assert (
            autosuspend.execute_wakeups([wakeup], datetime.now(UTC), mocker.MagicMock())
            is None
        )
    @pytest.mark.parametrize(
        "illegal",
        [None, dateutil.parser.parse("20040605T090000Z")],
    )
    def test_skips_none_outdated_and_continues(
        self, mocker: MockerFixture, illegal: datetime | None
    ) -> None:
        wakeup_none = mocker.MagicMock(spec=autosuspend.Wakeup)
        wakeup_none.check.return_value = illegal
        now = dateutil.parser.parse("20040705T090000Z")
        wake_up_at = now + timedelta(minutes=10)
        wakeup_real = mocker.MagicMock(spec=autosuspend.Wakeup)
        wakeup_real.check.return_value = wake_up_at
        assert (
            autosuspend.execute_wakeups(
                [wakeup_none, wakeup_real],
                now,
                mocker.MagicMock(),
            )
            == wake_up_at
        )
        assert wakeup_none.check.called
    def test_basic_return(self, mocker: MockerFixture) -> None:
        wakeup = mocker.MagicMock(spec=autosuspend.Wakeup)
        now = datetime.now(UTC)
        wakeup_time = now + timedelta(seconds=10)
        wakeup.check.return_value = wakeup_time
        assert (
            autosuspend.execute_wakeups([wakeup], now, mocker.MagicMock())
            == wakeup_time
        )
    def test_soonest_taken(self, mocker: MockerFixture) -> None:
        reference = datetime.now(UTC)
        wakeup = mocker.MagicMock(spec=autosuspend.Wakeup)
        wakeup.check.return_value = reference + timedelta(seconds=20)
        earlier = reference + timedelta(seconds=10)
        wakeup_earlier = mocker.MagicMock(spec=autosuspend.Wakeup)
        wakeup_earlier.check.return_value = earlier
        in_between = reference + timedelta(seconds=15)
        wakeup_later = mocker.MagicMock(spec=autosuspend.Wakeup)
        wakeup_later.check.return_value = in_between
        assert (
            autosuspend.execute_wakeups(
                [wakeup, wakeup_earlier, wakeup_later], reference, mocker.MagicMock()
            )
            == earlier
        )
    def test_ignore_temporary_errors(self, mocker: MockerFixture) -> None:
        now = datetime.now(UTC)
        wakeup = mocker.MagicMock(spec=autosuspend.Wakeup)
        wakeup.check.return_value = now + timedelta(seconds=20)
        wakeup_error = mocker.MagicMock(spec=autosuspend.Wakeup)
        wakeup_error.check.side_effect = autosuspend.TemporaryCheckError()
        wakeup_earlier = mocker.MagicMock(spec=autosuspend.Wakeup)
        wakeup_earlier.check.return_value = now + timedelta(seconds=10)
        assert autosuspend.execute_wakeups(
            [wakeup, wakeup_error, wakeup_earlier], now, mocker.MagicMock()
        ) == now + timedelta(seconds=10)
    def test_ignore_too_early(self, mocker: MockerFixture) -> None:
        now = datetime.now(UTC)
        wakeup = mocker.MagicMock(spec=autosuspend.Wakeup)
        wakeup.check.return_value = now
        assert autosuspend.execute_wakeups([wakeup], now, mocker.MagicMock()) is None
        assert (
            autosuspend.execute_wakeups(
                [wakeup], now + timedelta(seconds=1), mocker.MagicMock()
            )
            is None
        )
class TestNotifySuspend:
    def test_date(self, mocker: MockerFixture) -> None:
        mock = mocker.patch("subprocess.check_call")
        dt = datetime.fromtimestamp(1525270801, timezone(timedelta(hours=4)))
        autosuspend.notify_suspend("echo {timestamp:.0f} {iso}", "not this", dt)
        mock.assert_called_once_with(
            "echo 1525270801 2018-05-02T18:20:01+04:00", shell=True
        )
    def test_date_no_command(self, mocker: MockerFixture) -> None:
        mock = mocker.patch("subprocess.check_call")
        dt = datetime.fromtimestamp(1525270801, timezone(timedelta(hours=4)))
        autosuspend.notify_suspend(None, "not this", dt)
        mock.assert_not_called()
    def test_no_date(self, mocker: MockerFixture) -> None:
        mock = mocker.patch("subprocess.check_call")
        autosuspend.notify_suspend("echo {timestamp:.0f} {iso}", "echo nothing", None)
        mock.assert_called_once_with("echo nothing", shell=True)
    def test_no_date_no_command(self, mocker: MockerFixture) -> None:
        mock = mocker.patch("subprocess.check_call")
        autosuspend.notify_suspend("echo {timestamp:.0f} {iso}", None, None)
        mock.assert_not_called()
    def test_ignore_execution_errors(self, mocker: MockerFixture, caplog: Any) -> None:
        mock = mocker.patch("subprocess.check_call")
        mock.side_effect = subprocess.CalledProcessError(2, "cmd")
        dt = datetime.fromtimestamp(1525270801, timezone(timedelta(hours=4)))
        with caplog.at_level(logging.WARNING):
            autosuspend.notify_suspend("wakeup", "nowakeup", dt)
            assert "Unable to execute" in caplog.text
            assert mock.called
    def test_info_no_command(self, caplog: Any) -> None:
        with caplog.at_level(logging.INFO):
            autosuspend.notify_suspend(None, None, datetime.now(UTC))
            assert "suitable" in caplog.text
class TestConfigureProcessor:
    def test_minimal_config(self, mocker: MockerFixture) -> None:
        parser = configparser.ConfigParser()
        parser.read_string(
            """
            [general]
            suspend_cmd = suspend
            wakeup_cmd = wakeup
            """
        )
        args = mocker.MagicMock(spec=argparse.Namespace)
        type(args).all_checks = mocker.PropertyMock(return_value=True)
        processor = autosuspend.configure_processor(args, parser, [], [])
        assert processor._idle_time == 300
        assert processor._min_sleep_time == 1200
        assert processor._wakeup_delta == 30
        assert processor._all_activities
def test_notify_and_suspend(mocker: MockerFixture) -> None:
    mock = mocker.patch("subprocess.check_call")
    dt = datetime.fromtimestamp(1525270801, timezone(timedelta(hours=4)))
    autosuspend.notify_and_suspend(
        "echo suspend", "echo notify {timestamp:.0f} {iso}", "not this", dt
    )
    mock.assert_has_calls(
        [
            mocker.call("echo notify 1525270801 2018-05-02T18:20:01+04:00", shell=True),
            mocker.call("echo suspend", shell=True),
        ]
    )
class _StubCheck(autosuspend.Activity):
    @classmethod
    def create(cls, name: str, config: configparser.SectionProxy) -> "_StubCheck":
        raise NotImplementedError()
    def __init__(self, name: str, match: str | None) -> None:
        autosuspend.Activity.__init__(self, name)
        self.match = match
    def check(self) -> str | None:
        return self.match
class SleepFn:
    def __init__(self) -> None:
        self.called = False
        self.call_arg: float | None = None
    def reset(self) -> None:
        self.called = False
        self.call_arg = None
    def __call__(self, arg: float) -> None:
        self.called = True
        self.call_arg = arg
@pytest.fixture
def sleep_fn() -> SleepFn:
    return SleepFn()
class WakeupFn:
    def __init__(self) -> None:
        self.call_arg: datetime | None = None
    def reset(self) -> None:
        self.call_arg = None
    def __call__(self, arg: datetime) -> None:
        self.call_arg = arg
@pytest.fixture
def wakeup_fn() -> WakeupFn:
    return WakeupFn()
class TestProcessor:
    def test_smoke(self, sleep_fn: SleepFn, wakeup_fn: WakeupFn) -> None:
        processor = autosuspend.Processor(
            [_StubCheck("stub", None)], [], 2, 0, 0, sleep_fn, wakeup_fn, False
        )
        # should init the timestamp initially
        start = datetime.now(UTC)
        processor.iteration(start, False)
        assert not sleep_fn.called
        # not yet reached
        processor.iteration(start + timedelta(seconds=1), False)
        assert not sleep_fn.called
        # time must be greater, not equal
        processor.iteration(start + timedelta(seconds=2), False)
        assert not sleep_fn.called
        # go to sleep
        processor.iteration(start + timedelta(seconds=3), False)
        assert sleep_fn.called
        assert sleep_fn.call_arg is None
        sleep_fn.reset()
        # second iteration to check that the idle time got reset
        processor.iteration(start + timedelta(seconds=4), False)
        assert not sleep_fn.called
        # go to sleep again
        processor.iteration(start + timedelta(seconds=6, milliseconds=2), False)
        assert sleep_fn.called
        assert wakeup_fn.call_arg is None
    def test_just_woke_up_handling(
        self, sleep_fn: SleepFn, wakeup_fn: WakeupFn
    ) -> None:
        processor = autosuspend.Processor(
            [_StubCheck("stub", None)], [], 2, 0, 0, sleep_fn, wakeup_fn, False
        )
        # should init the timestamp initially
        start = datetime.now(UTC)
        processor.iteration(start, False)
        assert not sleep_fn.called
        # should go to sleep but we just woke up
        processor.iteration(start + timedelta(seconds=3), True)
        assert not sleep_fn.called
        # start over again
        processor.iteration(start + timedelta(seconds=4), False)
        assert not sleep_fn.called
        # not yet sleeping
        processor.iteration(start + timedelta(seconds=6), False)
        assert not sleep_fn.called
        # now go to sleep
        processor.iteration(start + timedelta(seconds=7), False)
        assert sleep_fn.called
        assert wakeup_fn.call_arg is None
    def test_wakeup_blocks_sleep(
        self, mocker: MockerFixture, sleep_fn: SleepFn, wakeup_fn: WakeupFn
    ) -> None:
        start = datetime.now(UTC)
        wakeup = mocker.MagicMock(spec=autosuspend.Wakeup)
        wakeup.check.return_value = start + timedelta(seconds=6)
        processor = autosuspend.Processor(
            [_StubCheck("stub", None)], [wakeup], 2, 3.1, 0, sleep_fn, wakeup_fn, False
        )
        # init iteration
        processor.iteration(start, False)
        # no activity and enough time passed to start sleeping
        processor.iteration(start + timedelta(seconds=3), False)
        assert not sleep_fn.called
        assert wakeup_fn.call_arg is None
    def test_wakeup_exact_hit_does_not_block(
        self,
        mocker: MockerFixture,
        sleep_fn: SleepFn,
        wakeup_fn: WakeupFn,
    ) -> None:
        start = datetime.now(UTC)
        wakeup = mocker.MagicMock(spec=autosuspend.Wakeup)
        wakeup.check.return_value = start + timedelta(seconds=6)
        processor = autosuspend.Processor(
            [_StubCheck("stub", None)], [wakeup], 2, 3, 0, sleep_fn, wakeup_fn, False
        )
        # init iteration
        processor.iteration(start, False)
        # no activity and enough time passed to start sleeping
        processor.iteration(start + timedelta(seconds=3), False)
        assert sleep_fn.called
        assert wakeup_fn.call_arg is not None
    def test_wakeup_scheduled(
        self, mocker: MockerFixture, sleep_fn: SleepFn, wakeup_fn: WakeupFn
    ) -> None:
        start = datetime.now(UTC)
        wakeup = mocker.MagicMock(spec=autosuspend.Wakeup)
        wakeup.check.return_value = start + timedelta(seconds=25)
        processor = autosuspend.Processor(
            [_StubCheck("stub", None)], [wakeup], 2, 10, 0, sleep_fn, wakeup_fn, False
        )
        # init iteration
        processor.iteration(start, False)
        # no activity and enough time passed to start sleeping
        processor.iteration(start + timedelta(seconds=3), False)
        assert sleep_fn.called
        assert sleep_fn.call_arg == start + timedelta(seconds=25)
        assert wakeup_fn.call_arg == start + timedelta(seconds=25)
        sleep_fn.reset()
        wakeup_fn.reset()
        # ensure that wake up is not scheduled again
        processor.iteration(start + timedelta(seconds=25), False)
        assert wakeup_fn.call_arg is None
    def test_wakeup_delta_blocks(
        self, mocker: MockerFixture, sleep_fn: SleepFn, wakeup_fn: WakeupFn
    ) -> None:
        start = datetime.now(UTC)
        wakeup = mocker.MagicMock(spec=autosuspend.Wakeup)
        wakeup.check.return_value = start + timedelta(seconds=25)
        processor = autosuspend.Processor(
            [_StubCheck("stub", None)], [wakeup], 2, 10, 22, sleep_fn, wakeup_fn, False
        )
        # init iteration
        processor.iteration(start, False)
        # no activity and enough time passed to start sleeping
        processor.iteration(start + timedelta(seconds=3), False)
        assert not sleep_fn.called
    def test_wakeup_delta_applied(
        self, mocker: MockerFixture, sleep_fn: SleepFn, wakeup_fn: WakeupFn
    ) -> None:
        start = datetime.now(UTC)
        wakeup = mocker.MagicMock(spec=autosuspend.Wakeup)
        wakeup.check.return_value = start + timedelta(seconds=25)
        processor = autosuspend.Processor(
            [_StubCheck("stub", None)], [wakeup], 2, 10, 4, sleep_fn, wakeup_fn, False
        )
        # init iteration
        processor.iteration(start, False)
        # no activity and enough time passed to start sleeping
        processor.iteration(start + timedelta(seconds=3), False)
        assert sleep_fn.called
        assert wakeup_fn.call_arg == start + timedelta(seconds=21)
                                                                                                                                                        autosuspend-9.0.0/tests/test_checks.py                                                              0000664 0000000 0000000 00000001224 15043744726 0020120 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        import configparser
from autosuspend.checks import Check
class DummyCheck(Check):
    @classmethod
    def create(cls, name: str, config: configparser.SectionProxy) -> "DummyCheck":
        raise NotImplementedError()
    def check(self) -> str | None:
        pass
class TestCheck:
    class TestName:
        def test_returns_the_provided_name(self) -> None:
            name = "test"
            assert DummyCheck(name).name == name
        def test_has_a_sensible_default(self) -> None:
            assert DummyCheck().name is not None
    def test_has_a_string_representation(self) -> None:
        assert isinstance(str(DummyCheck("test")), str)
                                                                                                                                                                                                                                                                                                                                                                            autosuspend-9.0.0/tests/test_checks_activity.py                                                     0000664 0000000 0000000 00000001226 15043744726 0022036 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        import pytest
from autosuspend.checks import Activity
@pytest.mark.parametrize(
    "name",
    [
        "ActiveCalendarEvent",
        "ActiveConnection",
        "ExternalCommand",
        "JsonPath",
        "Kodi",
        "KodiIdleTime",
        "LastLogActivity",
        "Load",
        "LogindSessionsIdle",
        "Mpd",
        "NetworkBandwidth",
        "Ping",
        "Processes",
        "Smb",
        "Users",
        "XIdleTime",
        "XPath",
    ],
)
def test_legacy_check_names_are_available(name: str) -> None:
    res = __import__("autosuspend.checks.activity", fromlist=[name])
    assert issubclass(getattr(res, name), Activity)
                                                                                                                                                                                                                                                                                                                                                                          autosuspend-9.0.0/tests/test_checks_command.py                                                      0000664 0000000 0000000 00000010304 15043744726 0021615 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        from datetime import datetime, UTC
import subprocess
import pytest
from pytest_mock import MockerFixture
from autosuspend.checks import (
    Activity,
    Check,
    ConfigurationError,
    SevereCheckError,
    TemporaryCheckError,
)
from autosuspend.checks.command import CommandActivity, CommandMixin, CommandWakeup
from . import CheckTest
from .utils import config_section
class _CommandMixinSub(CommandMixin, Activity):
    def __init__(self, name: str, command: str) -> None:
        Activity.__init__(self, name)
        CommandMixin.__init__(self, command)
    def check(self) -> str | None:
        pass
class TestCommandMixin:
    class TestCreate:
        def test_it_works(self) -> None:
            section = config_section({"command": "narf bla"})
            check: _CommandMixinSub = _CommandMixinSub.create(
                "name",
                section,
            )
            assert check._command == "narf bla"
        def test_throws_if_no_command_is_configured(self) -> None:
            with pytest.raises(ConfigurationError):
                _CommandMixinSub.create("name", config_section())
class TestCommandActivity(CheckTest):
    def create_instance(self, name: str) -> Check:
        return CommandActivity(name, "asdfasdf")
    def test_reports_activity_if_the_command_succeeds(
        self, mocker: MockerFixture
    ) -> None:
        mock = mocker.patch("subprocess.check_call")
        assert (
            CommandActivity.create(
                "name", config_section({"command": "foo bar"})
            ).check()
            is not None
        )
        mock.assert_called_once_with("foo bar", shell=True)
    def test_reports_no_activity_if_the_command_fails(
        self, mocker: MockerFixture
    ) -> None:
        mock = mocker.patch("subprocess.check_call")
        mock.side_effect = subprocess.CalledProcessError(2, "foo bar")
        assert (
            CommandActivity.create(
                "name", config_section({"command": "foo bar"})
            ).check()
            is None
        )
        mock.assert_called_once_with("foo bar", shell=True)
    def test_reports_missing_commands(self) -> None:
        with pytest.raises(SevereCheckError):
            CommandActivity.create(
                "name", config_section({"command": "thisreallydoesnotexist"})
            ).check()
class TestCommandWakeup(CheckTest):
    def create_instance(self, name: str) -> Check:
        return CommandWakeup(name, "asdf")
    def test_reports_the_wakup_time_received_from_the_command(self) -> None:
        check = CommandWakeup("test", "echo 1234")
        assert check.check(datetime.now(UTC)) == datetime.fromtimestamp(1234, UTC)
    def test_reports_no_wakeup_without_command_output(self) -> None:
        check = CommandWakeup("test", "echo")
        assert check.check(datetime.now(UTC)) is None
    def test_raises_an_error_if_the_command_output_cannot_be_parsed(self) -> None:
        check = CommandWakeup("test", "echo asdfasdf")
        with pytest.raises(TemporaryCheckError):
            check.check(datetime.now(UTC))
    def test_uses_only_the_first_output_line(self, mocker: MockerFixture) -> None:
        mock = mocker.patch("subprocess.check_output")
        mock.return_value = "1234\nignore\n"
        check = CommandWakeup("test", "echo bla")
        assert check.check(datetime.now(UTC)) == datetime.fromtimestamp(1234, UTC)
    def test_uses_only_the_first_line_even_if_empty(
        self, mocker: MockerFixture
    ) -> None:
        mock = mocker.patch("subprocess.check_output")
        mock.return_value = "   \nignore\n"
        check = CommandWakeup("test", "echo bla")
        assert check.check(datetime.now(UTC)) is None
    def test_raises_if_the_called_command_fails(self, mocker: MockerFixture) -> None:
        mock = mocker.patch("subprocess.check_output")
        mock.side_effect = subprocess.CalledProcessError(2, "foo bar")
        check = CommandWakeup("test", "echo bla")
        with pytest.raises(TemporaryCheckError):
            check.check(datetime.now(UTC))
    def test_reports_missing_executables(self) -> None:
        check = CommandWakeup("test", "reallydoesntexist bla")
        with pytest.raises(SevereCheckError):
            check.check(datetime.now(UTC))
                                                                                                                                                                                                                                                                                                                            autosuspend-9.0.0/tests/test_checks_ical.py                                                         0000664 0000000 0000000 00000052416 15043744726 0021121 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        from collections.abc import Callable
from datetime import timedelta
from pathlib import Path
from dateutil import parser
from freezegun import freeze_time
import tzlocal
from autosuspend.checks import Check
from autosuspend.checks.ical import (
    ActiveCalendarEvent,
    Calendar,
    CalendarEvent,
    list_calendar_events,
)
from . import CheckTest
from .utils import config_section
class TestCalendarEvent:
    def test_str(self) -> None:
        start = parser.parse("2018-06-11 02:00:00 UTC")
        end = start + timedelta(hours=1)
        event = CalendarEvent("summary", start, end)
        assert "summary" in str(event)
class TestListCalendarEvents:
    def test_simple_recurring(self, datadir: Path) -> None:
        """Tests for basic recurrence.
        Events are collected with the same DST setting as their original
        creation.
        """
        with (datadir / "simple-recurring.ics").open("rb") as f:
            start = parser.parse("2018-06-18 04:00:00 UTC")
            end = start + timedelta(weeks=2)
            events = list_calendar_events(f, start, end)
            expected_start_times = [
                parser.parse("2018-06-18 07:00:00 UTC"),
                parser.parse("2018-06-19 07:00:00 UTC"),
                parser.parse("2018-06-20 07:00:00 UTC"),
                parser.parse("2018-06-21 07:00:00 UTC"),
                parser.parse("2018-06-22 07:00:00 UTC"),
                parser.parse("2018-06-25 07:00:00 UTC"),
                parser.parse("2018-06-26 07:00:00 UTC"),
                parser.parse("2018-06-27 07:00:00 UTC"),
                parser.parse("2018-06-28 07:00:00 UTC"),
                parser.parse("2018-06-29 07:00:00 UTC"),
            ]
            expected_end_times = [
                parser.parse("2018-06-18 16:00:00 UTC"),
                parser.parse("2018-06-19 16:00:00 UTC"),
                parser.parse("2018-06-20 16:00:00 UTC"),
                parser.parse("2018-06-21 16:00:00 UTC"),
                parser.parse("2018-06-22 16:00:00 UTC"),
                parser.parse("2018-06-25 16:00:00 UTC"),
                parser.parse("2018-06-26 16:00:00 UTC"),
                parser.parse("2018-06-27 16:00:00 UTC"),
                parser.parse("2018-06-28 16:00:00 UTC"),
                parser.parse("2018-06-29 16:00:00 UTC"),
            ]
            assert expected_start_times == [e.start for e in events]
            assert expected_end_times == [e.end for e in events]
    def test_recurrence_different_dst(self, datadir: Path) -> None:
        with (datadir / "simple-recurring.ics").open("rb") as f:
            start = parser.parse("2018-11-19 04:00:00 UTC")
            end = start + timedelta(weeks=2)
            events = list_calendar_events(f, start, end)
            expected_start_times = [
                parser.parse("2018-11-19 08:00:00 UTC"),
                parser.parse("2018-11-20 08:00:00 UTC"),
                parser.parse("2018-11-21 08:00:00 UTC"),
                parser.parse("2018-11-22 08:00:00 UTC"),
                parser.parse("2018-11-23 08:00:00 UTC"),
                parser.parse("2018-11-26 08:00:00 UTC"),
                parser.parse("2018-11-27 08:00:00 UTC"),
                parser.parse("2018-11-28 08:00:00 UTC"),
                parser.parse("2018-11-29 08:00:00 UTC"),
                parser.parse("2018-11-30 08:00:00 UTC"),
            ]
            assert expected_start_times == [e.start for e in events]
    def test_all_day_events(self, datadir: Path) -> None:
        with (datadir / "all-day-events.ics").open("rb") as f:
            start = parser.parse("2018-06-11 02:00:00 UTC")
            end = start + timedelta(weeks=1)
            events = list_calendar_events(f, start, end)
            assert len(events) == 3
            expected_summaries = ["start", "between", "end"]
            assert [e.summary for e in events] == expected_summaries
    def test_normal_events(self, datadir: Path) -> None:
        with (datadir / "normal-events-corner-cases.ics").open("rb") as f:
            start = parser.parse("2018-06-04 00:00:00 +0200")
            end = start + timedelta(weeks=1)
            events = list_calendar_events(f, start, end)
            expected = [
                (
                    "overlapping",
                    parser.parse("2018-06-02 20:00:00 +0200"),
                    parser.parse("2018-06-12 23:00:00 +0200"),
                ),
                (
                    "before include",
                    parser.parse("2018-06-03 21:00:00 +0200"),
                    parser.parse("2018-06-04 02:00:00 +0200"),
                ),
                (
                    "direct start",
                    parser.parse("2018-06-04 00:00:00 +0200"),
                    parser.parse("2018-06-04 03:00:00 +0200"),
                ),
                (
                    "in between",
                    parser.parse("2018-06-07 04:00:00 +0200"),
                    parser.parse("2018-06-07 09:00:00 +0200"),
                ),
                (
                    "end overlap",
                    parser.parse("2018-06-10 21:00:00 +0200"),
                    parser.parse("2018-06-11 02:00:00 +0200"),
                ),
                (
                    "direct end",
                    parser.parse("2018-06-10 22:00:00 +0200"),
                    parser.parse("2018-06-11 00:00:00 +0200"),
                ),
            ]
            assert [(e.summary, e.start, e.end) for e in events] == expected
    def test_floating_time(self, datadir: Path) -> None:
        with (datadir / "floating.ics").open("rb") as f:
            tzinfo = {"LOCAL": tzlocal.get_localzone()}
            start = parser.parse("2018-06-09 00:00:00 LOCAL", tzinfos=tzinfo)
            end = start + timedelta(weeks=1)
            events = list_calendar_events(f, start, end)
            expected = [
                (
                    "floating",
                    parser.parse("2018-06-10 15:00:00 LOCAL", tzinfos=tzinfo),
                    parser.parse("2018-06-10 17:00:00 LOCAL", tzinfos=tzinfo),
                ),
                (
                    "floating recurring",
                    parser.parse("2018-06-12 18:00:00 LOCAL", tzinfos=tzinfo),
                    parser.parse("2018-06-12 20:00:00 LOCAL", tzinfos=tzinfo),
                ),
                (
                    "floating recurring",
                    parser.parse("2018-06-13 18:00:00 LOCAL", tzinfos=tzinfo),
                    parser.parse("2018-06-13 20:00:00 LOCAL", tzinfos=tzinfo),
                ),
                (
                    "floating recurring",
                    parser.parse("2018-06-14 18:00:00 LOCAL", tzinfos=tzinfo),
                    parser.parse("2018-06-14 20:00:00 LOCAL", tzinfos=tzinfo),
                ),
                (
                    "floating recurring",
                    parser.parse("2018-06-15 18:00:00 LOCAL", tzinfos=tzinfo),
                    parser.parse("2018-06-15 20:00:00 LOCAL", tzinfos=tzinfo),
                ),
            ]
            assert [(e.summary, e.start, e.end) for e in events] == expected
    def test_floating_time_other_dst(self, datadir: Path) -> None:
        with (datadir / "floating.ics").open("rb") as f:
            tzinfo = {"LOCAL": tzlocal.get_localzone()}
            start = parser.parse("2018-12-09 00:00:00 LOCAL", tzinfos=tzinfo)
            end = start + timedelta(weeks=1)
            events = list_calendar_events(f, start, end)
            expected = [
                (
                    "floating recurring",
                    parser.parse("2018-12-09 18:00:00 LOCAL", tzinfos=tzinfo),
                    parser.parse("2018-12-09 20:00:00 LOCAL", tzinfos=tzinfo),
                ),
                (
                    "floating recurring",
                    parser.parse("2018-12-10 18:00:00 LOCAL", tzinfos=tzinfo),
                    parser.parse("2018-12-10 20:00:00 LOCAL", tzinfos=tzinfo),
                ),
                (
                    "floating recurring",
                    parser.parse("2018-12-11 18:00:00 LOCAL", tzinfos=tzinfo),
                    parser.parse("2018-12-11 20:00:00 LOCAL", tzinfos=tzinfo),
                ),
                (
                    "floating recurring",
                    parser.parse("2018-12-12 18:00:00 LOCAL", tzinfos=tzinfo),
                    parser.parse("2018-12-12 20:00:00 LOCAL", tzinfos=tzinfo),
                ),
                (
                    "floating recurring",
                    parser.parse("2018-12-13 18:00:00 LOCAL", tzinfos=tzinfo),
                    parser.parse("2018-12-13 20:00:00 LOCAL", tzinfos=tzinfo),
                ),
                (
                    "floating recurring",
                    parser.parse("2018-12-14 18:00:00 LOCAL", tzinfos=tzinfo),
                    parser.parse("2018-12-14 20:00:00 LOCAL", tzinfos=tzinfo),
                ),
                (
                    "floating recurring",
                    parser.parse("2018-12-15 18:00:00 LOCAL", tzinfos=tzinfo),
                    parser.parse("2018-12-15 20:00:00 LOCAL", tzinfos=tzinfo),
                ),
            ]
            assert [(e.summary, e.start, e.end) for e in events] == expected
    def test_exclusions(self, datadir: Path) -> None:
        with (datadir / "exclusions.ics").open("rb") as f:
            start = parser.parse("2018-06-09 04:00:00 UTC")
            end = start + timedelta(weeks=2)
            events = list_calendar_events(f, start, end)
            expected_start_times = [
                parser.parse("2018-06-11 12:00:00 UTC"),
                parser.parse("2018-06-12 12:00:00 UTC"),
                parser.parse("2018-06-13 12:00:00 UTC"),
                parser.parse("2018-06-15 12:00:00 UTC"),
                parser.parse("2018-06-16 12:00:00 UTC"),
                parser.parse("2018-06-17 12:00:00 UTC"),
            ]
            assert expected_start_times == [e.start for e in events]
    def test_reucrring_single_changes(self, datadir: Path) -> None:
        with (datadir / "single-change.ics").open("rb") as f:
            start = parser.parse("2018-06-11 00:00:00 UTC")
            end = start + timedelta(weeks=1)
            events = list_calendar_events(f, start, end)
            expected_start_times = [
                parser.parse("2018-06-11 11:00:00 UTC"),
                parser.parse("2018-06-12 11:00:00 UTC"),
                parser.parse("2018-06-13 14:00:00 UTC"),
                parser.parse("2018-06-14 11:00:00 UTC"),
                parser.parse("2018-06-15 09:00:00 UTC"),
                parser.parse("2018-06-16 11:00:00 UTC"),
                parser.parse("2018-06-17 11:00:00 UTC"),
            ]
            assert expected_start_times == [e.start for e in events]
    def test_reucrring_change_dst(self, datadir: Path) -> None:
        with (datadir / "recurring-change-dst.ics").open("rb") as f:
            start = parser.parse("2018-12-10 00:00:00 UTC")
            end = start + timedelta(weeks=1)
            events = list_calendar_events(f, start, end)
            expected_start_times = [
                parser.parse("2018-12-10 13:00:00 UTC"),
                parser.parse("2018-12-11 13:00:00 UTC"),
                parser.parse("2018-12-12 10:00:00 UTC"),
                parser.parse("2018-12-13 13:00:00 UTC"),
                parser.parse("2018-12-15 13:00:00 UTC"),
                parser.parse("2018-12-16 13:00:00 UTC"),
            ]
            assert expected_start_times == [e.start for e in events]
    def test_recurring_start_and_end_inclusive(self, datadir: Path) -> None:
        with (datadir / "issue-41.ics").open("rb") as f:
            start = parser.parse("2018-06-26 15:13:51 UTC")
            end = start + timedelta(weeks=1)
            events = list_calendar_events(f, start, end)
            expected_start_times = [
                parser.parse("2018-06-26 15:00:00 UTC"),
                parser.parse("2018-06-27 15:00:00 UTC"),
                parser.parse("2018-06-28 15:00:00 UTC"),
                parser.parse("2018-06-29 15:00:00 UTC"),
                parser.parse("2018-06-30 15:00:00 UTC"),
                parser.parse("2018-07-01 15:00:00 UTC"),
                parser.parse("2018-07-02 15:00:00 UTC"),
                parser.parse("2018-07-03 15:00:00 UTC"),
            ]
            assert expected_start_times == [e.start for e in events]
    def test_single_start_end_inclusive(self, datadir: Path) -> None:
        with (datadir / "old-event.ics").open("rb") as f:
            start = parser.parse("2004-06-05 11:15:00 UTC")
            end = start + timedelta(hours=1)
            events = list_calendar_events(f, start, end)
            expected_start_times = [
                parser.parse("2004-06-05 11:00:00 UTC"),
            ]
            assert expected_start_times == [e.start for e in events]
    def test_single_all_day_start_end_inclusive(self, datadir: Path) -> None:
        with (datadir / "all-day-starts.ics").open("rb") as f:
            start = parser.parse("2018-06-25 10:00:00 UTC")
            end = start + timedelta(hours=2)
            events = list_calendar_events(f, start, end)
            expected_start_times = [
                parser.parse("2018-06-25 02:00:00 UTC").date(),
            ]
            assert expected_start_times == [e.start for e in events]
            expected_end_times = [
                parser.parse("2018-06-26 02:00:00 UTC").date(),
            ]
            assert expected_end_times == [e.end for e in events]
    def test_longer_single_all_day_start_end_inclusive(self, datadir: Path) -> None:
        with (datadir / "all-day-starts.ics").open("rb") as f:
            start = parser.parse("2018-06-29 10:00:00 UTC")
            end = start + timedelta(hours=2)
            events = list_calendar_events(f, start, end)
            expected_start_times = [
                parser.parse("2018-06-28 02:00:00 UTC").date(),
            ]
            assert expected_start_times == [e.start for e in events]
    def test_recurring_all_day_start_end_inclusive(self, datadir: Path) -> None:
        with (datadir / "all-day-recurring.ics").open("rb") as f:
            start = parser.parse("2018-06-29 10:00:00 UTC")
            end = start + timedelta(hours=2)
            events = list_calendar_events(f, start, end)
            expected_start_times = [
                parser.parse("2018-06-29 02:00:00 UTC").date(),
            ]
            assert expected_start_times == [e.start for e in events]
            expected_end_times = [
                parser.parse("2018-06-30 02:00:00 UTC").date(),
            ]
            assert expected_end_times == [e.end for e in events]
    def test_recurring_all_day_start_in_between(self, datadir: Path) -> None:
        with (datadir / "all-day-recurring.ics").open("rb") as f:
            start = parser.parse("2018-06-29 00:00:00 UTC")
            end = start + timedelta(days=1)
            events = list_calendar_events(f, start, end)
            expected_start_times = [
                parser.parse("2018-06-29 00:00:00 UTC").date(),
                parser.parse("2018-06-30 00:00:00 UTC").date(),
            ]
            assert expected_start_times == [e.start for e in events]
    def test_recurring_all_day_exclusions(self, datadir: Path) -> None:
        with (datadir / "all-day-recurring-exclusions.ics").open("rb") as f:
            start = parser.parse("2018-06-27 00:00:00 UTC")
            end = start + timedelta(days=4)
            events = list_calendar_events(f, start, end)
            expected_start_times = [
                parser.parse("2018-06-27 00:00:00 UTC").date(),
                parser.parse("2018-06-28 00:00:00 UTC").date(),
                parser.parse("2018-06-29 00:00:00 UTC").date(),
                parser.parse("2018-07-01 00:00:00 UTC").date(),
            ]
            assert expected_start_times == [e.start for e in events]
    def test_recurring_all_day_exclusions_end(self, datadir: Path) -> None:
        with (datadir / "all-day-recurring-exclusions.ics").open("rb") as f:
            start = parser.parse("2018-06-26 00:00:00 UTC")
            end = start + timedelta(days=4)
            events = list_calendar_events(f, start, end)
            expected_start_times = [
                parser.parse("2018-06-26 00:00:00 UTC").date(),
                parser.parse("2018-06-27 00:00:00 UTC").date(),
                parser.parse("2018-06-28 00:00:00 UTC").date(),
                parser.parse("2018-06-29 00:00:00 UTC").date(),
            ]
            assert expected_start_times == [e.start for e in events]
class TestActiveCalendarEvent(CheckTest):
    def create_instance(self, name: str) -> Check:
        return ActiveCalendarEvent(name, url="asdfasdf", timeout=5)
    def test_smoke(self, datadir: Path, serve_file: Callable[[Path], str]) -> None:
        result = ActiveCalendarEvent(
            "test",
            url=serve_file(datadir / "long-event.ics"),
            timeout=3,
        ).check()
        assert result is not None
        assert "long-event" in result
    def test_exact_range(
        self, datadir: Path, serve_file: Callable[[Path], str]
    ) -> None:
        with freeze_time("2016-06-05 13:00:00", tz_offset=-2):
            result = ActiveCalendarEvent(
                "test",
                url=serve_file(datadir / "long-event.ics"),
                timeout=3,
            ).check()
            assert result is not None
            assert "long-event" in result
    def test_before_exact_range(
        self, datadir: Path, serve_file: Callable[[Path], str]
    ) -> None:
        with freeze_time("2016-06-05 12:58:00", tz_offset=-2):
            result = ActiveCalendarEvent(
                "test",
                url=serve_file(datadir / "long-event.ics"),
                timeout=3,
            ).check()
            assert result is None
    def test_no_event(self, datadir: Path, serve_file: Callable[[Path], str]) -> None:
        assert (
            ActiveCalendarEvent(
                "test",
                url=serve_file(datadir / "old-event.ics"),
                timeout=3,
            ).check()
            is None
        )
    def test_create(self) -> None:
        check: ActiveCalendarEvent = ActiveCalendarEvent.create(
            "name",
            config_section(
                {
                    "url": "foobar",
                    "username": "user",
                    "password": "pass",
                    "timeout": "3",
                }
            ),
        )
        assert check._url == "foobar"
        assert check._username == "user"
        assert check._password == "pass"
        assert check._timeout == 3
class TestCalendar(CheckTest):
    def create_instance(self, name: str) -> Calendar:
        return Calendar(name, url="file:///asdf", timeout=3)
    def test_create(self) -> None:
        section = config_section(
            {
                "url": "url",
                "username": "user",
                "password": "pass",
                "timeout": "42",
            }
        )
        check: Calendar = Calendar.create(
            "name",
            section,
        )
        assert check._url == "url"
        assert check._username == "user"
        assert check._password == "pass"
        assert check._timeout == 42
    def test_empty(self, datadir: Path, serve_file: Callable[[Path], str]) -> None:
        timestamp = parser.parse("20050605T130000Z")
        assert (
            Calendar(
                "test",
                url=serve_file(datadir / "old-event.ics"),
                timeout=3,
            ).check(timestamp)
            is None
        )
    def test_smoke(self, datadir: Path, serve_file: Callable[[Path], str]) -> None:
        timestamp = parser.parse("20040605T090000Z")
        desired_start = parser.parse("20040605T110000Z")
        assert (
            Calendar(
                "test",
                url=serve_file(datadir / "old-event.ics"),
                timeout=3,
            ).check(timestamp)
            == desired_start
        )
    def test_select_earliest(
        self, datadir: Path, serve_file: Callable[[Path], str]
    ) -> None:
        timestamp = parser.parse("20040401T090000Z")
        desired_start = parser.parse("20040405T110000Z")
        assert (
            Calendar(
                "test",
                url=serve_file(datadir / "multiple.ics"),
                timeout=3,
            ).check(timestamp)
            == desired_start
        )
    def test_ignore_running(
        self, datadir: Path, serve_file: Callable[[Path], str]
    ) -> None:
        url = serve_file(datadir / "old-event.ics")
        timestamp = parser.parse("20040605T110000Z")
        # events are taken if start hits exactly the current time
        assert Calendar("test", url=url, timeout=3).check(timestamp) is not None
        timestamp = timestamp + timedelta(seconds=1)
        assert Calendar("test", url=url, timeout=3).check(timestamp) is None
    def test_limited_horizon(
        self, datadir: Path, serve_file: Callable[[Path], str]
    ) -> None:
        timestamp = parser.parse("20040101T000000Z")
        assert (
            Calendar(
                "test",
                url=serve_file(datadir / "after-horizon.ics"),
                timeout=3,
            ).check(timestamp)
            is None
        )
        assert (
            Calendar(
                "test",
                url=serve_file(datadir / "before-horizon.ics"),
                timeout=3,
            ).check(timestamp)
            is not None
        )
                                                                                                                                                                                                                                                  autosuspend-9.0.0/tests/test_checks_ical/                                                           0000775 0000000 0000000 00000000000 15043744726 0020537 5                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        autosuspend-9.0.0/tests/test_checks_ical/after-horizon.ics                                          0000664 0000000 0000000 00000001273 15043744726 0024031 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        BEGIN:VCALENDAR
PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN
VERSION:2.0
BEGIN:VTIMEZONE
TZID:Europe/Berlin
BEGIN:DAYLIGHT
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
TZNAME:CEST
DTSTART:19700329T020000
RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=3
END:DAYLIGHT
BEGIN:STANDARD
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
TZNAME:CET
DTSTART:19701025T030000
RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10
END:STANDARD
END:VTIMEZONE
BEGIN:VEVENT
CREATED:20180603T194125Z
LAST-MODIFIED:20180603T194144Z
DTSTAMP:20180603T194144Z
UID:6ff13ee1-e548-41b1-8e08-d7725423743a
SUMMARY:long-event
DTSTART;TZID=Europe/Berlin:20040618T000000
DTEND;TZID=Europe/Berlin:20040618T150000
TRANSP:OPAQUE
SEQUENCE:1
END:VEVENT
END:VCALENDAR
                                                                                                                                                                                                                                                                                                                                     autosuspend-9.0.0/tests/test_checks_ical/all-day-events.ics                                         0000664 0000000 0000000 00000002426 15043744726 0024070 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        BEGIN:VCALENDAR
PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN
VERSION:2.0
BEGIN:VEVENT
CREATED:20180601T194043Z
LAST-MODIFIED:20180601T194050Z
DTSTAMP:20180601T194050Z
UID:0f82aa78-1478-4093-85c5-16d754f362f6
SUMMARY:between
DTSTART;VALUE=DATE:20180613
DTEND;VALUE=DATE:20180615
TRANSP:TRANSPARENT
END:VEVENT
BEGIN:VEVENT
CREATED:20180601T194002Z
LAST-MODIFIED:20180601T194303Z
DTSTAMP:20180601T194303Z
UID:630f3b71-865e-4125-977d-a2fd0009ce7d
SUMMARY:start
DTSTART;VALUE=DATE:20180609
DTEND;VALUE=DATE:20180612
TRANSP:TRANSPARENT
X-MOZ-GENERATION:1
END:VEVENT
BEGIN:VEVENT
CREATED:20180601T194054Z
LAST-MODIFIED:20180601T194307Z
DTSTAMP:20180601T194307Z
UID:dc1c0bfc-633c-4d34-8de4-f6e9bcdb5fc6
SUMMARY:end
DTSTART;VALUE=DATE:20180617
DTEND;VALUE=DATE:20180620
TRANSP:TRANSPARENT
X-MOZ-GENERATION:1
END:VEVENT
BEGIN:VEVENT
CREATED:20180601T194313Z
LAST-MODIFIED:20180601T194317Z
DTSTAMP:20180601T194317Z
UID:5095407e-5e63-4609-93a0-5dcd45ed5bf5
SUMMARY:after
DTSTART;VALUE=DATE:20180619
DTEND;VALUE=DATE:20180620
TRANSP:TRANSPARENT
END:VEVENT
BEGIN:VEVENT
CREATED:20180601T195811Z
LAST-MODIFIED:20180601T195814Z
DTSTAMP:20180601T195814Z
UID:550119de-eef7-4820-9843-d260515807d2
SUMMARY:before
DTSTART;VALUE=DATE:20180605
DTEND;VALUE=DATE:20180606
TRANSP:TRANSPARENT
END:VEVENT
END:VCALENDAR
                                                                                                                                                                                                                                          autosuspend-9.0.0/tests/test_checks_ical/all-day-recurring-exclusions.ics                           0000664 0000000 0000000 00000000605 15043744726 0026753 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        BEGIN:VCALENDAR
PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN
VERSION:2.0
BEGIN:VEVENT
CREATED:20180627T111330Z
LAST-MODIFIED:20180627T111340Z
DTSTAMP:20180627T111340Z
UID:ccf1c6b9-44c4-4fdb-8a98-0165e6f2e369
SUMMARY:single all day
DTSTART;VALUE=DATE:20180625
DTEND;VALUE=DATE:20180626
EXDATE:20180630
RRULE:FREQ=DAILY
TRANSP:TRANSPARENT
END:VEVENT
END:VCALENDAR
                                                                                                                           autosuspend-9.0.0/tests/test_checks_ical/all-day-recurring.ics                                      0000664 0000000 0000000 00000000564 15043744726 0024565 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        BEGIN:VCALENDAR
PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN
VERSION:2.0
BEGIN:VEVENT
CREATED:20180627T111330Z
LAST-MODIFIED:20180627T111340Z
DTSTAMP:20180627T111340Z
UID:ccf1c6b9-44c4-4fdb-8a98-0165e6f2e369
SUMMARY:single all day
DTSTART;VALUE=DATE:20180625
DTEND;VALUE=DATE:20180626
RRULE:FREQ=DAILY
TRANSP:TRANSPARENT
END:VEVENT
END:VCALENDAR
                                                                                                                                            autosuspend-9.0.0/tests/test_checks_ical/all-day-starts.ics                                         0000664 0000000 0000000 00000001316 15043744726 0024101 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        BEGIN:VCALENDAR
PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN
VERSION:2.0
BEGIN:VEVENT
CREATED:20180627T111330Z
LAST-MODIFIED:20180627T111340Z
DTSTAMP:20180627T111340Z
UID:ccf1c6b9-44c4-4fdb-8a98-0165e6f2e369
SUMMARY:single all day
DTSTART;VALUE=DATE:20180625
DTEND;VALUE=DATE:20180626
TRANSP:TRANSPARENT
END:VEVENT
BEGIN:VEVENT
CREATED:20180627T111347Z
LAST-MODIFIED:20180627T111357Z
DTSTAMP:20180627T111357Z
UID:a2dab4dd-1ede-4733-af8e-90cff0e26f79
SUMMARY:two all days
DTSTART;VALUE=DATE:20180628
DTEND;VALUE=DATE:20180630
TRANSP:TRANSPARENT
BEGIN:VALARM
ACTION:DISPLAY
TRIGGER;VALUE=DURATION:-PT15M
DESCRIPTION:Default Mozilla Description
END:VALARM
END:VEVENT
END:VCALENDAR
                                                                                                                                                                                                                                                                                                                  autosuspend-9.0.0/tests/test_checks_ical/before-horizon.ics                                         0000664 0000000 0000000 00000001273 15043744726 0024172 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        BEGIN:VCALENDAR
PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN
VERSION:2.0
BEGIN:VTIMEZONE
TZID:Europe/Berlin
BEGIN:DAYLIGHT
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
TZNAME:CEST
DTSTART:19700329T020000
RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=3
END:DAYLIGHT
BEGIN:STANDARD
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
TZNAME:CET
DTSTART:19701025T030000
RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10
END:STANDARD
END:VTIMEZONE
BEGIN:VEVENT
CREATED:20180603T194125Z
LAST-MODIFIED:20180603T194144Z
DTSTAMP:20180603T194144Z
UID:6ff13ee1-e548-41b1-8e08-d7725423743a
SUMMARY:long-event
DTSTART;TZID=Europe/Berlin:20040617T000000
DTEND;TZID=Europe/Berlin:20040617T150000
TRANSP:OPAQUE
SEQUENCE:1
END:VEVENT
END:VCALENDAR
                                                                                                                                                                                                                                                                                                                                     autosuspend-9.0.0/tests/test_checks_ical/exclusions.ics                                             0000664 0000000 0000000 00000001415 15043744726 0023434 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        BEGIN:VCALENDAR
PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN
VERSION:2.0
BEGIN:VTIMEZONE
TZID:Europe/Berlin
BEGIN:DAYLIGHT
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
TZNAME:CEST
DTSTART:19700329T020000
RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=3
END:DAYLIGHT
BEGIN:STANDARD
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
TZNAME:CET
DTSTART:19701025T030000
RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10
END:STANDARD
END:VTIMEZONE
BEGIN:VEVENT
CREATED:20180602T160606Z
LAST-MODIFIED:20180602T160632Z
DTSTAMP:20180602T160632Z
UID:a40c5b76-e3f5-4259-92f5-26692f99f131
SUMMARY:recurring
RRULE:FREQ=DAILY;UNTIL=20180617T120000Z
EXDATE:20180614T120000Z
DTSTART;TZID=Europe/Berlin:20180611T140000
DTEND;TZID=Europe/Berlin:20180611T160000
TRANSP:OPAQUE
X-MOZ-GENERATION:4
SEQUENCE:2
END:VEVENT
END:VCALENDAR
                                                                                                                                                                                                                                                   autosuspend-9.0.0/tests/test_checks_ical/floating.ics                                               0000664 0000000 0000000 00000001167 15043744726 0023047 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        BEGIN:VCALENDAR
PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN
VERSION:2.0
BEGIN:VEVENT
CREATED:20180602T151629Z
LAST-MODIFIED:20180602T152512Z
DTSTAMP:20180602T152512Z
UID:f0028400-24e2-4f10-81a0-032372781443
SUMMARY:floating
DTSTART:20180610T150000
DTEND:20180610T170000
TRANSP:OPAQUE
SEQUENCE:5
X-MOZ-GENERATION:3
END:VEVENT
BEGIN:VEVENT
CREATED:20180602T151701Z
LAST-MODIFIED:20180602T152732Z
DTSTAMP:20180602T152732Z
UID:0ef23894-702e-40ac-ab09-94fa8c9c51fd
SUMMARY:floating recurring
RRULE:FREQ=DAILY
DTSTART:20180612T180000
DTEND:20180612T200000
TRANSP:OPAQUE
X-MOZ-GENERATION:5
SEQUENCE:3
END:VEVENT
END:VCALENDAR
                                                                                                                                                                                                                                                                                                                                                                                                         autosuspend-9.0.0/tests/test_checks_ical/issue-41.ics                                               0000664 0000000 0000000 00000001401 15043744726 0022605 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        BEGIN:VCALENDAR
PRODID:-//Inverse inc./SOGo 4.0.0//EN
VERSION:2.0
BEGIN:VTIMEZONE
TZID:Europe/Berlin
X-LIC-LOCATION:Europe/Berlin
BEGIN:DAYLIGHT
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
TZNAME:CEST
DTSTART:19700329T020000
RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
END:DAYLIGHT
BEGIN:STANDARD
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
TZNAME:CET
DTSTART:19701025T030000
RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
END:STANDARD
END:VTIMEZONE
BEGIN:VEVENT
UID:2C-5B315480-3-4D014C80
SUMMARY:StayAlive
LOCATION:Home
CLASS:PUBLIC
X-SOGO-SEND-APPOINTMENT-NOTIFICATIONS:NO
RRULE:FREQ=DAILY
TRANSP:OPAQUE
DTSTART;TZID=Europe/Berlin:20180626T170000
DTEND;TZID=Europe/Berlin:20180626T210000
CREATED:20180625T204700Z
DTSTAMP:20180625T204700Z
LAST-MODIFIED:20180625T204700Z
END:VEVENT
END:VCALENDAR
                                                                                                                                                                                                                                                               autosuspend-9.0.0/tests/test_checks_ical/long-event.ics                                             0000664 0000000 0000000 00000001273 15043744726 0023320 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        BEGIN:VCALENDAR
PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN
VERSION:2.0
BEGIN:VTIMEZONE
TZID:Europe/Berlin
BEGIN:DAYLIGHT
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
TZNAME:CEST
DTSTART:19700329T020000
RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=3
END:DAYLIGHT
BEGIN:STANDARD
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
TZNAME:CET
DTSTART:19701025T030000
RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10
END:STANDARD
END:VTIMEZONE
BEGIN:VEVENT
CREATED:20180603T194125Z
LAST-MODIFIED:20180603T194144Z
DTSTAMP:20180603T194144Z
UID:6ff13ee1-e548-41b1-8e08-d7725423743a
SUMMARY:long-event
DTSTART;TZID=Europe/Berlin:20160605T130000
DTEND;TZID=Europe/Berlin:20260605T150000
TRANSP:OPAQUE
SEQUENCE:1
END:VEVENT
END:VCALENDAR
                                                                                                                                                                                                                                                                                                                                     autosuspend-9.0.0/tests/test_checks_ical/multiple.ics                                               0000664 0000000 0000000 00000001716 15043744726 0023077 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        BEGIN:VCALENDAR
PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN
VERSION:2.0
BEGIN:VTIMEZONE
TZID:Europe/Berlin
BEGIN:DAYLIGHT
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
TZNAME:CEST
DTSTART:19700329T020000
RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=3
END:DAYLIGHT
BEGIN:STANDARD
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
TZNAME:CET
DTSTART:19701025T030000
RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10
END:STANDARD
END:VTIMEZONE
BEGIN:VEVENT
CREATED:20180603T194125Z
LAST-MODIFIED:20180603T194144Z
DTSTAMP:20180603T194144Z
UID:6ff13ee1-e548-41b1-8e08-d7725423743a
SUMMARY:long-event
DTSTART;TZID=Europe/Berlin:20040605T130000
DTEND;TZID=Europe/Berlin:20040605T150000
TRANSP:OPAQUE
SEQUENCE:1
END:VEVENT
BEGIN:VEVENT
CREATED:20180403T194125Z
LAST-MODIFIED:20180403T194144Z
DTSTAMP:20180403T194144Z
UID:6ff13ee1-e548-41b1-8e08-d7725423743b
SUMMARY:early-event
DTSTART;TZID=Europe/Berlin:20040405T130000
DTEND;TZID=Europe/Berlin:20040405T150000
TRANSP:OPAQUE
SEQUENCE:1
END:VEVENT
END:VCALENDAR
                                                  autosuspend-9.0.0/tests/test_checks_ical/normal-events-corner-cases.ics                             0000664 0000000 0000000 00000005315 15043744726 0026417 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        BEGIN:VCALENDAR
PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN
VERSION:2.0
BEGIN:VTIMEZONE
TZID:Europe/Berlin
BEGIN:DAYLIGHT
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
TZNAME:CEST
DTSTART:19700329T020000
RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=3
END:DAYLIGHT
BEGIN:STANDARD
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
TZNAME:CET
DTSTART:19701025T030000
RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10
END:STANDARD
END:VTIMEZONE
BEGIN:VEVENT
CREATED:20180601T200433Z
LAST-MODIFIED:20180601T200455Z
DTSTAMP:20180601T200455Z
UID:1c056498-9c83-4e0f-bb77-777c967c9a54
SUMMARY:before include
DTSTART;TZID=Europe/Berlin:20180603T210000
DTEND;TZID=Europe/Berlin:20180604T020000
TRANSP:OPAQUE
X-MOZ-GENERATION:2
SEQUENCE:1
END:VEVENT
BEGIN:VEVENT
CREATED:20180601T200328Z
LAST-MODIFIED:20180601T200511Z
DTSTAMP:20180601T200511Z
UID:db4b1c02-6ac2-4def-bfb0-9a96b510387e
SUMMARY:direct start
DTSTART;TZID=Europe/Berlin:20180604T000000
DTEND;TZID=Europe/Berlin:20180604T030000
TRANSP:OPAQUE
X-MOZ-GENERATION:2
SEQUENCE:1
END:VEVENT
BEGIN:VEVENT
CREATED:20180601T200518Z
LAST-MODIFIED:20180601T200531Z
DTSTAMP:20180601T200531Z
UID:06622f56-d945-490b-9fd7-0fe5015f3188
SUMMARY:in between
DTSTART;TZID=Europe/Berlin:20180607T040000
DTEND;TZID=Europe/Berlin:20180607T090000
TRANSP:OPAQUE
X-MOZ-GENERATION:1
END:VEVENT
BEGIN:VEVENT
CREATED:20180601T200351Z
LAST-MODIFIED:20180601T200555Z
DTSTAMP:20180601T200555Z
UID:48d1debe-e457-4bde-9bea-ab18be136d4a
SUMMARY:before do not include
DTSTART;TZID=Europe/Berlin:20180603T220000
DTEND;TZID=Europe/Berlin:20180604T000000
TRANSP:OPAQUE
X-MOZ-GENERATION:4
SEQUENCE:2
END:VEVENT
BEGIN:VEVENT
CREATED:20180601T200531Z
LAST-MODIFIED:20180601T200615Z
DTSTAMP:20180601T200615Z
UID:0a36a2e8-fac3-4337-8464-f52e5cf17bd5
SUMMARY:direct end
DTSTART;TZID=Europe/Berlin:20180610T220000
DTEND;TZID=Europe/Berlin:20180611T000000
TRANSP:OPAQUE
X-MOZ-GENERATION:4
SEQUENCE:1
END:VEVENT
BEGIN:VEVENT
CREATED:20180601T200619Z
LAST-MODIFIED:20180601T200633Z
DTSTAMP:20180601T200633Z
UID:19bf0d84-3286-44d8-8376-67549a419001
SUMMARY:end overlap
DTSTART;TZID=Europe/Berlin:20180610T210000
DTEND;TZID=Europe/Berlin:20180611T020000
TRANSP:OPAQUE
X-MOZ-GENERATION:2
SEQUENCE:1
END:VEVENT
BEGIN:VEVENT
CREATED:20180601T200643Z
LAST-MODIFIED:20180601T200651Z
DTSTAMP:20180601T200651Z
UID:ae376911-eab5-45fe-bb5b-14e9fd904b44
SUMMARY:end after
DTSTART;TZID=Europe/Berlin:20180611T000000
DTEND;TZID=Europe/Berlin:20180611T030000
TRANSP:OPAQUE
X-MOZ-GENERATION:1
END:VEVENT
BEGIN:VEVENT
CREATED:20180602T144323Z
LAST-MODIFIED:20180602T144338Z
DTSTAMP:20180602T144338Z
UID:f52ee7b1-810f-4b08-bf28-80e8ae226ac3
SUMMARY:overlapping
DTSTART;TZID=Europe/Berlin:20180602T200000
DTEND;TZID=Europe/Berlin:20180612T230000
TRANSP:OPAQUE
X-MOZ-GENERATION:2
SEQUENCE:1
END:VEVENT
END:VCALENDAR
                                                                                                                                                                                                                                                                                                                   autosuspend-9.0.0/tests/test_checks_ical/old-event.ics                                              0000664 0000000 0000000 00000001273 15043744726 0023137 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        BEGIN:VCALENDAR
PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN
VERSION:2.0
BEGIN:VTIMEZONE
TZID:Europe/Berlin
BEGIN:DAYLIGHT
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
TZNAME:CEST
DTSTART:19700329T020000
RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=3
END:DAYLIGHT
BEGIN:STANDARD
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
TZNAME:CET
DTSTART:19701025T030000
RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10
END:STANDARD
END:VTIMEZONE
BEGIN:VEVENT
CREATED:20180603T194125Z
LAST-MODIFIED:20180603T194144Z
DTSTAMP:20180603T194144Z
UID:6ff13ee1-e548-41b1-8e08-d7725423743a
SUMMARY:long-event
DTSTART;TZID=Europe/Berlin:20040605T130000
DTEND;TZID=Europe/Berlin:20040605T150000
TRANSP:OPAQUE
SEQUENCE:1
END:VEVENT
END:VCALENDAR
                                                                                                                                                                                                                                                                                                                                     autosuspend-9.0.0/tests/test_checks_ical/recurring-change-dst.ics                                   0000664 0000000 0000000 00000002640 15043744726 0025254 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        BEGIN:VCALENDAR
PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN
VERSION:2.0
BEGIN:VTIMEZONE
TZID:Europe/Berlin
BEGIN:DAYLIGHT
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
TZNAME:CEST
DTSTART:19700329T020000
RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=3
END:DAYLIGHT
BEGIN:STANDARD
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
TZNAME:CET
DTSTART:19701025T030000
RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10
END:STANDARD
END:VTIMEZONE
BEGIN:VEVENT
CREATED:20180603T200159Z
LAST-MODIFIED:20180603T200414Z
DTSTAMP:20180603T200414Z
UID:d083699e-6f37-4a85-b20d-f03750aa6691
SUMMARY:recurring
RRULE:FREQ=DAILY
EXDATE:20181214T130000Z
DTSTART;TZID=Europe/Berlin:20180606T140000
DTEND;TZID=Europe/Berlin:20180606T160000
TRANSP:OPAQUE
X-MOZ-GENERATION:4
SEQUENCE:2
END:VEVENT
BEGIN:VEVENT
CREATED:20180603T200213Z
LAST-MODIFIED:20180603T200243Z
DTSTAMP:20180603T200243Z
UID:d083699e-6f37-4a85-b20d-f03750aa6691
SUMMARY:recurring
RECURRENCE-ID;TZID=Europe/Berlin:20180612T140000
DTSTART;TZID=Europe/Berlin:20180612T140000
DTEND;TZID=Europe/Berlin:20180612T160000
SEQUENCE:5
TRANSP:OPAQUE
X-MOZ-GENERATION:4
END:VEVENT
BEGIN:VEVENT
CREATED:20180603T200401Z
LAST-MODIFIED:20180603T200407Z
DTSTAMP:20180603T200407Z
UID:d083699e-6f37-4a85-b20d-f03750aa6691
SUMMARY:recurring
RECURRENCE-ID;TZID=Europe/Berlin:20181212T140000
DTSTART;TZID=Europe/Berlin:20181212T110000
DTEND;TZID=Europe/Berlin:20181212T130000
SEQUENCE:2
TRANSP:OPAQUE
X-MOZ-GENERATION:4
END:VEVENT
END:VCALENDAR
                                                                                                autosuspend-9.0.0/tests/test_checks_ical/simple-recurring.ics                                       0000664 0000000 0000000 00000001365 15043744726 0024533 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        BEGIN:VCALENDAR
PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN
VERSION:2.0
BEGIN:VTIMEZONE
TZID:Europe/Berlin
BEGIN:DAYLIGHT
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
TZNAME:CEST
DTSTART:19700329T020000
RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=3
END:DAYLIGHT
BEGIN:STANDARD
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
TZNAME:CET
DTSTART:19701025T030000
RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10
END:STANDARD
END:VTIMEZONE
BEGIN:VEVENT
CREATED:20180601T182719Z
LAST-MODIFIED:20180601T182803Z
DTSTAMP:20180601T182803Z
UID:74c93379-f763-439b-9d11-eca4d431bfc7
SUMMARY:Stay awake
RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR
DTSTART;TZID=Europe/Berlin:20180327T090000
DTEND;TZID=Europe/Berlin:20180327T180000
TRANSP:OPAQUE
X-MOZ-GENERATION:2
SEQUENCE:1
END:VEVENT
END:VCALENDAR
                                                                                                                                                                                                                                                                           autosuspend-9.0.0/tests/test_checks_ical/single-change.ics                                          0000664 0000000 0000000 00000002610 15043744726 0023742 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        BEGIN:VCALENDAR
PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN
VERSION:2.0
BEGIN:VTIMEZONE
TZID:Europe/Berlin
BEGIN:DAYLIGHT
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
TZNAME:CEST
DTSTART:19700329T020000
RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=3
END:DAYLIGHT
BEGIN:STANDARD
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
TZNAME:CET
DTSTART:19701025T030000
RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10
END:STANDARD
END:VTIMEZONE
BEGIN:VEVENT
CREATED:20180603T194125Z
LAST-MODIFIED:20180603T194144Z
DTSTAMP:20180603T194144Z
UID:6ff13ee1-e548-41b1-8e08-d7725423743a
SUMMARY:recurring
RRULE:FREQ=DAILY
DTSTART;TZID=Europe/Berlin:20180605T130000
DTEND;TZID=Europe/Berlin:20180605T150000
TRANSP:OPAQUE
X-MOZ-GENERATION:4
SEQUENCE:1
END:VEVENT
BEGIN:VEVENT
CREATED:20180603T194138Z
LAST-MODIFIED:20180603T194140Z
DTSTAMP:20180603T194140Z
UID:6ff13ee1-e548-41b1-8e08-d7725423743a
SUMMARY:recurring
RECURRENCE-ID;TZID=Europe/Berlin:20180613T130000
DTSTART;TZID=Europe/Berlin:20180613T160000
DTEND;TZID=Europe/Berlin:20180613T180000
SEQUENCE:2
TRANSP:OPAQUE
X-MOZ-GENERATION:4
END:VEVENT
BEGIN:VEVENT
CREATED:20180603T194141Z
LAST-MODIFIED:20180603T194144Z
DTSTAMP:20180603T194144Z
UID:6ff13ee1-e548-41b1-8e08-d7725423743a
SUMMARY:recurring
RECURRENCE-ID;TZID=Europe/Berlin:20180615T130000
DTSTART;TZID=Europe/Berlin:20180615T110000
DTEND;TZID=Europe/Berlin:20180615T130000
SEQUENCE:2
TRANSP:OPAQUE
X-MOZ-GENERATION:4
END:VEVENT
END:VCALENDAR
                                                                                                                        autosuspend-9.0.0/tests/test_checks_json.py                                                         0000664 0000000 0000000 00000011021 15043744726 0021145 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        from collections.abc import Callable
from pathlib import Path
from typing import Any
from jsonpath_ng.ext import parse
import pytest
from pytest_mock import MockerFixture
from autosuspend.checks import ConfigurationError, TemporaryCheckError
from autosuspend.checks.json import JsonPath
from . import CheckTest
from .utils import config_section
class TestJsonPath(CheckTest):
    def create_instance(self, name: str) -> JsonPath:
        return JsonPath(
            name=name,
            url="url",
            timeout=5,
            username="userx",
            password="pass",
            jsonpath=parse("b"),
        )
    @staticmethod
    @pytest.fixture
    def json_get_mock(mocker: MockerFixture) -> Any:
        mock_reply = mocker.MagicMock()
        mock_reply.json.return_value = {"a": {"b": 42, "c": "ignore"}}
        return mocker.patch("requests.Session.get", return_value=mock_reply)
    def test_matching(self, json_get_mock: Any) -> None:
        url = "nourl"
        assert (
            JsonPath("foo", jsonpath=parse("a.b"), url=url, timeout=5).check()
            is not None
        )
        json_get_mock.assert_called_once_with(
            url, timeout=5, headers={"Accept": "application/json"}
        )
        json_get_mock().json.assert_called_once()
    def test_filter_expressions_work(self, json_get_mock: Any) -> None:
        url = "nourl"
        assert (
            JsonPath(
                "foo", jsonpath=parse("$[?(@.c=='ignore')]"), url=url, timeout=5
            ).check()
            is not None
        )
        json_get_mock.assert_called_once_with(
            url, timeout=5, headers={"Accept": "application/json"}
        )
        json_get_mock().json.assert_called_once()
    def test_not_matching(self, json_get_mock: Any) -> None:
        url = "nourl"
        assert (
            JsonPath("foo", jsonpath=parse("not.there"), url=url, timeout=5).check()
            is None
        )
        json_get_mock.assert_called_once_with(
            url, timeout=5, headers={"Accept": "application/json"}
        )
        json_get_mock().json.assert_called_once()
    def test_network_errors_are_passed(
        self, datadir: Path, serve_protected: Callable[[Path], tuple[str, str, str]]
    ) -> None:
        with pytest.raises(TemporaryCheckError):
            JsonPath(
                name="name",
                url=serve_protected(datadir / "data.txt")[0],
                timeout=5,
                username="wrong",
                password="wrong",
                jsonpath=parse("b"),
            ).check()
    def test_not_json(self, datadir: Path, serve_file: Callable[[Path], str]) -> None:
        with pytest.raises(TemporaryCheckError):
            JsonPath(
                name="name",
                url=serve_file(datadir / "invalid.json"),
                timeout=5,
                jsonpath=parse("b"),
            ).check()
    class TestCreate:
        def test_it_works(self) -> None:
            check: JsonPath = JsonPath.create(
                "name",
                config_section(
                    {
                        "url": "url",
                        "jsonpath": "a.b",
                        "username": "user",
                        "password": "pass",
                        "timeout": "42",
                    }
                ),
            )
            assert check._jsonpath == parse("a.b")
            assert check._url == "url"
            assert check._username == "user"
            assert check._password == "pass"
            assert check._timeout == 42
        def test_raises_on_missing_json_path(self) -> None:
            with pytest.raises(ConfigurationError):
                JsonPath.create(
                    "name",
                    config_section(
                        {
                            "url": "url",
                            "username": "user",
                            "password": "pass",
                            "timeout": "42",
                        }
                    ),
                )
        def test_raises_on_invalid_json_path(self) -> None:
            with pytest.raises(ConfigurationError):
                JsonPath.create(
                    "name",
                    config_section(
                        {
                            "url": "url",
                            "jsonpath": ",.asdfjasdklf",
                            "username": "user",
                            "password": "pass",
                            "timeout": "42",
                        }
                    ),
                )
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               autosuspend-9.0.0/tests/test_checks_json/                                                           0000775 0000000 0000000 00000000000 15043744726 0020600 5                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        autosuspend-9.0.0/tests/test_checks_json/invalid.json                                               0000664 0000000 0000000 00000000011 15043744726 0023111 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        {"broken
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       autosuspend-9.0.0/tests/test_checks_kodi.py                                                         0000664 0000000 0000000 00000017736 15043744726 0021145 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        import json
import pytest
from pytest_mock import MockerFixture
import requests.exceptions
from autosuspend.checks import Check, ConfigurationError, TemporaryCheckError
from autosuspend.checks.kodi import Kodi, KodiIdleTime
from . import CheckTest
from .utils import config_section
class TestKodi(CheckTest):
    def create_instance(self, name: str) -> Check:
        return Kodi(name, url="url", timeout=10)
    def test_playing(self, mocker: MockerFixture) -> None:
        mock_reply = mocker.MagicMock()
        mock_reply.json.return_value = {
            "id": 1,
            "jsonrpc": "2.0",
            "result": [{"playerid": 0, "type": "audio"}],
        }
        mocker.patch("requests.Session.get", return_value=mock_reply)
        assert Kodi("foo", url="url", timeout=10).check() is not None
        mock_reply.json.assert_called_once_with()
    def test_not_playing(self, mocker: MockerFixture) -> None:
        mock_reply = mocker.MagicMock()
        mock_reply.json.return_value = {"id": 1, "jsonrpc": "2.0", "result": []}
        mocker.patch("requests.Session.get", return_value=mock_reply)
        assert Kodi("foo", url="url", timeout=10).check() is None
        mock_reply.json.assert_called_once_with()
    def test_playing_suspend_while_paused(self, mocker: MockerFixture) -> None:
        mock_reply = mocker.MagicMock()
        mock_reply.json.return_value = {
            "id": 1,
            "jsonrpc": "2.0",
            "result": {"Player.Playing": True},
        }
        mocker.patch("requests.Session.get", return_value=mock_reply)
        assert (
            Kodi("foo", url="url", timeout=10, suspend_while_paused=True).check()
            is not None
        )
        mock_reply.json.assert_called_once_with()
    def test_not_playing_suspend_while_paused(self, mocker: MockerFixture) -> None:
        mock_reply = mocker.MagicMock()
        mock_reply.json.return_value = {
            "id": 1,
            "jsonrpc": "2.0",
            "result": {"Player.Playing": False},
        }
        mocker.patch("requests.Session.get", return_value=mock_reply)
        assert (
            Kodi("foo", url="url", timeout=10, suspend_while_paused=True).check()
            is None
        )
        mock_reply.json.assert_called_once_with()
    def test_assertion_no_result(self, mocker: MockerFixture) -> None:
        mock_reply = mocker.MagicMock()
        mock_reply.json.return_value = {"id": 1, "jsonrpc": "2.0"}
        mocker.patch("requests.Session.get", return_value=mock_reply)
        with pytest.raises(TemporaryCheckError):
            Kodi("foo", url="url", timeout=10).check()
    def test_request_error(self, mocker: MockerFixture) -> None:
        mocker.patch(
            "requests.Session.get", side_effect=requests.exceptions.RequestException()
        )
        with pytest.raises(TemporaryCheckError):
            Kodi("foo", url="url", timeout=10).check()
    def test_json_error(self, mocker: MockerFixture) -> None:
        mock_reply = mocker.MagicMock()
        mock_reply.json.side_effect = json.JSONDecodeError("test", "test", 42)
        mocker.patch("requests.Session.get", return_value=mock_reply)
        with pytest.raises(TemporaryCheckError):
            Kodi("foo", url="url", timeout=10).check()
    def test_create(self) -> None:
        check = Kodi.create(
            "name",
            config_section(
                {
                    "url": "anurl",
                    "timeout": "12",
                }
            ),
        )
        assert check._url.startswith("anurl")
        assert check._timeout == 12
        assert not check._suspend_while_paused
    def test_create_default_url(self) -> None:
        check = Kodi.create("name", config_section())
        assert check._url.split("?")[0] == "http://localhost:8080/jsonrpc"
    def test_create_timeout_no_number(self) -> None:
        with pytest.raises(ConfigurationError):
            Kodi.create("name", config_section({"url": "anurl", "timeout": "string"}))
    def test_create_suspend_while_paused(self) -> None:
        check = Kodi.create(
            "name", config_section({"url": "anurl", "suspend_while_paused": "True"})
        )
        assert check._url.startswith("anurl")
        assert check._suspend_while_paused
class TestKodiIdleTime(CheckTest):
    def create_instance(self, name: str) -> Check:
        return KodiIdleTime(name, url="url", timeout=10, idle_time=10)
    def test_create(self) -> None:
        check = KodiIdleTime.create(
            "name", config_section({"url": "anurl", "timeout": "12", "idle_time": "42"})
        )
        assert check._url.startswith("anurl")
        assert check._timeout == 12
        assert check._idle_time == 42
    def test_create_default_url(self) -> None:
        check = KodiIdleTime.create("name", config_section())
        assert check._url.split("?")[0] == "http://localhost:8080/jsonrpc"
    def test_create_timeout_no_number(self) -> None:
        with pytest.raises(ConfigurationError):
            KodiIdleTime.create(
                "name", config_section({"url": "anurl", "timeout": "string"})
            )
    def test_create_idle_time_no_number(self) -> None:
        with pytest.raises(ConfigurationError):
            KodiIdleTime.create(
                "name", config_section({"url": "anurl", "idle_time": "string"})
            )
    def test_no_result(self, mocker: MockerFixture) -> None:
        mock_reply = mocker.MagicMock()
        mock_reply.json.return_value = {"id": 1, "jsonrpc": "2.0"}
        mocker.patch("requests.Session.get", return_value=mock_reply)
        with pytest.raises(TemporaryCheckError):
            KodiIdleTime("foo", url="url", timeout=10, idle_time=42).check()
    def test_result_is_list(self, mocker: MockerFixture) -> None:
        mock_reply = mocker.MagicMock()
        mock_reply.json.return_value = {"id": 1, "jsonrpc": "2.0", "result": []}
        mocker.patch("requests.Session.get", return_value=mock_reply)
        with pytest.raises(TemporaryCheckError):
            KodiIdleTime("foo", url="url", timeout=10, idle_time=42).check()
    def test_result_no_entry(self, mocker: MockerFixture) -> None:
        mock_reply = mocker.MagicMock()
        mock_reply.json.return_value = {"id": 1, "jsonrpc": "2.0", "result": {}}
        mocker.patch("requests.Session.get", return_value=mock_reply)
        with pytest.raises(TemporaryCheckError):
            KodiIdleTime("foo", url="url", timeout=10, idle_time=42).check()
    def test_result_wrong_entry(self, mocker: MockerFixture) -> None:
        mock_reply = mocker.MagicMock()
        mock_reply.json.return_value = {
            "id": 1,
            "jsonrpc": "2.0",
            "result": {"narf": True},
        }
        mocker.patch("requests.Session.get", return_value=mock_reply)
        with pytest.raises(TemporaryCheckError):
            KodiIdleTime("foo", url="url", timeout=10, idle_time=42).check()
    def test_active(self, mocker: MockerFixture) -> None:
        mock_reply = mocker.MagicMock()
        mock_reply.json.return_value = {
            "id": 1,
            "jsonrpc": "2.0",
            "result": {"System.IdleTime(42)": False},
        }
        mocker.patch("requests.Session.get", return_value=mock_reply)
        assert (
            KodiIdleTime("foo", url="url", timeout=10, idle_time=42).check() is not None
        )
    def test_inactive(self, mocker: MockerFixture) -> None:
        mock_reply = mocker.MagicMock()
        mock_reply.json.return_value = {
            "id": 1,
            "jsonrpc": "2.0",
            "result": {"System.IdleTime(42)": True},
        }
        mocker.patch("requests.Session.get", return_value=mock_reply)
        assert KodiIdleTime("foo", url="url", timeout=10, idle_time=42).check() is None
    def test_request_error(self, mocker: MockerFixture) -> None:
        mocker.patch(
            "requests.Session.get", side_effect=requests.exceptions.RequestException()
        )
        with pytest.raises(TemporaryCheckError):
            KodiIdleTime("foo", url="url", timeout=10, idle_time=42).check()
                                  autosuspend-9.0.0/tests/test_checks_linux.py                                                        0000664 0000000 0000000 00000051126 15043744726 0021345 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        from collections import namedtuple
from collections.abc import Mapping
from datetime import datetime, UTC
from pathlib import Path
import re
import socket
import sys
from typing import Any
from freezegun import freeze_time
import psutil
import pytest
from pytest_httpserver import HTTPServer
from pytest_mock import MockerFixture
import requests
from autosuspend.checks import (
    Check,
    ConfigurationError,
    SevereCheckError,
    TemporaryCheckError,
)
from autosuspend.checks.linux import (
    ActiveConnection,
    File,
    Load,
    NetworkBandwidth,
    Ping,
    Processes,
    Users,
)
from . import CheckTest
from tests.utils import config_section
class TestUsers(CheckTest):
    def create_instance(self, name: str) -> Check:
        return Users(name, re.compile(".*"), re.compile(".*"), re.compile(".*"))
    @staticmethod
    def create_suser(
        name: str, terminal: str, host: str, started: float, pid: int
    ) -> psutil._common.suser:
        return psutil._common.suser(name, terminal, host, started, pid)
    def test_reports_no_activity_without_users(self, mocker: MockerFixture) -> None:
        mocker.patch("psutil.users").return_value = []
        assert (
            Users("users", re.compile(".*"), re.compile(".*"), re.compile(".*")).check()
            is None
        )
    def test_matching_users(self, mocker: MockerFixture) -> None:
        mocker.patch("psutil.users").return_value = [
            self.create_suser("foo", "pts1", "host", 12345, 12345)
        ]
        assert (
            Users("users", re.compile(".*"), re.compile(".*"), re.compile(".*")).check()
            is not None
        )
    def test_detect_no_activity_if_no_matching_user_exists(
        self, mocker: MockerFixture
    ) -> None:
        mocker.patch("psutil.users").return_value = [
            self.create_suser("foo", "pts1", "host", 12345, 12345)
        ]
        assert (
            Users(
                "users", re.compile("narf"), re.compile(".*"), re.compile(".*")
            ).check()
            is None
        )
    class TestCreate:
        def test_it_works_with_a_valid_config(self) -> None:
            check = Users.create(
                "name",
                config_section(
                    {
                        "name": "name.*name",
                        "terminal": "term.*term",
                        "host": "host.*host",
                    }
                ),
            )
            assert check._user_regex == re.compile("name.*name")
            assert check._terminal_regex == re.compile("term.*term")
            assert check._host_regex == re.compile("host.*host")
        def test_raises_with_invalid_expression(self) -> None:
            with pytest.raises(ConfigurationError):
                Users.create(
                    "name",
                    config_section(
                        {
                            "name": "name.*name",
                            "terminal": "term.[[a-9]term",
                            "host": "host.*host",
                        }
                    ),
                )
class TestProcesses(CheckTest):
    def create_instance(self, name: str) -> Check:
        return Processes(name, ["foo"])
    class StubProcess:
        def __init__(self, name: str) -> None:
            self._name = name
        def name(self) -> str:
            return self._name
    class RaisingProcess:
        def name(self) -> str:
            raise psutil.NoSuchProcess(42)
    def test_detects_activity_with_matching_process(
        self, mocker: MockerFixture
    ) -> None:
        mocker.patch("psutil.process_iter").return_value = [
            self.StubProcess("blubb"),
            self.StubProcess("nonmatching"),
        ]
        assert Processes("foo", ["dummy", "blubb", "other"]).check() is not None
    def test_ignores_no_such_process_errors(self, mocker: MockerFixture) -> None:
        mocker.patch("psutil.process_iter").return_value = [self.RaisingProcess()]
        Processes("foo", ["dummy"]).check()
    def test_detect_no_activity_for_non_matching_processes(
        self, mocker: MockerFixture
    ) -> None:
        mocker.patch("psutil.process_iter").return_value = [
            self.StubProcess("asdfasdf"),
            self.StubProcess("nonmatching"),
        ]
        assert Processes("foo", ["dummy", "blubb", "other"]).check() is None
    class TestCreate:
        def test_it_works_with_a_valid_config(self) -> None:
            assert Processes.create(
                "name", config_section({"processes": "foo, bar, narf"})
            )._processes == [
                "foo",
                "bar",
                "narf",
            ]
        def test_raises_if_no_processes_are_configured(self) -> None:
            with pytest.raises(ConfigurationError):
                Processes.create("name", config_section())
snic = namedtuple("snic", ["family", "address", "netmask", "broadcast", "ptp"])
class TestActiveConnection(CheckTest):
    MY_PORT = 22
    MY_ADDRESS = "123.456.123.456"
    MY_ADDRESS_IPV6 = "fe80::5193:518c:5c69:aedb"
    # this might sometimes happen:
    # https://superuser.com/a/99753/227177
    MY_ADDRESS_IPV6_SCOPED = "fe80::5193:518c:5c69:cccc%eth0"
    def create_instance(self, name: str) -> Check:
        return ActiveConnection(name, [10])
    @pytest.mark.parametrize(
        "connection",
        [
            # ipv4
            psutil._common.sconn(
                -1,
                socket.AF_INET,
                socket.SOCK_STREAM,
                (MY_ADDRESS, MY_PORT),
                ("42.42.42.42", 42),
                "ESTABLISHED",
                None,
            ),
            # ipv6
            psutil._common.sconn(
                -1,
                socket.AF_INET6,
                socket.SOCK_STREAM,
                (MY_ADDRESS_IPV6, MY_PORT),
                ("42.42.42.42", 42),
                "ESTABLISHED",
                None,
            ),
            # ipv6 where local address has scope
            psutil._common.sconn(
                -1,
                socket.AF_INET6,
                socket.SOCK_STREAM,
                (MY_ADDRESS_IPV6_SCOPED.split("%")[0], MY_PORT),
                ("42.42.42.42", 42),
                "ESTABLISHED",
                None,
            ),
            # ipv6 with mapping to ipv4
            # https://github.com/languitar/autosuspend/issues/116
            psutil._common.sconn(
                -1,
                socket.AF_INET6,
                socket.SOCK_STREAM,
                (f"::ffff:{MY_ADDRESS}", MY_PORT),
                ("42.42.42.42", 42),
                "ESTABLISHED",
                None,
            ),
        ],
    )
    def test_detect_activity_if_port_is_connected(
        self, mocker: MockerFixture, connection: psutil._common.sconn
    ) -> None:
        mocker.patch("psutil.net_if_addrs").return_value = {
            "dummy": [
                snic(socket.AF_INET, self.MY_ADDRESS, "255.255.255.0", None, None),
                snic(
                    socket.AF_INET6,
                    self.MY_ADDRESS_IPV6,
                    "ffff:ffff:ffff:ffff::",
                    None,
                    None,
                ),
                snic(
                    socket.AF_INET6,
                    self.MY_ADDRESS_IPV6_SCOPED,
                    "ffff:ffff:ffff:ffff::",
                    None,
                    None,
                ),
            ],
        }
        mocker.patch("psutil.net_connections").return_value = [connection]
        assert ActiveConnection("foo", [10, self.MY_PORT, 30]).check() is not None
    @pytest.mark.parametrize(
        "connection",
        [
            # not my port
            psutil._common.sconn(
                -1,
                socket.AF_INET,
                socket.SOCK_STREAM,
                (MY_ADDRESS, 32),
                ("42.42.42.42", 42),
                "ESTABLISHED",
                None,
            ),
            # not my local address
            psutil._common.sconn(
                -1,
                socket.AF_INET,
                socket.SOCK_STREAM,
                ("33.33.33.33", MY_PORT),
                ("42.42.42.42", 42),
                "ESTABLISHED",
                None,
            ),
            # not established
            psutil._common.sconn(
                -1,
                socket.AF_INET,
                socket.SOCK_STREAM,
                (MY_ADDRESS, MY_PORT),
                ("42.42.42.42", 42),
                "NARF",
                None,
            ),
            # I am the client
            psutil._common.sconn(
                -1,
                socket.AF_INET,
                socket.SOCK_STREAM,
                ("42.42.42.42", 42),
                (MY_ADDRESS, MY_PORT),
                "NARF",
                None,
            ),
        ],
    )
    def test_detects_no_activity_if_port_is_not_connected(
        self, mocker: MockerFixture, connection: psutil._common.sconn
    ) -> None:
        mocker.patch("psutil.net_if_addrs").return_value = {
            "dummy": [
                snic(socket.AF_INET, self.MY_ADDRESS, "255.255.255.0", None, None)
            ]
        }
        mocker.patch("psutil.net_connections").return_value = [connection]
        assert ActiveConnection("foo", [10, self.MY_PORT, 30]).check() is None
    class TestCreate:
        def test_it_works_with_a_valid_config(self) -> None:
            assert ActiveConnection.create(
                "name", config_section({"ports": "10,20,30"})
            )._ports == {10, 20, 30}
        def test_raises_if_no_ports_are_configured(self) -> None:
            with pytest.raises(ConfigurationError):
                ActiveConnection.create("name", config_section())
        def test_raises_if_ports_are_not_numeric(self) -> None:
            with pytest.raises(ConfigurationError):
                ActiveConnection.create("name", config_section({"ports": "10,20xx,30"}))
class TestLoad(CheckTest):
    def create_instance(self, name: str) -> Check:
        return Load(name, 0.4)
    def test_detects_no_activity_below_threshold(self, mocker: Any) -> None:
        threshold = 1.34
        mocker.patch("os.getloadavg").return_value = [0, threshold - 0.2, 0]
        assert Load("foo", threshold).check() is None
    def test_detects_activity_above_threshold(self, mocker: MockerFixture) -> None:
        threshold = 1.34
        mocker.patch("os.getloadavg").return_value = [0, threshold + 0.2, 0]
        assert Load("foo", threshold).check() is not None
    class TestCreate:
        def test_it_works_with_a_valid_config(self) -> None:
            assert (
                Load.create("name", config_section({"threshold": "3.2"}))._threshold
                == 3.2
            )
        def test_raises_if_threshold_is_not_numeric(self) -> None:
            with pytest.raises(ConfigurationError):
                Load.create("name", config_section({"threshold": "narf"}))
class TestNetworkBandwidth(CheckTest):
    def create_instance(self, name: str) -> Check:
        return NetworkBandwidth(name, psutil.net_if_addrs().keys(), 0, 0)
    @staticmethod
    @pytest.fixture
    def serve_data_url(httpserver: HTTPServer) -> str:
        httpserver.expect_request("").respond_with_json({"foo": "bar"})
        return httpserver.url_for("")
    def test_detects_non_mocked_activity(self, serve_data_url: str) -> None:
        check = NetworkBandwidth("name", psutil.net_if_addrs().keys(), 0, 0)
        # make some traffic
        requests.get(serve_data_url, timeout=5)
        assert check.check() is not None
    @pytest.fixture
    def _mock_interfaces(self, mocker: MockerFixture) -> None:
        mock = mocker.patch("psutil.net_if_addrs")
        mock.return_value = {"foo": None, "bar": None, "baz": None}
    class TestCreate:
        @pytest.mark.usefixtures("_mock_interfaces")
        def test_it_works_with_a_valid_config(self) -> None:
            check = NetworkBandwidth.create(
                "name",
                config_section(
                    {
                        "interfaces": "foo, baz",
                        "threshold_send": "200",
                        "threshold_receive": "300",
                    }
                ),
            )
            assert set(check._interfaces) == {"foo", "baz"}
            assert check._threshold_send == 200
            assert check._threshold_receive == 300
        @pytest.mark.usefixtures("_mock_interfaces")
        def test_default_values_work(self) -> None:
            check = NetworkBandwidth.create(
                "name", config_section({"interfaces": "foo, baz"})
            )
            assert set(check._interfaces) == {"foo", "baz"}
            assert check._threshold_send == 100
            assert check._threshold_receive == 100
        @pytest.mark.parametrize(
            ("config", "error_match"),
            [
                (
                    {
                        "interfaces": "foo, NOTEXIST",
                        "threshold_send": "200",
                        "threshold_receive": "300",
                    },
                    r"does not exist",
                ),
                (
                    {
                        "threshold_send": "200",
                        "threshold_receive": "300",
                    },
                    r"configuration key: \'interfaces\'",
                ),
                (
                    {
                        "interfaces": "",
                        "threshold_send": "200",
                        "threshold_receive": "300",
                    },
                    r"No interfaces configured",
                ),
                (
                    {
                        "interfaces": "foo, bar",
                        "threshold_send": "xxx",
                    },
                    r"Threshold in wrong format",
                ),
                (
                    {
                        "interfaces": "foo, bar",
                        "threshold_receive": "xxx",
                    },
                    r"Threshold in wrong format",
                ),
            ],
        )
        @pytest.mark.usefixtures("_mock_interfaces")
        def test_raises_with_an_invalid_config(
            self, config: Mapping[str, str], error_match: str
        ) -> None:
            with pytest.raises(ConfigurationError, match=error_match):
                NetworkBandwidth.create("name", config_section(config))
    @pytest.mark.parametrize(
        ("send_threshold", "receive_threshold", "match"),
        [(sys.float_info.max, 0, "receive"), (0, sys.float_info.max, "sending")],
    )
    def test_detects_activity_in_direction(
        self,
        send_threshold: float,
        receive_threshold: float,
        match: str,
        serve_data_url: str,
    ) -> None:
        check = NetworkBandwidth(
            "name", psutil.net_if_addrs().keys(), send_threshold, receive_threshold
        )
        # make some traffic
        requests.get(serve_data_url, timeout=5)
        res = check.check()
        assert res is not None
        assert match in res
    def test_reports_no_activity_below_threshold(self, serve_data_url: str) -> None:
        check = NetworkBandwidth(
            "name", psutil.net_if_addrs().keys(), sys.float_info.max, sys.float_info.max
        )
        # make some traffic
        requests.get(serve_data_url, timeout=5)
        assert check.check() is None
    def test_internal_state_updating_works(self, serve_data_url: str) -> None:
        check = NetworkBandwidth(
            "name", psutil.net_if_addrs().keys(), sys.float_info.max, sys.float_info.max
        )
        check.check()
        old_state = check._previous_values
        requests.get(serve_data_url, timeout=5)
        check.check()
        assert old_state != check._previous_values
    def test_delta_calculation_send_work(self, mocker: MockerFixture) -> None:
        first = mocker.MagicMock()
        type(first).bytes_sent = mocker.PropertyMock(return_value=1000)
        type(first).bytes_recv = mocker.PropertyMock(return_value=800)
        mocker.patch("psutil.net_io_counters").return_value = {
            "eth0": first,
        }
        with freeze_time("2019-10-01 10:00:00"):
            check = NetworkBandwidth("name", ["eth0"], 0, sys.float_info.max)
        second = mocker.MagicMock()
        type(second).bytes_sent = mocker.PropertyMock(return_value=1222)
        type(second).bytes_recv = mocker.PropertyMock(return_value=900)
        mocker.patch("psutil.net_io_counters").return_value = {
            "eth0": second,
        }
        with freeze_time("2019-10-01 10:00:01"):
            res = check.check()
            assert res is not None
            assert " 222.0 " in res
    def test_delta_calculation_receive_work(self, mocker: MockerFixture) -> None:
        first = mocker.MagicMock()
        type(first).bytes_sent = mocker.PropertyMock(return_value=1000)
        type(first).bytes_recv = mocker.PropertyMock(return_value=800)
        mocker.patch("psutil.net_io_counters").return_value = {
            "eth0": first,
        }
        with freeze_time("2019-10-01 10:00:00"):
            check = NetworkBandwidth("name", ["eth0"], sys.float_info.max, 0)
        second = mocker.MagicMock()
        type(second).bytes_sent = mocker.PropertyMock(return_value=1222)
        type(second).bytes_recv = mocker.PropertyMock(return_value=900)
        mocker.patch("psutil.net_io_counters").return_value = {
            "eth0": second,
        }
        with freeze_time("2019-10-01 10:00:01"):
            res = check.check()
            assert res is not None
            assert " 100.0 " in res
class TestPing(CheckTest):
    def create_instance(self, name: str) -> Check:
        return Ping(name, "8.8.8.8")
    def test_calls_ping_correctly(self, mocker: MockerFixture) -> None:
        mock = mocker.patch("subprocess.call")
        mock.return_value = 1
        hosts = ["abc", "129.123.145.42"]
        assert Ping("name", hosts).check() is None
        assert mock.call_count == len(hosts)
        for (args, _), host in zip(mock.call_args_list, hosts):
            assert args[0][-1] == host
    def test_raises_if_the_ping_binary_is_missing(self, mocker: MockerFixture) -> None:
        mock = mocker.patch("subprocess.call")
        mock.side_effect = FileNotFoundError()
        with pytest.raises(SevereCheckError):
            Ping("name", ["test"]).check()
    def test_detect_activity_if_ping_succeeds(self, mocker: MockerFixture) -> None:
        mock = mocker.patch("subprocess.call")
        mock.return_value = 0
        assert Ping("name", ["foo"]).check() is not None
    class TestCreate:
        def test_raises_if_hosts_are_missing(self) -> None:
            with pytest.raises(ConfigurationError):
                Ping.create("name", config_section())
        def test_comma_separated_hosts_are_split(self) -> None:
            ping = Ping.create("name", config_section({"hosts": "a,b,c"}))
            assert ping._hosts == ["a", "b", "c"]
class TestFile(CheckTest):
    def create_instance(self, name: str) -> Check:
        return File(name, Path("asdf"))
    class TestCreate:
        def test_it_works_with_a_valid_config(self) -> None:
            check = File.create("name", config_section({"path": "/tmp/test"}))
            assert check._path == Path("/tmp/test")
        def test_raises_without_path(self) -> None:
            with pytest.raises(ConfigurationError):
                File.create("name", config_section())
    def test_extracts_data_from_file(self, tmp_path: Path) -> None:
        test_file = tmp_path / "file"
        test_file.write_text("42\n\n")
        assert File("name", test_file).check(
            datetime.now(UTC)
        ) == datetime.fromtimestamp(42, UTC)
    def test_reports_no_wakeup_if_file_does_not_exist(self, tmp_path: Path) -> None:
        assert File("name", tmp_path / "narf").check(datetime.now(UTC)) is None
    def test_raises_on_permissions_errors(self, tmp_path: Path) -> None:
        file_path = tmp_path / "test"
        file_path.write_bytes(b"2314898")
        file_path.chmod(0)
        with pytest.raises(TemporaryCheckError):
            File("name", file_path).check(datetime.now(UTC))
    def test_raises_on_io_errors(self, tmp_path: Path, mocker: MockerFixture) -> None:
        file_path = tmp_path / "test"
        file_path.write_bytes(b"2314898")
        mocker.patch("pathlib.Path.read_text").side_effect = IOError
        with pytest.raises(TemporaryCheckError):
            File("name", file_path).check(datetime.now(UTC))
    def test_raises_if_file_contents_are_not_a_timestamp(self, tmp_path: Path) -> None:
        test_file = tmp_path / "filexxx"
        test_file.write_text("nonumber\n\n")
        with pytest.raises(TemporaryCheckError):
            File("name", test_file).check(datetime.now(UTC))
                                                                                                                                                                                                                                                                                                                                                                                                                                          autosuspend-9.0.0/tests/test_checks_logs.py                                                         0000664 0000000 0000000 00000021241 15043744726 0021145 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        from datetime import timedelta, timezone, UTC
from pathlib import Path
import re
from zoneinfo import ZoneInfo
from freezegun import freeze_time
import pytest
from autosuspend.checks import ConfigurationError, TemporaryCheckError
from autosuspend.checks.logs import LastLogActivity
from . import CheckTest
from .utils import config_section
class TestLastLogActivity(CheckTest):
    def create_instance(self, name: str) -> LastLogActivity:
        return LastLogActivity(
            name=name,
            log_file=Path("some_file"),
            pattern=re.compile("^(.*)$"),
            delta=timedelta(minutes=10),
            encoding="ascii",
            default_timezone=UTC,
        )
    def test_is_active(self, tmpdir: Path) -> None:
        file_path = tmpdir / "test.log"
        file_path.write_text("2020-02-02 12:12:23", encoding="ascii")
        with freeze_time("2020-02-02 12:15:00"):
            assert (
                LastLogActivity(
                    "test",
                    file_path,
                    re.compile(r"^(.*)$"),
                    timedelta(minutes=10),
                    "ascii",
                    UTC,
                ).check()
                is not None
            )
    def test_is_not_active(self, tmpdir: Path) -> None:
        file_path = tmpdir / "test.log"
        file_path.write_text("2020-02-02 12:12:23", encoding="ascii")
        with freeze_time("2020-02-02 12:35:00"):
            assert (
                LastLogActivity(
                    "test",
                    file_path,
                    re.compile(r"^(.*)$"),
                    timedelta(minutes=10),
                    "ascii",
                    UTC,
                ).check()
                is None
            )
    def test_uses_last_line(self, tmpdir: Path) -> None:
        file_path = tmpdir / "test.log"
        # last line is too old and must be used
        file_path.write_text(
            "\n".join(["2020-02-02 12:12:23", "1900-01-01"]), encoding="ascii"
        )
        with freeze_time("2020-02-02 12:15:00"):
            assert (
                LastLogActivity(
                    "test",
                    file_path,
                    re.compile(r"^(.*)$"),
                    timedelta(minutes=10),
                    "ascii",
                    UTC,
                ).check()
                is None
            )
    def test_ignores_lines_that_do_not_match(self, tmpdir: Path) -> None:
        file_path = tmpdir / "test.log"
        file_path.write_text("ignored", encoding="ascii")
        assert (
            LastLogActivity(
                "test",
                file_path,
                re.compile(r"^foo(.*)$"),
                timedelta(minutes=10),
                "ascii",
                UTC,
            ).check()
            is None
        )
    def test_uses_pattern(self, tmpdir: Path) -> None:
        file_path = tmpdir / "test.log"
        file_path.write_text("foo2020-02-02 12:12:23bar", encoding="ascii")
        with freeze_time("2020-02-02 12:15:00"):
            assert (
                LastLogActivity(
                    "test",
                    file_path,
                    re.compile(r"^foo(.*)bar$"),
                    timedelta(minutes=10),
                    "ascii",
                    UTC,
                ).check()
                is not None
            )
    def test_uses_given_timezone(self, tmpdir: Path) -> None:
        file_path = tmpdir / "test.log"
        # would match if timezone wasn't used
        file_path.write_text("2020-02-02 12:12:00", encoding="ascii")
        with freeze_time("2020-02-02 12:15:00"):
            assert (
                LastLogActivity(
                    "test",
                    file_path,
                    re.compile(r"^(.*)$"),
                    timedelta(minutes=10),
                    "ascii",
                    timezone(offset=timedelta(hours=10)),
                ).check()
                is None
            )
    def test_prefers_parsed_timezone(self, tmpdir: Path) -> None:
        file_path = tmpdir / "test.log"
        # would not match if provided timezone wasn't used
        file_path.write_text("2020-02-02T12:12:01-01:00", encoding="ascii")
        with freeze_time("2020-02-02 13:15:00"):
            assert (
                LastLogActivity(
                    "test",
                    file_path,
                    re.compile(r"^(.*)$"),
                    timedelta(minutes=10),
                    "ascii",
                    UTC,
                ).check()
                is not None
            )
    def test_fails_if_dates_cannot_be_parsed(self, tmpdir: Path) -> None:
        file_path = tmpdir / "test.log"
        # would match if timezone wasn't used
        file_path.write_text("202000xxx", encoding="ascii")
        with pytest.raises(TemporaryCheckError):
            LastLogActivity(
                "test",
                file_path,
                re.compile(r"^(.*)$"),
                timedelta(minutes=10),
                "ascii",
                UTC,
            ).check()
    def test_fails_if_dates_are_in_the_future(self, tmpdir: Path) -> None:
        file_path = tmpdir / "test.log"
        # would match if timezone wasn't used
        file_path.write_text("2022-01-01", encoding="ascii")
        with freeze_time("2020-02-02 12:15:00"), pytest.raises(TemporaryCheckError):
            LastLogActivity(
                "test",
                file_path,
                re.compile(r"^(.*)$"),
                timedelta(minutes=10),
                "ascii",
                UTC,
            ).check()
    def test_fails_if_file_cannot_be_read(self, tmpdir: Path) -> None:
        file_path = tmpdir / "test.log"
        with pytest.raises(TemporaryCheckError):
            LastLogActivity(
                "test",
                file_path,
                re.compile(r"^(.*)$"),
                timedelta(minutes=10),
                "ascii",
                UTC,
            ).check()
    def test_create(self) -> None:
        created = LastLogActivity.create(
            "thename",
            config_section(
                {
                    "name": "somename",
                    "log_file": "/some/file",
                    "pattern": "^foo(.*)bar$",
                    "minutes": "42",
                    "encoding": "utf-8",
                    "timezone": "Europe/Berlin",
                }
            ),
        )
        assert created.log_file == Path("/some/file")
        assert created.pattern == re.compile(r"^foo(.*)bar$")
        assert created.delta == timedelta(minutes=42)
        assert created.encoding == "utf-8"
        assert created.default_timezone == ZoneInfo("Europe/Berlin")
    def test_create_handles_pattern_errors(self) -> None:
        with pytest.raises(ConfigurationError):
            LastLogActivity.create(
                "thename",
                config_section(
                    {
                        "name": "somename",
                        "log_file": "/some/file",
                        "pattern": "^^foo((.*)bar$",
                    }
                ),
            )
    def test_create_handles_delta_errors(self) -> None:
        with pytest.raises(ConfigurationError):
            LastLogActivity.create(
                "thename",
                config_section(
                    {
                        "name": "somename",
                        "log_file": "/some/file",
                        "pattern": "(.*)",
                        "minutes": "test",
                    }
                ),
            )
    def test_create_handles_negative_deltas(self) -> None:
        with pytest.raises(ConfigurationError):
            LastLogActivity.create(
                "thename",
                config_section(
                    {
                        "name": "somename",
                        "log_file": "/some/file",
                        "pattern": "(.*)",
                        "minutes": "-42",
                    }
                ),
            )
    def test_create_handles_missing_pattern_groups(self) -> None:
        with pytest.raises(ConfigurationError):
            LastLogActivity.create(
                "thename",
                config_section(
                    {
                        "name": "somename",
                        "log_file": "/some/file",
                        "pattern": ".*",
                    }
                ),
            )
    def test_create_handles_missing_keys(self) -> None:
        with pytest.raises(ConfigurationError):
            LastLogActivity.create(
                "thename",
                config_section(
                    {
                        "name": "somename",
                    }
                ),
            )
                                                                                                                                                                                                                                                                                                                                                               autosuspend-9.0.0/tests/test_checks_mpd.py                                                          0000664 0000000 0000000 00000006656 15043744726 0020776 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        from typing import Any
import mpd
import pytest
from pytest_mock import MockerFixture
from autosuspend.checks import Check, ConfigurationError, TemporaryCheckError
from autosuspend.checks.mpd import Mpd
from . import CheckTest
from .utils import config_section
class TestMpd(CheckTest):
    def create_instance(self, name: str) -> Check:
        # concrete values are never used in the tests
        return Mpd(name, None, None, None)  # type: ignore
    def test_playing(self, monkeypatch: Any) -> None:
        check = Mpd("test", None, None, None)  # type: ignore
        def get_state() -> dict:
            return {"state": "play"}
        monkeypatch.setattr(check, "_get_state", get_state)
        assert check.check() is not None
    def test_not_playing(self, monkeypatch: Any) -> None:
        check = Mpd("test", None, None, None)  # type: ignore
        def get_state() -> dict:
            return {"state": "pause"}
        monkeypatch.setattr(check, "_get_state", get_state)
        assert check.check() is None
    def test_correct_mpd_interaction(self, mocker: MockerFixture) -> None:
        mock_instance = mocker.MagicMock(spec=mpd.MPDClient)
        mock_instance.status.return_value = {"state": "play"}
        timeout_property = mocker.PropertyMock()
        type(mock_instance).timeout = timeout_property
        mock = mocker.patch("autosuspend.checks.mpd.MPDClient")
        mock.return_value = mock_instance
        host = "foo"
        port = 42
        timeout = 17
        assert Mpd("name", host, port, timeout).check() is not None
        timeout_property.assert_called_once_with(timeout)
        mock_instance.connect.assert_called_once_with(host, port)
        mock_instance.status.assert_called_once_with()
        mock_instance.close.assert_called_once_with()
        mock_instance.disconnect.assert_called_once_with()
    @pytest.mark.parametrize("exception_type", [ConnectionError, mpd.ConnectionError])
    def test_handle_connection_errors(self, exception_type: type) -> None:
        check = Mpd("test", None, None, None)  # type: ignore
        def _get_state() -> dict:
            raise exception_type()
        # https://github.com/python/mypy/issues/2427
        check._get_state = _get_state  # type: ignore
        with pytest.raises(TemporaryCheckError):
            check.check()
    def test_create(self) -> None:
        check = Mpd.create(
            "name",
            config_section(
                {
                    "host": "host",
                    "port": "1234",
                    "timeout": "12",
                }
            ),
        )
        assert check._host == "host"
        assert check._port == 1234
        assert check._timeout == 12
    def test_create_port_no_number(self) -> None:
        with pytest.raises(ConfigurationError):
            Mpd.create(
                "name",
                config_section(
                    {
                        "host": "host",
                        "port": "string",
                        "timeout": "12",
                    }
                ),
            )
    def test_create_timeout_no_number(self) -> None:
        with pytest.raises(ConfigurationError):
            Mpd.create(
                "name",
                config_section(
                    {
                        "host": "host",
                        "port": "10",
                        "timeout": "string",
                    }
                ),
            )
                                                                                  autosuspend-9.0.0/tests/test_checks_smb.py                                                          0000664 0000000 0000000 00000002776 15043744726 0020776 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        from pathlib import Path
import subprocess
import pytest
from pytest_mock import MockerFixture
from autosuspend.checks import Check, SevereCheckError, TemporaryCheckError
from autosuspend.checks.smb import Smb
from . import CheckTest
class TestSmb(CheckTest):
    def create_instance(self, name: str) -> Check:
        return Smb(name)
    def test_no_connections(self, datadir: Path, mocker: MockerFixture) -> None:
        mocker.patch("subprocess.check_output").return_value = (
            datadir / "smbstatus_no_connections"
        ).read_bytes()
        assert Smb("foo").check() is None
    def test_with_connections(self, datadir: Path, mocker: MockerFixture) -> None:
        mocker.patch("subprocess.check_output").return_value = (
            datadir / "smbstatus_with_connections"
        ).read_bytes()
        res = Smb("foo").check()
        assert res is not None
        assert len(res.splitlines()) == 3
    def test_call_error(self, mocker: MockerFixture) -> None:
        mocker.patch(
            "subprocess.check_output",
            side_effect=subprocess.CalledProcessError(2, "cmd"),
        )
        with pytest.raises(TemporaryCheckError):
            Smb("foo").check()
    def test_missing_executable(self, mocker: MockerFixture) -> None:
        mocker.patch("subprocess.check_output", side_effect=FileNotFoundError)
        with pytest.raises(SevereCheckError):
            Smb("foo").check()
    def test_create(self) -> None:
        assert isinstance(Smb.create("name", None), Smb)
  autosuspend-9.0.0/tests/test_checks_smb/                                                            0000775 0000000 0000000 00000000000 15043744726 0020410 5                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        autosuspend-9.0.0/tests/test_checks_smb/smbstatus_no_connections                                    0000664 0000000 0000000 00000000447 15043744726 0025463 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        
Samba version 4.7.0
PID     Username     Group        Machine                                   Protocol Version  Encryption           Signing              
----------------------------------------------------------------------------------------------------------------------------------------
                                                                                                                                                                                                                         autosuspend-9.0.0/tests/test_checks_smb/smbstatus_with_connections                                  0000664 0000000 0000000 00000000421 15043744726 0026012 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        
Samba version 3.5.1
PID     Username      Group         Machine
-------------------------------------------------------------------
14944            it            131.169.214.117 (131.169.214.117)
14944            it            131.169.214.117 (131.169.214.117)
                                                                                                                                                                                                                                               autosuspend-9.0.0/tests/test_checks_stub.py                                                         0000664 0000000 0000000 00000002757 15043744726 0021171 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        from datetime import datetime, timedelta, UTC
import pytest
from autosuspend.checks import Check, ConfigurationError
from autosuspend.checks.stub import Periodic
from . import CheckTest
from .utils import config_section
class TestPeriodic(CheckTest):
    def create_instance(self, name: str) -> Check:
        delta = timedelta(seconds=10, minutes=42)
        return Periodic(name, delta)
    def test_create(self) -> None:
        check = Periodic.create(
            "name", config_section({"unit": "seconds", "value": "13"})
        )
        assert check._delta == timedelta(seconds=13)
    def test_create_wrong_unit(self) -> None:
        with pytest.raises(ConfigurationError):
            Periodic.create("name", config_section({"unit": "asdfasdf", "value": "13"}))
    def test_create_not_numeric(self) -> None:
        with pytest.raises(ConfigurationError):
            Periodic.create(
                "name", config_section({"unit": "seconds", "value": "asdfasd"})
            )
    def test_create_no_unit(self) -> None:
        with pytest.raises(ConfigurationError):
            Periodic.create("name", config_section({"value": "13"}))
    def test_create_float(self) -> None:
        Periodic.create(
            "name", config_section({"unit": "seconds", "value": "21312.12"})
        )
    def test_check(self) -> None:
        delta = timedelta(seconds=10, minutes=42)
        check = Periodic("test", delta)
        now = datetime.now(UTC)
        assert check.check(now) == now + delta
                 autosuspend-9.0.0/tests/test_checks_systemd.py                                                      0000664 0000000 0000000 00000011255 15043744726 0021675 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        from datetime import datetime, timedelta, UTC
import re
from unittest.mock import Mock
from dbus.proxies import ProxyObject
import pytest
from pytest_mock import MockerFixture
from autosuspend.checks import Check, ConfigurationError, TemporaryCheckError
from autosuspend.checks.systemd import (
    LogindSessionsIdle,
    next_timer_executions,
    SystemdTimer,
)
from . import CheckTest
from .utils import config_section
@pytest.mark.skip(reason="No dbusmock implementation available")
def test_next_timer_executions() -> None:
    assert next_timer_executions() is not None
class TestSystemdTimer(CheckTest):
    @staticmethod
    @pytest.fixture
    def next_timer_executions(mocker: MockerFixture) -> Mock:
        return mocker.patch("autosuspend.checks.systemd.next_timer_executions")
    def create_instance(self, name: str) -> Check:
        return SystemdTimer(name, re.compile(".*"))
    def test_create_handles_incorrect_expressions(self) -> None:
        with pytest.raises(ConfigurationError):
            SystemdTimer.create("somename", config_section({"match": "(.*"}))
    def test_create_raises_if_match_is_missing(self) -> None:
        with pytest.raises(ConfigurationError):
            SystemdTimer.create("somename", config_section())
    def test_works_without_timers(self, next_timer_executions: Mock) -> None:
        next_timer_executions.return_value = {}
        now = datetime.now(UTC)
        assert SystemdTimer("foo", re.compile(".*")).check(now) is None
    def test_ignores_non_matching_timers(self, next_timer_executions: Mock) -> None:
        now = datetime.now(UTC)
        next_timer_executions.return_value = {"ignored": now}
        assert SystemdTimer("foo", re.compile("needle")).check(now) is None
    def test_finds_matching_timers(self, next_timer_executions: Mock) -> None:
        pattern = "foo"
        now = datetime.now(UTC)
        next_timer_executions.return_value = {pattern: now}
        assert SystemdTimer("foo", re.compile(pattern)).check(now) is now
    def test_selects_the_closest_execution_if_multiple_match(
        self, next_timer_executions: Mock
    ) -> None:
        now = datetime.now(UTC)
        next_timer_executions.return_value = {
            "later": now + timedelta(minutes=1),
            "matching": now,
        }
        assert SystemdTimer("foo", re.compile(".*")).check(now) is now
class TestLogindSessionsIdle(CheckTest):
    def create_instance(self, name: str) -> Check:
        return LogindSessionsIdle(name, ["tty", "x11", "wayland"], ["active", "online"])
    def test_active(self, logind: ProxyObject) -> None:
        logind.AddSession("c1", "seat0", 1042, "auser", True)
        check = LogindSessionsIdle("test", ["test"], ["active", "online"])
        assert check.check() is not None
    @pytest.mark.skip(reason="No known way to set idle hint in dbus mock right now")
    def test_inactive(self, logind: ProxyObject) -> None:
        logind.AddSession("c1", "seat0", 1042, "auser", False)
        check = LogindSessionsIdle("test", ["test"], ["active", "online"])
        assert check.check() is None
    def test_ignore_unknow_type(self, logind: ProxyObject) -> None:
        logind.AddSession("c1", "seat0", 1042, "auser", True)
        check = LogindSessionsIdle("test", ["not_test"], ["active", "online"])
        assert check.check() is None
    def test_ignore_unknown_class(self, logind: ProxyObject) -> None:
        logind.AddSession("c1", "seat0", 1042, "user", True)
        check = LogindSessionsIdle(
            "test", ["test"], ["active", "online"], ["nosuchclass"]
        )
        assert check.check() is None
    def test_configure_defaults(self) -> None:
        check = LogindSessionsIdle.create("name", config_section())
        assert check._types == ["tty", "x11", "wayland"]
        assert check._states == ["active", "online"]
    def test_configure_types(self) -> None:
        check = LogindSessionsIdle.create(
            "name", config_section({"types": "test, bla,foo"})
        )
        assert check._types == ["test", "bla", "foo"]
    def test_configure_states(self) -> None:
        check = LogindSessionsIdle.create(
            "name", config_section({"states": "test, bla,foo"})
        )
        assert check._states == ["test", "bla", "foo"]
    def test_configure_classes(self) -> None:
        check = LogindSessionsIdle.create(
            "name", config_section({"classes": "test, bla,foo"})
        )
        assert check._classes == ["test", "bla", "foo"]
    @pytest.mark.usefixtures("_logind_dbus_error")
    def test_dbus_error(self) -> None:
        check = LogindSessionsIdle("test", ["test"], ["active", "online"])
        with pytest.raises(TemporaryCheckError):
            check.check()
                                                                                                                                                                                                                                                                                                                                                   autosuspend-9.0.0/tests/test_checks_util.py                                                         0000664 0000000 0000000 00000007422 15043744726 0021163 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        from collections.abc import Callable
from pathlib import Path
from unittest.mock import ANY
import pytest
from pytest_httpserver import HTTPServer
from pytest_mock import MockerFixture
import requests
from autosuspend.checks import ConfigurationError, TemporaryCheckError
from autosuspend.checks.util import NetworkMixin
from .utils import config_section
class TestNetworkMixin:
    def test_collect_missing_url(self) -> None:
        with pytest.raises(ConfigurationError, match=r"^Lacks 'url'.*"):
            NetworkMixin.collect_init_args(config_section())
    def test_username_missing(self) -> None:
        with pytest.raises(ConfigurationError, match=r"^Username and.*"):
            NetworkMixin.collect_init_args(
                config_section({"url": "required", "password": "lacks username"})
            )
    def test_password_missing(self) -> None:
        with pytest.raises(ConfigurationError, match=r"^Username and.*"):
            NetworkMixin.collect_init_args(
                config_section({"url": "required", "username": "lacks password"})
            )
    def test_collect_default_timeout(self) -> None:
        args = NetworkMixin.collect_init_args(config_section({"url": "required"}))
        assert args["timeout"] == 5
    def test_collect_timeout(self) -> None:
        args = NetworkMixin.collect_init_args(
            config_section({"url": "required", "timeout": "42"})
        )
        assert args["timeout"] == 42
    def test_collect_invalid_timeout(self) -> None:
        with pytest.raises(ConfigurationError, match=r"^Configuration error .*"):
            NetworkMixin.collect_init_args(
                config_section({"url": "required", "timeout": "xx"})
            )
    def test_request(self, datadir: Path, serve_file: Callable[[Path], str]) -> None:
        reply = NetworkMixin(
            serve_file(datadir / "xml_with_encoding.xml"),
            5,
        ).request()
        assert reply is not None
        assert reply.status_code == 200
    def test_requests_exception(self, mocker: MockerFixture) -> None:
        mock_method = mocker.patch("requests.Session.get")
        mock_method.side_effect = requests.exceptions.ReadTimeout()
        with pytest.raises(TemporaryCheckError):
            NetworkMixin("url", timeout=5).request()
    def test_smoke(self, datadir: Path, serve_file: Callable[[Path], str]) -> None:
        response = NetworkMixin(serve_file(datadir / "data.txt"), timeout=5).request()
        assert response is not None
        assert response.text == "iamhere\n"
    def test_exception_404(self, httpserver: HTTPServer) -> None:
        with pytest.raises(TemporaryCheckError):
            NetworkMixin(httpserver.url_for("/does/not/exist"), timeout=5).request()
    def test_authentication(
        self, datadir: Path, serve_protected: Callable[[Path], tuple[str, str, str]]
    ) -> None:
        url, username, password = serve_protected(datadir / "data.txt")
        NetworkMixin(url, 5, username=username, password=password).request()
    def test_invalid_authentication(
        self, datadir: Path, serve_protected: Callable[[Path], tuple[str, str, str]]
    ) -> None:
        with pytest.raises(TemporaryCheckError):
            NetworkMixin(
                serve_protected(datadir / "data.txt")[0],
                5,
                username="userx",
                password="pass",
            ).request()
    def test_file_url(self) -> None:
        NetworkMixin("file://" + __file__, 5).request()
    def test_content_type(self, mocker: MockerFixture) -> None:
        mock_method = mocker.patch("requests.Session.get")
        content_type = "foo/bar"
        NetworkMixin("url", timeout=5, accept=content_type).request()
        mock_method.assert_called_with(
            ANY, timeout=ANY, headers={"Accept": content_type}
        )
                                                                                                                                                                                                                                              autosuspend-9.0.0/tests/test_checks_util/                                                           0000775 0000000 0000000 00000000000 15043744726 0020604 5                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        autosuspend-9.0.0/tests/test_checks_util/data.txt                                                   0000664 0000000 0000000 00000000010 15043744726 0022245 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        iamhere
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        autosuspend-9.0.0/tests/test_checks_util/xml_with_encoding.xml                                      0000664 0000000 0000000 00000003454 15043744726 0025035 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        
                                                                                                                                                                                                                    autosuspend-9.0.0/tests/test_checks_wakeup.py                                                       0000664 0000000 0000000 00000000651 15043744726 0021477 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        import pytest
from autosuspend.checks import Wakeup
@pytest.mark.parametrize(
    "name",
    [
        "Calendar",
        "Command",
        "File",
        "Periodic",
        "SystemdTimer",
        "XPath",
        "XPathDelta",
    ],
)
def test_legacy_check_names_are_available(name: str) -> None:
    res = __import__("autosuspend.checks.wakeup", fromlist=[name])
    assert issubclass(getattr(res, name), Wakeup)
                                                                                       autosuspend-9.0.0/tests/test_checks_xorg.py                                                         0000664 0000000 0000000 00000020225 15043744726 0021161 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        from getpass import getuser
import logging
from pathlib import Path
import re
import subprocess
from typing import Any
import pytest
from pytest_mock import MockerFixture
from autosuspend.checks import (
    Check,
    ConfigurationError,
    SevereCheckError,
    TemporaryCheckError,
)
from autosuspend.checks.xorg import (
    list_sessions_logind,
    list_sessions_sockets,
    XIdleTime,
    XorgSession,
)
from autosuspend.util.systemd import LogindDBusException
from . import CheckTest
from .utils import config_section
class TestListSessionsSockets:
    def test_empty(self, tmp_path: Path) -> None:
        assert list_sessions_sockets(tmp_path) == []
    @pytest.mark.parametrize("number", [0, 10, 1024])
    def test_extracts_valid_sockets(self, tmp_path: Path, number: int) -> None:
        session_sock = tmp_path / f"X{number}"
        session_sock.touch()
        assert list_sessions_sockets(tmp_path) == [
            XorgSession(number, session_sock.owner())
        ]
    @pytest.mark.parametrize("invalid_number", ["", "string", "  "])
    def test_ignores_and_warns_on_invalid_numbers(
        self,
        tmp_path: Path,
        invalid_number: str,
        caplog: Any,
    ) -> None:
        (tmp_path / f"X{invalid_number}").touch()
        with caplog.at_level(logging.WARNING):
            assert list_sessions_sockets(tmp_path) == []
            assert caplog.records != []
    def test_ignores_and_warns_on_unknown_users(
        self,
        tmp_path: Path,
        mocker: MockerFixture,
        caplog: Any,
    ) -> None:
        (tmp_path / "X0").touch()
        mocker.patch("pathlib.Path.owner").side_effect = KeyError()
        with caplog.at_level(logging.WARNING):
            assert list_sessions_sockets(tmp_path) == []
            assert caplog.records != []
    def test_ignores_other_files(
        self,
        tmp_path: Path,
    ) -> None:
        (tmp_path / "asdf").touch()
        assert list_sessions_sockets(tmp_path) == []
    def test_returns_multiple(self, tmp_path: Path) -> None:
        (tmp_path / "X0").touch()
        (tmp_path / "X1").touch()
        assert len(list_sessions_sockets(tmp_path)) == 2
_LIST_LOGIND_SESSIONS_TO_PATCH = "autosuspend.checks.xorg.list_logind_sessions"
class TestListSessionsLogind:
    def test_extracts_valid_sessions(self, mocker: MockerFixture) -> None:
        username = "test_user"
        display = 42
        mocker.patch(_LIST_LOGIND_SESSIONS_TO_PATCH).return_value = [
            ("id", {"Name": username, "Display": f":{display}"})
        ]
        assert list_sessions_logind() == [XorgSession(display, username)]
    def test_ignores_sessions_with_missing_properties(
        self, mocker: MockerFixture
    ) -> None:
        mocker.patch(_LIST_LOGIND_SESSIONS_TO_PATCH).return_value = [
            ("id", {"Name": "someuser"}),
            ("id", {"Display": ":42"}),
        ]
        assert list_sessions_logind() == []
    def test_ignores_and_warns_on_invalid_display_numbers(
        self,
        mocker: MockerFixture,
        caplog: Any,
    ) -> None:
        mocker.patch(_LIST_LOGIND_SESSIONS_TO_PATCH).return_value = [
            ("id", {"Name": "someuser", "Display": "XXX"}),
        ]
        with caplog.at_level(logging.WARNING):
            assert list_sessions_logind() == []
            assert caplog.records != []
class TestXIdleTime(CheckTest):
    def create_instance(self, name: str) -> Check:
        # concrete values are never used in the test
        return XIdleTime(name, 10, "sockets", None, None)  # type: ignore
    def test_smoke(self, mocker: MockerFixture) -> None:
        check = XIdleTime("name", 100, "logind", re.compile(r"a^"), re.compile(r"a^"))
        mocker.patch.object(check, "_provide_sessions").return_value = [
            XorgSession(42, getuser()),
        ]
        co_mock = mocker.patch("subprocess.check_output")
        co_mock.return_value = "123"
        res = check.check()
        assert res is not None
        assert " 0.123 " in res
        args, kwargs = co_mock.call_args
        assert getuser() in args[0]
        assert kwargs["env"]["DISPLAY"] == ":42"
        assert getuser() in kwargs["env"]["XAUTHORITY"]
    def test_no_activity(self, mocker: MockerFixture) -> None:
        check = XIdleTime("name", 100, "logind", re.compile(r"a^"), re.compile(r"a^"))
        mocker.patch.object(check, "_provide_sessions").return_value = [
            XorgSession(42, getuser()),
        ]
        mocker.patch("subprocess.check_output").return_value = "120000"
        assert check.check() is None
    def test_multiple_sessions(self, mocker: MockerFixture) -> None:
        check = XIdleTime("name", 100, "logind", re.compile(r"a^"), re.compile(r"a^"))
        mocker.patch.object(check, "_provide_sessions").return_value = [
            XorgSession(42, getuser()),
            XorgSession(17, "root"),
        ]
        co_mock = mocker.patch("subprocess.check_output")
        co_mock.side_effect = [
            "120000",
            "123",
        ]
        res = check.check()
        assert res is not None
        assert " 0.123 " in res
        assert co_mock.call_count == 2
        # check second call for correct values, not checked before
        args, kwargs = co_mock.call_args_list[1]
        assert "root" in args[0]
        assert kwargs["env"]["DISPLAY"] == ":17"
        assert "root" in kwargs["env"]["XAUTHORITY"]
    def test_handle_call_error(self, mocker: MockerFixture) -> None:
        check = XIdleTime("name", 100, "logind", re.compile(r"a^"), re.compile(r"a^"))
        mocker.patch.object(check, "_provide_sessions").return_value = [
            XorgSession(42, getuser()),
        ]
        mocker.patch(
            "subprocess.check_output",
        ).side_effect = subprocess.CalledProcessError(2, "foo")
        with pytest.raises(TemporaryCheckError):
            check.check()
    def test_create_default(self) -> None:
        check = XIdleTime.create("name", config_section())
        assert check._timeout == 600
        assert check._ignore_process_re == re.compile(r"a^")
        assert check._ignore_users_re == re.compile(r"a^")
        assert check._provide_sessions == list_sessions_sockets
    def test_create(self) -> None:
        check = XIdleTime.create(
            "name",
            config_section(
                {
                    "timeout": "42",
                    "ignore_if_process": ".*test",
                    "ignore_users": "test.*test",
                    "method": "logind",
                }
            ),
        )
        assert check._timeout == 42
        assert check._ignore_process_re == re.compile(r".*test")
        assert check._ignore_users_re == re.compile(r"test.*test")
        assert check._provide_sessions == list_sessions_logind
    def test_create_no_int(self) -> None:
        with pytest.raises(ConfigurationError):
            XIdleTime.create("name", config_section({"timeout": "string"}))
    def test_create_broken_process_re(self) -> None:
        with pytest.raises(ConfigurationError):
            XIdleTime.create("name", config_section({"ignore_if_process": "[[a-9]"}))
    def test_create_broken_users_re(self) -> None:
        with pytest.raises(ConfigurationError):
            XIdleTime.create("name", config_section({"ignore_users": "[[a-9]"}))
    def test_create_unknown_method(self) -> None:
        with pytest.raises(ConfigurationError):
            XIdleTime.create("name", config_section({"method": "asdfasdf"}))
    def test_list_sessions_logind_dbus_error(self, mocker: MockerFixture) -> None:
        check = XIdleTime.create("name", config_section())
        mocker.patch.object(check, "_provide_sessions").side_effect = (
            LogindDBusException()
        )
        with pytest.raises(TemporaryCheckError):
            check._safe_provide_sessions()
    def test_sudo_not_found(self, mocker: MockerFixture) -> None:
        check = XIdleTime("name", 100, "logind", re.compile(r"a^"), re.compile(r"a^"))
        mocker.patch.object(check, "_provide_sessions").return_value = [
            XorgSession(42, getuser()),
        ]
        mocker.patch("subprocess.check_output").side_effect = FileNotFoundError
        with pytest.raises(SevereCheckError):
            check.check()
                                                                                                                                                                                                                                                                                                                                                                           autosuspend-9.0.0/tests/test_checks_xpath.py                                                        0000664 0000000 0000000 00000024646 15043744726 0021341 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        from collections.abc import Callable
from datetime import datetime, timedelta, UTC
from pathlib import Path
from typing import Any
import pytest
from pytest_mock import MockerFixture
from autosuspend.checks import Activity, Check, ConfigurationError, TemporaryCheckError
from autosuspend.checks.xpath import (
    XPathActivity,
    XPathDeltaWakeup,
    XPathMixin,
    XPathWakeup,
)
from . import CheckTest
from .utils import config_section
class _XPathMixinSub(XPathMixin, Activity):
    def __init__(self, name: str, **kwargs: Any) -> None:
        Activity.__init__(self, name)
        XPathMixin.__init__(self, **kwargs)
    def check(self) -> str | None:
        pass
class TestXPathMixin:
    def test_smoke(self, datadir: Path, serve_file: Callable[[Path], str]) -> None:
        result = _XPathMixinSub(
            "foo",
            xpath="/b",
            url=serve_file(datadir / "xml_with_encoding.xml"),
            timeout=5,
        ).evaluate()
        assert result is not None
        assert len(result) == 0
    def test_broken_xml(self, mocker: MockerFixture) -> None:
        mock_reply = mocker.MagicMock()
        content_property = mocker.PropertyMock()
        type(mock_reply).content = content_property
        content_property.return_value = b"//broken"
        mocker.patch("requests.Session.get", return_value=mock_reply)
        with pytest.raises(TemporaryCheckError):
            _XPathMixinSub("foo", xpath="/b", url="nourl", timeout=5).evaluate()
    def test_xml_with_encoding(self, mocker: MockerFixture) -> None:
        mock_reply = mocker.MagicMock()
        content_property = mocker.PropertyMock()
        type(mock_reply).content = content_property
        content_property.return_value = (
            b''
        )
        mocker.patch("requests.Session.get", return_value=mock_reply)
        _XPathMixinSub("foo", xpath="/b", url="nourl", timeout=5).evaluate()
    def test_xpath_prevalidation(self) -> None:
        with pytest.raises(ConfigurationError, match=r"^Invalid xpath.*"):
            _XPathMixinSub.create(
                "name", config_section({"xpath": "|34/ad", "url": "required"})
            )
    @pytest.mark.parametrize("entry", ["xpath", "url"])
    def test_missing_config_entry(self, entry: str) -> None:
        section = config_section({"xpath": "/valid", "url": "required"})
        del section[entry]
        with pytest.raises(ConfigurationError, match=r"^Lacks '" + entry + "'.*"):
            _XPathMixinSub.create("name", section)
    def test_invalid_config_entry(self) -> None:
        with pytest.raises(ConfigurationError, match=r"^Configuration error .*"):
            _XPathMixinSub.create(
                "name",
                config_section(
                    {"xpath": "/valid", "url": "required", "timeout": "xxx"}
                ),
            )
class TestXPathActivity(CheckTest):
    def create_instance(self, name: str) -> Check:
        return XPathActivity(
            name=name,
            url="url",
            timeout=5,
            username="userx",
            password="pass",
            xpath="/b",
        )
    def test_matching(self, mocker: MockerFixture) -> None:
        mock_reply = mocker.MagicMock()
        content_property = mocker.PropertyMock()
        type(mock_reply).content = content_property
        content_property.return_value = ""
        mock_method = mocker.patch("requests.Session.get", return_value=mock_reply)
        url = "nourl"
        assert XPathActivity("foo", xpath="/a", url=url, timeout=5).check() is not None
        mock_method.assert_called_once_with(url, timeout=5, headers=None)
        content_property.assert_called_once_with()
    def test_not_matching(self, mocker: MockerFixture) -> None:
        mock_reply = mocker.MagicMock()
        content_property = mocker.PropertyMock()
        type(mock_reply).content = content_property
        content_property.return_value = ""
        mocker.patch("requests.Session.get", return_value=mock_reply)
        assert XPathActivity("foo", xpath="/b", url="nourl", timeout=5).check() is None
    def test_create(self) -> None:
        check: XPathActivity = XPathActivity.create(
            "name",
            config_section(
                {
                    "url": "url",
                    "xpath": "/xpath",
                    "username": "user",
                    "password": "pass",
                    "timeout": "42",
                }
            ),
        )
        assert check._xpath == "/xpath"
        assert check._url == "url"
        assert check._username == "user"
        assert check._password == "pass"
        assert check._timeout == 42
    def test_network_errors_are_passed(
        self, datadir: Path, serve_protected: Callable[[Path], tuple[str, str, str]]
    ) -> None:
        with pytest.raises(TemporaryCheckError):
            XPathActivity(
                name="name",
                url=serve_protected(datadir / "data.txt")[0],
                timeout=5,
                username="wrong",
                password="wrong",
                xpath="/b",
            ).request()
class TestXPathWakeup(CheckTest):
    def create_instance(self, name: str) -> Check:
        return XPathWakeup(name, xpath="/a", url="nourl", timeout=5)
    def test_matching(self, mocker: MockerFixture) -> None:
        mock_reply = mocker.MagicMock()
        content_property = mocker.PropertyMock()
        type(mock_reply).content = content_property
        content_property.return_value = ''
        mock_method = mocker.patch("requests.Session.get", return_value=mock_reply)
        url = "nourl"
        assert XPathWakeup("foo", xpath="/a/@value", url=url, timeout=5).check(
            datetime.now(UTC)
        ) == datetime.fromtimestamp(42.3, UTC)
        mock_method.assert_called_once_with(url, timeout=5, headers=None)
        content_property.assert_called_once_with()
    def test_not_matching(self, mocker: MockerFixture) -> None:
        mock_reply = mocker.MagicMock()
        content_property = mocker.PropertyMock()
        type(mock_reply).content = content_property
        content_property.return_value = ""
        mocker.patch("requests.Session.get", return_value=mock_reply)
        assert (
            XPathWakeup("foo", xpath="/b", url="nourl", timeout=5).check(
                datetime.now(UTC)
            )
            is None
        )
    def test_not_a_string(self, mocker: MockerFixture) -> None:
        mock_reply = mocker.MagicMock()
        content_property = mocker.PropertyMock()
        type(mock_reply).content = content_property
        content_property.return_value = ""
        mocker.patch("requests.Session.get", return_value=mock_reply)
        with pytest.raises(TemporaryCheckError):
            XPathWakeup("foo", xpath="/a", url="nourl", timeout=5).check(
                datetime.now(UTC)
            )
    def test_not_a_number(self, mocker: MockerFixture) -> None:
        mock_reply = mocker.MagicMock()
        content_property = mocker.PropertyMock()
        type(mock_reply).content = content_property
        content_property.return_value = ''
        mocker.patch("requests.Session.get", return_value=mock_reply)
        with pytest.raises(TemporaryCheckError):
            XPathWakeup("foo", xpath="/a/@value", url="nourl", timeout=5).check(
                datetime.now(UTC)
            )
    def test_multiple_min(self, mocker: MockerFixture) -> None:
        mock_reply = mocker.MagicMock()
        content_property = mocker.PropertyMock()
        type(mock_reply).content = content_property
        content_property.return_value = """
            
                
                
                
            
        """
        mocker.patch("requests.Session.get", return_value=mock_reply)
        assert XPathWakeup("foo", xpath="//a/@value", url="nourl", timeout=5).check(
            datetime.now(UTC)
        ) == datetime.fromtimestamp(10, UTC)
    def test_create(self) -> None:
        check: XPathWakeup = XPathWakeup.create(
            "name",
            config_section(
                {
                    "xpath": "/valid",
                    "url": "nourl",
                    "timeout": "20",
                }
            ),
        )
        assert check._xpath == "/valid"
class TestXPathDeltaWakeup(CheckTest):
    def create_instance(self, name: str) -> Check:
        return XPathDeltaWakeup(name, xpath="/a", url="nourl", timeout=5, unit="days")
    @pytest.mark.parametrize(
        ("unit", "factor"),
        [
            ("microseconds", 0.000001),
            ("milliseconds", 0.001),
            ("seconds", 1),
            ("minutes", 60),
            ("hours", 60 * 60),
            ("days", 60 * 60 * 24),
            ("weeks", 60 * 60 * 24 * 7),
        ],
    )
    def test_smoke(self, mocker: MockerFixture, unit: str, factor: float) -> None:
        mock_reply = mocker.MagicMock()
        content_property = mocker.PropertyMock()
        type(mock_reply).content = content_property
        content_property.return_value = ''
        mocker.patch("requests.Session.get", return_value=mock_reply)
        url = "nourl"
        now = datetime.now(UTC)
        result = XPathDeltaWakeup(
            "foo", xpath="/a/@value", url=url, timeout=5, unit=unit
        ).check(now)
        assert result == now + timedelta(seconds=42) * factor
    def test_create(self) -> None:
        check = XPathDeltaWakeup.create(
            "name",
            config_section(
                {
                    "xpath": "/valid",
                    "url": "nourl",
                    "timeout": "20",
                    "unit": "weeks",
                }
            ),
        )
        assert check._unit == "weeks"
    def test_create_wrong_unit(self) -> None:
        with pytest.raises(ConfigurationError):
            XPathDeltaWakeup.create(
                "name",
                config_section(
                    {
                        "xpath": "/valid",
                        "url": "nourl",
                        "timeout": "20",
                        "unit": "unknown",
                    }
                ),
            )
    def test_init_wrong_unit(self) -> None:
        with pytest.raises(ValueError, match=r".*unit.*"):
            XPathDeltaWakeup(
                "name", url="url", xpath="/a", timeout=5, unit="unknownunit"
            )
                                                                                          autosuspend-9.0.0/tests/test_checks_xpath/                                                          0000775 0000000 0000000 00000000000 15043744726 0020753 5                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        autosuspend-9.0.0/tests/test_checks_xpath/xml_with_encoding.xml                                     0000664 0000000 0000000 00000003454 15043744726 0025204 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        
                                                                                                                                                                                                                    autosuspend-9.0.0/tests/test_integration.py                                                         0000664 0000000 0000000 00000015177 15043744726 0021217 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        from collections.abc import Iterable
from datetime import datetime, timedelta, UTC
import logging
from pathlib import Path
from typing import Any
from freezegun import freeze_time
import pytest
from pytest_mock import MockerFixture
import autosuspend
pytestmark = pytest.mark.integration
SUSPENSION_FILE = "would_suspend"
SCHEDULED_FILE = "wakeup_at"
WOKE_UP_FILE = "test-woke-up"
LOCK_FILE = "test-woke-up.lock"
NOTIFY_FILE = "notify"
def configure_config(config: str, datadir: Path, tmp_path: Path) -> Path:
    out_path = tmp_path / config
    with out_path.open("w") as out_config:
        out_config.write(
            (datadir / config).read_text().replace("@TMPDIR@", str(tmp_path)),
        )
    return out_path
@pytest.fixture
def _rapid_sleep(mocker: MockerFixture) -> Iterable[None]:
    with freeze_time() as frozen_time:
        sleep_mock = mocker.patch("time.sleep")
        sleep_mock.side_effect = lambda seconds: frozen_time.tick(
            timedelta(seconds=seconds)
        )
        yield
@pytest.mark.usefixtures("_rapid_sleep")
def test_no_suspend_if_matching(datadir: Path, tmp_path: Path) -> None:
    autosuspend.main(
        [
            "-c",
            str(configure_config("dont_suspend.conf", datadir, tmp_path)),
            "-d",
            "daemon",
            "-r",
            "10",
        ]
    )
    assert not (tmp_path / SUSPENSION_FILE).exists()
@pytest.mark.usefixtures("_rapid_sleep")
def test_suspend(tmp_path: Path, datadir: Path) -> None:
    autosuspend.main(
        [
            "-c",
            str(configure_config("would_suspend.conf", datadir, tmp_path)),
            "-d",
            "daemon",
            "-r",
            "10",
        ]
    )
    assert (tmp_path / SUSPENSION_FILE).exists()
@pytest.mark.usefixtures("_rapid_sleep")
def test_wakeup_scheduled(tmp_path: Path, datadir: Path) -> None:
    # configure when to wake up
    now = datetime.now(UTC)
    wakeup_at = now + timedelta(hours=4)
    (tmp_path / "wakeup_time").write_text(str(wakeup_at.timestamp()))
    autosuspend.main(
        [
            "-c",
            str(configure_config("would_schedule.conf", datadir, tmp_path)),
            "-d",
            "daemon",
            "-r",
            "10",
        ]
    )
    assert (tmp_path / SUSPENSION_FILE).exists()
    assert (tmp_path / SCHEDULED_FILE).exists()
    assert int((tmp_path / SCHEDULED_FILE).read_text()) == round(
        (wakeup_at - timedelta(seconds=30)).timestamp()
    )
@pytest.mark.usefixtures("_rapid_sleep")
def test_woke_up_file_removed(tmp_path: Path, datadir: Path) -> None:
    (tmp_path / WOKE_UP_FILE).touch()
    autosuspend.main(
        [
            "-c",
            str(configure_config("dont_suspend.conf", datadir, tmp_path)),
            "-d",
            "daemon",
            "-r",
            "5",
        ]
    )
    assert not (tmp_path / WOKE_UP_FILE).exists()
@pytest.mark.usefixtures("_rapid_sleep")
def test_notify_call(tmp_path: Path, datadir: Path) -> None:
    autosuspend.main(
        [
            "-c",
            str(configure_config("notify.conf", datadir, tmp_path)),
            "-d",
            "daemon",
            "-r",
            "10",
        ]
    )
    assert (tmp_path / SUSPENSION_FILE).exists()
    assert (tmp_path / NOTIFY_FILE).exists()
    assert len((tmp_path / NOTIFY_FILE).read_text()) == 0
@pytest.mark.usefixtures("_rapid_sleep")
def test_notify_call_wakeup(tmp_path: Path, datadir: Path) -> None:
    # configure when to wake up
    now = datetime.now(UTC)
    wakeup_at = now + timedelta(hours=4)
    (tmp_path / "wakeup_time").write_text(str(wakeup_at.timestamp()))
    autosuspend.main(
        [
            "-c",
            str(configure_config("notify_wakeup.conf", datadir, tmp_path)),
            "-d",
            "daemon",
            "-r",
            "10",
        ]
    )
    assert (tmp_path / SUSPENSION_FILE).exists()
    assert (tmp_path / NOTIFY_FILE).exists()
    assert int((tmp_path / NOTIFY_FILE).read_text()) == round(
        (wakeup_at - timedelta(seconds=10)).timestamp()
    )
def test_error_no_checks_configured(tmp_path: Path, datadir: Path) -> None:
    with pytest.raises(autosuspend.ConfigurationError):
        autosuspend.main(
            [
                "-c",
                str(configure_config("no_checks.conf", datadir, tmp_path)),
                "-d",
                "daemon",
                "-r",
                "10",
            ]
        )
@pytest.mark.usefixtures("_rapid_sleep")
def test_temporary_errors_logged(tmp_path: Path, datadir: Path, caplog: Any) -> None:
    autosuspend.main(
        [
            "-c",
            str(configure_config("temporary_error.conf", datadir, tmp_path)),
            "-d",
            "daemon",
            "-r",
            "10",
        ]
    )
    warnings = [
        r
        for r in caplog.record_tuples
        if r[1] == logging.WARNING and "XPath" in r[2] and "failed" in r[2]
    ]
    assert len(warnings) > 0
def test_loop_defaults(tmp_path: Path, datadir: Path, mocker: MockerFixture) -> None:
    loop = mocker.patch("autosuspend.loop")
    loop.side_effect = StopIteration
    with pytest.raises(StopIteration):
        autosuspend.main(
            [
                "-c",
                str(configure_config("minimal.conf", datadir, tmp_path)),
                "-d",
                "daemon",
                "-r",
                "10",
            ]
        )
    args, kwargs = loop.call_args
    assert args[1] == 60
    assert kwargs["run_for"] == 10
    assert kwargs["woke_up_file"] == Path("/var/run/autosuspend-just-woke-up")
def test_hook_success(tmp_path: Path, datadir: Path) -> None:
    autosuspend.main(
        [
            "-c",
            str(configure_config("would_suspend.conf", datadir, tmp_path)),
            "-d",
            "presuspend",
        ]
    )
    assert (tmp_path / WOKE_UP_FILE).exists()
def test_hook_call_wakeup(tmp_path: Path, datadir: Path) -> None:
    # configure when to wake up
    now = datetime.now(UTC)
    wakeup_at = now + timedelta(hours=4)
    (tmp_path / "wakeup_time").write_text(str(wakeup_at.timestamp()))
    autosuspend.main(
        [
            "-c",
            str(configure_config("would_schedule.conf", datadir, tmp_path)),
            "-d",
            "presuspend",
        ]
    )
    assert (tmp_path / SCHEDULED_FILE).exists()
    assert int((tmp_path / SCHEDULED_FILE).read_text()) == round(
        (wakeup_at - timedelta(seconds=30)).timestamp()
    )
def test_version(tmp_path: Path, datadir: Path) -> None:
    autosuspend.main(
        [
            "-c",
            str(configure_config("would_schedule.conf", datadir, tmp_path)),
            "version",
        ]
    )
                                                                                                                                                                                                                                                                                                                                                                                                 autosuspend-9.0.0/tests/test_integration/                                                           0000775 0000000 0000000 00000000000 15043744726 0020632 5                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        autosuspend-9.0.0/tests/test_integration/dont_suspend.conf                                          0000664 0000000 0000000 00000000410 15043744726 0024201 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        [general]
interval = 2
idle_time = 5
suspend_cmd = touch @TMPDIR@/would_suspend
wakeup_cmd = echo {timestamp:d} > @TMPDIR@/wakeup_at
woke_up_file = @TMPDIR@/test-woke-up
lock_file = @TMPDIR@/test-woke-up.lock
[check.ExternalCommand]
enabled = True
command = true
                                                                                                                                                                                                                                                        autosuspend-9.0.0/tests/test_integration/minimal.conf                                               0000664 0000000 0000000 00000000242 15043744726 0023125 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        [general]
suspend_cmd = touch @TMPDIR@/would_suspend
wakeup_cmd = echo {timestamp:d} > @TMPDIR@/wakeup_at
[check.ExternalCommand]
enabled = True
command = false
                                                                                                                                                                                                                                                                                                                                                              autosuspend-9.0.0/tests/test_integration/no_checks.conf                                             0000664 0000000 0000000 00000000350 15043744726 0023433 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        [general]
interval = 2
idle_time = 5
suspend_cmd = touch @TMPDIR@/would_suspend
wakeup_cmd = echo {timestamp:d} > @TMPDIR@/wakeup_at
woke_up_file = @TMPDIR@/test-woke-up
[check.ExternalCommand]
# lacks enabled=True
command = false
                                                                                                                                                                                                                                                                                        autosuspend-9.0.0/tests/test_integration/notify.conf                                                0000664 0000000 0000000 00000000563 15043744726 0023015 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        [general]
interval = 2
idle_time = 5
suspend_cmd = touch @TMPDIR@/would_suspend
wakeup_cmd = echo {timestamp:.0f} > @TMPDIR@/wakeup_at
notify_cmd_wakeup = echo {timestamp:.0f} > @TMPDIR@/notify
notify_cmd_no_wakeup = touch @TMPDIR@/notify
woke_up_file = @TMPDIR@/test-woke-up
lock_file = @TMPDIR@/test-woke-up.lock
[check.ExternalCommand]
enabled = True
command = false
                                                                                                                                             autosuspend-9.0.0/tests/test_integration/notify_wakeup.conf                                         0000664 0000000 0000000 00000000677 15043744726 0024377 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        [general]
interval = 2
idle_time = 5
suspend_cmd = touch @TMPDIR@/would_suspend
wakeup_cmd = echo {timestamp:.0f} > @TMPDIR@/wakeup_at
notify_cmd_wakeup = echo {timestamp:.0f} > @TMPDIR@/notify
notify_cmd_no_wakeup = touch @TMPDIR@/notify
woke_up_file = @TMPDIR@/test-woke-up
lock_file = @TMPDIR@/test-woke-up.lock
wakeup_delta = 10
[check.ExternalCommand]
enabled = True
command = false
[wakeup.File]
enabled = True
path = @TMPDIR@/wakeup_time
                                                                 autosuspend-9.0.0/tests/test_integration/temporary_error.conf                                       0000664 0000000 0000000 00000000435 15043744726 0024736 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        [general]
interval = 20
idle_time = 50
suspend_cmd = touch @TMPDIR@/would_suspend
wakeup_cmd = echo {timestamp:d} > @TMPDIR@/wakeup_at
woke_up_file = @TMPDIR@/test-woke-up
lock_file = @TMPDIR@/test-woke-up.lock
[check.XPath]
enabled = True
xpath = /a
url = asdfjlkasdjkfkasdlfjaklsdf
                                                                                                                                                                                                                                   autosuspend-9.0.0/tests/test_integration/would_schedule.conf                                        0000664 0000000 0000000 00000000505 15043744726 0024507 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        [general]
interval = 2
idle_time = 5
suspend_cmd = touch @TMPDIR@/would_suspend
wakeup_cmd = echo {timestamp:.0f} > @TMPDIR@/wakeup_at
woke_up_file = @TMPDIR@/test-woke-up
lock_file = @TMPDIR@/test-woke-up.lock
[check.ExternalCommand]
enabled = True
command = false
[wakeup.File]
enabled = True
path = @TMPDIR@/wakeup_time
                                                                                                                                                                                           autosuspend-9.0.0/tests/test_integration/would_suspend.conf                                         0000664 0000000 0000000 00000000411 15043744726 0024370 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        [general]
interval = 2
idle_time = 5
suspend_cmd = touch @TMPDIR@/would_suspend
wakeup_cmd = echo {timestamp:d} > @TMPDIR@/wakeup_at
woke_up_file = @TMPDIR@/test-woke-up
lock_file = @TMPDIR@/test-woke-up.lock
[check.ExternalCommand]
enabled = True
command = false
                                                                                                                                                                                                                                                       autosuspend-9.0.0/tests/test_util.py                                                                0000664 0000000 0000000 00000001576 15043744726 0017647 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        from autosuspend.util import logger_by_class, logger_by_class_instance
class DummyClass:
    pass
class TestLoggerByClass:
    def test_smoke(self) -> None:
        logger = logger_by_class(DummyClass)
        assert logger is not None
        assert logger.name == "tests.test_util.DummyClass"
    def test_name(self) -> None:
        logger = logger_by_class(DummyClass, "foo")
        assert logger is not None
        assert logger.name == "tests.test_util.DummyClass.foo"
class TestLoggerByClassInstance:
    def test_smoke(self) -> None:
        logger = logger_by_class_instance(DummyClass())
        assert logger is not None
        assert logger.name == "tests.test_util.DummyClass"
    def test_name(self) -> None:
        logger = logger_by_class_instance(DummyClass(), "foo")
        assert logger is not None
        assert logger.name == "tests.test_util.DummyClass.foo"
                                                                                                                                  autosuspend-9.0.0/tests/test_util_systemd.py                                                        0000664 0000000 0000000 00000001121 15043744726 0021401 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        from dbus.proxies import ProxyObject
import pytest
from autosuspend.util.systemd import list_logind_sessions, LogindDBusException
def test_list_logind_sessions_empty(logind: ProxyObject) -> None:
    assert len(list(list_logind_sessions())) == 0
    logind.AddSession("c1", "seat0", 1042, "auser", True)
    sessions = list(list_logind_sessions())
    assert len(sessions) == 1
    assert sessions[0][0] == "c1"
@pytest.mark.usefixtures("_logind_dbus_error")
def test_list_logind_sessions_dbus_error() -> None:
    with pytest.raises(LogindDBusException):
        list_logind_sessions()
                                                                                                                                                                                                                                                                                                                                                                                                                                               autosuspend-9.0.0/tests/utils.py                                                                    0000664 0000000 0000000 00000000470 15043744726 0016763 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        from collections.abc import Mapping
import configparser
def config_section(
    entries: Mapping[str, str] | None = None,
) -> configparser.SectionProxy:
    parser = configparser.ConfigParser()
    section_name = "a_section"
    parser.read_dict({section_name: entries or {}})
    return parser[section_name]
                                                                                                                                                                                                        autosuspend-9.0.0/tox.ini                                                                           0000664 0000000 0000000 00000004056 15043744726 0015426 0                                                                                                    ustar 00root                            root                            0000000 0000000                                                                                                                                                                        [tox]
envlist = coverage-clean,test-py311-psutil59-dateutil28-tzlocal2, test-py{311,312,313}-psutillatest-dateutillatest-tzlocal{4,latest}, integration-py{311,312,313}, mindeps, check, docs, coverage
[testenv]
extras = test
setenv =
    COVERAGE_FILE = ./.coverage.{envname}
deps =
    psutil59: psutil>=5.9,<5.10
    psutillatest: psutil
    dateutil28: python-dateutil>=2.8,<2.9
    dateutillatest: python-dateutil
    tzlocal2: tzlocal<3
    tzlocal4: tzlocal>3,<5
    tzlocallatest: tzlocal>4
commands =
    {envbindir}/python -V
    {envbindir}/python -c 'import psutil; print(psutil.__version__)'
    {envbindir}/python -c 'import dateutil; print(dateutil.__version__)'
    test: {envbindir}/pytest --cov -m "not integration" {posargs}
    integration: {envbindir}/pytest --cov -m "integration" {posargs}
depends = coverage-clean
[testenv:coverage-clean]
deps = coverage
skip_install = true
commands = coverage erase
depends =
[testenv:coverage]
depends = test-py311-psutil59-dateutillatest, test-py{311,312,313}-psutillatest-dateutillatest, integration-py{311,312,313}
deps =
    coverage
skip_install = true
setenv =
commands =
    - coverage combine
    {envbindir}/coverage html
    {envbindir}/coverage report
[testenv:mindeps]
description = tests whether the project can be used without any extras
extras =
deps =
depends =
commands =
    {envbindir}/python -V
    {envbindir}/python -c "import autosuspend; import autosuspend.checks.activity; import autosuspend.checks.wakeup"
    {envbindir}/autosuspend -c tests/data/mindeps-test.conf daemon -r 1
[testenv:check]
depends =
deps =
    -rrequirements-check.txt
commands =
    {envbindir}/python -V
    {envbindir}/ruff check src tests
    {envbindir}/isort --check src tests
    {envbindir}/black --check src tests
    {envbindir}/mypy src tests
[testenv:docs]
basepython = python3.13
depends =
deps = -rrequirements-doc.txt
commands = {envbindir}/sphinx-build -W -b html -d {envtmpdir}/doctrees doc/source {envtmpdir}/html
[gh-actions]
python =
    3.11: py311, coverage
    3.12: py312, coverage
    3.13: py313, coverage